Model IR and compilation

Every PyMixEF authoring surface compiles to a pymixef.ir.ModelIR. The IR stores mathematical meaning rather than the source syntax:

  • response and fixed predictors;

  • random-effect terms, grouping, and covariance;

  • likelihood family, links, and distributional predictors;

  • residual covariance;

  • natural and unconstrained parameter transforms;

  • units, priors, states, events, and requested outputs;

  • source locations and schema version.

IR objects are immutable and serialize to canonical JSON. Unknown future schema major versions are rejected. Migrations must be explicit and tested.

The formula parser (pymixef.formula.FormulaSpec.to_ir()), structured public builder (pymixef.model.Model.to_ir()), and pharmacometrics declaration layer (pymixef.pharmacometrics.dsl.CompiledModel.to_ir()) all produce this same schema. Pharmacometric parameters, random-effect blocks, covariates, ODEs, dose mappings, residual error models, and observation outputs remain typed nodes rather than opaque backend metadata. Custom expression or error-model operations without a defined IR mapping are rejected explicitly.

Public pymixef.model.Model declarations become pymixef.ir.PriorIR nodes. A prior can be a distribution name, a mapping containing distribution plus inline or nested parameters, or an already constructed PriorIR; it is never stored only as untyped metadata.

pymixef.model.Model.validate() checks syntax and static estimator compatibility. pymixef.model.Model.compile() additionally builds matrices, audits rows, fixes factor-level order, checks ranks, and creates an execution plan. Neither method optimizes.

Formula expressions use a safe grammar. Arbitrary Python or R code is never evaluated. Transform a column explicitly when a migration report says an R function call is unsupported.

Typed node inventory

Node

Meaning

ParameterIR

named value/support/bounds/unit and optimizer transform

FixedEffectIR

population predictor term and coefficient

RandomEffectIR

grouped latent effect, terms, level, and covariance

PredictorIR

covariate/symbol role, reference, and unit

LikelihoodIR

endpoint, family, link, prediction, and error metadata

CovarianceIR

structured covariance name, axis, grouping, and parameters

TransformIR

natural ↔ unconstrained parameter mapping

PriorIR

prior target, distribution, and parameters

StateEquationIR

dynamic state, derivative expression, and dependencies

EventIR

dose/event mapping into model state

OutputIR

named model output and dependencies

Every node inherits the immutable pymixef.ir.IRNode contract and has a JSON-compatible to_dict(). Node dependencies make the model graph inspectable without executing an estimator.

Serialize and verify identity

ir = model.to_ir()

document = ir.to_json(indent=2)
loaded = pymixef.ModelIR.from_json(document)

assert loaded == ir
assert loaded.semantic_hash == ir.semantic_hash

ModelIR.to_json without indentation returns canonical compact JSON:

  • mapping keys are deterministically ordered;

  • nonfinite numeric values are refused;

  • tuples/mappings are frozen inside the object;

  • the SHA-256 semantic_hash is computed from canonical content.

semantic_hash and its hash alias are properties, not methods.

Validation and versions

The current schema version is exposed as MODEL_IR_SCHEMA_VERSION (also IR_SCHEMA_VERSION). SUPPORTED_IR_SCHEMA_VERSIONS lists accepted historical versions.

pymixef.ir.ModelIR.from_dict() and pymixef.ir.ModelIR.from_json() validate the full document. Unknown fields, wrong node types, duplicate parameter names, invalid references, and unsupported future major versions raise pymixef.errors.IRValidationError or pymixef.errors.IRVersionError rather than being discarded.

Set migrate=False when a workflow must reject any older document:

ir = pymixef.ModelIR.from_json(payload, migrate=False)

Explicit migrations

pymixef.ir.migrate_ir() walks registered forward edges. pymixef.ir.register_ir_migration() adds a deliberate migration. A migration is code, versioned behavior, and should have fixtures that demonstrate preservation or explicitly document semantic change.

Unknown migration paths are refused; there is no “best effort” dropping of future fields.

Compare model meaning

from pymixef import diff_models

difference = diff_models(before_ir, after_ir)
print(difference.categories)
print(difference.to_json(indent=2))
assert not difference.equal

pymixef.ir.ModelDiff contains deterministic pymixef.ir.DiffEntry records with path, before/after value, and change category. pymixef.ir.ModelIR.diff() is the object-oriented form. Use the diff to drive change review; deciding whether a change is scientifically material remains a context-specific judgment.

What the hash does—and does not—establish

The semantic hash is strong evidence that two canonical IR documents are identical. It does not prove:

  • that data, numerical options, or software environment are identical;

  • that a model is identifiable or scientifically appropriate;

  • that two external tools use the same likelihood constants;

  • authorship, approval, or regulated-system validation.

The pymixef.provenance.RunManifest combines the model hash with data fingerprint, environment, engine/method, controls, and warnings.

API and example

  • pymixef.ir documents every node, property, migration, and diff type.

  • Tutorial 09 visualizes a compiled dependency graph, node inventory, and incidence matrix, then verifies JSON/hash preservation.