Connecting to input files¶
You’ll take a Model subclass you wrote yourself and make it
loadable from an HIT input file, the same way the built-in models in
Running your first model are. Two ingredients do
the work: a decorator that registers the class under a type name, and
the HitSchema you already saw in
Declaring inputs, outputs, and parameters.
The example model¶
We’ll reuse the projectile model from the previous tutorial — a Vec
velocity in, a Vec acceleration out, under gravity plus linear drag:
with \(\mu\) a Scalar parameter and \(\boldsymbol{g}\) a constant Vec
buffer.
# A minimal NEML2 Model subclass illustrating the input-file wiring.
#
# Forward operator: gravity-plus-linear-drag projectile acceleration,
# a = g - mu * v
# with `g` a Vec buffer (constant gravity) and `mu` a single Scalar
# parameter. The point of this file is the *connection to HIT* — the
# schema declaration and the `@register_neml2_object` decorator — not the
# physics.
from __future__ import annotations
from neml2.factory import register_neml2_object
from neml2.models.model import Model
from neml2.schema import HitSchema, buffer, input, output, parameter
from neml2.types import Scalar, Vec
@register_neml2_object("ProjectileAcceleration")
class ProjectileAcceleration(Model):
"""Projectile acceleration under gravity and linear drag: a = g - mu * v."""
hit = HitSchema(
input("velocity", Vec, "Projectile velocity"),
output("acceleration", Vec, "Projectile acceleration"),
parameter("dynamic_viscosity", Scalar, "Drag coefficient mu", attr="mu"),
buffer(
"gravity",
Vec,
"Gravitational acceleration vector",
attr="g",
default=Vec.fill(0.0, -9.81, 0.0),
),
)
def forward(self, velocity, *nl_params, v=None):
raise NotImplementedError
Two things to notice:
@register_neml2_object("ProjectileAcceleration")is what makestype = ProjectileAccelerationwork in an input file. The string is the HIT type name; by convention it matches the Python class name, but it doesn’t have to.hit = HitSchema(...)lists the options the input block accepts. Eachinput/output/parameter/bufferentry becomes one HIT option of the same name. See Declaring inputs, outputs, and parameters for the full set of field helpers and what they accept.
The input file¶
Here’s a minimal HIT block that instantiates the class:
[Models]
[accel]
type = ProjectileAcceleration
velocity = 'v'
acceleration = 'a'
dynamic_viscosity = 0.05
[]
[]
Each line maps to one schema field:
type = ProjectileAccelerationpicks the class out of the registry.velocity = 'v'andacceleration = 'a'rename the input/output variables — without these lines the canonical names (velocity,acceleration) are used.dynamic_viscosity = 0.05sets the parameter value. It lands on the instance asself.mu(the attribute we asked for viaattr="mu").
Loading and inspecting¶
load_model looks up the type name in a registry that’s populated
as a side effect of importing your module — so you need to import
it first. In a normal script that’s just an import line; here,
projectile.py sits next to input.i, so we add the directory to
sys.path and import it.
import sys
sys.path.insert(0, ".")
import projectile # the @register_neml2_object runs at import time
import neml2
model = neml2.load_model("input.i", "accel")
model
ProjectileAcceleration()
The renames from the input file show up in input_spec and
output_spec:
model.input_spec, model.output_spec
({'v': neml2.types.vec.Vec}, {'a': neml2.types.vec.Vec})
The canonical names (velocity, acceleration) were replaced by the
HIT values (v, a). These are the names sibling models in a
ComposedModel would use to wire to ours — see
Composing with existing models.
The parameter is exposed under the attr name we picked:
model.mu
Scalar(data=Parameter containing:
tensor(0.0500, dtype=torch.float64, requires_grad=True), sub_batch_ndim=0, sub_batch_state=(), sub_batch_meta=(), k_ndim=0, k_state=(), k_pairing=())
Debugging “not registered” errors¶
If load_model raises a KeyError mentioning “not registered in
NativeRegistry”, the module holding @register_neml2_object was
almost certainly never imported. Fix the import (or reorder imports)
so the decorator runs first. To confirm a type is registered, ask
neml2-syntax:
neml2-syntax --load ./projectile.py --type ProjectileAcceleration
Loading the extension from the CLI¶
To use your custom model from the neml2-* shell tools, pass
--load pointing at the file (or a dotted module name on
sys.path). The flag runs the import before the input file is
parsed, so the registered type is ready by the time load_model is
called:
neml2-run --load ./projectile.py input.i driver
neml2-inspect --load ./projectile.py input.i accel
Repeat --load for multiple extensions — they import in the given
order, so later modules can depend on names registered earlier. See
CLI utilities for the full list of tools.
Where to go next¶
Declaring inputs, outputs, and parameters covers the schema fields in detail — typed inputs, options, parameters that can be promoted to runtime inputs.
The forward operator is the next step: fill in
forward()so the model actually computes something.Once your model loads, you’ll usually want to compose it with others inside a single
[Models]block — see Composing with existing models.