Skip to content

Calibration artifact

The CalibrationArtifact is the interface between calibration and estimation. It is a versioned YAML file that calibration writes and estimation reads when the live filter starts up.

Workflow

PyADM1ODE_calibration                     PyADM1ODE_estimate
  │                                            │
  │  optimizes kinetic parameters,             │
  │  fits against historical measurements      │
  │                                            │
  ▼                                            │
[ artifact.yaml ] ───────── handoff ──────────►│
                                               │
                                               │  load_artifact(...)
                                               │  apply_to_plant(...)
                                               │
                                               ▼
                                       calibrated BiogasPlant
                                               │
                                               ▼
                                       UKF starts from here

Format overview

The YAML has five main sections (each except metadata is optional):

schema_version: 1

metadata:                # Provenance: which plant, which data window
  plant_id: plant_id_xyz
  calibration_run_id: 2026-05-14-3w
  timestamp: 2026-05-14T12:30:00Z
  data_window_start: 2026-04-01
  data_window_end: 2026-04-21
  adm1_version: "ADM1da-0.3.4"
  fitted_against: [Q_gas, P_el]

kinetic:                 # adm1._kinetic overrides per digester
  primary:
    k_dis_PS: 0.045
    k_hyd_ch: 4.2
    k_m_ac: 8.5

substrates:              # Substrate disintegration fractions
  maize_silage:
    f_ch_xc: 0.55

initial_state:           # ADM1da state at the end of the calibration window
  primary:
    _values: [...41 values in canonical ADM1da order...]

residuals:               # Fit quality per channel (informational)
  Q_gas:
    rmse: 145.0
    units: m3/d

Full example: calibration_artifact_example.yaml.

Writing (calibration side)

from pyadm1ode_estimation.artifacts import (
    CalibrationArtifact, CalibrationMetadata, save_artifact,
)

artifact = CalibrationArtifact(
    metadata=CalibrationMetadata(
        plant_id="plant_id_xyz",
        calibration_run_id="2026-05-14-3w",
        timestamp="2026-05-14T12:30:00Z",
        fitted_against=["Q_gas", "P_el"],
    ),
    kinetic={"primary": {"k_dis_PS": 0.045, "k_hyd_ch": 4.2}},
    initial_state={"primary": {"_values": list_of_41_floats}},
)
save_artifact(artifact, "calibrated/plant_2026-05-14.yaml")

Reading (estimation side)

from pyadm1ode_estimation.artifacts import load_artifact, apply_to_plant

artifact = load_artifact("calibrated/plant_2026-05-14.yaml")
plant = build_plant(schema)            # plant-specific plant builder

# strict=True: fail-fast on topology mismatch (recommended for production startup)
# strict=False: warnings only, graceful degradation (good for tests, legacy artifacts)
diag = apply_to_plant(artifact, plant, strict=True)

print(f"Applied: {len(diag['applied'])} parameters")
print(f"Skipped: {len(diag['skipped'])} parameters")

Design decisions

Decision Why
schema_version mandatory Unknown versions → ValueError, no silent mis-read
plant_id + run_id mandatory Audit trail for multiple parallel calibrations
Sections optional Partial calibrations (kinetic only) allowed
strict=False default Graceful degradation on evolved topology
strict=True for production Live filter startup should fail-fast on incompatibility
Dict structures instead of fixed fields Plant-agnostic — a new plant doesn't require a schema update
_values: [41 values] array form Pragmatic without pyadm1 index-map introspection