Model parameters

You’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:

Listing 2 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'
  []
[]

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 you 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 you 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 you’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.999386098603

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 you’re comfortable reading and mutating parameters, the Parameter calibration tutorials cover calibrating them against experimental data via PyTorch autograd.