Signorini’s contact problem with Interior point optimizer (IPOPT)

Algorithm to solve the contact between an elastic body and a rigid foundation (obstacle).


We consider Signorini’s problem which is the contact between a body and a rigid foundation. We treat only the linear elastic materials case and the plane strain hypothesis (2D) is assumed. Let $\Omega \subset \mathbb{R}^{2}$ denotes the body and $\Gamma= \partial \Omega$ its boundary. $\Gamma_{0} \subset \Gamma$ denotes the boundary part where a displacement is imposed, $\Gamma_{1} \subset \Gamma$ is the boundary part where a traction vector $\mathbf{t}$ is applied, finally $\Gamma_{C} \subset \Gamma$ denotes the potential contact area.

By imposing a null displacement on $\Gamma_{0}$, the displacement admissible set is defined as follows

$ \displaystyle{ \mathbf{V}= \left\lbrace \mathbf{v} \in \left( H^{1}(\Omega)\right)^{2} \, | \, \mathbf{v}=0 \text{ on } \Gamma_{0} \right\rbrace } $

The total potential energy $E_{p}$ is given by the following

$ \displaystyle{ E_{p}(\mathbf{v})= \frac{1}{2} \int_{\Omega} \boldsymbol{\sigma}^{T}.\boldsymbol{\epsilon} \, dV - \int_{\Gamma_{1}} \mathbf{t} . \mathbf{v} \, dA } $

where $\boldsymbol{\epsilon}$ and $\boldsymbol{\sigma}$ is respectively the strain vector and the stress vector, if $\mathbf{v}=(v_{1},v_{2})$ we have

$ \displaystyle{ \boldsymbol{\epsilon}=\begin{pmatrix} \epsilon_{x} \newline \epsilon_{y} \newline \gamma_{xy} \end{pmatrix}=\begin{pmatrix} \frac{\partial v_{1}}{\partial x} \newline \frac{\partial v_{2}}{\partial y} \newline \frac{\partial v_{1}}{\partial y} + \frac{\partial v_{2}}{\partial x} \end{pmatrix} \, \, \, and \, \, \, \boldsymbol{\sigma} = \mathbf{D} \boldsymbol{\epsilon} } $

In addition

$ \displaystyle{ \mathbf{D}=\frac{E}{(1+\nu)(1-2\nu)} \, \begin{bmatrix} 1-\nu & \nu & 0 \newline \nu & 1-\nu & 0 \newline 0 & 0 & \frac{1-2\nu}{2} \end{bmatrix} } $

with $E$ the Young’s modulus and $\nu$ the Poisson’s ratio.

The solution (the displacement field) of the contact problem involving a linear elastic body, can be written as the following minimization problem

$ \displaystyle{ \mathbf{u}= \underset{\mathbf{v} \in \mathbf{K}}{\text{argmin}} \, (E_{p}(\mathbf{v})) } $

where $\mathbf{K}$ is the set describing the non-penetration condition for the Signorini’s problem, and is given by

$ \displaystyle{ \mathbf{K} = \left\lbrace \mathbf{v} \in \mathbf{V} \, | \, (\mathbf{v}+\mathbf{X}-\bar{\mathbf{X}}).\mathbf{n} \geq 0 \text{ on } \Gamma_{C} \right\rbrace } $

where $\mathbf{X}$ is the position in the initial configuration, $\bar{\mathbf{X}}$ its projection on the obstacle and $\mathbf{n}$ is the outward unit normal vector at $\bar{\mathbf{X}}$.


In order to describe the non-pentration between the body $\Omega$ and the obstacle, the node-to-segment discretization is used, in other words the body nodes can not penetrate the obstacle. Let $i$ be a node of the body, belonging to the potential contact area $\Gamma_{C}$, then the non-penetration conditions in this case can be given as follows

$ \displaystyle{ (\mathbf{x}^{i}-\bar{\mathbf{x}}^{i}) . \mathbf{n}^{i} \geq 0 \, \, \, \forall \, i=1,\ldots,n_{C} } $

where $n_{C}$ is the number of the contact nodes, $ \mathbf{x}^{i} $ the actual position of the node $i$, $\bar{\mathbf{x}}^{i}$ the projection on the obstacle of $\mathbf{x}^{i}$ which is equal to $\bar{\mathbf{X}}^{i}$, the projection of the initial position, in the case of small displacements. Finally $\mathbf{n}^{i}$ is the outward unit normal vector at $\bar{\mathbf{x}}^{i}$.

In general, better and robust results can be obtained using the weak contact formulation (see [1,2]).

In the case of small deformations, the total potential energy $E_{p}$ can be given as follows

$ \displaystyle{ E_{p}(U)=\frac{1}{2}U^{T}KU - FU } $

where $K$ is the rigidity matrix, $F$ the nodale forces and $U \in \mathbb{R}^{n}$ the displacement field degrees of freedom vector. In addition the non-penetration constraints are linear and can be expressed as follows

$ \displaystyle{ AU + b \geq 0 } $

Finally, the contact problem for the linear elastic body, can be written as the following minimization problem

$ \begin{cases} \underset{V \in \mathbb{R}^{n}}{\text{min}} \, \frac{1}{2}V^{T}KV - FV \newline AV + b \geq 0 \end{cases} $

NB : One can find in [3] another formulation for the Signorini’s problem.


we consider the contact between an elastic arch and a rigid one, due to an applied pressure. A new FreeFEM function, called projection, is used in order to compute the projection points.

Please cite [1] or [2] if you want to use the following code.


File Geometry.idp

// Body geometry

// Mesh quality
int nu1  =100;           // Mesh quality for the body
int nu2  =102;           // Mesh quality for the obstacle

// Type of the Finite element space
func Pk1 = P1;	// Finite element space
func Pk2 = P1;	// Finite element space

// Pressure amplitude
real pres=30;

// Amplification
real amplify =1;

// The body
int Contact1  = 1;
int Force1    = 2;
int Symmy     = 3;

real R1=40;
real e1=10;
real R2=R1-e1;
border b11(t=0,pi){x=R1*cos(t);y=R1*sin(t);label=Contact1;}
border b12(t=0,e1){x=-R1+t;y=0.;label=Symmy;}
border b13(t=pi,0){x=R2*cos(t);y=R2*sin(t);label=Force1;}
border b14(t=0,e1){x=R2+t;y=0;label=Symmy;}
mesh Th1=buildmesh(b11(nu1)+b12(nu1/10)+b13(nu1)+b14(nu1/10));

// The obstacle
int Found2    = 4;
int Contact2  = 5;
// Foundation mesh
real R5=50;
real R6=R1+1.;
real e2=R5-R6;
border b21(t=0,pi){x=R5*cos(t);y=R5*sin(t);label=Found2;}
border b22(t=0,e2){x=-R5+t;y=0.;label=Found2;}
border b23(t=pi,0){x=R6*cos(t);y=R6*sin(t);label=Contact2;}
border b24(t=0,e2){x=R6+t;y=0;label=Found2;}
mesh Th2=buildmesh(b21(nu2)+b22(nu2/10)+b23(nu2)+b24(nu2/10));

// The finite element spaces
fespace Wh1(Th1,[Pk1,Pk1]); //FE for the body
fespace Wh2(Th2,[Pk2,Pk2]); //FE for the obstacle (to define the normal vector)

Material parameters

Definition of the elasticity law in 2D.

File Elastic_lin_2d.idp

// Material law

// Density energy for the elastic body and it differentials
real E1=1*1e3; // Young modulus (Pa)
real pois1=0.; // Poisson modulus
real lambda1 = E1*pois1/((1.+pois1)*(1.-2.*pois1)); // Lamé coefficients
real mu1 = E1/(2.*(1.+pois1)) ; // Lamé coefficients
real twomul1=2*mu1+lambda1;
//Elasticity matrix
func AA1 = [[twomul1,lambda1, 0. ], [lambda1,twomul1, 0. ],[ 0. , 0. , mu1 ]];
// Strain vector
macro epsV1(d) ([dx(d[0]),dy(d[1]),dy(d[0])+dx(d[1])]) //
// Strain first differential
macro deps1(d,dd) ([dx((dd)[0]),dy((dd)[1]),dy((dd)[0])+dx((dd)[1])] ) //
//Density energy for elastic bodies
macro W2d1(d)  ((1./2.)*( epsV1(d)'* AA1 *epsV1(d))) //
// Density energy first differential
macro dW2d1(d,dd)(
(1./2.)*( epsV1(d)'*AA1*deps1(d,dd)) + (1./2.)*((deps1(d,dd))'*AA1*epsV1(d))
) //
// Density energy second differential
macro ddW2d1(d,dd,ddd)(
(1./2.)*( deps1(d,ddd)'*AA1*deps1(d,dd)) + (1./2.)*((deps1(d,dd))'*AA1*deps1(d,ddd))
) //

macro Sigma1(d) ( AA1*epsV1(d) ) // The stress vector

Minimization algorithm

// Linear elasticity is assumed
// The contact conditions are described by the node-to-segment discretization (each node can not penetrate the obstacle)
load "ff-Ipopt"                  // Load the interior point optimizer
load "msh3"                      // Even if we are in 2D case, it's used to compute the projection points
include "Elastic_lin_2d.idp"     // Material data
include "Geometry.idp"           // Geometrical data

//Degree of Freedom
int ndof1=Wh1.ndof;        // Number of the degree of freedom for the body
meshL Lh = extract(Th2);   // The border of Th2 (used to compute the projection points)

// Define the normal field
varf normvar([Dx, Dy], [v1, v2]) = int1d(Th2,Contact2)(v1*N.x+v2*N.y);
real[int] normfieldvec = normvar(0, Wh2, tgv=1);  // Not normalized
Wh2 [normx,normy];

// Vectors & arrays for Contact
int[int] Jb1;
real[int] Xb1(0);
int nC1=0;

// Extract contact borders for the body
varf vborder1([Dx, Dy], [v1, v2]) = on(Contact1, Dx=1, Dy=1);
real[int] onBorder1 = vborder1(0, Wh1, tgv=1);
int numB1 = onBorder1.sum;
int k = 0;
for [i, B1i:onBorder1]
  if (B1i) { Jb1[k] = i; k++; }
// End extract borders for the body

// Working space (All internal variables are deleted after)
 Wh1 [xx1,yy1]=[x,y]; // position vector
 nC1=Jb1.n/2;         // Constraints number
 Xb1=xx1[](Jb1);      // Coord on Border x, y
} // End working space

Wh1 [U1x, U1y];          // Displacement for the body

matrix KRIG;
// Compute rigidity matrix (Linear elasticity)
Wh1 [D01x, D01y];
D01x[] = 0.;
varf ddW1 ([Dx, Dy], [Vx, Vy])
  = int2d(Th1)(ddW2d1([D01x, D01y], [Dx, Dy], [Vx, Vy]));
KRIG = ddW1(Wh1, Wh1); // Rigidity matrix for the body

// Compute external forces
varf dW1 ([Dx, Dy], [Vx, Vy]) = int1d(Th1,Force1)(-Vx*pres*N.x-Vy*pres*N.y);
real[int] FEXT = dW1(0, Wh1); // External forces for the body

real[int] DIS(ndof1); 	   // The Unknown displacement of the body
DIS=0.;                    // Initial displacement
real cpu0=clock();         // Time before resolution
real[int] Proj(2*nC1);     // (x,y) of the projected points
real[int] norm(2*nC1);     // The normal (nx,ny) of the projected points

// Compute the projection points
for(int i=0;i<nC1;i++){
  x=Xb1[2*i];                            // The abssica of the contact node
  y=Xb1[2*i+1];                          // The ordinate of the contact node
  z=0;                                   // 2D case
  int nu;                                // The border segment number (not used here)
  R3 ph;
  R3 proj=projection(Lh,nu=nu,Phat=ph);   // Projection of the contact node
  Proj[2*i]  =proj.x;                     // Abssica of the projection of the contact node
  Proj[2*i+1]=proj.y;                     // Ordinate of the projection of the contact node
  norm[2*i]=normx(Proj[2*i],Proj[2*i+1])/( (normx(Proj[2*i],Proj[2*i+1]))^2 + (normy(Proj[2*i],Proj[2*i+1]))^2)^0.5;  // The normal x vector at a node
  norm[2*i+1]=normy(Proj[2*i],Proj[2*i+1])/( (normx(Proj[2*i],Proj[2*i+1]))^2 + (normy(Proj[2*i],Proj[2*i+1]))^2)^0.5;  // The normal y vector at a node


// The Energy of the body
func real iW (real[int] &X) {
  real[int] idWW(ndof1);
  real res =X'*idWW;
  res=0.5*res-X'*FEXT;   // Energy = 1/2* (X^T)*K*X - (X^T)*Fext
  return res;
// End Energy function

// Energy Gradient
func real[int] idW (real[int] &X) {
  real[int] idWW(ndof1);
  idWW=idWW - FEXT;
  return idWW; // The gradient of the total energy
// End of the gradient energy

// The Energy Hessian
func matrix iddW (real[int] &X) {
  return KRIG;
// End of the Hessian energy

real[int]  Wb1(2*nC1);
//Constraints function
func real[int] Cnst2 (real[int] &X) {
  real[int] Co(nC1);
  Wb1 += X(Jb1);
  for (int i=0;i<nC1;i++){          // Defining the nC1 constraints (each node is not allowed to penetrate the foundation)
    real xpr=Proj[2*i];             // x of the projected point
    real ypr=Proj[2*i+1];           // y of the projected point
    Co[i]=(Wb1[2*i]-xpr)*norm[2*i]+(Wb1[2*i+1]-ypr)*norm[2*i+1];    // (x-\bar{x}).n >=0
  return Co;
} //Constraints

// Jacobian of the constraints
real[int,int] Jac1(nC1,ndof1);
matrix JACOB;
{ // The Jacobian of the constraints is constant
  for (int i=0;i<nC1;i++){          // Defining the nC1 constraints (each node is not allowed to penetrate the foundation)

real[int] GAP(nC1);
real[int] NULDIS(ndof1);
GAP=Cnst2(NULDIS);    // Initial Gap

//Constraints (or one can use the constraints function Cnst2 )
func real[int] Cnst (real[int] &X) {    //
  real[int] AX(nC1);
  return AX;
} //Constraints

func matrix jacCnst (real[int] &X){
  return JACOB;

// Hessian of the constraints
matrix Hessian;
func matrix hessianCnst (real[int] &X, real sigma, real[int] &lambda){
  Hessian = sigma*iddW(X);
  return Hessian;

real[int] cl(nC1); cl=0.; // Constraints lower bounds (no upper bounds)  

// Boundary conditions for the body
Wh1 [ub11, ub12] = [1e19, 1e19];                            // Unbounded in interior
Wh1 [lb11, lb12] = [-1e19, -1e19];                          // Unbounded in interior
varf vGamma1([Dx, Dy], [v1, v2]) = on(Symmy, Dx=1, Dy=1);   // For Boundary conditions
real[int] onGamma1 = vGamma1(0, Wh1, tgv=1);
Wh1 [ubb11, ubb12] = [ub11,0.];                             // Symmetry w.r.t y
Wh1 [lbb11, lbb12] = [lb11,0.];                             // Symmetry w.r.t y
ub11[]= onGamma1 ? ubb11[] : ub11[];                        // enforcing the boundary condition
lb11[]= onGamma1 ? lbb11[] : lb11[];                        // enforcing the boundary condition

IPOPT(iW, idW, hessianCnst, Cnst, jacCnst, DIS, lb=lb11[], ub=ub11[], clb=cl); // Minimize with IPOPT

// Plotting
plot(Th1,[U1x, U1y],Th2);
mesh Thm1 = movemesh(Th1, [x+U1x*amplify, y+U1y*amplify]);

//Compute the stresses
fespace Vh1(Th1,P0);
Vh1 Sigmavm1, sigy1, sigx1, sigxy1, sigz1, sigmar1;
sigx1=Sigma1([U1x, U1y])[0];  // Stress Component
sigy1=Sigma1([U1x, U1y])[1];  // Stress Component
sigxy1=Sigma1([U1x, U1y])[2]; // Stress Component
sigz1=pois1*(sigx1+sigy1);    // Stress Component

Sigmavm1 =sqrt(0.5*((sigx1-sigy1)^2 + (sigy1-sigz1)^2 + (sigz1-sigx1)^2 )+3*sigxy1^2); // Von-Mises stress
sigmar1=sigx1*(x*x/(x^2+y^2))+sigy1*(y*y/(x^2+y^2)) +2.*sigxy1*(x*y/(x^2+y^2)); // Radial stress

plot(Sigmavm1,value=1,fill=1,cmm="Von Mises");
plot(sigmar1,value=1,fill=1,cmm="Radial stress");

  load "iovtk"
  fespace PVh1(Th1, P0);
  PVh1 DDx1=U1x, DDy1=U1y;
  PVh1 vm1=Sigmavm1;

  int[int] order = [1,1];
  string dataname1 = "u VonMises";

  savevtk("Signorini_NTS.vtu",Th1,[DDx1, DDy1, 0],vm1,dataname=dataname1, order=order);

real cpu1=clock(); // IPOPT resolution time
cout << "------------------------------" << endl;
cout << "RESOLUTION TIME= " << cpu1-cpu0 << endl;
cout << "------------------------------" << endl;
Result - Von Mises stress


[1] Finite element modeling of mechanical contact problems for industrial applications, H. Houssein, PhD thesis, Sorbonne Université

[2] A symmetric algorithm for solving mechanical contact problems using FreeFEM, H. Houssein, S. Garnotel, F. Hecht (To appear in Computational Methods in Applied Sciences, Springer)

[3] Frictionless contact problem for hyperelastic materials with interior point optimizer, H. Houssein, S. Garnotel, F. Hecht (HAL)


Author: Houssam Houssein