Reduced-order FE² for hyperelastic materials in DOLFINx — first-order and
micromorphic computational homogenization on periodic RVEs, with POD + ECM
hyper-reduction and full-order buckling solvers included. Built on
FEniCSx / DOLFINx and
dolfinx_mpc.
The package ships with ready-to-use solvers in 2D and 3D for:
- quasi-static hyperelasticity with Newton–Raphson and arc-length continuation,
- post-buckling tracing through eigenvalue perturbation of unstable equilibria,
- first-order computational homogenization (CH1) on periodic RVEs
(Hill–Mandel averaging of
F̄,P̄, energyW̄, and tangentĀ), - micromorphic homogenization (MM, [1]) with a user-supplied enriched ansatz
u_total = (F̄ − I)·X + Σᵢ (vᵢ + X·gᵢ) φᵢ + w, returning effective stressesP̄,Πᵢ,Λᵢand the full 3 × 3 grid of tangents, - reduced-order modelling (POD + ECM hyper-reduction, [2]) and matching reduced RVE solvers for both CH1 and micromorphic kinematics,
- two-scale FE² simulation — a (higher-order) macroscopic continuum whose constitutive response at every quadrature point comes from a nested RVE, with either the full periodic solver or the reduced ROM as the inner driver, in both the classical and the micromorphic flavour.
Compression and buckling of various 2D/3D microstructures.
- Hyperelastic material models —
NeoHookeanout of the box, plus a genericMaterialModelinterface to plug in any strain energy density. - Stability monitoring — every Newton step optionally solves an eigenproblem (SLEPc) on the tangent stiffness. When an instability is detected, the lowest eigenmode is used to perturb the solution and continue onto the post-buckled branch.
- Adaptive time stepping with automatic step-size cutbacks on Newton failure.
- Arc-length continuation (
CylindricalArcLength) for tracing snap-through / snap-back responses where load- or displacement-control alone fails. - First-order homogenization (
fe2_rom.ch1) on Gmsh-generated RVEs usingdolfinx_mpcperiodic constraints, with a modularAverageQuantity/TangentBlockframework for the effective quantities (F̄,P̄,W̄,Ā) and the matching tangents. - Micromorphic homogenization (
fe2_rom.mm) — enriched ansatz with a user-supplied family of global modesφᵢ(from a linear buckling analysis or any other source) and extra macro variables(v, g=∇v). ReportsP̄,Πᵢ,Λᵢand the 3 × 3 grid of tangent blocksd{P̄, Π, Λ} / d{F̄, v, g}. - Projected linear constraint enforcement —
⟨w⟩ = 0,⟨w·φᵢ⟩ = 0,⟨(w·φᵢ) X⟩ = 0, etc. are imposed through a projected Newton solve (P K P x = P b); acorner_periodicflag toggles between fixed-corner BCs and full periodic constraints. - ROM toolkit (
fe2_rom.rom) — POD basis construction withH¹/L²inner products, ECM hyper-reduction (magic-point selection), and reduced online solvers for both CH1 (ReducedMicroSolver) and micromorphic kinematics. - FE² macro solvers —
fe2_rom.ch1.MacroSolverwires a macroscopic continuum to a nested first-order RVE at every quadrature point throughdolfinx_materials. A singlefull=True/Falseflag switches the inner driver between the full periodicMicroSolverand the ECM-reducedReducedMicroSolver.fe2_rom.mm.MacroMicromorphicSolvercouples the macroscopic displacement fielduwithN_modesscalar enrichment-amplitude fieldsvᵢ, with the constitutive response provided either by aMicromorphicRVEMaterial(nested RVE, FOM or ROM).- Both drivers feature adaptive load stepping, optional macro-level stability monitoring, and MPI parallelism over quadrature points.
- VTX (ADIOS2) output for ParaView visualisation and CSV reaction-force logging.
A conda/mamba environment file is provided:
mamba env create -f environment.yml
mamba activate fe2_rom_env
pip install -e .The FE² macro solvers (fe2_rom.ch1.MacroSolver,
fe2_rom.mm.MacroMicromorphicSolver) additionally require
dolfinx_materials, which is
not on PyPI. Clone it alongside this repo and install:
git clone https://github.com/bleyerj/dolfinx_materials
pip install dolfinx_materials/ --userA Dockerfile and Singularity.def are also included for containerised
deployments (HPC, CI, reproducible runs).
fe2_rom/
├── hyperelastic_solver/ # full-order FE solver
│ ├── solver.py # HyperelasticStabilitySolver
│ ├── solvers.py # NewtonSolver, ArcLengthSolver, CylindricalArcLength, ...
│ ├── stability.py # SLEPc-based eigenvalue / instability analysis
│ ├── material.py # MaterialModel, NeoHookean
│ ├── forms.py # weak-form assembly, basis_tensor_ufl
│ ├── boundary.py # ReactionProbe
│ ├── timestepping.py # adaptive TimeStepper
│ └── output.py # VTXManager, ReactionForceLogger
├── ch1/ # first-order classical homogenization
│ ├── microsolver.py # MicroSolver (full-order periodic RVE)
│ ├── macrosolver.py # MacroSolver (FE² macro driver, FOM or ROM inner)
│ ├── material.py # RVEMaterial (dolfinx_materials bridge)
│ ├── averages.py # AverageQuantity framework: F̄, P̄, W̄, Ā, TangentBlock
│ ├── constraints.py # LinearConstraint, ZeroVolumeAverage
│ ├── solvers.py # NewtonSolverFE2 (projected, MPC-aware)
│ └── exceptions.py # RVEConvergenceError
├── mm/ # micromorphic homogenization
│ ├── microsolver.py # MicroSolver — enriched ansatz with φ + (v, g)
│ ├── macrosolver.py # MacroMicromorphicSolver — coupled (u, v) macro driver
│ ├── material.py # DummyMicromorphicMaterial, MicromorphicRVEMaterial
│ ├── averages.py # EffectivePi, EffectiveLambda
│ └── constraints.py # ZeroVolumeAverageDot, ZeroVolumeAverageOuter
└── rom/ # reduced-order modelling
├── pod.py # POD basis construction (H¹ / L² inner products)
├── ecm.py # ECM hyper-reduction (magic-point selection)
├── solver_ch1.py # ReducedMicroSolver — CH1 reduced online stage
└── solver_mm.py # ReducedMicroSolver — micromorphic reduced online stage
examples/
├── hyperelastic_solver/
│ ├── example_1/ # 3D tetragonal lattice — compressive buckling
│ ├── example_2/ # 3D hexagonal lattice — compressive buckling
│ ├── example_3/ # 3D extruded honeycomb beam — bending/buckling
│ └── arc-length/ # snap-back of a deep parabolic arch
├── ch1/ # first-order computational homogenization
│ ├── example_1/ # 2D perforated RVE — FOM and ROM
│ ├── example_2/ # 3D periodic RVE — FOM and ROM
│ └── example_3/ # FE² — single-element macro cube × 3D periodic RVE (reuses example_2's ECM)
└── mm/ # micromorphic homogenization
├── example_1/ # standalone micromorphic RVE + snapshot sampling + ROM build
├── example_2/ # FE² macro micromorphic with dummy constitutive law (3D)
└── example_3/ # FE² macro micromorphic with nested RVE (FOM or ROM, 2D)
from mpi4py import MPI
from dolfinx import fem, io
from petsc4py import PETSc
from fe2_rom.hyperelastic_solver import (
HyperelasticStabilitySolver, NeoHookean,
TimeStepper, VTXManager, ReactionForceLogger,
)
comm = MPI.COMM_WORLD
mesh, cell_tags, facet_tags, *_ = io.gmsh.read_from_msh("lattice.msh", comm, 0, gdim=3)
material = NeoHookean(mu=1000.0, lmbda=2000.0)
solver = HyperelasticStabilitySolver(mesh, cell_tags, facet_tags, material)
u_top = fem.Constant(mesh, PETSc.ScalarType(0.0))
solver.add_bc(2, lambda x: x[2] < 1e-8, fem.Constant(mesh, 0.0))
solver.add_bc(2, lambda x: x[2] > 1 - 1e-8, u_top,
measure_reaction=True, reaction_direction=(0.0, 0.0, 1.0))
solver.setup(check_stability=True)
solver.run(
load_schedule=lambda t: setattr(u_top, "value", -0.25 * t),
timestepper=TimeStepper(t_end=1.0, dt_init=0.1, dt_min=1e-5),
output_manager=VTXManager(comm, "out.bp",
[solver.u_int, solver.F_func, solver.P_func, solver.J_func]),
reaction_logger=ReactionForceLogger(),
pert_amplitude_init=1e1, # eigenmode perturbation at the first bifurcation
)Run the full example:
cd examples/hyperelastic_solver/example_1
python run_solver.py # serial
mpirun -n 4 python run_solver.py # parallelimport numpy as np
from mpi4py import MPI
from fe2_rom.hyperelastic_solver import NeoHookean
from fe2_rom.ch1 import MicroSolver
solver = MicroSolver(
mesh_path="rve.msh", comm=MPI.COMM_WORLD, gdim=2,
material=NeoHookean(mu=1153.8, lmbda=1730.8),
average_quantities=["F", "P", "A"], # F̄, P̄, Ā via the modular framework
save_snapshots=["u_fluc", "P"], # for later POD
check_stability=True,
)
# Apply a macroscopic deformation gradient F̄
F_bar = np.array([[0.8, 0.0], [0.0, 1.0]])
history = solver(F_bar, pert_amplitude_init=1e-1)
# history[i] is a dict per accepted load step with keys "Fbar", "Pbar",
# "dPbar_dFbar", ...Run the full example:
cd examples/ch1/example_1
python run_homogenization.pycd examples/ch1/example_1
python run_homogenization.py # generate snapshots in output/
python build_rom.py # POD + ECM → ecm/
python run_homogenization_rom.py # reduced online stagebuild_rom.py constructs POD bases (energy criterion 99.99%) for the
fluctuation displacement u_fluc (H¹ inner product) and first
Piola–Kirchhoff stress P (L²), then ECM hyper-reduction selects "magic
points" on a sub-mesh. The reduced solver
(fe2_rom.rom.ReducedMicroSolver) reproduces P̄(F̄) and the tangent
Ā(F̄) at a fraction of the full-order cost. The POD + ECM construction
follows Guo et al. (2024) [2].
fe2_rom.ch1.MacroSolver wires a macroscopic continuum to a nested RVE at
every macro quadrature point. The inner driver is selected by a single
full flag — set full=True to use the full periodic MicroSolver, or
full=False (with rom_dir=...) to drive each qp with the ECM-reduced
ReducedMicroSolver.
import numpy as np
from mpi4py import MPI
from dolfinx import fem, mesh as dmesh
from fe2_rom.hyperelastic_solver import NeoHookean, TimeStepper, ReactionForceLogger
from fe2_rom.ch1 import MacroSolver
comm = MPI.COMM_WORLD
domain = dmesh.create_unit_cube(comm, 1, 1, 1, cell_type=dmesh.CellType.hexahedron)
solver = MacroSolver(
mesh=domain,
full=True, # full=False + rom_dir=... for ROM-backed FE²
n_qp=1,
rve_mesh_path="mesh.msh",
rve_material=NeoHookean(mu=1153.8, lmbda=1730.8),
rve_average_quantities=["P", "A"], # MacroSolver reads Pbar, dPbar_dFbar
rve_check_stability=True,
gdim=3, degree=1,
check_stability=True, # macro-level instability monitoring
)
zero, disp = fem.Constant(domain, 0.0), fem.Constant(domain, 0.0)
for sub in (0, 1, 2):
solver.add_bc(sub, lambda x: np.isclose(x[2], 0.0), zero)
solver.add_bc(2, lambda x: np.isclose(x[2], 1.0), disp,
measure_reaction=True, reaction_direction=(0.0, 0.0, 1.0))
solver.setup()
solver.solve(
output_dir="output",
timestepper=TimeStepper(t_end=1.0, dt_init=1.0, dt_min=1e-3),
loadhistory=lambda t: setattr(disp, "value", -0.05 * t),
reaction_logger=ReactionForceLogger(),
)Run the full example (single hex element, 3D RVE inside):
cd examples/ch1/example_3
python run_macro.pyThe example runs with full=True (FOM RVE) out of the box. To switch the
inner driver to the ROM (full=False), first build the ECM artifacts in
examples/ch1/example_2 (python build_rom.py) — run_macro.py reads
rom_dir=../example_2/ecm so the ROM is shared with that example rather
than duplicated on disk.
Each accepted macro step commits every RVE's state so nested solves warm-start from their previous converged configuration. On RVE non-convergence the macro step is rejected and the timestepper halves dt; on macro instability the displacement is perturbed along the lowest eigenmode and the Newton solve is re-driven.
The micromorphic MicroSolver enriches the classical periodic ansatz with a
user-supplied family of global modes φᵢ (extracted here from a linear
buckling analysis of the RVE) and a set of macro variables (v, g), following
the formulation of Rokoš et al. (2019) [1]:
u_total = (F̄ − I)·X + Σᵢ (vᵢ + X·gᵢ) φᵢ + w
import numpy as np
from mpi4py import MPI
from fe2_rom.hyperelastic_solver import NeoHookean
from fe2_rom.mm import MicroSolver
solver = MicroSolver(
mesh_path="rve.msh", comm=MPI.COMM_WORLD, gdim=2,
material=NeoHookean(mu=1153.8, lmbda=1730.8),
N=1, # number of enrichment modes φᵢ
degree=2,
save_snapshots=["u_fluc", "P"],
check_stability=True,
)
# Populate φᵢ from a linear buckling analysis of the reference state
eigvals = solver.compute_linear_buckling_modes(N=1, save_modes=True)
# Apply macro inputs (F̄, v, g)
Fbar = np.array([[0.9, 0.0], [0.0, 1.0]])
v = np.array([1.0])
g = np.array([[-0.2, 0.1]])
history = solver(Fbar, v, g, pert_amplitude_init=0.1)
# history[i] keys: Fbar, Pbar, Pi, Lambda, plus the 3×3 grid of tangents
# dPbar_dFbar, dPbar_dv, dPbar_dg, dPi_dFbar, dPi_dv, dPi_dg,
# dLambda_dFbar, dLambda_dv, dLambda_dgRun the standalone example and build a micromorphic ROM:
cd examples/mm/example_1
python run_micromorphic.py # FD-verified standalone solve, saves φ snapshots
python sample_micromorphic.py # sample (F̄, v, g) space for ROM training
python build_rom.py # POD + ECM on the micromorphic snapshots
python run_micromorphic_rom.py # reduced micromorphic online stagefe2_rom.mm.MacroMicromorphicSolver couples the macroscopic displacement
field u with N_modes scalar enrichment-amplitude fields vᵢ through the
weak form
∫ P : ∇(δu) dX = 0 (u-block)
∫ Πᵢ δvᵢ dX + ∫ Λᵢ · ∇(δvᵢ) dX = 0 ∀ i (v-block)
The constitutive response at each macro qp can be supplied by either a
DummyMicromorphicMaterial (linear, decoupled — useful for verifying the
mixed formulation) or a MicromorphicRVEMaterial (nested micromorphic RVE,
FOM or ROM).
import numpy as np
from mpi4py import MPI
from dolfinx import fem
from dolfinx.mesh import create_unit_square, CellType
from fe2_rom.hyperelastic_solver import NeoHookean, TimeStepper
from fe2_rom.mm import MacroMicromorphicSolver, MicromorphicRVEMaterial
from fe2_rom.rom import ReducedMicroSolver
phi_arrays = [np.load("phi_0.npy")] # produced by run_micromorphic.py
N_MODES = len(phi_arrays)
def rve_factory(rank, index):
return ReducedMicroSolver(
mesh_path="rve.msh", rom_dir="ecm",
material=NeoHookean(mu=1153.8, lmbda=1730.8),
phi=phi_arrays, gdim=2, degree=2,
comm=MPI.COMM_SELF,
)
material = MicromorphicRVEMaterial(rve_factory, N_modes=N_MODES, gdim=2)
mesh = create_unit_square(MPI.COMM_WORLD, 4, 4, CellType.quadrilateral)
solver = MacroMicromorphicSolver(mesh, n_qp=2, N_modes=N_MODES, material=material)
zero, disp = fem.Constant(mesh, 0.0), fem.Constant(mesh, 0.0)
solver.add_bc((0, 0), lambda x: np.isclose(x[0], 0.0), zero)
solver.add_bc((0, 1), lambda x: np.isclose(x[0], 0.0), zero)
solver.add_bc((0, 0), lambda x: np.isclose(x[0], 1.0), disp,
measure_reaction=True)
for i in range(N_MODES):
solver.add_bc((i + 1,), lambda x: np.ones(x.shape[1], dtype=bool), zero)
solver.setup()
solver.solve(
output_dir="output",
timestepper=TimeStepper(t_end=1.0, dt_init=0.25, dt_min=1e-6),
loadhistory=lambda t: setattr(disp, "value", -0.02 * t),
)Run the full examples:
cd examples/mm/example_2
python run_macro_micromorphic.py # 3D macro cube, dummy material — verifies formulation
cd ../example_3
python run_macro_micromorphic.py # 2D macro × micromorphic RVE (FOM or ROM)Set USE_ROM = True in example_3/run_macro_micromorphic.py to swap the
nested FOM MicroSolver for the reduced ReducedMicroSolver. The micromorphic
example_3 expects phi_*.npy and the ecm/ directory to already exist in
example_1/output/snapshots/ and example_1/ecm/ respectively — run
example_1 first.
Note: the FE² macro solvers (
ch1.MacroSolver,mm.MacroMicromorphicSolver) depend ondolfinx_materials— see the install instructions above.
-
Rokoš, O., Ameen, M. M., Peerlings, R. H. J., & Geers, M. G. D. (2019). Micromorphic computational homogenization for mechanical metamaterials with patterning fluctuation fields. Journal of the Mechanics and Physics of Solids, 123, 119–137. doi:10.1016/j.jmps.2018.08.019.
-
Guo, T., Rokoš, O., & Veroy, K. (2024). A reduced order model for geometrically parameterized two-scale simulations of elasto-plastic microstructures under large deformations. Computer Methods in Applied Mechanics and Engineering, 418, 116467. doi:10.1016/j.cma.2023.116467.
Released under the MIT License — free for academic and commercial use; please retain the copyright notice.


