Model parameters#
In this tutorial, we’ll pick up the LinearIsotropicElasticity model from
Running your first model and read, change, and
differentiate through its moduli from Python.
The input file#
Same input file as the previous tutorial — re-used here so we can focus on the parameters:
%%writefile input.i
# Same linear-isotropic-elasticity model as the previous tutorial.
# Re-used here so the focus stays on parameter access / mutation rather
# than on a new physical setup.
[Models]
[elasticity]
type = LinearIsotropicElasticity
coefficients = '200e3 0.3'
coefficient_types = 'YOUNGS_MODULUS POISSONS_RATIO'
[]
[]
Writing input.i
Listing the parameters#
A NEML2 model is a torch.nn.Module, so named_parameters() works
the same way it does for any PyTorch model:
import neml2
model = neml2.load_model("input.i", "elasticity")
for name, param in model.named_parameters():
print(f"{name}: {param.data.item()}")
E: 200000.0
nu: 0.3
The two parameters E and nu correspond to the two coefficients
we passed in the input file.
Reading a parameter by name#
Each parameter is also exposed as a Python attribute on the model.
Reading model.E returns a Scalar — one of NEML2’s typed tensor
wrappers (see Tensor types for the full list):
print("E =", model.E)
print("nu =", model.nu)
E = Scalar(data=Parameter containing:
tensor(200000., dtype=torch.float64, requires_grad=True), sub_batch_ndim=0, sub_batch_state=(), sub_batch_meta=(), k_ndim=0, k_state=(), k_pairing=())
nu = Scalar(data=Parameter containing:
tensor(0.3000, dtype=torch.float64, requires_grad=True), sub_batch_ndim=0, sub_batch_state=(), sub_batch_meta=(), k_ndim=0, k_state=(), k_pairing=())
The underlying torch.nn.Parameter lives at .data. Use .data
when we need the raw torch.Tensor — for example to pull out a
numerical value with .item(). (This is user-facing; library code
stays on the typed wrappers.)
print("E =", model.E.data.item())
print("nu =", model.nu.data.item())
E = 200000.0
nu = 0.3
Changing a parameter#
There are two ways to change a parameter at runtime. Pick based on
whether we want to keep the existing nn.Parameter (and any
optimizer state attached to it) or replace it outright.
In place, on .data#
In-place mutation keeps the same nn.Parameter object. Leaf tensors
that require grad can’t be mutated in place directly — autograd would
lose the history. Wrap the mutation in torch.no_grad() to tell
PyTorch we’re intentionally side-stepping the graph for this
assignment:
import torch
with torch.no_grad():
model.E.data.fill_(150e3)
print("E =", model.E.data.item())
E = 150000.0
Use this inside an optimization loop — the optimizer holds a
reference to the underlying nn.Parameter (the one
named_parameters() returns), and in-place mutation through .data
writes back to that same Parameter.
Rebinding the attribute#
Assigning a fresh torch.nn.Parameter swaps the slot wholesale:
model.nu = torch.nn.Parameter(torch.tensor(0.25, dtype=torch.float64))
print("nu =", model.nu.data.item())
nu = 0.25
Reach for this when the new parameter has different properties — a
different dtype, a different requires_grad, or a batched shape
(see Model parameters revisited for batched parameter
values, or Vectorization for batched inputs).
Rebinding invalidates any optimizer that was tracking the old
nn.Parameter, so re-create the optimizer afterwards if there was
one.
Freezing a parameter#
Toggle requires_grad off and the parameter is excluded from
autodiff — its gradient stays None through a backward pass, and any
optimizer tracking it will leave it alone:
model.E.data.requires_grad_(False)
print("E.requires_grad =", model.E.data.requires_grad)
E.requires_grad = False
Use this to hold one modulus fixed while calibrating the other — see Parameter calibration for the full workflow.
Parameters flow into outputs#
Changing a parameter changes the next evaluation’s output. Reload the
model so the state is clean, then evaluate it once, halve E, and
evaluate again:
from neml2.types import SR2
model = neml2.load_model("input.i", "elasticity")
strain = SR2.fill(0.01, 0.0, 0.0, 0.0, 0.0, 0.0)
print("E = 200e3 ->", model(strain).data)
model.E = torch.nn.Parameter(torch.tensor(100e3, dtype=torch.float64))
print("E = 100e3 ->", model(strain).data)
E = 200e3 -> tensor([2692.3076, 1153.8462, 1153.8462, 0.0000, 0.0000, 0.0000],
grad_fn=<AddBackward0>)
E = 100e3 -> tensor([1346.1538, 576.9231, 576.9231, 0.0000, 0.0000, 0.0000],
grad_fn=<AddBackward0>)
For linear elasticity the stress scales linearly with \(E\), so the second row is exactly half the first. Any parameter change shows up on the next forward call — no need to re-load the input file.
Differentiating through parameters#
Autodiff flows through NEML2 parameters just like it does through any
other nn.Module weight. Backward from a scalar reduction of the
output gives gradients on each parameter:
model = neml2.load_model("input.i", "elasticity")
strain = SR2.fill(0.01, 0.0, 0.0, 0.0, 0.0, 0.0)
stress = model(strain)
stress.data.sum().backward()
print("dL/dE =", model.E.data.grad.item())
print("dL/dnu =", model.nu.data.grad.item())
dL/dE = 0.02499999979940744
dL/dnu = 24999.999386098607
This is what calibration workflows build on — embed the model in a
larger PyTorch graph, define a loss against experimental data, and
optimize the moduli with any torch.optim optimizer.
Automatic differentiation and
Parameter calibration walk the full pipeline; for
calibration through a time-stepped transient, see
Recurrent calibration with pyzag.
Where to go next#
A parameter doesn’t have to be a literal — it can also be supplied by another model, promoted to an input on the host, or shared across siblings in a
ComposedModel. See Model parameters revisited.Real material models compose several pieces (elasticity, hardening, flow rule, …), each contributing its own parameters. The mechanism is the subject of Cross-referencing and Model composition.
Batched evaluation — both over the inputs and over the parameters themselves — is covered in Vectorization.
Once we’re comfortable reading and mutating parameters, the Parameter calibration tutorials cover calibrating them against experimental data via PyTorch autograd.