CLI utilities

The command line is the third way to use neml2 — alongside the Python (Python integration) and C++ (C++ integration) runtimes, you can drive a model end to end from a shell, with no code. The NEML2 wheel installs five console scripts:

Tool

Purpose

neml2-run

Drive a model through a load history from the shell.

neml2-inspect

Print the structural summary of a model.

neml2-syntax

Browse the registered-object catalog.

neml2-compile

Export a model to an AOT-Inductor package.

neml2-stub

Regenerate .pyi type stubs for the pybind11 extension modules.

The first four share a common style — each takes an input file (where one is needed), each forwards trailing positional tokens as HIT overrides (Models/elasticity/E:=210000), each is implemented in pure Python under neml2/cli/ and is also importable as a regular Python function.

All four of those tools accept a cumulative --load PATH flag for importing user-defined extensions (custom Model / Driver / Solver / [Tensors] / [Data] classes) before the tool’s own work begins. PATH is either a path to a .py file or a package directory, or a dotted module name on sys.path. The matching @register_neml2_object decorators fire on import and the new types become resolvable from the input file and visible to neml2-syntax. Repeat --load for several extensions — they import in the order given, so later modules may depend on names registered by earlier ones.

# A custom model defined in ./my_models.py is now driveable by neml2-run
# (and inspectable by neml2-inspect / neml2-compile, listed by neml2-syntax).
neml2-run --load ./my_models.py input.i my_driver

# Multiple extensions in priority order — later may depend on earlier:
neml2-syntax --load ./shared.py --load ./project_models.py --summary --json -

See Connecting to input files for the author’s side of the contract.

Most of the worked examples below reuse a single input file — a linear isotropic elastic model named elasticity, wrapped in a five-step TransientDriver named driver:

Listing 27 input.i
# Small worked example for the CLI utilities tutorial: a linear isotropic
# elastic model driven through a 5-step uniaxial tension history. The same
# input is reused across the neml2-run / neml2-inspect / neml2-compile
# subsections.

[Tensors]
  [times]
    type = Python
    expr = 'Scalar.linspace(0, 1, 5)'
  []
  [strains]
    type = Python
    expr = 'SR2(torch.tensor([0.01, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=torch.float64).reshape(1, 6) * torch.linspace(0.0, 1.0, 5, dtype=torch.float64).reshape(5, 1))'
  []
[]

[Models]
  [elasticity]
    type = LinearIsotropicElasticity
    coefficients      = '200e3          0.3'
    coefficient_types = 'YOUNGS_MODULUS POISSONS_RATIO'
  []
[]

[Drivers]
  [driver]
    type = TransientDriver
    model = 'elasticity'
    prescribed_time = 'times'
    force_SR2_names = 'strain'
    force_SR2_values = 'strains'
    save_as = 'result.pt'
  []
[]

neml2-run

Loads an input file, instantiates the named driver, and calls its run() method. Use it whenever you want to step a model through a load history from the shell — from a shell script, a CI job, a parameter sweep — without writing any Python.

usage: neml2-run [-h] [--device {cpu,cuda}] [--dtype {float64,float32}]
                 [--load PATH]
                 input driver

Run a driver from an input file. Trailing positional tokens are treated as HIT
overrides (e.g. 'Models/elasticity/E:=210000').

positional arguments:
  input                 path to the input file
  driver                name of the driver in the input file

options:
  -h, --help            show this help message and exit
  --device {cpu,cuda}   Set torch's default device before loading. Tensors
                        built by [Tensors] Python expressions inherit this.
                        Default: cpu.
  --dtype {float64,float32}
                        Set torch's default dtype before loading. NEML2 models
                        are uniformly float64 by convention; AOTI artifacts
                        compile-pin their dtype and the runtime rejects silent
                        coercion, so float64 is the safe default. Override
                        only if you know the artifact was compiled with
                        float32.
  --load PATH           Import a user extension before processing. PATH is
                        either a filesystem path to a .py file / package
                        directory, or a dotted module name already importable
                        from sys.path. Repeatable; modules are imported in the
                        order given so later modules may depend on names
                        registered by earlier ones.

Running our shared example: neml2-run itself emits no output on success and returns a zero exit code, so a clean run looks empty in CI. (Drivers can still print their own progress.)

$ neml2-run input.i driver

The trailing-token convention lets you tweak scalar options without editing the file — anything after the two declared positionals is forwarded to the HIT parser as an override. The same convention works for neml2-inspect and neml2-compile:

$ neml2-run input.i driver Drivers/driver/save_as:=other.pt

List-valued overrides (e.g. swapping coefficients = '200e3 0.3' for a different pair) need careful shell quoting around the whole value. If that gets fiddly, edit the input file directly or use the Python API.

neml2-inspect

Loads a single model from an input file and prints its structural summary: input variables, output variables, parameters, and buffers (each with dtype, device, and shape). Use it any time you want to verify the wiring of a model before driving it — wrong variable names, missing parameters, and unexpected shapes all show up here in one screenful.

usage: neml2-inspect [-h] [--json] [--load PATH] input model

Summarize the structure of a model. Trailing positional tokens are treated as
HIT overrides (e.g. 'Models/elasticity/E:=210000').

positional arguments:
  input        path to the input file
  model        name of the model in the input file to inspect

options:
  -h, --help   show this help message and exit
  --json       emit a structured JSON description on stdout instead of the
               human-readable format
  --load PATH  Import a user extension before processing. PATH is either a
               filesystem path to a .py file / package directory, or a dotted
               module name already importable from sys.path. Repeatable;
               modules are imported in the order given so later modules may
               depend on names registered by earlier ones.

The summary view (default):

$ neml2-inspect input.i elasticity
Model: LinearIsotropicElasticity

Inputs (1):
  strain: type=SR2

Outputs (1):
  stress: type=SR2

Parameters (2):
  E: dtype=torch.float64, device=cpu, shape=[]
  nu: dtype=torch.float64, device=cpu, shape=[]

Buffers (0):

The same data is available as a JSON document for programmatic consumption via --json:

$ neml2-inspect --json input.i elasticity
{"name": "LinearIsotropicElasticity", "inputs": [{"name": "strain", "type": "SR2"}], "outputs": [{"name": "stress", "type": "SR2"}], "parameters": [{"name": "E", "dtype": "torch.float64", "device": "cpu", "shape": []}, {"name": "nu", "dtype": "torch.float64", "device": "cpu", "shape": []}], "buffers": [], "retcode": 0}
...

neml2-syntax

Browses the registered-object catalog: every concrete class that can appear in a HIT input file, along with its section, docstring, and full option schema. It is the discovery tool — use it when you don’t yet know which class to instantiate, or when you want to see what options a class accepts without opening the source.

usage: neml2-syntax [-h] [--json JSON] [--section SECTION] [--type TYPE]
                    [--summary] [--server] [--load PATH]

Extract Python-native object syntax from the native registry as JSON.

options:
  -h, --help         show this help message and exit
  --json JSON        redirect JSON output to a file
  --section SECTION  only emit objects whose input-file section matches
  --type TYPE        only emit the object whose registered type matches
  --summary          emit only type, section, source_path, and doc string for
                     each object
  --server           run as a JSON server on stdin/stdout
  --load PATH        Import a user extension before processing. PATH is either
                     a filesystem path to a .py file / package directory, or a
                     dotted module name already importable from sys.path.
                     Repeatable; modules are imported in the order given so
                     later modules may depend on names registered by earlier
                     ones.

--summary drops the per-option schema and emits one entry per registered type — perfect for browsing. Combine with --section to restrict to a single input-file section:

$ neml2-syntax --section Drivers --summary --json -
[
  {
    "type": "TransientDriver",
    "section": "Drivers",
    "doc": "Drive a model over a prescribed time history.\n\nMirrors C++ ``TransientDriver``. See module docstring for differences.",
    "source_path": "drivers/TransientDriver.py",
    "class_name": ""
  },
  {
    "type": "TransientRegression",
    "section": "Drivers",
    "doc": "Run a TransientDriver and diff its result against a gold ``.pt`` file.",
    "source_path": "drivers/TransientRegression.py",
    "class_name": ""
  },
  {
...

Drop --summary and use --type <Name> for the full schema of one class — the same data the documentation’s syntax catalog under Syntax catalog is generated from:

$ neml2-syntax --type LinearIsotropicElasticity --json -
[
  {
    "type": "LinearIsotropicElasticity",
    "section": "Models",
    "doc": "Relate elastic strain to stress (or stress to strain, in compliance form)\nfor a linear isotropic material.",
    "source_path": "models/solid_mechanics/elasticity/LinearIsotropicElasticity.py",
    "class_name": "",
    "options": [
      {
        "name": "strain",
        "doc": "Elastic strain",
        "ftype": "INPUT",
        "required": true,
        "type": "SR2",
        "value": ""
      },
...

neml2-compile

Exports a model to an AOT-Inductor package — one or more .pt2 files plus a metadata JSON and a drop-in HIT stub — that can be loaded later from either Python or C++ without parsing the original input file. Use it when you want a dependency-light artifact for inference: shipping a calibrated model to a downstream simulator, dropping the HIT parser from a deployment image, baking parameters into a frozen graph for performance.

usage: neml2-compile [-h] (--model NAME | --driver NAME) [--output-dir DIR]
                     [--device DEVICE [DEVICE ...]]
                     [--dtype {float64,float32}] [-p NAME] [-d OUT:IN]
                     [--example-batch-shape SPEC] [--dynamic-batch]
                     [--no-dynamic-batch] [--load PATH]
                     INPUT.i

Compile a NEML2 Python-native model to AOTI artifacts (.pt2 + metadata JSON)
and emit a runnable HIT stub.

positional arguments:
  INPUT.i               HIT input file path.

options:
  -h, --help            show this help message and exit
  --model NAME          Compile the named [Models] block. The emitted stub
                        contains only the AOTIModel shim for this model -- no
                        [Drivers] block. Pair with a custom driver in a
                        separate file when you want to run it.
  --driver NAME         Compile whatever model the named [Drivers] block
                        targets (via its `model = '...'` field). The stub
                        keeps the named driver so the result is runnable as-is
                        via `neml2-run`. Use this when you want a self-
                        contained, drop-in replacement for the original
                        driver.
  --output-dir DIR      Collection dir for the compiled artifacts (default:
                        ./aoti/). Each model lands in <DIR>/<NAME>/<device>/
                        with a standalone <DIR>/<NAME>_aoti.i stub next to it.
  --device DEVICE [DEVICE ...]
                        Target device(s) for the artifact, baked at export
                        time. Accepts more than one (e.g. --device cpu cuda):
                        one complete artifact is emitted per device into a
                        subfolder named by the device (<output-dir>/cpu/,
                        <output-dir>/cuda/), ready for a multi-device
                        dispatcher to load.
  --dtype {float64,float32}
                        Floating-point dtype for the artifact (baked at export
                        time).
  -p NAME, --parameter NAME
                        Promote a parameter or buffer (fully-qualified dotted
                        name) to runtime-flexible status -- it becomes a graph
                        input rather than a baked constant. Repeatable. With
                        no -p flags the model is fully baked.
  -d OUT:IN, --derivative OUT:IN
                        Compile the derivative (Jacobian / JVP) graph for the
                        OUT:IN output-input pair. Repeatable. Omit a side to
                        select all on that side: 'stress:strain' is one pair,
                        'stress:' all inputs of stress, ':strain' all outputs
                        wrt strain, ':' all pairs. With no -d flags NO
                        derivative graphs are compiled (smallest artifact) and
                        the runtime jvp()/jacobian() raise until recompiled
                        with -d.
  --example-batch-shape SPEC
                        Example shape used when tracing. Two forms: --example-
                        batch-shape '(2,)' (uniform across all inputs)
                        --example-batch-shape strain='(2;100)' --example-
                        batch-shape temperature='(2,)' (per-variable;
                        repeatable) The semicolon delimits dynamic-batch from
                        sub-batch axes -- '(2;100)' means dyn=(2,),
                        sub=(100,). Overrides [Settings]/example_batch_shape.
  --dynamic-batch       Compile a dynamic-batch artifact: the leading batch
                        dim of the example shape becomes a runtime-variable
                        torch.export Dim. Default unless overridden by
                        [Settings]/dynamic_batch in the input.
  --no-dynamic-batch    Pin the batch dim at the example shape. Required when
                        a baked parameter has rank>=1 and would specialize the
                        dynamic dim.
  --load PATH           Import a user extension before processing. PATH is
                        either a filesystem path to a .py file / package
                        directory, or a dotted module name already importable
                        from sys.path. Repeatable; modules are imported in the
                        order given so later modules may depend on names
                        registered by earlier ones.

Derivative graphs are opt-in: with no -d flag only forward is compiled and jvp / jacobian raise at runtime. Request the pairs you need with -d OUT:IN (e.g. -d stress:strain, or -d : for all).

The end-to-end walkthrough — emitted file layout, loading the artifact from Python, parameter promotion (-p <name>), and the trade-offs against eager mode — lives in Compiled models.

neml2-stub

Regenerates .pyi type stubs for every pybind11 extension module in the installed neml2 package (currently neml2.aoti._aoti). The stubs are written next to their .so files so that pyright and IDE autocompletion resolve from ._aoti import Model cleanly.

neml2-stub: generating stubs for neml2.aoti._aoti -> /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages
usage: pybind11-stubgen [-h] [-o OUTPUT_DIR] [--root-suffix ROOT_SUFFIX]
                        [--ignore-invalid-expressions REGEX]
                        [--ignore-invalid-identifiers REGEX]
                        [--ignore-unresolved-names REGEX]
                        [--ignore-all-errors]
                        [--enum-class-locations REGEX:LOC]
                        [--numpy-array-wrap-with-annotated | --numpy-array-use-type-var | --numpy-array-remove-parameters]
                        [--print-invalid-expressions-as-is]
                        [--print-safe-value-reprs REGEX] [--exit-code]
                        [--dry-run] [--stub-extension EXT]
                        MODULE_NAME

Generates stubs for specified modules

positional arguments:
  MODULE_NAME           module name

options:
  -h, --help            show this help message and exit
  -o OUTPUT_DIR, --output-dir OUTPUT_DIR
                        The root directory for output stubs
  --root-suffix ROOT_SUFFIX
                        Top-level module directory suffix
  --ignore-invalid-expressions REGEX
                        Ignore invalid expressions matching REGEX
  --ignore-invalid-identifiers REGEX
                        Ignore invalid identifiers matching REGEX
  --ignore-unresolved-names REGEX
                        Ignore unresolved names matching REGEX
  --ignore-all-errors   Ignore all errors during module parsing
  --enum-class-locations REGEX:LOC
                        Locations of enum classes in <enum-class-name-
                        regex>:<path-to-class> format. Example:
                        `MyEnum:foo.bar.Baz`
  --numpy-array-wrap-with-annotated
                        Replace numpy/scipy arrays of 'ARRAY_T[TYPE, [*DIMS],
                        *FLAGS]' format with 'Annotated[ARRAY_T, TYPE,
                        FixedSize|DynamicSize(*DIMS), *FLAGS]'
  --numpy-array-use-type-var
                        Replace 'numpy.ndarray[numpy.float32[m, 1]]' with
                        'numpy.ndarray[tuple[M, typing.Literal[1]],
                        numpy.dtype[numpy.float32]]'
  --numpy-array-remove-parameters
                        Replace 'numpy.ndarray[...]' with 'numpy.ndarray'
  --print-invalid-expressions-as-is
                        Suppress the replacement with '...' of invalid
                        expressionsfound in annotations
  --print-safe-value-reprs REGEX
                        Override the print-safe check for values matching
                        REGEX
  --exit-code           On error exits with 1 and skips stub generation
  --dry-run             Don't write stubs. Parses module and report errors
  --stub-extension EXT  The file extension of the generated stubs. Must be
                        'pyi' (default) or 'py'

This tool is primarily used by CI (between install and pyright) and by the wheel-building pipeline. Any extra arguments are forwarded verbatim to pybind11_stubgen.

Where to go next

  • The five scripts are also importable as neml2.cli.run.main, neml2.cli.inspect.main, neml2.cli.syntax.main, neml2.cli.aoti_compile.main, and neml2.cli.stub.main if you want to drive them in-process rather than via the shell.

  • The Python-first path — neml2.load_model(...) + direct call — is the subject of Running your first model and is usually what you want when you need to inspect or post-process results in memory.

  • Compiled models picks up where neml2-compile leaves off: loading .pt2 artifacts, runtime parameter promotion, and the C++ deployment story.