Skip to content

Architecture

Modules

graph TD A[Script or notebook] --> B[Calibrator] B --> C[InitialCalibrator] B --> D[OnlineCalibrator] C --> E[Optimizer] D --> E E --> F[PlantSimulator] F --> G[PyADM1ODE plant] H[PlantSchema] --> I[MeasurementBuilder] I --> J[MeasurementData] K[CSV / database] --> I J --> B C --> L[Sensitivity + Identifiability] C --> M[CalibrationValidator] D --> M

Calibrator is a facade. The two calibrators below it share one PlantSimulator, which is the only place that talks to PyADM1ODE, and one optimizer interface, which is the only place that talks to SciPy. Data reaches them as a MeasurementData frame, no matter whether it came from a CSV, a database or a schema-driven builder.

The optimization loop

sequenceDiagram participant C as Calibrator participant O as Optimizer participant S as PlantSimulator participant J as Objective C->>O: bounds, start values loop until max_iterations O->>S: candidate parameter set S->>J: simulated channels J->>O: weighted error end O->>C: best parameter set C->>C: validate on the held-out split

One iteration is one full plant simulation over the training window, which is why the run time is set by the number of candidates rather than by the model. The objective converts a failed simulation into a large error instead of an exception, so a single unsolvable candidate cannot abort a run.

Design decisions worth knowing

  • Parameters are named, not positional, so the mapping to the optimizer's vector lives in one place.
  • Bounds are data: create_default_bounds() carries units, defaults and a soft/hard flag for 41 parameters.
  • Plant topologies stay Python, because the PyADM1ODE plant API is a component graph.
  • Stored timestamps are naive UTC, written through timeutils.utc_now().