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.