Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Added

- `qsp-codegen verify` subcommand: end-to-end self-test that generates C++,
scaffolds and builds a minimal `qsp_sim`, and compares its trajectories
against a MATLAB SimBiology reference (PASS/FAIL). The CLI now uses
`generate`/`verify` subcommands; the legacy `qsp-codegen --sbml … --out-dir …`
form still routes to `generate`.
- `generate` now also emits a complete, ready-to-run `param_all.xml` (ICs +
parameters from the SBML), so the generated `qsp_sim` runs with no manual
`<Param>` wrapping or `qsp-refresh-param-xml` merge step.
- SBML `<functionDefinition>` (user lambda) inlining at call sites.
- Codegen-time validation of generated C++: catches `AUX_VAR_*`
use-before-definition (a dependency-ordering bug class) with a located,
human-readable error instead of an opaque downstream C++ compiler error.
- Tests for the MathML→C++ converter, generated-code validation,
`functionDefinition` inlining, and SBML event parsing.

### Fixed

- MathML→C++ converter now handles SimBiology's `<ci>nthroot</ci>` (and a
broader set of `<ci>`-exported operators: `log2`/`log10`, `ceiling`, and the
hyperbolic / inverse-trig families). Previously emitted a `/* unknown op */`
comment that crashed the Jacobian `sympify`. Unrecognized operators now
raise a clear, named error.
- Dependency-ordering bug for concentration assignment-rule species in a
dynamic-volume compartment (e.g. a drug concentration in a growing tumor
compartment): the compartment-volume temporary is now seeded into the
init / `update_y_other` emission closures, fixing an undefined `AUX_VAR_*`
reference in the generated C++.

- `qsp_sim` runtime flags `--time-unit days|seconds` and `--time-factor <N>`
to override the compile-time time-scaling default per invocation. Lets a
single binary integrate both unit-annotated SBML (default, runs in SI
Expand Down
85 changes: 68 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,87 @@ consumed by a CVODEBase-backed QSP simulator:

- `QSP_enum.h`, `ODE_system.h`, `ODE_system.cpp`
- `QSPParam.h`, `QSPParam.cpp`
- `qsp_params_xml_snippet.xml` (merged into consumer `param_all.xml` via
`qsp-refresh-param-xml`)
- `param_all.xml` — a complete, ready-to-run parameter file (initial
conditions + parameters taken straight from the SBML). Run the generated
`qsp_sim` against it directly — no hand-editing, no merge step.
- `qsp_params_xml_snippet.xml` — the bare `<QSP>` block, for consumers that
merge it into a maintained `param_all.xml` via `qsp-refresh-param-xml`.

## Prerequisites

- **Python ≥ 3.10** with `sympy` (for the analytical Jacobian) and `numpy`.
- **A C++17 compiler + CMake ≥ 3.18** to build the generated simulator. The
first configure fetches and compiles SUNDIALS and yaml-cpp via CMake
`FetchContent` (one-time, a few minutes).
- **MATLAB + SimBiology** only if you want to (a) export SBML from a
SimBiology model or (b) run `qsp-codegen verify` against a SimBiology
reference. Pure SBML-in → C++-out needs neither.

## Install

```bash
pip install ~/Projects/qsp-codegen
pip install ~/Projects/qsp-codegen # or: uv pip install -e ~/Projects/qsp-codegen
```

## Usage
## Quick start (SBML in → trajectories out)

```bash
qsp-codegen --sbml path/to/PDAC_model.sbml --out-dir path/to/ode/
# 1. Generate C++ + a ready-to-run param file from a SimBiology SBML export.
qsp-codegen generate --sbml MyModel.sbml --out-dir build/qsp/ode

qsp-refresh-param-xml \
--snippet path/to/ode/qsp_params_xml_snippet.xml \
--xml path/to/param_all.xml \
--xml path/to/param_all_test.xml
# 2. Build a minimal qsp_sim against the generated ODE (driver + ODE, default
# no-op init hook). See "Bundled C++ driver" below for the CMakeLists; or
# let `qsp-codegen verify` scaffold and build it for you.
cmake -S build/sim -B build/sim/build -DCMAKE_BUILD_TYPE=Release
cmake --build build/sim/build --target qsp_sim -j4

# 3. Run it directly against the emitted param_all.xml — no hand-editing.
build/sim/build/qsp_sim build/qsp/ode/param_all.xml out.csv 365 4.0
```

Run whenever the QSP model structure changes (species/parameters/reactions).
Not needed for parameter-value tweaks.
The legacy form `qsp-codegen --sbml … --out-dir …` (no `generate` word) still
works. Re-run codegen whenever the model *structure* changes
(species/parameters/reactions); not needed for parameter-value tweaks.

## Self-test: does the C++ match SimBiology?

`qsp-codegen verify` runs the whole pipeline end-to-end — codegen, scaffold +
build a minimal `qsp_sim`, then compare its trajectories against a MATLAB
SimBiology reference over the same window — and reports PASS/FAIL:

## Scope
```bash
qsp-codegen verify \
--sbml MyModel.sbml \
--matlab-dir /path/to/model/repo \ # has startup.m + the model script
--matlab-script build_my_model \ # builds `model`; must NOT `clear`
--stop-time 365
```

- Parses SBML Level 2 v4 (SimBiology export dialect).
- Derives analytical Jacobian via sympy + CSE when sympy is available;
falls back to numerical Jacobian otherwise.
- Consumer-side invariants (sync checks, ABM-specific param codegen) are
*not* in scope — they live in the consumer repo.
## Supported SBML features

Parses **SBML Level 2 v4** (the SimBiology export dialect):

- **Rate-law math**: the full SBML L2 operator set, including SimBiology's
`<ci>`-exported named operators (`max`, `min`, `nthroot`, …), `<root>` with
`<degree>`, and the trig / hyperbolic / log families.
- **User functions**: SBML `<functionDefinition>` lambdas are inlined at their
call sites.
- **Rules**: assignment (`repeatedAssignment`) and initial assignments,
including dynamic compartment volumes (e.g. a growing tumor compartment).
- **Events**: single-comparison triggers (`lt`/`leq`/`gt`/`geq`) with event
assignments, mapped to CVODE root functions. *Not yet supported* (fail
loudly with a clear message): event `<delay>`s and compound (`and`/`or`)
triggers.
- **Jacobian**: analytical via sympy + CSE when sympy is present; numerical
fallback otherwise.
- **Units**: converts `<listOfUnitDefinitions>` to SI for integration (see
*Time units* below).

Anything outside this surface raises a **located, human-readable error** at
codegen time (naming the operator/function/event) rather than emitting broken
C++ — and the generator self-checks its output for undefined temporaries
before writing. Consumer-side invariants (sync checks, ABM-specific param
codegen) are out of scope — they live in the consumer repo.

## Bundled C++ driver (`qsp_sim_core`)

Expand Down
4 changes: 2 additions & 2 deletions cpp/include/qsp_sim_core/model_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <string>
#include <vector>

namespace CancerVCT {
namespace qsp_sim_core {

class ODE_system;

Expand Down Expand Up @@ -73,6 +73,6 @@ struct EvolveResult {
// success=true, t_diagnosis_days=0 — i.e. no pre-scenario evolve.
EvolveResult evolve_to_diagnosis(ODE_system& ode, const EvolveOpts& opts);

} // namespace CancerVCT
} // namespace qsp_sim_core

#endif
4 changes: 2 additions & 2 deletions cpp/include/qsp_sim_core/trajectory_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
#include <ostream>
#include <stdexcept>

namespace CancerVCT {
namespace qsp_sim_core {

class TrajectoryWriter {
public:
Expand Down Expand Up @@ -128,6 +128,6 @@ class TrajectoryWriter {
uint64_t n_times_ = 0;
};

} // namespace CancerVCT
} // namespace qsp_sim_core

#endif // QSP_SIM_CORE_TRAJECTORY_WRITER_H
4 changes: 2 additions & 2 deletions cpp/src/default_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include "qsp_sim_core/model_hooks.h"

namespace CancerVCT {
namespace qsp_sim_core {

EvolveResult evolve_to_diagnosis(ODE_system& /*ode*/, const EvolveOpts& /*opts*/) {
EvolveResult r;
Expand All @@ -20,4 +20,4 @@ EvolveResult evolve_to_diagnosis(ODE_system& /*ode*/, const EvolveOpts& /*opts*/
return r;
}

} // namespace CancerVCT
} // namespace qsp_sim_core
2 changes: 1 addition & 1 deletion cpp/src/qsp_sim_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
#include "qsp_sim_core/model_hooks.h"
#include "qsp_sim_core/trajectory_writer.h"

using namespace CancerVCT;
using namespace qsp_sim_core;

namespace {

Expand Down
Loading
Loading