Identity, status and validity#
A code that stores results computed with optmat needs to know, later, which model
produced them, whether that model has changed since, and how far it was to be trusted.
Every model, response and bundle of the public surface answers all three through its
identity, its status, and its qualified intervals (OEP 1,
sections 4.1, 4.8 and 6.2).
The Quick start shows how to select a model. Read this chapter to decide what to store with a calculation, then continue through Gas dispersion and absorption or use Using optmat from a propagation code for the integration interfaces.
from optmat.gas.models.registry import model
n2 = model("fit/N2")
identity = n2.identity
assert identity.name == "fit/N2"
assert identity.kind == "gas_refractive"
assert identity.status == "validated"
assert len(identity.digest) == 64
The identity#
identity is a ModelIdentity: the model’s name, the quantity
it gives (kind), the material, the source records it rests on, its status, and a
digest. The digest is the SHA-256 of a canonical record of the model:
the class, and the
EVALUATION_VERSIONof the class and of every class and shared evaluator module its evaluation goes through; a version is raised whenever that code changes its output;every field of the model: the parameters that fix a state (temperature, wavelength, composition) and the values evaluation reads (a fit’s terms and offsets, a spectrum’s grid), not the files that produced them;
the qualified intervals (below);
the values a model resolves once from another component, and the digests of the components it is bound to: the refractivity virial ratio and a formula’s reference density, the Kerr fit and linear model behind silica’s Raman kernel, the nitrogen model behind a solid formula relative to nitrogen.
The record is canonical JSON: sorted keys, no insignificant whitespace, floats in
Python’s shortest round-trip form, non-finite floats as "inf", "-inf" and "nan",
arrays as lists and a complex number as [re, im]. The same model gives the same digest
in every process and on every IEEE-754 platform whose arithmetic produced the same
values.
The digest changes whenever the model’s numbers can change. The converse is not
promised: an edit that leaves every number unchanged may still change it. The package
version is recorded beside the digest (optmat_version), not in it, so a digest
survives a release that did not touch the model. A code that cannot accept the risk of
an evaluation change escaping the version bump also compares the version.
The test suite checks the versions at sampled arguments using recorded model identities and golden values. For gas Raman responses, derived widths can differ in their last bits between numerical libraries. The tests compare their canonical identity records with an arithmetic tolerance, while checking versions and discrete metadata exactly; the runtime digest remains an exact hash. Other models retain exact digest checks. All sampled outputs are compared independently. See Development checks for the tolerances and fixture workflow. These checks cannot establish equivalence at every argument.
identity.kind names the quantity (gas_refractive, gas_hyperpolarizability,
ionisation_rate, raman_response, solid, kerr). It is not a dispersion model’s
own kind, which names its provenance (published, fitted, …).
Records about a model. A hyperpolarizability model’s support, the record of where
its fit is supported and of the gas’s resonance limits (Third-order nonlinearity), is not a field
of the model and does not enter its digest: it changes no evaluated number. It is
identified in its own right (identity.kind is gas_hyperpolarizability_support), and
its digest covers every fact, witness, kind, uncertainty, missing reason and the
digests of what it was derived from: the selection, the fit’s configuration and each
selected dataset. Its model_digest names the model it describes. A code stores both
digests.
The status#
Status |
Meaning |
|---|---|
|
A default, with a stated uncertainty |
|
A known stopgap, with its reason: CO2’s and N2O’s Raman responses (TC-542) |
|
No reference to test it against: SF6’s molecular ionisation rate (TC-678) |
|
A curated model kept to compare with the default: the other published formulas, the refractiveindex.info entries, the literature’s rate forms, the other silica shapes |
|
Built from parameters a caller supplied; a curated model’s stated uncertainty does not carry over |
status_reason says why, where that needs saying. A mixture’s Raman response takes the
least trusted status of its Raman-active constituents.
from optmat.gas.molecular_ionisation_rates import molecular_rate_model
from optmat.gas.raman_response import raman_response
assert molecular_rate_model("SF6", 800e-9).status == "untested"
assert raman_response("CO2", 295.0).status == "provisional"
assert raman_response("dry air", 295.0).status == "validated"
Qualified intervals#
A dispersion model’s source supports it over one or more disjoint intervals of vacuum
wavelength, intervals; wavelength_range is their envelope. A wavelength outside
every interval warns (ModelRangeWarning); a NaN does not. Single-interval models use their stated range. A fit configured with OEP 3’s windows = true has several,
the windows between the molecule’s absorption bands, and between two of them the
warning names the gap’s reason (absorption, representation, or width) (see
Fitting dispersion models and OEP 3).
assert n2.intervals == (n2.wavelength_range,)
Ownership#
A model owns its storage: its arrays are copied at construction and made read-only, and its mappings are copied into read-only mappings. Changing an array after passing it to a model changes nothing, and writing to a model’s array raises. A model can be pickled and sent to another process, and keeps its digest and read-only storage there. The digest is computed once and cached.
assert not n2.covariance.flags.writeable
This guards against accidents; it is not a security boundary.
Explicit parameters and overrides#
A model can be built from a caller’s parameters, checked exactly as a curated one is:
IonisationRateModel.from_parameters, MolecularRateModel.from_parameters,
OscillatorModel.from_parameters (poles in SI), GasRamanResponse.from_lines, and for
the solids FormulaModel.sellmeier and TabulatedModel.from_arrays. The curated
builders go through the same constructors, so the two paths cannot differ. The species
may be a plain name, which becomes the identity’s material (OEP 1, section 7.4). A
custom species has no curated virial ratio or equation of state: its index takes the
ideal-gas density.
model.with_parameters(**changes) rebuilds a model with some of its inputs
changed, the names each class declares in INPUTS:
Class |
Inputs |
|---|---|
|
the channels (and |
|
|
gas dispersion models |
|
solid models |
|
|
|
|
|
A field derived from the inputs is refused by name (DERIVED): the molecular structure
factors and Hartree coefficient, a range that follows its intervals, a fit’s
covariance, the Raman damping groups and density range, silica’s paired. Everything
derived is recomputed by the constructor’s own rules. The equation-of-state, transport,
air, hyperpolarizability and Kerr models have no inputs: a different model of those is
a curated choice, not an override.
An overridden model has the status user. Its identity records each change in
modified, and the curated model’s digest in base. Its stated uncertainty does not
carry over: a rate’s uncertainty_factor and a fit’s covariance become NaN unless the
change gives them.
from optmat.gas.ionisation_rates import ionisation_rate_model
rate = ionisation_rate_model("Ar", 800e-9)
changed = rate.with_parameters(electrons=1.5)
assert changed.identity.status == "user"
assert changed.identity.modified == (("electrons", "1.5"),)
assert changed.identity.base == rate.identity.digest
narrow = raman_response("N2", 295.0).with_parameters(width_scale=0.8)
assert narrow.identity.status == "user"
This page documents what OEP 1 proposed and implemented: its theory, decisions and phase reports are the record.