Modular UMAT Example — Composable Elasto-Plasticity

Demonstrates the simcoon.modular high-level Python interface that composes a constitutive model declaratively and runs it through the in-memory simcoon.solver.solve() driver. The C++ ModularUMAT infrastructure (ElasticityModule, YieldCriterion, hardening, …) is internal — the user only builds a ModularMaterial and hands its .props / .nstatev to the "MODUL" UMAT code registered in simcoon’s UMAT table.

The loading applies a monotonic tensile ramp to 2% strain, then two strain-controlled cycles between -2% and +2%, exposing isotropic-hardening growth and the initial yield plateau.

Both the material and the loading path are built in Python, and the results come back in memory.

import matplotlib.pyplot as plt
from simcoon import solver
from simcoon.modular import (
    ModularMaterial,
    IsotropicElasticity,
    Plasticity,
    VonMisesYield,
    VoceHardening,
)

plt.rcParams["figure.figsize"] = (14, 6)

1. Compose the constitutive model

Isotropic elasticity + von Mises yield + Voce isotropic hardening. Parameters: E=210 GPa, nu=0.3, sigma_Y=300 MPa, Q=200 MPa, b=10.

The elastic constants C1/C2 are ordinal slots whose meaning is fixed by the convention argument — here "Enu" (C1 = E, C2 = nu). Other parameterizations (“Kmu”, “lambdamu”, …) are accepted as well.

mat = ModularMaterial(
    elasticity=IsotropicElasticity(
        C1=210000.0, C2=0.3, alpha=1.2e-5, convention="Enu"
    ),
    mechanisms=[
        Plasticity(
            sigma_Y=300.0,
            yield_criterion=VonMisesYield(),
            isotropic_hardening=VoceHardening(Q=200.0, b=10.0),
        ),
    ],
)

print(mat.summary())
ModularMaterial:
  Elasticity: IsotropicElasticity
    C1=210000.0, C2=0.3, alpha=1.2e-05 [ENU]
  Mechanisms (1):
    [0] Plasticity
        sigma_Y=300.0
        yield: VonMisesYield
        iso_hard: VoceHardening
        kin_hard: NoKinematicHardening
  nprops=15, nstatev=8

2. Build the loading path

Two blocks, both small-strain and uniaxial (axial strain driven, the five other components stress-free): a monotonic ramp to +2%, then a two-cycle block alternating -2% / +2%. A Block repeats its step sequence ncycle times, which is how the cycling is expressed.

uniaxial = ["strain"] + ["stress"] * 5


def ramp(target, ninc):
    return solver.StepMeca(
        control=uniaxial, value=[target, 0, 0, 0, 0, 0],
        time=1.0, ninc=ninc, Dn_mini=1.0,
    )


path = [
    solver.Block(steps=[ramp(0.02, 200)]),
    solver.Block(steps=[ramp(-0.02, 100), ramp(0.02, 100)], ncycle=2),
]

3. Run the solver

mat.umat_name is "MODUL", the UMAT code registered at umat_smart.cpp:316 (id 200). mat.props serializes the composition into the flat array that the C++ umat_modular deserializes.

res = solver.solve(path, mat.umat_name, mat.props, mat.nstatev, T_init=293.0)

4. Plot the stress-strain curve

Results come back as numpy arrays in a components-first layout: [0] is the 11 component of each tensor history.

e11 = res["Strain"][0]
s11 = res["Stress"][0]
Wm, Wm_r, Wm_ir, _ = res["Wm"]

fig = plt.figure()

# Stress-strain curve
ax1 = fig.add_subplot(1, 2, 1)
plt.grid(True)
plt.tick_params(axis="both", which="major", labelsize=13)
plt.xlabel(r"Strain $\varepsilon_{11}$", size=14)
plt.ylabel(r"Stress $\sigma_{11}$ (MPa)", size=14)
plt.plot(e11, s11, c="royalblue", lw=1.5, label="MODUL: iso-elastic + VM + Voce")
plt.axhline(y=300.0, color="0.6", linestyle="--", lw=0.8, label=r"initial $\sigma_Y$")
plt.axhline(y=-300.0, color="0.6", linestyle="--", lw=0.8)
plt.legend(loc="best")
plt.title("Stress-strain response")

# Work terms vs time
ax2 = fig.add_subplot(1, 2, 2)
plt.grid(True)
plt.tick_params(axis="both", which="major", labelsize=13)
plt.xlabel("time (s)", size=14)
plt.ylabel("Work (MPa)", size=14)
plt.plot(res["Time"], Wm, c="black", label=r"$W_m$ (total)")
plt.plot(res["Time"], Wm_r, c="green", label=r"$W_m^r$ (recoverable)")
plt.plot(res["Time"], Wm_ir, c="blue", label=r"$W_m^{ir}$ (irreversible)")
plt.legend(loc="best")
plt.title("Energy decomposition")

plt.tight_layout()
plt.savefig("MODUL_stress_strain.png", dpi=120)
plt.show()
Stress-strain response, Energy decomposition

Total running time of the script: (0 minutes 0.454 seconds)

Gallery generated by Sphinx-Gallery