October 13, 2025

Implementation

import numpy as np
import scipy
from scipy.sparse import bmat, csr_matrix, coo_matrix, lil_matrix

import skfem
from skfem.helpers import dot, ddot, sym_grad
from skfem import InteriorBasis, FacetBasis, ElementTetP1, ElementVectorH1

from skfem.supermeshing import intersect, elementwise_quadrature


mesh_top = skfem.MeshTet.init_tensor(
    np.linspace(0, 2, 20),
    np.linspace(0, 2, 20),
    np.linspace(0, 1, 10)
)
mesh_bot = skfem.MeshTet.init_tensor(
    np.linspace(0.5, 1.5, 20),
    np.linspace(0.5, 1.5, 20),
    np.linspace(-0.5, 0.0, 5)
)


def is_contact_surface(x):
    return np.isclose(x[2], 0.0)


def is_force_surface(x):
    return np.isclose(x[2], 1.0)


def is_dirichlet_surface(x):
    return np.isclose(x[2], -0.5)


mesh_top = mesh_top.with_boundaries({
    "contact": is_contact_surface,
    "force": is_force_surface
})
mesh_bot = mesh_bot.with_boundaries({
    "contact": is_contact_surface,
    "dirichlet": is_dirichlet_surface
})


elem = skfem.ElementTetP1()
u_elem = skfem.ElementVectorH1(elem)
basis_top = skfem.InteriorBasis(mesh_top, u_elem)
basis_bot = skfem.InteriorBasis(mesh_bot, u_elem)

E, nu = 210e9, 0.3
mu = E/(2*(1+nu))
lam = E*nu/((1+nu)*(1-2*nu))
@skfem.BilinearForm
def a_elastic(u, v, w):
    eps_u = sym_grad(u)      # symmetric gradient of u
    eps_v = sym_grad(v)
    return lam * np.trace(eps_u) * np.trace(eps_v) + 2*mu * ddot(eps_u, eps_v)


K1 = a_elastic.assemble(basis_top)
K2 = a_elastic.assemble(basis_bot)

interface_facets = mesh_bot.facets_satisfying(lambda pts: np.isclose(pts[2], 0.0))
mortar_basis = FacetBasis(mesh_bot, elem, facets=interface_facets) 


basis_top = skfem.Basis(mesh_top, ElementVectorH1(skfem.ElementTetP1()))
basis_bot = skfem.Basis(mesh_bot, ElementVectorH1(skfem.ElementTetP1()))

facets_if = mesh_bot.facets_satisfying(is_contact_surface)
lambda_basis = skfem.FacetBasis(
    mesh_bot,
    skfem.ElementTetP1(),
    facets=facets_if
)


elem_s = ElementTetP1()
elem_v = ElementVectorH1(elem_s)
m1t, orig1 = mesh_top.trace('contact', mtype=skfem.MeshTri, project=lambda p: p[[0, 1]])
m2t, orig2 = mesh_bot.trace('contact', mtype=skfem.MeshTri, project=lambda p: p[[0, 1]])
m12, t1, t2 = intersect(m1t, m2t)
quad1 = elementwise_quadrature(m1t, m12, t1)
quad2 = elementwise_quadrature(m2t, m12, t2)

fb_u_top = FacetBasis(
    mesh_top, elem_v, facets=orig1[t1], quadrature=quad1
)
fb_u_bot = FacetBasis(
    mesh_bot, elem_v, facets=orig2[t2], quadrature=quad2
)
fb_lam_v = FacetBasis(mesh_bot, elem_v, facets=orig2[t2], quadrature=quad2)


@skfem.BilinearForm
def b_top(u1, u2, v1, v2, w):
    if v1.ndim == 3:
        return dot(v1, u2)
    return 0.0 * (w.n[0] * 0 + 1)

@skfem.BilinearForm
def b_bot(u1, u2, v1, v2, w):
    if v1.ndim == 3:
        return - dot(v1, u2)
    return 0.0 * (w.n[0] * 0 + 1)


Bfull_top = skfem.asm(b_top, fb_u_top*fb_lam_v)
Bfull_bot = skfem.asm(b_bot, fb_u_bot*fb_lam_v)

Nu_top = int(fb_u_top.N)
Nu_bot = int(fb_u_bot.N)
Nlam_v = int(fb_lam_v.N)

B1 = Bfull_top[:Nu_top, Nu_top:Nu_top + Nlam_v]
B2 = Bfull_bot[:Nu_bot, Nu_bot:Nu_top + Nu_bot]


print("K1:", K1.shape)
print("K2:", K1.shape)
print("B1:", B1.shape)
print("B2:", B1.shape)
print("basis_top.N:", basis_top.N)

B1_full = lil_matrix((B1.shape[0], basis_top.N))
B2_full = lil_matrix((B2.shape[0], basis_bot.N))

K = bmat([
    [K1,        None,     B1],
    [None,      K2,      -B2],
    [B1.T,     -B2.T,     None]
], format='csr')

# Assemble RHS vector
F_facet_ids = mesh_top.facets_satisfying(is_force_surface)
# F_facet_ids = mesh.facets_satisfying(lambda x: np.isclose(x[0], 1.0))

fbasis = skfem.FacetBasis(mesh_top, elem_v, facets=F_facet_ids)
total_force = 100.0  # [N]


@skfem.Functional
def surface_measure(w):
    return 1.0


A_proj_z = surface_measure.assemble(fbasis)
pressure = total_force / A_proj_z


# ---- traction: t = p * n (l(v) = ∫_Γ (p n) · v ds)----
@skfem.LinearForm
def l_comp(v, w):
    return pressure * v[2]

# @skfem.LinearForm
# def l_comp(v, w):
#     return pressure * dot(w.n, v)


top_F = l_comp.assemble(fbasis)
bot_F = np.zeros(mesh_bot.p.shape[1] * mesh_bot.dim())
F = np.hstack([top_F, bot_F, np.zeros(B1.T.shape[0])])


D_facets = mesh_bot.facets_satisfying(is_dirichlet_surface)
D_dofs = basis_bot.get_dofs(facets=D_facets).all() + K1.shape[0]

AII, bI, xI, I = skfem.condense(K, F, D=D_dofs)