Version: dev

models

Components with variables and equations.

models is an object keyed by component name. Each model requires exactly two fields: variables and equations.

{
  "esm": "1.0.0",
  "metadata": { "name": "SimpleDecay" },
  "models": {
    "ExponentialDecay": {
      "variables": {
        "N": {
          "type": "unknown",
          "units": "mol",
          "default": 100.0,
          "description": "Amount of decaying species"
        },
        "lambda": {
          "type": "parameter",
          "units": "1/s",
          "default": 0.1,
          "description": "Decay constant"
        }
      },
      "equations": [
        {
          "lhs": { "op": "D", "args": ["N"], "wrt": "t" },
          "rhs": { "op": "*", "args": [{ "op": "-", "args": ["lambda"] }, "N"] }
        }
      ]
    }
  }
}

variables is an object keyed by name, not an array — the name is the key, and there is no name field inside a variable.

Variables

There are exactly two declared types, and no third:

TypeMeaning
unknownA quantity the solver solves for. Its behaviour is stated by equations and nowhere else — there is no expression field on a variable.
parameterA quantity supplied to the solver: a default, or a distribution, optionally refreshed by an update.
FieldApplies toMeaning
typebothRequired. "unknown" or "parameter".
unitsbothUnit string, e.g. "kg/m^3". See units .
defaultbothConstant value. For an unknown this is its initial value.
default_unitsbothUnits of default, if different from units.
descriptionbothFree text.
shapebothOrdered list of index-set names the variable is arrayed over. Omitted means scalar.
locationbothAdvisory staggering tag ("cell_center", "x_face", …). Metadata only.
distributionparameterDraw the value instead of fixing it. Mutually exclusive with default.
updateparameterWhen the parameter refreshes and from what.

Everything else is derived

The finer categories a solver needs are not declared — they follow from the equations, and every binding exposes the same functions to recover them.

Unknowns partition into three sets:

SetDefinition
ode_statesunknowns appearing under D(·, t) on some equation LHS
observed_unknownsunknowns defined by an equation whose LHS names them — y ~ f(…) or y[i] ~ f(…)
algebraic_unknownsunknowns only constrained, never defined — no equation names them on its LHS, e.g. H*H*SO4 ~ Ksp

The split between the last two is semantic, not syntactic. An indexed LHS like y[i] ~ … defines the whole array y, so it is observed, even though it is not literally a bare variable. Only a genuine expression LHS — one naming no single variable — is an implicit constraint.

Parameters partition similarly into constant, sampled, discrete, and Brownian, according to their update and distribution.

Equations

An equation is { "lhs": <Expression>, "rhs": <Expression> }, optionally with a _comment. The form of the LHS is what gives the equation its role:

LHS formRole
{"op":"D","args":["u"],"wrt":"t"}time derivative — makes u an ODE state
{"op":"ic","args":["u"]}initial condition for u
"y" or {"op":"index","args":["y", …]}defines y — an observed unknown
any other expressionan implicit constraint (algebraic)

A structural time derivative is strictly unary: args holds exactly the differentiated variable, and the independent variable goes in wrt. Writing {"op":"D","args":["N","t"]} is wrong — t belongs in wrt.

{
  "equations": [
    { "lhs": { "op": "D", "args": ["N"], "wrt": "t" },
      "rhs": { "op": "*", "args": [{ "op": "-", "args": ["lambda"] }, "N"] } },

    { "lhs": { "op": "ic", "args": ["N"] }, "rhs": 100.0 },

    { "lhs": "halflife",
      "rhs": { "op": "/", "args": [{"op":"log","args":[2]}, "lambda"] } }
  ]
}

Other model fields

FieldMeaning
initialization_equationsEquations solved once at initialization.
guessesInitial guesses for an implicit solve, name -> value or expression.
system_kindDeclared system kind, when it cannot be derived.
discrete_events / continuous_eventsEvent definitions; see coupling .
subsystemsNested components, inline or by $ref.
tolerancePer-component solver tolerances.
testsInline tests carried with the model.
expression_templates / expression_template_importsComponent-scoped rewrite rules; see templates .

Arrayed variables

shape names the index sets a variable is arrayed over, in order:

{
  "index_sets": {
    "cells": { "kind": "range", "size": 100 },
    "species": { "kind": "enum", "members": ["O3", "NO", "NO2"] }
  },
  "models": {
    "Chem": {
      "variables": {
        "C": { "type": "unknown", "units": "mol/m^3", "shape": ["cells", "species"] }
      },
      "equations": []
    }
  }
}

Array-level expressions align their operands by index-set name and replicate along axes an operand does not declare. An operand carrying an index set the result is not shaped over is rejected with array_shape_mismatch.

Spatiality comes from shape, not from a per-component domain: a 0-D model is simply one whose variables are scalar.