Version: dev

observed_unknowns

Stable function · Variable classification

Tier: stable · Kind: function · Topic: Variable classification

JuliaTypeScriptPythonRustGo
observed_unknownsobservedUnknownsobserved_unknownsobserved_unknownsObservedUnknowns

Julia

observed_unknowns(model::Model)
observed_unknowns(model::Model) -> Vector{String}

The unknowns defined by a bare-variable LHS (y ~ f(…)) — eliminable and materializable (esm-spec §6.3.1). Sorted lexicographically.

Source

Example (inspect)

model = doc.models["Decay"]
@assert sort(collect(keys(model.variables))) == ["N", "half_life", "lambda"]

# The two declared kinds...
@assert unknowns(model) == ["N", "half_life"]
@assert parameters(model) == ["lambda"]

# ...and what each unknown is, derived from the equations.
@assert ode_states(model) == ["N"]                 # has a D(N)/dt equation
@assert observed_unknowns(model) == ["half_life"]  # defined explicitly
@assert algebraic_unknowns(model) == String[]      # none solved implicitly

defs = observed_definitions(model)
@assert to_ascii(defs["half_life"]) == "log(2) / lambda"

From docs/api/examples/julia/documents.jl

TypeScript

function observedUnknowns(model: Model): string[]

Unknowns an equation DEFINES — its LHS naming them, bare (y ~ f(…)) or indexed (y[i] ~ f(…), which defines the whole array y) — eliminable, materializable (esm-spec §6.3.1). An unknown that is already an ODE state is not observed.

The split from algebraicUnknowns is SEMANTIC, not syntactic: observed when an equation defines the unknown, algebraic when it is only constrained. The defining form is read through the LHS’s BASE NAME, so an arrayed definition is observed exactly as its scalar counterpart is; only a genuine expression LHS, one that names no single variable (H*H*SO4 ~ Ksp), is an implicit constraint (esm-spec §6.3.1).

Note that eliminable is not inlineable: a scalar observed is eliminated by substituting its definition into every consumer, while an arrayed one materializes into a buffer its consumers index. Both are observed; a caller that wants the strict inlineable form asks observedDefinitions for it with bareOnly.

Source

Example (inspect)

const doc = loadPath(fixture('decay.esm'))
assert.deepEqual(Object.keys(doc.models ?? {}), ['Decay'])
const model = doc.models!.Decay
assert.ok(!('ref' in model)) // narrows away an unresolved `{ ref }` mount

// The document declares only `unknown` and `parameter`; everything else is derived.
assert.deepEqual(Object.keys(model.variables), ['N', 'lambda', 'half_life'])
assert.deepEqual(unknowns(model), ['N', 'half_life'])
assert.deepEqual(parameters(model), ['lambda'])

// N has a time derivative; half_life is defined by an explicit equation.
assert.deepEqual(odeStates(model), ['N'])
assert.deepEqual(observedUnknowns(model), ['half_life'])
assert.deepEqual(algebraicUnknowns(model), [])
assert.equal(isOdeState(model, 'N'), true)
assert.deepEqual(constantParameters(model), ['lambda'])
assert.equal(systemKind(model), 'ode')

// Each observed's defining expression:
const defs = observedDefinitions(model)
assert.equal(toAscii(defs.get('half_life')!), 'log(2) / lambda')

From docs/api/examples/typescript/inspect.ts

Python

observed_unknowns(model: Any) -> list[str]

Unknowns defined by a bare-variable LHS (y ~ f(…)) — eliminable, materializable (esm-spec §6.3.1), sorted.

Source

Example (inspect)

model = doc.models["Decay"]
for name, var in model.variables.items():
    print(f"{name}: {var.type} [{var.units}]")
assert {n: v.type for n, v in model.variables.items()} == {
    "N": "unknown",
    "lambda": "parameter",
    "half_life": "unknown",
}

# The file declares only "unknown" and "parameter"; the finer classes are derived.
assert earthsci_ast.ode_states(model) == ["N"]
assert earthsci_ast.observed_unknowns(model) == ["half_life"]
assert earthsci_ast.algebraic_unknowns(model) == []
assert earthsci_ast.constant_parameters(model) == ["lambda"]
assert earthsci_ast.unknowns(model) == ["N", "half_life"]

From docs/api/examples/python/documents.py

Rust

pub fn observed_unknowns(model: &Model) -> Vec<String>

Unknowns defined by a bare-variable LHS (y ~ f(…)) — eliminable, materializable.

Names are sorted lexicographically, not in declaration order (API_SPEC.md §5.3).

Source · Rust docs

Example (inspect)

let doc = load_path(fixture("decay.esm"))?;
let model = &doc.models.as_ref().unwrap()["Decay"];

// Declared variables, in document order, with their declared type.
for (name, var) in &model.variables {
    println!("{name}: {:?} [{}]", var.var_type, var.units.as_deref().unwrap_or(""));
}
assert_eq!(model.variables.keys().collect::<Vec<_>>(), ["N", "lambda", "half_life"]);
assert_eq!(model.variables["lambda"].var_type, VariableType::Parameter);

// Both N and half_life are declared `unknown`; their equations decide
// the class. `D(N) = ...` makes N a state, `half_life = ...` makes
// half_life an observed.
assert_eq!(ode_states(model), ["N"]);
assert_eq!(observed_unknowns(model), ["half_life"]);
assert!(algebraic_unknowns(model).is_empty());
assert_eq!(constant_parameters(model), ["lambda"]);
assert!(is_ode_state(model, "N") && !is_ode_state(model, "half_life"));
assert_eq!(system_kind(model), SystemKind::Ode);

From docs/api/examples/rust/inspect.rs

Go

func ObservedUnknowns(model *Model) []string

ObservedUnknowns returns the model’s unknowns an equation DEFINES — its LHS naming them, bare (y ~ f(…)) or indexed (y[i] ~ f(…), which defines the whole array y) — eliminable, materializable — sorted lexicographically.

The split from AlgebraicUnknowns is SEMANTIC, not syntactic: observed when an equation defines the unknown, algebraic when it is only constrained. See observedDefinitions for how the defining form is read.

An unknown that is BOTH differentiated somewhere and given a defining equation elsewhere is an ODE state; the three sets partition, and ODE-ness wins, because the defining equation is then an extra constraint on a state rather than that state’s definition.

Source · Go docs

Example (inspect)

doc, err := esm.LoadPath(fixture("decay.esm"))
if err != nil {
	log.Fatal(err)
}
for _, name := range sortedKeys(doc.Models) {
	model := doc.Models[name]
	fmt.Println(name, sortedKeys(model.Variables))

	fmt.Println("ODE states:", esm.ODEStates(&model))
	fmt.Println("observed:", esm.ObservedUnknowns(&model))
	fmt.Println("algebraic:", esm.AlgebraicUnknowns(&model))
	fmt.Println("constant parameters:", esm.ConstantParameters(&model))

	fmt.Println(esm.IsODEState(&model, "N"), esm.IsODEState(&model, "half_life"))
	if def, ok := esm.ObservedDefinition(&model, "half_life"); ok {
		fmt.Println("half_life =", esm.ToASCII(def))
	}
}

From docs/api/examples/go/inspect_test.go