Kinematics¶
Overview¶
The kinematics catalog supplies the small, composable primitives that encode non-mechanical contributions to a material’s deformation — the strains or volumetric changes that arise from temperature excursions, phase transformations, density changes during sintering, or freezing/melting of a contained fluid. Pair them with an elastic or inelastic constitutive model, which subtracts the non-mechanical contribution from the total kinematic measure to recover the mechanical part.
The catalog carves the space along two axes:
Output measure. Eigenstrain primitives return a symmetric rank-2 tensor
eigenstrain : SR2suitable for the small-strain additive split \(\boldsymbol{\varepsilon} = \boldsymbol{\varepsilon}^{\text{mech}} + \boldsymbol{\varepsilon}^{*}\). Deformation Jacobian primitives return ajacobian : Scalar\(J^{*}\) suitable for the finite-strain multiplicative split \(\boldsymbol{F} = \boldsymbol{F}^{\text{mech}} \boldsymbol{F}^{*}\) (combined with VolumeAdjustDeformationGradient to lift the scalar Jacobian into a deformation gradient correction).Driving quantity. Temperature, volume change, phase fraction, or a coupled swelling/phase-change state.
For the small-strain workflow these eigenstrains are typically wired into an SR2LinearCombination that subtracts every non-mechanical strain from the total strain before the elastic constitutive call. For the finite-strain workflow the deformation Jacobian primitives are multiplied together and then fed through VolumeAdjustDeformationGradient to produce the volume-corrected deformation gradient.
Math¶
Eigenstrain (small-strain additive split)¶
For an isotropic eigenstrain driven by a scalar field \(q\) (temperature, volume, phase fraction), NEML2 adopts the standard cumulative form
where \(\varepsilon^{*}_v\) is the (linear) volumetric eigenstrain and \(\boldsymbol{I}\) is the rank-2 identity. The three concrete leaves in the catalog differ only in how \(\varepsilon^{*}_v\) depends on its driver:
Here \(\alpha\) is the coefficient of thermal expansion, \(T_0\) the
stress-free reference temperature, \(V_0\) the reference volume,
\(\Delta V\) the volume fraction change between phases A and B, and
\(f \in [0,1]\) the current phase fraction. Each variant exposes the
same eigenstrain output name, which lets a downstream model treat
them interchangeably.
The mechanical (elastic) strain is then recovered by the additive split
with any plastic strain \(\boldsymbol{\varepsilon}^{p}\) also subtracted in inelastic flows.
Deformation Jacobian (finite-strain multiplicative split)¶
In the finite-strain setting NEML2 represents the same physics through a scalar deformation Jacobian \(J^{*}\) — the determinant of the volumetric correction to be removed from the total deformation gradient:
For the coupled swelling/phase-change case, \(\phi^{f}\) is the fluid volume fraction that participates in the swelling, \(c \in [0,1]\) is the phase fraction (0 for fully solid, 1 for fully liquid), \(\alpha\) is the swelling coefficient, and \(\Delta\Omega\) is the relative difference in reference volume between the two phases.
Once the per-mechanism Jacobians are multiplied together into a single \(J\), VolumeAdjustDeformationGradient lifts the scalar back to a rank-2 correction on the deformation gradient,
so that the mechanical part carries only the deviatoric (shape-changing) piece while the volumetric piece \(J\) is removed before the elastic call.
Example: thermal eigenstrain in a free-sintering model¶
The free_sintering regression scenario composes
ThermalEigenstrain into a full GTN poroplastic
free-sintering model. The eigenstrain enters through the elastic-strain
combination, so the elastic constitutive call sees only the mechanical
strain after both plastic and thermal contributions are removed:
# neml2
# Native port of tests/regression/solid_mechanics/viscoplasticity/free_sintering/model.i.
# Free sintering at zero applied strain with thermal eigenstrain + Olevsky-Skorohod
# sintering stress driving GTN poroplasticity. Batch (100, 10): 100 time steps,
# 10 surface-tension values (gamma in [0, 150]).
[Tensors]
# end_time = LogspaceScalar(3, 3, 10) -> 10 copies of 10^3 = 1000.
[end_time]
type = Python
expr = 'Scalar(torch.logspace(3.0, 3.0, 10, dtype=torch.float64))'
[]
# times = LinspaceScalar(0, end_time, 100) -> shape (100, 10).
[times]
type = Python
expr = 'Scalar(end_time.data.unsqueeze(0) * torch.linspace(0.0, 1.0, 100, dtype=torch.float64).unsqueeze(-1))'
[]
# start_temperature = LinspaceScalar(300, 300, 10) -> 10 copies of 300.
[start_temperature]
type = Python
expr = 'Scalar.linspace(300.0, 300.0, 10)'
[]
# end_temperature = LinspaceScalar(1800, 1800, 10) -> 10 copies of 1800.
[end_temperature]
type = Python
expr = 'Scalar.linspace(1800.0, 1800.0, 10)'
[]
# temperatures = LinspaceScalar(start_temperature, end_temperature, 100) -> (100, 10).
[temperatures]
type = Python
expr = 'Scalar(start_temperature.data.unsqueeze(0) + (end_temperature.data - start_temperature.data).unsqueeze(0) * torch.linspace(0.0, 1.0, 100, dtype=torch.float64).unsqueeze(-1))'
[]
# max_strain = FillSR2(exx=0, eyy=0, ezz=0) batched (10,) -> all zeros.
# Diagonal 3-arg fill places exx/eyy/ezz on slots 0/1/2 with no Mandel scaling.
[max_strain]
type = Python
expr = 'SR2(torch.zeros((10, 6), dtype=torch.float64))'
[]
# strains = LinspaceSR2(0, max_strain, 100) -> (100, 10, 6), all zeros.
[strains]
type = Python
expr = 'SR2(torch.zeros((100, 10, 6), dtype=torch.float64))'
[]
# f0 = FullScalar(0.36, (10,))
[f0]
type = Python
expr = 'Scalar.full(10, fill_value=0.36)'
[]
# gamma = LinspaceScalar(0, 150, 10) -> shape (10,)
[gamma]
type = Python
expr = 'Scalar.linspace(0.0, 150.0, 10)'
[]
[]
[Drivers]
[driver]
type = TransientDriver
model = 'model'
prescribed_time = 'times'
force_SR2_names = 'E'
force_SR2_values = 'strains'
force_Scalar_names = 'temperature'
force_Scalar_values = 'temperatures'
ic_Scalar_names = 'void_fraction'
ic_Scalar_values = 'f0'
save_as = 'result.pt'
[]
[regression]
type = TransientRegression
driver = 'driver'
reference = 'gold/result.pt'
[]
[]
[Models]
[isoharden]
type = VoceIsotropicHardening
saturated_hardening = 5
saturation_rate = 1.2
[]
[sintering_stress]
type = OlevskySinteringStress
surface_tension = 'gamma'
particle_radius = 3e-4
[]
[eigenstrain]
type = ThermalEigenstrain
reference_temperature = 300
CTE = 1e-6
[]
[elastic_strain]
type = SR2LinearCombination
from = 'E plastic_strain eigenstrain'
to = 'elastic_strain'
weights = '1 -1 -1'
[]
[elasticity]
type = LinearIsotropicElasticity
coefficients = '3e4 0.3'
coefficient_types = 'YOUNGS_MODULUS POISSONS_RATIO'
strain = 'elastic_strain'
[]
[mandel_stress]
type = IsotropicMandelStress
cauchy_stress = 'stress'
[]
[j2]
type = SR2Invariant
invariant_type = 'VONMISES'
tensor = 'mandel_stress'
invariant = 'flow_invariant'
[]
[sh]
type = SR2Invariant
invariant_type = 'I1'
tensor = 'mandel_stress'
invariant = 'hydrostatic_stress'
[]
[sp]
type = ScalarLinearCombination
from = 'hydrostatic_stress sintering_stress'
to = 'poro_invariant'
weights = '1 -1'
[]
[q1]
type = ArrheniusParameter
temperature = 'temperature'
reference_value = 8000
activation_energy = 5e4
ideal_gas_constant = 8.314
[]
[yield_surface]
type = GTNYieldFunction
yield_stress = 60.0
q1 = 'q1'
q2 = 0.01
q3 = 1.57
isotropic_hardening = 'isotropic_hardening'
[]
[flow]
type = ComposedModel
models = 'j2 sh sp yield_surface'
[]
[flow_rate]
type = PerzynaPlasticFlowRate
reference_stress = 500
exponent = 2
[]
[normality]
type = Normality
model = 'flow'
function = 'yield_function'
from = 'mandel_stress isotropic_hardening'
to = 'flow_direction isotropic_hardening_direction'
[]
[Eprate]
type = AssociativePlasticFlow
[]
[eprate]
type = AssociativeIsotropicPlasticHardening
[]
[voidrate]
type = GursonCavitation
[]
[integrate_Ep]
type = SR2BackwardEulerTimeIntegration
variable = 'plastic_strain'
[]
[integrate_ep]
type = ScalarBackwardEulerTimeIntegration
variable = 'equivalent_plastic_strain'
[]
[integrate_void]
type = ScalarBackwardEulerTimeIntegration
variable = 'void_fraction'
[]
[surface]
type = ComposedModel
models = 'isoharden sintering_stress elastic_strain elasticity mandel_stress flow flow_rate normality Eprate eprate voidrate integrate_Ep integrate_ep integrate_void'
[]
[]
[EquationSystems]
[eq_sys]
type = NonlinearSystem
model = 'surface'
unknowns = 'plastic_strain equivalent_plastic_strain void_fraction'
residuals = 'plastic_strain_residual equivalent_plastic_strain_residual void_fraction_residual'
[]
[]
[Solvers]
[newton]
type = Newton
linear_solver = 'lu'
[]
[lu]
type = DenseLU
[]
[]
[Models]
[predictor]
type = ConstantExtrapolationPredictor
unknowns_SR2 = 'plastic_strain'
unknowns_Scalar = 'equivalent_plastic_strain void_fraction'
[]
[return_map]
type = ImplicitUpdate
equation_system = 'eq_sys'
solver = 'newton'
predictor = 'predictor'
[]
[model]
type = ComposedModel
models = 'eigenstrain return_map elastic_strain elasticity'
additional_outputs = 'plastic_strain equivalent_plastic_strain void_fraction'
[]
[]
Explanation¶
The kinematic piece of the model is concentrated in two [Models]
blocks.
[eigenstrain] declares a ThermalEigenstrain with
reference_temperature = 300 and CTE = 1e-6. At every batch entry
its temperature input is sourced from the temperature force
prescribed by the driver, and it produces eigenstrain : SR2 —
the cumulative thermal strain \(\alpha(T - T_0)\,\boldsymbol{I}\)
relative to the stress-free 300 K state.
[elastic_strain] is an SR2LinearCombination that
implements the additive split
with from = 'E plastic_strain eigenstrain' and
weights = '1 -1 -1'. The output elastic_strain is then the strain
that the LinearIsotropicElasticity block consumes — so by
the time stress is evaluated the thermal contribution has already
been peeled off. This is the typical wiring pattern for eigenstrains
in NEML2: declare the eigenstrain model, then subtract its
eigenstrain output from the total strain inside an
SR2LinearCombination whose result is consumed by elasticity.
To extend this composition to additional eigenstrain sources — say
adding a phase-transformation contribution via
PhaseTransformationEigenstrain — declare the second
eigenstrain block, give its output a distinct variable name, and add
it to the from / weights lists of the same SR2LinearCombination.
No other part of the model needs to change because every eigenstrain
leaf in the catalog publishes the same eigenstrain : SR2 surface.
For finite-strain workflows the analogous pattern uses
ThermalDeformationJacobian or
SwellingAndPhaseChangeDeformationJacobian, multiplied
together via a ScalarMultiplication block, then handed
to VolumeAdjustDeformationGradient to produce the
mechanical deformation gradient that the elastic model consumes.
On the small-strain side, VolumeChangeEigenstrain plays
the corresponding role when a volume-change eigenstrain is needed.
Note
All eigenstrain leaves are cumulative — they return the total non-mechanical strain relative to the reference state, not an increment. This is why subtraction at the elastic-strain step yields the correct mechanical strain regardless of how the temperature or phase history evolves through time.
See also¶
Model composition — the underlying mechanics of wiring small
Modelpieces together into a full constitutive composition.Elasticity — the elastic constitutive models that consume the mechanical strain produced by the eigenstrain subtraction shown above.
Plasticity — plastic-strain primitives combine with eigenstrains via the same
SR2LinearCombinationpattern.Syntax catalog entries for the individual types: ThermalEigenstrain, VolumeChangeEigenstrain, PhaseTransformationEigenstrain, ThermalDeformationJacobian, SwellingAndPhaseChangeDeformationJacobian, VolumeAdjustDeformationGradient.
Syntax catalog — the full per-type option reference.