observed_unknowns
Stable function · Variable classification
Tier: stable · Kind: function · Topic: Variable classification
| Julia | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
observed_unknowns | observedUnknowns | observed_unknowns | observed_unknowns | ObservedUnknowns |
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.
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"
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.
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')
Python
observed_unknowns(model: Any) -> list[str]
Unknowns defined by a bare-variable LHS (y ~ f(…)) — eliminable,
materializable (esm-spec §6.3.1), sorted.
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"]
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).
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);
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.
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))
}
}