from __future__ import annotations
import importlib.metadata
import basix.ufl
import dolfinx_mpc
import numpy as np
import ufl
from dolfinx import default_scalar_type, fem, mesh
from dolfinx.fem import petsc as fem_petsc
from mpi4py import MPI
from petsc4py import PETSc
EXX = 0.03 # applied macro strain
NCELLS = 3 # uniform NCELLS³ mesh
NSTEPS = 6 # load steps
MU = 1.0 # shear modulus
BULK = 200.0 # bulk modulus (bulk/mu = 200 → near-incompressible)
TOL = 1e-10
_SNES_OPTS = {
"snes_type": "newtonls",
"snes_linesearch_type": "bt",
"snes_max_it": 30,
"snes_rtol": 1e-8,
"snes_atol": 1e-8,
"ksp_type": "preonly",
"pc_type": "lu",
}
def _face(axis, value):
return lambda x: np.isclose(x[axis], value, atol=TOL)
def _origin(x):
return np.isclose(x[0], 0.) & np.isclose(x[1], 0.) & np.isclose(x[2], 0.)
def _periodic_indicator(axis):
def f(x):
mask = np.isclose(x[axis], 1., atol=TOL)
for later in range(axis + 1, 3):
mask &= x[later] < 1. - TOL
return mask
return f
def _periodic_relation(axis):
def f(x):
y = x.copy(); y[axis] -= 1.; return y
return f
def _zero_fn(W_sub):
V, _ = W_sub.collapse()
fn = fem.Function(V); fn.x.array[:] = 0.; fn.x.scatter_forward()
return V, fn
def _build_forms(msh, W, up, macro_H):
mu = fem.Constant(msh, default_scalar_type(MU))
kappa = fem.Constant(msh, default_scalar_type(BULK))
du, v = ufl.TrialFunction(W), ufl.TestFunction(W)
u, p = ufl.split(up)
I = ufl.Identity(3)
F = ufl.variable(I + macro_H + ufl.grad(u))
I1 = ufl.tr(F.T * F)
J = ufl.det(F)
J_star = ((kappa + p) + ufl.sqrt((kappa + p)**2 + 4.*kappa*mu)) / (2.*kappa)
psi = (p*(J - J_star) + .5*mu*(I1 - 3.)
- mu*ufl.ln(J_star) + .5*kappa*(J_star - 1.)**2)
R = ufl.derivative(psi * ufl.dx, up, v)
K = ufl.derivative(R, up, du)
return R, K
# ── Case 1: Dirichlet BCs (converges) ────────────────────────────────────────
def run_nonperiodic():
comm = MPI.COMM_WORLD
msh = mesh.create_unit_cube(comm, NCELLS, NCELLS, NCELLS)
u_el = basix.ufl.element(basix.ElementFamily.P, msh.basix_cell(), 2,
shape=(3,), dtype=default_scalar_type)
p_el = basix.ufl.element(basix.ElementFamily.P, msh.basix_cell(), 1,
dtype=default_scalar_type)
W = fem.functionspace(msh, basix.ufl.mixed_element([u_el, p_el]))
V_ux, ux0 = _zero_fn(W.sub(0).sub(0))
V_uy, uy0 = _zero_fn(W.sub(0).sub(1))
V_uz, uz0 = _zero_fn(W.sub(0).sub(2))
Q_p, p0 = _zero_fn(W.sub(1))
ux_load = fem.Function(V_ux)
bcs = [
fem.dirichletbc(ux0, fem.locate_dofs_geometrical((W.sub(0).sub(0), V_ux), _face(0, 0.)), W.sub(0).sub(0)),
fem.dirichletbc(uy0, fem.locate_dofs_geometrical((W.sub(0).sub(1), V_uy), _face(1, 0.)), W.sub(0).sub(1)),
fem.dirichletbc(uz0, fem.locate_dofs_geometrical((W.sub(0).sub(2), V_uz), _face(2, 0.)), W.sub(0).sub(2)),
fem.dirichletbc(ux_load, fem.locate_dofs_geometrical((W.sub(0).sub(0), V_ux), _face(0, 1.)), W.sub(0).sub(0)),
fem.dirichletbc(p0, fem.locate_dofs_geometrical((W.sub(1), Q_p), _origin), W.sub(1)),
]
up = fem.Function(W)
macro_H = fem.Constant(msh, default_scalar_type(np.zeros((3, 3))))
R, K = _build_forms(msh, W, up, macro_H)
problem = fem_petsc.NonlinearProblem(R, up, bcs=bcs, J=K,
petsc_options_prefix="nonperiodic_",
petsc_options=_SNES_OPTS)
ok = True
for step, alpha in enumerate(np.linspace(0., 1., NSTEPS + 1)):
ux_load.x.array[:] = alpha * EXX; ux_load.x.scatter_forward()
if step == 0:
continue
try:
problem.solve()
reason = problem.solver.getConvergedReason()
iters = problem.solver.getIterationNumber()
print(f"[nonperiodic] step={step} alpha={alpha:.4f} reason={reason} iters={iters}")
except PETSc.Error as exc:
print(f"[nonperiodic] step={step} alpha={alpha:.4f} FAILED: {exc}")
ok = False; break
return ok
# ── Case 2: dolfinx_mpc periodic BCs (diverges — this is the bug) ────────────
def run_periodic():
comm = MPI.COMM_WORLD
msh = mesh.create_unit_cube(comm, NCELLS, NCELLS, NCELLS)
u_el = basix.ufl.element(basix.ElementFamily.P, msh.basix_cell(), 2,
shape=(3,), dtype=default_scalar_type)
p_el = basix.ufl.element(basix.ElementFamily.P, msh.basix_cell(), 1,
dtype=default_scalar_type)
W = fem.functionspace(msh, basix.ufl.mixed_element([u_el, p_el]))
mpc = dolfinx_mpc.MultiPointConstraint(W)
bcs = []
for comp in range(3):
V_ui, ui0 = _zero_fn(W.sub(0).sub(comp))
dofs = fem.locate_dofs_geometrical((W.sub(0).sub(comp), V_ui), _origin)
bc = fem.dirichletbc(ui0, dofs, W.sub(0).sub(comp))
bcs.append(bc)
for axis in range(3):
mpc.create_periodic_constraint_geometrical(
W.sub(0).sub(comp),
_periodic_indicator(axis),
_periodic_relation(axis),
[bc], default_scalar_type(1.), default_scalar_type(TOL),
)
Q_p, p0 = _zero_fn(W.sub(1))
bcs.append(fem.dirichletbc(p0,
fem.locate_dofs_geometrical((W.sub(1), Q_p), _origin),
W.sub(1)))
mpc.finalize()
up = fem.Function(mpc.function_space)
macro_H = fem.Constant(msh, default_scalar_type(np.zeros((3, 3))))
R, K = _build_forms(msh, W, up, macro_H)
problem = dolfinx_mpc.NonlinearProblem(
R, up, mpc=mpc, bcs=bcs, J=K,
petsc_options={**_SNES_OPTS, "snes_error_if_not_converged": False},
)
ok = True
for step, alpha in enumerate(np.linspace(0., 1., NSTEPS + 1)):
macro_H.value[...] = default_scalar_type(np.diag([alpha * EXX, 0., 0.]))
if step == 0:
continue
_, reason, iters = problem.solve()
converged = reason > 0
print(f"[periodic] step={step} alpha={alpha:.4f} reason={reason} iters={iters} converged={converged}")
if not converged:
ok = False
return ok
def _print_versions():
import dolfinx, basix, ufl, petsc4py, mpi4py, numpy as np
libs = [
("dolfinx", getattr(dolfinx, "__version__", "n/a")),
("basix", getattr(basix, "__version__", "n/a")),
("ufl", getattr(ufl, "__version__", "n/a")),
("petsc4py", getattr(petsc4py, "__version__", "n/a")),
("mpi4py", getattr(mpi4py, "__version__", "n/a")),
("numpy", np.__version__),
]
for name in ("dolfinx_mpc",):
try:
libs.append((name, importlib.metadata.version(name)))
except Exception:
import dolfinx_mpc as _m
libs.append((name, getattr(_m, "__version__", "n/a")))
print("Library versions:")
for name, ver in libs:
print(f" {name:<14} {ver}")
print()
if __name__ == "__main__":
_print_versions()
print("=" * 60)
print("Case 1: non-periodic (Dirichlet BCs)")
print("=" * 60)
ok_np = run_nonperiodic()
print("=" * 60)
print("Case 2: periodic (dolfinx_mpc geometrical constraints)")
print("=" * 60)
ok_p = run_periodic()
print("=" * 60)
print(f"nonperiodic converged : {ok_np}")
print(f"periodic converged : {ok_p}")
Hi Jorgen, many thanks for the fix in #237. I was trying to implement a homogenization example which fails on the nonlinear solve and wanted to check what could be going wrong in the problem setup (since the same material model with normal dirichlet BC's converges just fine).
Summary
dolfinx_mpc.NonlinearProblemwithcreate_periodic_constraint_geometricalon amixed displacement–pressure space diverges with
SNES reason = -6(
DIVERGED_LINE_SEARCH) at every load step. The identical problem with standardDirichlet BCs converges in 2–3 Newton iterations per step.
Problem description
Kinematics
Unit cube$\Omega = [0,1]^3$ , periodic homogenization. Total deformation gradient:
where$\overline{\mathbf{H}}$ is a prescribed macro gradient (a
$\tilde{\mathbf{u}}$ is the periodic fluctuation.
fem.Constant) andConstitutive model
Near-incompressible Neo-Hookean,$[\mathbb{P}_2]^3 \times \mathbb{P}_1$ :
mixed space
Parameters:$\mu = 1$ , $\kappa = 200$ .
Periodic constraint Abaqus
*EQUATIONstyleFor every opposite-face node pair$(\mathbf{x}^+, \mathbf{x}^-)$ with
$\mathbf{x}^+ = \mathbf{x}^- + \mathbf{e}_k$ :
In Abaqus CTENODS notation (with corner reference nodes$C_0, C_k$ ):
Here$\overline{\mathbf{H}}$ enters via
fem.Constantdirectly in UFL, so nomacroscopic strain is carried by the corner nodes; the constraint reduces to
slave = master (zero RHS). The origin is pinned for rigid-body modes; pressure
is pinned at the origin.
dolfinx_mpcimplementationReproducer
issue_mpc_periodic_nonlinear.pyOutput
reason = -6→SNES_DIVERGED_LINE_SEARCHat every load step.Expected behaviour
The periodic and non-periodic formulations are mathematically equivalent for a
homogeneous single-material cube (no inclusions). Both should converge in a small
number of Newton iterations.
Actual behaviour
dolfinx_mpc.NonlinearProblemdiverges immediately withSNES_DIVERGED_LINE_SEARCH(reason = -6) at every load step,even at very small applied strains (
Any clues what is wrong with the setup ? Maybe the way the mpc is setup?