Stress continuity using mixed-dimensional branch

Dear all,

Thinking in a more complex problem, I’m trying to solve the elasticity equation on two coupled domains \Omega_l=[0,1]\times[0,1] and \Omega_r=[1,2]\times[0,1] (see figure). The problem is as follows:

\begin{alignat}{2} -\text{div}(\sigma(\boldsymbol{u}_l)) &= \boldsymbol{0} \quad &&\text{in } \Omega_l \\ -\text{div}(\sigma(\boldsymbol{u}_r)) &= \boldsymbol{0} \quad &&\text{in } \Omega_r \end{alignat}

subject to boundary and coupling conditions

\begin{alignat}{2} \boldsymbol{u_l} = \boldsymbol{u_r} &= \boldsymbol{0} \quad &&\text{in } \Gamma_D \\ \sigma(\boldsymbol{u_l})\boldsymbol{n}_l &= \boldsymbol{g} \quad &&\text{in } \Gamma_N \\ \boldsymbol{u_l} &= \boldsymbol{u_r} \quad &&\text{in } \Gamma_I \\ \sigma(\boldsymbol{u_l})\boldsymbol{n}_l &= -\sigma(\boldsymbol{u_r})\boldsymbol{n}_r \quad &&\text{in } \Gamma_I \end{alignat}

where \boldsymbol{u}_l and \boldsymbol{u}_r are the displacement fields on \Omega_l and \Omega_r, \boldsymbol{n}_l and \boldsymbol{n}_r outward normal vectors on the boundaries of \Omega_l and \Omega_r, and \sigma the stress tensor.

I managed to impose the displacement continuity in \Gamma_I using Lagrange multipliers. However, I’m still not sure about the stress continuity. How can I manage the stress continuity in \Gamma_I?

The below code is a MWE of my attempt.

from dolfin import *

class Dirichlet(SubDomain):
  def inside(self, x, on_boundary):
    return near(x[0], 2) \
        or near(x[1], 0) \
        or near(x[1], 1)

class Neumann(SubDomain):
  def inside(self, x, on_boundary):
    return near(x[0], 0)

class Interface(SubDomain):
  def inside(self, x, on_boundary):
    return near(x[0], 1)

# Mesh generation
n = 20
mesh = RectangleMesh(Point(0,0,0),Point(2,1,0),2*n,n)

# Domain markers
domain = MeshFunction("size_t", mesh, mesh.geometry().dim(), 0)
for c in cells(mesh):
  domain[c] = c.midpoint().x() > 1.0

# Interface markers
facets = MeshFunction("size_t", mesh, mesh.geometry().dim()-1, 0)
Interface().mark(facets,1)

# Meshes
mesh_L = MeshView.create(domain, 0)
mesh_R = MeshView.create(domain, 1)
mesh_I = MeshView.create(facets, 1)

# Function spaces
FE = VectorElement("CG", mesh_I.ufl_cell(), 1, dim=2) # Lagrange multiplier
VE = VectorElement("CG", mesh.ufl_cell(), 1, dim=2)   # Displacement
V_L  = FunctionSpace(mesh_L,VE)    # Displacement on left domain
V_R  = FunctionSpace(mesh_R,VE)    # Displacement on right domain
V_I  = FunctionSpace(mesh_I,FE)   # Lagrange multiplier on interface
W = MixedFunctionSpace(V_L, V_R, V_I)

# Markers for Dirichlet and Neuman bcs
ff_L = MeshFunction("size_t", mesh_L, mesh_L.geometry().dim()-1, 0)
Dirichlet().mark(ff_L, 1)
Neumann().mark(ff_L, 2)
Interface().mark(ff_L, 3)
ff_R = MeshFunction("size_t", mesh_R, mesh_R.geometry().dim()-1, 0)
Dirichlet().mark(ff_R, 1)
Interface().mark(ff_R, 3)

# Boundary conditions
bc_L = DirichletBC(W.sub_space(0), Constant((0,0)), ff_L, 1)
bc_R = DirichletBC(W.sub_space(1), Constant((0,0)), ff_R, 1)
bcs = [bc_L, bc_R]

# Elasticity parameters
rho   = Constant(2200)
lmbda = Constant(7.428e+04)
mu    = Constant(7.428e+04)

# Neumann condition
g = Constant((1e+5, 0))

# Measures
dx_L  = Measure("dx", domain=W.sub_space(0).mesh())
ds_L  = Measure("ds", domain=W.sub_space(0).mesh(), subdomain_data=ff_L)
dx_R = Measure("dx", domain=W.sub_space(1).mesh())
ds_R  = Measure("ds", domain=W.sub_space(1).mesh(), subdomain_data=ff_R)
ds_I   = Measure("dx", domain=W.sub_space(2).mesh())

# Function and test functions
(ul, ur, l) = TrialFunctions(W)
(wl, wr, e) = TestFunctions(W)

# Stress tensor
def sigma(r):
    return 2.0*mu*sym(grad(r)) + lmbda*tr(sym(grad(r)))*Identity(len(r))

# Variational problem
a_left = inner(sigma(ul),sym(grad(wl)))*dx_L \
       + inner(sigma(ul)*FacetNormal(mesh_L), wl)*ds_L(3)
L_left = inner(g, wl)*ds_L(2)

a_right = inner(sigma(ur),sym(grad(wr)))*dx_R \
        - inner(sigma(ul)*FacetNormal(mesh_R), wr)*ds_R(3)
L_right = inner(Constant((0,0)), wr)*dx_R

a = a_left + a_right \
  + inner(l, wr)*ds_I + inner(ur-ul, e)*ds_I
L = L_left + L_right

# Output files
pvda_file = File("output/ul.pvd")
pvdb_file = File("output/ur.pvd")

# Solve
u_mixed = Function(W)
solve(a == L, u_mixed, bcs, solver_parameters={"linear_solver":"direct"})
ul, ur, uc = u_mixed.sub(0), u_mixed.sub(1), u_mixed.sub(2)

# Save solution
pvda_file << ul
pvdb_file << ur

PS: sorry for the number of edits. It took me a lot to figure out how to write the equations.

1 Like

Hi Hernan,

To impose the stress continuity condition, I guess you could use a second Lagrange multiplier, as you did for the displacement continuity condition.
You would then have 4 spaces in your MixedFunctionSpace :

V_I_disp  = FunctionSpace(mesh_I,FE)   # Lagrange multiplier - displacement continuity
V_I_stress  = FunctionSpace(mesh_I,FE)   # Lagrange multiplier -stress continuity
W = MixedFunctionSpace(V_L, V_R, V_I_disp, V_I_stress)

# Function and test functions
(ul, ur, l_disp, l_stress) = TrialFunctions(W)
(wl, wr, e_disp, e_stress) = TestFunctions(W)

Regarding your variational formulation, I suspect a few mistakes :
(1) in a_left : the condition on the neumann boundary \sigma(u_l)n_l = g ~\text{in}~ \Gamma_N (note : I guess you meant u_l here since u_r is not defined on that boundary) uses ds_L(3) where 3 is the marker for the interface. I think it should be ds_L(2) instead (and it has to match with the term involving g).
(2) I am not sure we need the term inner(sigma(ul)*FacetNormal(mesh_R), wr)*ds_R(3) in a_right.
(3) To be coherent with the term inner(ur-ul, e_disp)*ds_I (1st Lagrange multiplier), shouldn’t we have inner(l_disp, wr-wl)*ds_I ?

Imposing the stress continuity condition with the second Lagrange multiplier, the variational formulation should look like :

# Variational problem
a_left = inner(sigma(ul),sym(grad(wl)))*dx_L + inner(sigma(ul)*FacetNormal(mesh_L), wl)*ds_L(2)
L_left = inner(g, wl)*ds_L(2)

a_right = inner(sigma(ur),sym(grad(wr)))*dx_R # - inner(sigma(ul)*FacetNormal(mesh_R), wr)*ds_R(3)
L_right = inner(Constant((0,0)), wr)*dx_R

nL = Constant((1,0))
nR = Constant((-1,0))

a = a_left + a_right \
  + inner(l_disp, wr-wl)*ds_I + inner(ur-ul, e_disp)*ds_I \
  + inner(l_stress, sigma(wl)*nL + sigma(wr)*nR)*ds_I + inner(sigma(ul)*nL + sigma(ur)*nR, e_stress)*ds_I
L = L_left + L_right

Note : FacetNormal cannot be used directly in cell type integrals (as ds_I which is of type dx). Here, the domain is simple and we know the expression of the normal at the interface. When it is not the case, you can define nL as the projection of FacetNormal(mesh_L) on V_L (same for nR).

Hope that helps!

4 Likes

Note that it follows from integration by parts that the tractions on the two subdomains are \pm the Lagrange multiplier enforcing the displacement continuity. So, the traction compatibility follows directly from enforcing displacement compatibility with a multiplier field. See the derivation here (Section 2) in the context of fluid–structure coupling.

5 Likes

Hi @cdaversin,

Thanks for your nice answer. Regarding to your comments:

(1) Yes, that was a typo (it’s fixed now).
(2) Me neither :sweat_smile:.
(3) Yes, you are right. Using just inner(l_disp, wr)*ds_I it was not the correct way of imposing the displacement continuity (I wrote by hand the formulation with the Lagrange multipliers and now is clear for me).

Wrt the Lagrange multiplier for the stress continuity it seems to give a different solution when compared with displacement field obtained by solving the elasticity equation on \Omega_l\cup\Omega_r . I tried the below formulation and now the solutions match (I didn’t know why, but with the comment of @kamensky now is crystal clear).

# Variational problem
a_left = inner(sigma(ul),sym(grad(wl)))*dx_L # + inner(sigma(ul)*FacetNormal(mesh_L), wl)*ds_L(3)
L_left = inner(g, wl)*ds_L(2)

a_right = inner(sigma(ur),sym(grad(wr)))*dx_R # + inner(sigma(ur)*FacetNormal(mesh_R), wr)*ds_R(3)
L_right = inner(Constant((0,0)), wr)*dx_R

a = a_left + a_right \
  + inner(l_disp, wr-wl)*ds_I + inner(ur-ul, e_disp)*ds_I
L = L_left + L_right

PS: Is there a way to format the code snippets as python or cpp code?

1 Like

Thanks @kamensky! that was an excellent reference to understand what was happening

PS: Is there a way to format the code snippets as python or cpp code?

Yes, you can specify the language right after the back-tics, e.g.,

```python
def f(x):
    return x
```
1 Like

One further question:
Should the solutions match when comparing these two approaches?

For me, the solutions match perfectly and all my Lagrange multipliers are completely zero, but I have two domains with different material parameters, so I’m not sure, whether this is actually correct.