From f892ff61b04d7c8ca9136a8e04ab3a56a3e86b6d Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Mon, 29 Jun 2026 19:20:26 +0100 Subject: [PATCH 1/5] #3 Add SBML template project --- .github/workflows/test-sbml-project.yml | 62 ++++ README.md | 64 ++++ examples/goldbeter_1991/README.md | 146 +++++++++ .../TestGoldbeter1991SbmlSrnModel.hpp | 55 ++++ scripts/sbml_install.sh | 42 +++ setup_project.py | 64 ++++ src/AbstractSbmlCellCycleModel.cpp | 165 ++++++++++ src/AbstractSbmlCellCycleModel.hpp | 165 ++++++++++ src/AbstractSbmlOdeSystem.cpp | 148 +++++++++ src/AbstractSbmlOdeSystem.hpp | 229 +++++++++++++ src/AbstractSbmlSrnModel.cpp | 124 +++++++ src/AbstractSbmlSrnModel.hpp | 138 ++++++++ src/SbmlEventType.hpp | 10 + src/SbmlMath.cpp | 190 +++++++++++ src/SbmlMath.hpp | 310 ++++++++++++++++++ src/fortests/SbmlTestHelpers.cpp | 143 ++++++++ src/fortests/SbmlTestHelpers.hpp | 195 +++++++++++ src/fortests/SbmlTestOdeSolution.cpp | 97 ++++++ src/fortests/SbmlTestOdeSolution.hpp | 104 ++++++ 19 files changed, 2451 insertions(+) create mode 100644 .github/workflows/test-sbml-project.yml create mode 100644 examples/goldbeter_1991/README.md create mode 100644 examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp create mode 100755 scripts/sbml_install.sh create mode 100644 src/AbstractSbmlCellCycleModel.cpp create mode 100644 src/AbstractSbmlCellCycleModel.hpp create mode 100644 src/AbstractSbmlOdeSystem.cpp create mode 100644 src/AbstractSbmlOdeSystem.hpp create mode 100644 src/AbstractSbmlSrnModel.cpp create mode 100644 src/AbstractSbmlSrnModel.hpp create mode 100644 src/SbmlEventType.hpp create mode 100644 src/SbmlMath.cpp create mode 100644 src/SbmlMath.hpp create mode 100644 src/fortests/SbmlTestHelpers.cpp create mode 100644 src/fortests/SbmlTestHelpers.hpp create mode 100644 src/fortests/SbmlTestOdeSolution.cpp create mode 100644 src/fortests/SbmlTestOdeSolution.hpp diff --git a/.github/workflows/test-sbml-project.yml b/.github/workflows/test-sbml-project.yml new file mode 100644 index 0000000..eb97927 --- /dev/null +++ b/.github/workflows/test-sbml-project.yml @@ -0,0 +1,62 @@ +name: Test SBML Project + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +concurrency: + group: test-sbml-project-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-sbml-project: + runs-on: ubuntu-latest + + container: + image: chaste/develop + options: --user root + + env: + CHASTE_BUILD_DIR: /tmp/build + CHASTE_TEST_OUTPUT: /tmp/testoutput + PROJECT_DIR: /tmp/myproject + + steps: + - name: Checkout template + uses: actions/checkout@v7 + + - name: Mark Chaste source as safe for git + run: git config --global --add safe.directory "${CHASTE_SOURCE_DIR}" + + - name: Install clang-format + run: apt-get update && apt-get install -y clang-format + + - name: Create test project + run: | + cp -r . ${{ env.PROJECT_DIR }} + cd ${{ env.PROJECT_DIR }} + # Prompt defaults for proceed/components/no-python-bindings, yes to SBML, yes to confirm + printf '\n\n\n\n\n\ny\ny\n' | python3 setup_project.py + + - name: Install SBML code generator + run: ${{ env.PROJECT_DIR }}/scripts/sbml_install.sh + + - name: Import the Goldbeter 1991 model + run: | + cd ${{ env.PROJECT_DIR }} + curl -L "https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000003?filename=BIOMD0000000003_url.xml" \ + -o Goldbeter1991.xml + .virtualenv/bin/chaste_codegen_sbml Goldbeter1991.xml --model-type srn --output-dir src/ + cp examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp test/ + echo "TestGoldbeter1991SbmlSrnModel.hpp" >> test/ContinuousTestPack.txt + + - name: Configure + run: ${{ env.PROJECT_DIR }}/scripts/configure.sh + + - name: Compile + run: ${{ env.PROJECT_DIR }}/scripts/compile.sh + + - name: Test + run: ${{ env.PROJECT_DIR }}/scripts/test.sh diff --git a/README.md b/README.md index a8d13a4..846f905 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,67 @@ Alternatively, if you aren't a github user, you can download a zip (see Releases Then see the [User Projects](https://chaste.github.io/docs/user-guides/user-projects/) guide page on the Chaste website for more information. If you clone this repository, you should make sure to rename the template_project folder with your project name and run the 'setup_project.py' script to avoid conflicts if you have multiple projects. + +## SBML models + +This template can turn an [SBML](https://sbml.org/) model into a Chaste model using +[chaste-codegen-sbml](https://github.com/Chaste/chaste-codegen-sbml). For a complete, +worked example see [examples/goldbeter_1991/README.md](examples/goldbeter_1991/README.md). + +### 1. Enable SBML support + +When you run `setup_project.py`, answer **yes** to: + +``` +Do you want to create an SBML user project? +``` + +### 2. Activate the virtualenv + +```sh +source .virtualenv/bin/activate +``` + +(If you ever need to (re)install the generator by hand, run `scripts/sbml_install.sh`.) + +### 3. Convert an SBML model into a Chaste model + +```sh +chaste_codegen_sbml my_model.xml --model-type srn --output-dir src/ +``` + +* `--model-type` is one of `generic`, `srn` (sub-cellular reaction network), or + `cell-cycle`. +* The generated class and file names are derived from the **input file name**, suffixed + with `Sbml`. For example `my_model.xml` produces `MyModelSbmlOdeSystem.{hpp,cpp}`, plus + `MyModelSbmlSrnModel.{hpp,cpp}` (for `srn`) or `MyModelSbmlCellCycleModel.{hpp,cpp}` + (for `cell-cycle`). + +### 4. Write a test + +Add a test under `test/` that `#include`s the generated model, then list it in +`test/ContinuousTestPack.txt`. See the walkthrough for a full example. + +### 5. Build and run the test + +From the project directory (with `CHASTE_SOURCE_DIR` pointing at your Chaste source): + +```sh +scripts/configure.sh # register the project and configure the Chaste build +scripts/compile.sh # build the project +scripts/test.sh # run the project's tests +``` + +### The SBML base classes + +These live in `src/` and are required by the generated code: + +| File | Purpose | +| --- | --- | +| `AbstractSbmlOdeSystem.{hpp,cpp}` | Base ODE system for a generated SBML model. | +| `AbstractSbmlSrnModel.{hpp,cpp}` | Base sub-cellular reaction network (SRN) model. | +| `AbstractSbmlCellCycleModel.{hpp,cpp}` | Base cell-cycle model. | +| `SbmlEventType.hpp` | Enum of SBML event types (e.g. cell division). | +| `SbmlMath.{hpp,cpp}` | Math helper functions used by generated equations. | +| `fortests/SbmlTestHelpers.{hpp,cpp}` | Utility helpers for tests. | +| `fortests/SbmlTestOdeSolution.{hpp,cpp}` | `OdeSolution` Recording per-step parameters, for tests. | diff --git a/examples/goldbeter_1991/README.md b/examples/goldbeter_1991/README.md new file mode 100644 index 0000000..9dbeffe --- /dev/null +++ b/examples/goldbeter_1991/README.md @@ -0,0 +1,146 @@ +# Walkthrough: importing the Goldbeter 1991 model from BioModels + +This walkthrough imports [BIOMD0000000003](https://biomodels.org/BIOMD0000000003) — the +**Goldbeter 1991** minimal cascade model of the mitotic oscillator — into a Chaste user +project as a sub-cellular reaction network (SRN) model, then builds and tests it. + +It assumes you have already created your project from this template and answered **yes** +to the SBML prompt in `setup_project.py`, so that: + +* the SBML base classes are present in `src/`, +* `cell_based` is listed in `CMakeLists.txt`, and +* `chaste-codegen-sbml` is installed in `.virtualenv/`. + +Run every command below from your project's root directory. + +## 1. Activate the virtualenv + +```sh +source .virtualenv/bin/activate +``` + +## 2. Download the SBML model and give it a clean name + +The generated C++ class names come from the **file name**, so download the model and +rename it to something C++-friendly. Here we use `Goldbeter1991`: + +```sh +curl -L "https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000003?filename=BIOMD0000000003_url.xml" -o Goldbeter1991.xml +``` + +## 3. Convert the model into a Chaste model + +Goldbeter 1991 is a sub-cellular reaction network, so use `--model-type srn`: + +```sh +chaste_codegen_sbml Goldbeter1991.xml --model-type srn --output-dir src/ +``` + +This generates four files in `src/`: + +* `Goldbeter1991SbmlOdeSystem.hpp` / `.cpp` — the ODE system (state variables `C`, `M`, `X`). +* `Goldbeter1991SbmlSrnModel.hpp` / `.cpp` — the SRN model that wraps the ODE system. + +These have the base classes (`AbstractSbmlOdeSystem`, `AbstractSbmlSrnModel`). + +## 4. Create the test file + +Create `test/TestGoldbeter1991SbmlSrnModel.hpp` with the following contents. It +builds a cell carrying the imported SRN model, integrates it to a steady state, +and checks the concentrations of cyclin (`C`), active cdc2 kinase (`M`), and +active cyclin protease (`X`). + +```cpp +#ifndef TESTGOLDBETER1991SBMLSRNMODEL_HPP_ +#define TESTGOLDBETER1991SBMLSRNMODEL_HPP_ + +#include + +#include "AbstractCellBasedTestSuite.hpp" +#include "Cell.hpp" +#include "NoCellCycleModel.hpp" +#include "SimulationTime.hpp" +#include "SmartPointers.hpp" +#include "StemCellProliferativeType.hpp" +#include "WildTypeCellMutationState.hpp" + +// The header generated from Goldbeter1991.xml in step 3. +#include "Goldbeter1991SbmlSrnModel.hpp" + +// This is a serial test. +#include "FakePetscSetup.hpp" + +class TestGoldbeter1991SbmlSrnModel : public AbstractCellBasedTestSuite +{ +public: + void TestSteadyStateSimulation() + { + // Integrate to t = 1000 in 1000 steps (AbstractCellBasedTestSuite has set the start time to 0). + SimulationTime* p_simulation_time = SimulationTime::Instance(); + p_simulation_time->SetEndTimeAndNumberOfTimeSteps(1000.0, 1000); + + // Create the SRN model from the imported SBML model and attach it to a cell. + Goldbeter1991SbmlSrnModel* p_srn_model = new Goldbeter1991SbmlSrnModel(); + + MAKE_PTR(WildTypeCellMutationState, p_state); + MAKE_PTR(StemCellProliferativeType, p_stem_type); + NoCellCycleModel* p_cc_model = new NoCellCycleModel(); + + CellPtr p_cell(new Cell(p_state, p_cc_model, p_srn_model)); + p_cell->SetCellProliferativeType(p_stem_type); + p_cell->InitialiseCellCycleModel(); + p_cell->InitialiseSrnModel(); + + // Step the simulation to the end time, advancing the SRN model each step. + while (!p_simulation_time->IsFinished()) + { + p_simulation_time->IncrementTimeOneStep(); + p_srn_model->SimulateToCurrentTime(); + } + + // Check the steady state of the mitotic oscillator. + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("C"), 0.5470, 1e-2); + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("M"), 0.2936, 1e-2); + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("X"), 0.0067, 1e-3); + } +}; + +#endif /*TESTGOLDBETER1991SBMLSRNMODEL_HPP_*/ +``` + +A ready-made copy of this file lives next to this walkthrough at +[`TestGoldbeter1991SbmlSrnModel.hpp`](TestGoldbeter1991SbmlSrnModel.hpp); you can simply +copy it into your `test/` directory: + +```sh +cp examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp test/ +``` + +Then register the test by adding its file name to `test/ContinuousTestPack.txt`: + +``` +TestGoldbeter1991SbmlSrnModel.hpp +``` + +## 5. Build and run the test + +With `CHASTE_SOURCE_DIR` pointing at your Chaste source tree: + +```sh +scripts/configure.sh # register the project and configure the Chaste build +scripts/compile.sh # build the project (including the generated model) +scripts/test.sh # run the project's tests +``` + +You should see `TestGoldbeter1991SbmlSrnModel` pass, confirming the steady-state values: + +``` +C ≈ 0.547 M ≈ 0.294 X ≈ 0.0067 +``` + +## Next steps + +* To import a different model, repeat steps 2–4 with your own `.xml` file, choosing + `--model-type generic`, `srn`, or `cell-cycle` to match the model. +* See [chaste-codegen-sbml](https://github.com/Chaste/chaste-codegen-sbml) + for more information. diff --git a/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp b/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp new file mode 100644 index 0000000..a3d8592 --- /dev/null +++ b/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp @@ -0,0 +1,55 @@ +#ifndef TESTGOLDBETER1991SBMLSRNMODEL_HPP_ +#define TESTGOLDBETER1991SBMLSRNMODEL_HPP_ + +#include + +#include "AbstractCellBasedTestSuite.hpp" +#include "Cell.hpp" +#include "NoCellCycleModel.hpp" +#include "SimulationTime.hpp" +#include "SmartPointers.hpp" +#include "StemCellProliferativeType.hpp" +#include "WildTypeCellMutationState.hpp" + +// The header generated from Goldbeter1991.xml by chaste_codegen_sbml. +#include "Goldbeter1991SbmlSrnModel.hpp" + +// This is a serial test. +#include "FakePetscSetup.hpp" + +class TestGoldbeter1991SbmlSrnModel : public AbstractCellBasedTestSuite +{ +public: + void TestSteadyStateSimulation() + { + // Integrate to t = 1000 in 1000 steps (AbstractCellBasedTestSuite has set the start time to 0). + SimulationTime* p_simulation_time = SimulationTime::Instance(); + p_simulation_time->SetEndTimeAndNumberOfTimeSteps(1000.0, 1000); + + // Create the SRN model from the imported SBML model and attach it to a cell. + Goldbeter1991SbmlSrnModel* p_srn_model = new Goldbeter1991SbmlSrnModel(); + + MAKE_PTR(WildTypeCellMutationState, p_state); + MAKE_PTR(StemCellProliferativeType, p_stem_type); + NoCellCycleModel* p_cc_model = new NoCellCycleModel(); + + CellPtr p_cell(new Cell(p_state, p_cc_model, p_srn_model)); + p_cell->SetCellProliferativeType(p_stem_type); + p_cell->InitialiseCellCycleModel(); + p_cell->InitialiseSrnModel(); + + // Step the simulation to the end time, advancing the SRN model each step. + while (!p_simulation_time->IsFinished()) + { + p_simulation_time->IncrementTimeOneStep(); + p_srn_model->SimulateToCurrentTime(); + } + + // Check the steady state of the mitotic oscillator. + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("C"), 0.5470, 1e-2); + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("M"), 0.2936, 1e-2); + TS_ASSERT_DELTA(p_srn_model->GetStateVariable("X"), 0.0067, 1e-3); + } +}; + +#endif /*TESTGOLDBETER1991SBMLSRNMODEL_HPP_*/ diff --git a/scripts/sbml_install.sh b/scripts/sbml_install.sh new file mode 100755 index 0000000..eacb584 --- /dev/null +++ b/scripts/sbml_install.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Create the project virtualenv and install the SBML code generator into it. + +# Abort if number of arguments is incorrect. +if [[ $# -ne 0 ]]; then + echo "Usage: $(basename "$0")" >&2 + exit 1 +fi + +# The project root is the parent of this script's directory. +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "${script_dir}/.." && pwd)" + +# Abort with an error if the given command is not on PATH. +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: $1 is not available on PATH." >&2 + exit 1 + fi +} + +require_command python3 + +# The SBML generator formats its output with clang-format; warn (non-fatal) if absent. +if ! command -v clang-format >/dev/null 2>&1; then + echo "Warning: clang-format is not on PATH; chaste_codegen_sbml needs it to format generated code." >&2 +fi + +# Create the project virtualenv (idempotent; shared with Python bindings if both are set up). +venv_dir="${PROJECT_ROOT}/.virtualenv" +python3 -m venv "${venv_dir}" + +# Install the SBML code generator from GitHub. +"${venv_dir}/bin/pip" install --upgrade pip +"${venv_dir}/bin/pip" install "git+https://github.com/Chaste/chaste-codegen-sbml@develop" + +echo "" +echo "Installed chaste-codegen-sbml into '${venv_dir}'." +echo "Activate the virtualenv with: source '${venv_dir}/bin/activate'" +echo "Then convert an SBML model with: chaste_codegen_sbml --help" diff --git a/setup_project.py b/setup_project.py index 53ee85d..94fa5c9 100644 --- a/setup_project.py +++ b/setup_project.py @@ -37,6 +37,7 @@ import os import re import shutil +import subprocess class Settings: @@ -105,6 +106,26 @@ def __init__(self) -> None: # Python package template directory (renamed to / during setup). self.PYTHON_PKG_TEMPLATE_DIR = os.path.join(self.PROJECT_ROOT, "src", "py", "template_project") + # SBML support files vendored from chaste-codegen-sbml (kept if SBML opted in, deleted otherwise). + # Their names are kept unchanged: generated model code #includes and subclasses them by name. + self.SBML_SUPPORT_FILES = [ + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlOdeSystem.hpp"), + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlOdeSystem.cpp"), + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlSrnModel.hpp"), + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlSrnModel.cpp"), + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlCellCycleModel.hpp"), + os.path.join(self.PROJECT_ROOT, "src", "AbstractSbmlCellCycleModel.cpp"), + os.path.join(self.PROJECT_ROOT, "src", "SbmlEventType.hpp"), + os.path.join(self.PROJECT_ROOT, "src", "SbmlMath.hpp"), + os.path.join(self.PROJECT_ROOT, "src", "SbmlMath.cpp"), + ] + + # SBML test-helper directory (removed alongside SBML_SUPPORT_FILES if SBML is declined). + self.SBML_FORTESTS_DIR = os.path.join(self.PROJECT_ROOT, "src", "fortests") + + # The script that creates the project virtualenv and installs the SBML code generator. + self.SBML_INSTALL_SCRIPT = os.path.join(self.PROJECT_ROOT, "scripts", "sbml_install.sh") + def find_and_replace(filename: str, old_string: str, new_string: str) -> None: """Replace every occurrence of old_string with new_string in a file, in place.""" @@ -159,6 +180,26 @@ def print_banner(*lines: str) -> None: print(border) +def install_sbml_codegen(settings: Settings) -> None: + """Create the project virtualenv and install chaste-codegen-sbml into it. + + Runs scripts/sbml_install.sh. On any failure this is non-fatal: it prints the + manual commands so the user can finish the install themselves. + """ + try: + subprocess.run([settings.SBML_INSTALL_SCRIPT], check=True) + except (subprocess.CalledProcessError, OSError) as error: + venv_dir = os.path.join(settings.PROJECT_ROOT, ".virtualenv") + print("") + print(f"WARNING: could not install chaste-codegen-sbml automatically ({error}).") + print("Install it manually with:") + print(f" python3 -m venv {venv_dir}") + print( + f" {os.path.join(venv_dir, 'bin', 'pip')} install " + "'git+https://github.com/Chaste/chaste-codegen-sbml@develop'" + ) + + def is_setup(settings: Settings) -> bool: """Return True if the project has already been set up (any of the example files are renamed).""" return not all(os.path.exists(file) for file in settings.TEMPLATE_SOURCE_FILES) @@ -203,12 +244,19 @@ def setup(settings: Settings) -> None: # Ask whether to create Python bindings python_bindings = ask_for_response("Do you want to create Python bindings for this project?") + # Ask whether to create an SBML user project + sbml = ask_for_response("Do you want to create an SBML user project?") + if sbml and "cell_based" not in components: + # The SBML base classes subclass cell_based classes, so the component is required. + components.append("cell_based") + # Summarise the chosen options and confirm before making any changes print("") print("Summary:") print(f" Project name: {settings.PROJECT_NAME}") print(f" Chaste components: {', '.join(components) if components else '(template default)'}") print(f" Python bindings: {'Yes' if python_bindings else 'No'}") + print(f" SBML project: {'Yes' if sbml else 'No'}") print("") if not ask_for_response("Proceed with these settings?"): print("No changes made.") @@ -261,6 +309,16 @@ def setup(settings: Settings) -> None: shutil.rmtree(os.path.join(settings.PROJECT_ROOT, "dynamic")) shutil.rmtree(os.path.join(settings.PROJECT_ROOT, "src", "py")) + # Set up or remove SBML support + if sbml: + # Keep the vendored SBML files (unchanged) and install the code generator into the venv. + install_sbml_codegen(settings) + else: + # Remove the vendored SBML support files and test helpers. + for file in settings.SBML_SUPPORT_FILES: + os.remove(file) + shutil.rmtree(settings.SBML_FORTESTS_DIR) + # Summarise the changes that were made print("") print("Setup complete.") @@ -279,6 +337,12 @@ def setup(settings: Settings) -> None: else: print("* Removed Python bindings template files in dynamic/ and src/py/.") + if sbml: + print("* Kept the SBML base classes in src/ and installed chaste-codegen-sbml into .virtualenv.") + print(" See the README and examples/goldbeter_1991/ for how to import an SBML model.") + else: + print("* Removed the SBML base classes from src/.") + def main() -> None: """Set up the project from the template.""" diff --git a/src/AbstractSbmlCellCycleModel.cpp b/src/AbstractSbmlCellCycleModel.cpp new file mode 100644 index 0000000..e7bc3f7 --- /dev/null +++ b/src/AbstractSbmlCellCycleModel.cpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "AbstractOdeBasedCellCycleModel.hpp" +#include "BackwardEulerIvpOdeSolver.hpp" +#include "CellCycleModelOdeSolver.hpp" +#include "CvodeAdaptor.hpp" +#include "SbmlEventType.hpp" +#include "StemCellProliferativeType.hpp" +#include "TransitCellProliferativeType.hpp" + +#include "AbstractSbmlCellCycleModel.hpp" + +AbstractSbmlCellCycleModel::AbstractSbmlCellCycleModel(boost::shared_ptr pOdeSolver) + : AbstractOdeBasedCellCycleModel(SimulationTime::Instance()->GetTime(), pOdeSolver) +{ + if (!mpOdeSolver) + { +#ifdef CHASTE_CVODE + // Default to CVODE where available + mpOdeSolver = CellCycleModelOdeSolver::Instance(); + mpOdeSolver->Initialise(); + mpOdeSolver->SetMaxSteps(10000); // Safe defaults + mpOdeSolver->SetTolerances(1e-6, 1e-8); + // CVODE needs to be instructed to check for stopping events + mpOdeSolver->CheckForStoppingEvents(); +#else + // Default to Chaste Runge-Kutta solver where CVODE is not available + mpOdeSolver = CellCycleModelOdeSolver::Instance(); + mpOdeSolver->Initialise(); + this->SetDt(0.0001); // Safe default +#endif // CHASTE_CVODE + } + + assert(mpOdeSolver->IsSetUp()); +} + +AbstractSbmlCellCycleModel::AbstractSbmlCellCycleModel(const AbstractSbmlCellCycleModel& rModel) + : AbstractOdeBasedCellCycleModel(rModel) +{ + /* + * Set each member variable of the new Cell Cycle model that inherits + * its value from the parent. + * + * Note 1: some of the new Cell Cycle model's member variables + * will already have been correctly initialized in its constructor. + * + * Note 2: one or more of the new Cell Cycle model's member variables + * may be set/overwritten as soon as InitialiseDaughterCell() is called on + * the new Cell Cycle model. + * + * Note 3: Only set the variables defined in this class. Variables defined + * in parent classes will be defined there. + */ +} + +AbstractSbmlCellCycleModel::~AbstractSbmlCellCycleModel() +{ +} + +void AbstractSbmlCellCycleModel::AdjustOdeParameters(double currentTime) +{ + static_cast(mpOdeSystem)->AdjustParameters(currentTime); +} + +bool AbstractSbmlCellCycleModel::CanCellTerminallyDifferentiate() +{ + return false; +} + +double AbstractSbmlCellCycleModel::GetAverageTransitCellCycleTime() +{ + // A default value, should be overridden in subclasses + return 1.25; +} + +double AbstractSbmlCellCycleModel::GetAverageStemCellCycleTime() +{ + // A default value, should be overridden in subclasses + return 1.25; +} + +double AbstractSbmlCellCycleModel::GetStateVariable(const std::string& rName) +{ + assert(mpOdeSystem != nullptr); + return mpOdeSystem->GetStateVariable(rName); +} + +void AbstractSbmlCellCycleModel::OutputCellCycleModelParameters(out_stream& rParamsFile) +{ + // No new parameters to output, so just call method on direct parent class + AbstractOdeBasedCellCycleModel::OutputCellCycleModelParameters(rParamsFile); +} + +bool AbstractSbmlCellCycleModel::ReadyToDivide() +{ + if (!mReadyToDivide) + { + bool was_ready_to_divide = mReadyToDivide; + double previous_divide_time = mDivideTime; + + // Solves ODE to current time and update cell division flag and time + bool stopping_event_occurred = AbstractOdeBasedCellCycleModel::ReadyToDivide(); + + if (stopping_event_occurred) + { + // Reset division flag and time if stopping event is not cell division + if (!static_cast(mpOdeSystem)->HasEventOccurred(SbmlEventType::CELL_DIVISION)) + { + mReadyToDivide = was_ready_to_divide; + mDivideTime = previous_divide_time; + } + } + } + return mReadyToDivide; +} + +void AbstractSbmlCellCycleModel::ResetForDivision() +{ + assert(mReadyToDivide); + AbstractOdeBasedCellCycleModel::ResetForDivision(); + + assert(mpOdeSystem != nullptr); + static_cast(mpOdeSystem)->ResetEventsOccurred(); +} + +// Register class with Boost serialization +#include "SerializationExportWrapperForCpp.hpp" +CHASTE_CLASS_EXPORT(AbstractSbmlCellCycleModel) + +// Register the CellCycleModel classes with Boost serialization +#include "CellCycleModelOdeSolverExportWrapper.hpp" +EXPORT_CELL_CYCLE_MODEL_ODE_SOLVER(AbstractSbmlCellCycleModel) diff --git a/src/AbstractSbmlCellCycleModel.hpp b/src/AbstractSbmlCellCycleModel.hpp new file mode 100644 index 0000000..b5b078b --- /dev/null +++ b/src/AbstractSbmlCellCycleModel.hpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ABSTRACT_SBML_CELL_CYCLE_MODEL_HPP_ +#define ABSTRACT_SBML_CELL_CYCLE_MODEL_HPP_ + +#include + +#include "AbstractCellCycleModelOdeSolver.hpp" +#include "AbstractOdeBasedCellCycleModel.hpp" +#include "AbstractSbmlOdeSystem.hpp" +#include "ChasteSerialization.hpp" +#include "ClassIsAbstract.hpp" + +/** + * A base class for cell cycle models generated from SBML + */ + +class AbstractSbmlCellCycleModel : public AbstractOdeBasedCellCycleModel +{ +private: + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Archive / unarchive the AbstractSbmlCellCycleModel. + * + * @param archive the archive + * @param version the current version of this class + */ + template + void serialize(Archive& archive, const unsigned int version) + { + archive& boost::serialization::base_object(*this); + } + +protected: + /** + * Protected copy-constructor for use by CreateCellCycleModel(). + * + * The only way to copy an instance of a subclass of AbstractCellCycleModel is + * by calling CreateCellCycleModel(), which ensures that the instance is copied + * correctly. + * + * This copy-constructor helps subclasses of AbstractCellCycleModel to + * ensure that all their members are copied over correctly. It is primarily + * used during cell division to set member variables for a daughter cell. + * Note that the cell-cycle model of the parent cell will have run ResetForDivision() + * just before calling CreateCellCycleModel(), so performing an exact copy of the + * parent cell's cell-cycle model is suitable behaviour. Any further initialisation + * specific to the daughter cell can be completed via InitialiseDaughterCell(). + * + * @param rModel the cell-cycle model to copy. + */ + AbstractSbmlCellCycleModel(const AbstractSbmlCellCycleModel& rModel); + +public: + /** + * Default constructor calls base class. + * + * @param pOdeSolver An optional pointer to a cell-cycle model ODE solver object (allows the use of different ODE solvers) + */ + AbstractSbmlCellCycleModel(boost::shared_ptr pOdeSolver = boost::shared_ptr()); + + /** + * Destructor. + */ + virtual ~AbstractSbmlCellCycleModel(); + + /** + * Adjust any ODE parameters needed before solving until currentTime. + * + * @param currentTime the time up to which the system will be solved. + */ + void AdjustOdeParameters(double currentTime); + + /** + * Overridden CanCellTerminallyDifferentiate() method. + * @return whether cell can terminally differentiate + */ + bool CanCellTerminallyDifferentiate(); + + /** + * Overridden GetAverageStemCellCycleTime() method. + * @return time + */ + double GetAverageStemCellCycleTime(); + + /** + * Overridden GetAverageTransitCellCycleTime() method. + * @return time + */ + double GetAverageTransitCellCycleTime(); + + /** + * @return the value of a given state variable. + * + * @param rName the name of the state variable + */ + double GetStateVariable(const std::string& rName); + + /** + * Outputs cell-cycle model parameters to file. + * + * @param rParamsFile the file stream to which the parameters are output + */ + void OutputCellCycleModelParameters(out_stream& rParamsFile); + + /** + * See AbstractCellCycleModel::ResetForDivision() + * + * @return whether the cell is ready to divide (enter M phase). + */ + bool ReadyToDivide() override; + + /** + * Each cell-cycle model must be able to be reset 'after' a cell division. + * + * Actually, this method is called from Cell::Divide() to + * reset the cell cycle just before the daughter cell is created. + * CreateCellCycleModel() can then clone our state to generate a + * cell-cycle model instance for the daughter cell. + */ + void ResetForDivision() override; +}; + +// Register abstract class with Boost serialization +CLASS_IS_ABSTRACT(AbstractSbmlCellCycleModel) + +// Register the CellCycleModel classes with Boost serialization +#include "CellCycleModelOdeSolverExportWrapper.hpp" +EXPORT_CELL_CYCLE_MODEL_ODE_SOLVER(AbstractSbmlCellCycleModel) + +#endif // ABSTRACT_SBML_CELL_CYCLE_MODEL_HPP_ diff --git a/src/AbstractSbmlOdeSystem.cpp b/src/AbstractSbmlOdeSystem.cpp new file mode 100644 index 0000000..152cea3 --- /dev/null +++ b/src/AbstractSbmlOdeSystem.cpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "ChasteSerialization.hpp" +#include "SbmlEventType.hpp" + +#include "AbstractSbmlOdeSystem.hpp" + +AbstractSbmlOdeSystem::AbstractSbmlOdeSystem(unsigned numberOfStateVariables, unsigned numberOfParameters, unsigned numberOfEvents) + : AbstractOdeSystem(numberOfStateVariables), + mNumberOfParameters(numberOfParameters), + mNumberOfEvents(numberOfEvents) +{ + if (mNumberOfEvents > 0) + { + mEventType.resize(mNumberOfEvents, SbmlEventType::UNKNOWN); + + mEventSatisfied.resize(mNumberOfEvents, true); // Prevent events from triggering at the start + mEventClampActive.resize(mNumberOfEvents, true); // Re-derived at each Solve segment start + mEventTriggered.resize(mNumberOfEvents, false); + + if (mNumberOfStateVariables > 0) + { + mEventAdjustedStateVars.resize(mNumberOfStateVariables, false); + mEventAdjustedStateValues.resize(mNumberOfStateVariables, 0.0); + mEventAdjustedStatePriority.resize(mNumberOfStateVariables, 0.0); + } + + if (mNumberOfParameters > 0) + { + mEventAdjustedParameters.resize(mNumberOfParameters, false); + mEventAdjustedParameterValues.resize(mNumberOfParameters, 0.0); + mEventAdjustedParameterPriority.resize(mNumberOfParameters, 0.0); + } + } +} + +AbstractSbmlOdeSystem::~AbstractSbmlOdeSystem() +{ +} + +void AbstractSbmlOdeSystem::AdjustParameters(double time) +{ + for (unsigned i = 0; i < mEventAdjustedParameters.size(); ++i) + { + if (mEventAdjustedParameters[i]) + { + SetParameter(i, mEventAdjustedParameterValues[i]); + } + } + + for (unsigned i = 0; i < mEventAdjustedStateVars.size(); ++i) + { + if (mEventAdjustedStateVars[i]) + { + SetStateVariable(i, mEventAdjustedStateValues[i]); + mEventAdjustedStateVars[i] = false; + } + } +} + +double AbstractSbmlOdeSystem::CalculateRootFunction(double time, const std::vector& rY) +{ + return ProcessModelEvents(time, rY); +} + +bool AbstractSbmlOdeSystem::CalculateStoppingEvent(double time, const std::vector& rY) +{ + // Clear all event flags before a fresh BackwardEuler evaluation. ProcessModelEvents + // no longer resets these itself (so CVODE bisection doesn't erase a stored halving), + // so we must do it here to avoid stale flags from prior detections causing false + // positives on the initial-condition check at the start of the next Solve() call. + std::fill(mEventTriggered.begin(), mEventTriggered.end(), false); + std::fill(mEventAdjustedStateVars.begin(), mEventAdjustedStateVars.end(), false); + std::fill(mEventAdjustedParameters.begin(), mEventAdjustedParameters.end(), false); + + ProcessModelEvents(time, rY); + + // Freeze the clamp set for this Solve segment: clamp exactly those events whose trigger + // is active here (carried over from a previous segment) so CVODE reports no spurious root + // at the initial condition. ProcessModelEvents then clears each flag the instant its + // trigger first goes false, leaving the clamp stable across CVODE's in-step root + // bracketing so events localize at the true crossing rather than the step endpoint. + mEventClampActive = mEventSatisfied; + + for (unsigned i = 0; i < mEventTriggered.size(); ++i) + { + if (mEventTriggered[i]) return true; + } + return false; +} + +bool AbstractSbmlOdeSystem::HasEventOccurred(SbmlEventType eventType) +{ + for (unsigned i = 0; i < mEventTriggered.size(); ++i) + { + if (mEventTriggered[i] && mEventType[i] == eventType) + { + return true; + } + } + return false; +} + +void AbstractSbmlOdeSystem::ResetEventsOccurred() +{ + std::fill(mEventTriggered.begin(), mEventTriggered.end(), false); +} + +void AbstractSbmlOdeSystem::RunModelRules(double time, const std::vector& rY) +{ + UpdateStateVariables(time, rY); + UpdateParameters(time); + RunAssignmentRules(time); + RunReactions(time); +} diff --git a/src/AbstractSbmlOdeSystem.hpp b/src/AbstractSbmlOdeSystem.hpp new file mode 100644 index 0000000..7476e14 --- /dev/null +++ b/src/AbstractSbmlOdeSystem.hpp @@ -0,0 +1,229 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ABSTRACT_SBML_ODE_SYSTEM_HPP_ +#define ABSTRACT_SBML_ODE_SYSTEM_HPP_ + +#include + +#include "AbstractOdeSystem.hpp" +#include "ChasteSerialization.hpp" +#include "ClassIsAbstract.hpp" +#include "SbmlEventType.hpp" + +/** + * Abstract SBML ODE System class. + * + * Sets up variables and functions for an ODE system imported from SBML. + * + * Instances can store event state internally in the mEvent* vectors - the + * vectors may be empty if the model does not have any events defined. + */ +class AbstractSbmlOdeSystem : public AbstractOdeSystem +{ +private: + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Archive / unarchive the AbstractSbmlOdeSystem. + * + * @param archive the archive + * @param version the current version of this class + */ + template + void serialize(Archive& archive, const unsigned int version) + { + archive& boost::serialization::base_object(*this); + archive & mNumberOfParameters; + archive & mNumberOfEvents; + archive & mEventSatisfied; + archive & mEventClampActive; + archive & mEventTriggered; + archive & mEventType; + archive & mEventAdjustedStateVars; + archive & mEventAdjustedStateValues; + archive & mEventAdjustedStatePriority; + archive & mEventAdjustedParameters; + archive & mEventAdjustedParameterValues; + archive & mEventAdjustedParameterPriority; + } + + /** + * Process the events in the model. + * + * @param time The current time + * @param rY The current state variables + * + * @return How close we are to the time of the next event + */ + virtual double ProcessModelEvents(double time, const std::vector& rY) = 0; + + /** + * Run the assignment rules to update state. + * + * @param time The current time + * @param rY The current state variables + */ + virtual void RunAssignmentRules(double time) = 0; + + /** + * Run the initial assignments to set initial state. + * + * @param time The current time + */ + virtual void RunInitialAssignments(double time) = 0; + + /** + * Run the reactions to update state. + * + * @param time The current time + * @param rY The current state variables + */ + virtual void RunReactions(double time) = 0; + + /** + * Update variable parameters from current ODE system parameter settings. + * + * @param time The current time + */ + virtual void UpdateParameters(double time) = 0; + + /** + * Update state variables from the given ODE system state. + * + * @param time The current time + * @param rStateVariables The state variables to use + */ + virtual void UpdateStateVariables(double time, const std::vector& rStateVariables) = 0; + +protected: + /** The number of parameters in the model */ + unsigned mNumberOfParameters; + + /** The number of events in the model */ + unsigned mNumberOfEvents; + + // Event handling + std::vector mEventSatisfied; + /** + * Per-event clamp flag used only by the CVODE root function. Frozen at the start of each + * Solve segment (a snapshot of mEventSatisfied in CalculateStoppingEvent) and cleared + * permanently the instant an event's trigger first goes false (the event re-arms). While + * set, the event's root distance is forced large-negative so CVODE reports no spurious + * root for a trigger already active at the initial condition. Being monotonic within a + * segment (only ever cleared, never re-set) it stays constant across CVODE's in-step root + * bracketing, so events localize at the true crossing instead of the step endpoint, while + * an event that re-arms and re-fires within the same segment is still detected. + */ + std::vector mEventClampActive; + std::vector mEventTriggered; + std::vector mEventType; + std::vector mEventAdjustedStateVars; + std::vector mEventAdjustedStateValues; + std::vector mEventAdjustedStatePriority; + std::vector mEventAdjustedParameters; + std::vector mEventAdjustedParameterValues; + std::vector mEventAdjustedParameterPriority; + + /** + * Run the equations governing the model to update state. + * + * @param time The current time + * @param rY The current state variables + */ + void RunModelRules(double time, const std::vector& rY); + +public: + /** + * Constructor. + * + * @param numberOfStateVariables The number of state variables in the model + * @param numberOfParameters The number of parameters in the model + * @param numberOfEvents The number of events in the model + */ + AbstractSbmlOdeSystem(unsigned numberOfStateVariables, unsigned numberOfParameters, unsigned numberOfEvents); + + /** + * Destructor. + */ + virtual ~AbstractSbmlOdeSystem(); + + /** + * Adjust parameters and state variables after a stopping event + * + * @param time The current time + */ + void AdjustParameters(double time); + + /** + * Calculate whether the conditions to trigger an event have been met + * (Used by CVODE solver to find exact stopping position) + * + * @param time The current time + * @param rY The current state variables + * + * @return How close we are to the root of the stopping condition + */ + double CalculateRootFunction(double time, const std::vector& rY) override; + + /** + * Calculate whether the conditions to trigger an event have been met + * + * @param time The current time + * @param rY The current state variables + * + * @return True if conditions for an event are met, false otherwise + */ + bool CalculateStoppingEvent(double time, const std::vector& rY) override; + + /** + * Check if a specific type of event has occurred. + * + * @param eventType The type of event to check + * + * @return True if the type of event has occurred, false otherwise + */ + bool HasEventOccurred(SbmlEventType eventType); + + /** + * Reset the flags that indicate which events have been triggered. + */ + void ResetEventsOccurred(); +}; + +// Register abstract class with Boost serialization +CLASS_IS_ABSTRACT(AbstractSbmlOdeSystem) + +#endif // ABSTRACT_SBML_ODE_SYSTEM_HPP_ diff --git a/src/AbstractSbmlSrnModel.cpp b/src/AbstractSbmlSrnModel.cpp new file mode 100644 index 0000000..e08427c --- /dev/null +++ b/src/AbstractSbmlSrnModel.cpp @@ -0,0 +1,124 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "AbstractOdeSrnModel.hpp" +#include "AbstractSbmlOdeSystem.hpp" +#include "CellCycleModelOdeSolver.hpp" +#include "ChasteSerialization.hpp" +#include "CvodeAdaptor.hpp" +#include "RungeKutta4IvpOdeSolver.hpp" + +#include "AbstractSbmlSrnModel.hpp" + +AbstractSbmlSrnModel::AbstractSbmlSrnModel(unsigned stateSize, boost::shared_ptr pOdeSolver) + : AbstractOdeSrnModel(stateSize, pOdeSolver) +{ + if (mpOdeSolver == boost::shared_ptr()) + { +#ifdef CHASTE_CVODE + // Default to CVODE where available + mpOdeSolver = CellCycleModelOdeSolver::Instance(); + mpOdeSolver->Initialise(); + mpOdeSolver->SetMaxSteps(10000); // Safe default + mpOdeSolver->SetTolerances(1e-6, 1e-8); + // CVODE needs to be instructed to check for stopping events + mpOdeSolver->CheckForStoppingEvents(); +#else + // Default to Chaste Runge-Kutta solver where CVODE is not available + mpOdeSolver = CellCycleModelOdeSolver::Instance(); + mpOdeSolver->Initialise(); + this->SetDt(0.0001); // Safe default +#endif // CHASTE_CVODE + } + + assert(mpOdeSolver->IsSetUp()); +} + +AbstractSbmlSrnModel::AbstractSbmlSrnModel(const AbstractSbmlSrnModel& rModel) + : AbstractOdeSrnModel(rModel) +{ + /* + * Set each member variable of the new SRN model that inherits + * its value from the parent. + * + * Note 1: some of the new SRN model's member variables + * will already have been correctly initialized in its constructor. + * + * Note 2: one or more of the new SRN model's member variables + * may be set/overwritten as soon as InitialiseDaughterCell() is called on + * the new SRN model. + * + * Note 3: Only set the variables defined in this class. Variables defined + * in parent classes will be defined there. + */ +} + +AbstractSbmlSrnModel::~AbstractSbmlSrnModel() +{ +} + +double AbstractSbmlSrnModel::GetStateVariable(const std::string& rName) +{ + assert(mpOdeSystem != nullptr); + return mpOdeSystem->GetStateVariable(rName); +} + +void AbstractSbmlSrnModel::Initialise(AbstractSbmlOdeSystem* pOdeSystem) +{ + AbstractOdeSrnModel::Initialise(pOdeSystem); +} + +void AbstractSbmlSrnModel::OutputSrnModelParameters(out_stream& rParamsFile) +{ + // No new parameters to output, so just call method on direct parent class + AbstractOdeSrnModel::OutputSrnModelParameters(rParamsFile); +} + +void AbstractSbmlSrnModel::SimulateToCurrentTime() +{ + assert(mpOdeSystem != NULL); + assert(mpCell != NULL); + + // Run the ODE simulation as needed + AbstractOdeSrnModel::SimulateToCurrentTime(); +} + +// Register class with Boost serialization +#include "SerializationExportWrapperForCpp.hpp" +CHASTE_CLASS_EXPORT(AbstractSbmlSrnModel) + +// Register the CellCycleModel classes with Boost serialization +#include "CellCycleModelOdeSolverExportWrapper.hpp" +EXPORT_CELL_CYCLE_MODEL_ODE_SOLVER(AbstractSbmlSrnModel) diff --git a/src/AbstractSbmlSrnModel.hpp b/src/AbstractSbmlSrnModel.hpp new file mode 100644 index 0000000..db0dd18 --- /dev/null +++ b/src/AbstractSbmlSrnModel.hpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ABSTRACT_SBML_SRN_MODEL_HPP_ +#define ABSTRACT_SBML_SRN_MODEL_HPP_ + +#include + +#include "AbstractOdeSrnModel.hpp" +#include "AbstractSbmlOdeSystem.hpp" +#include "ChasteSerialization.hpp" +#include "ClassIsAbstract.hpp" + +/** + * A base class for SRN models generated from SBML + */ + +class AbstractSbmlSrnModel : public AbstractOdeSrnModel +{ +private: + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Archive / unarchive the AbstractSbmlSrnModel. + * + * @param archive the archive + * @param version the current version of this class + */ + template + void serialize(Archive& archive, const unsigned int version) + { + archive & boost::serialization::base_object(*this); + } + +protected: + /** + * Protected copy-constructor for use by CreateSrnModel(). + * + * The only way to copy an instance of a subclass of AbstractCellCycleModel is + * by calling CreateSrnModel(), which ensures that the instance is copied + * correctly. + * + * This copy-constructor helps subclasses of AbstractSrnModel to + * ensure that all their members are copied over correctly. It is primarily + * used during cell division to set member variables for a daughter cell. + * Note that the SRN model of the parent cell will have run ResetForDivision() + * just before calling CreateSrnModel(), so performing an exact copy of the + * parent cell's SRN model is suitable behaviour. Any further initialisation + * specific to the daughter cell can be completed via InitialiseDaughterCell(). + * + * @param rModel the SRN model to copy. + */ + AbstractSbmlSrnModel(const AbstractSbmlSrnModel& rModel); + + using AbstractOdeSrnModel::Initialise; + /** + * Overridden Initialise() method to set up the ODE system. + * + * @param pOdeSystem pointer to an ODE system + */ + void Initialise(AbstractSbmlOdeSystem* pOdeSystem); + +public: + /** + * Constructor. + * + * @param stateSize The number of state variables in the ODE system. + * @param pOdeSolver An optional pointer to a cell-cycle model ODE solver + * object (allows the use of different ODE solvers) + */ + AbstractSbmlSrnModel(unsigned stateSize, + boost::shared_ptr pOdeSolver = boost::shared_ptr()); + + /** + * Destructor. + */ + virtual ~AbstractSbmlSrnModel(); + + /** + * @return the value of a given state variable. + * + * @param rName the name of the state variable + */ + double GetStateVariable(const std::string& rName); + + /** + * Outputs SRN model parameters to file. + * + * @param rParamsFile the file stream to which the parameters are output + */ + void OutputSrnModelParameters(out_stream& rParamsFile); + + /** + * Overridden SimulateToCurrentTime() method for custom behaviour + */ + void SimulateToCurrentTime(); +}; + +// Register abstract class with Boost serialization +CLASS_IS_ABSTRACT(AbstractSbmlSrnModel) + +// Register the CellCycleModel classes with Boost serialization +#include "CellCycleModelOdeSolverExportWrapper.hpp" +EXPORT_CELL_CYCLE_MODEL_ODE_SOLVER(AbstractSbmlSrnModel) + +#endif // ABSTRACT_SBML_SRN_MODEL_HPP_ diff --git a/src/SbmlEventType.hpp b/src/SbmlEventType.hpp new file mode 100644 index 0000000..e2b39b5 --- /dev/null +++ b/src/SbmlEventType.hpp @@ -0,0 +1,10 @@ +#ifndef SBML_EVENT_TYPE_HPP_ +#define SBML_EVENT_TYPE_HPP_ + +enum class SbmlEventType +{ + CELL_DIVISION, + UNKNOWN +}; + +#endif // SBML_EVENT_TYPE_HPP_ diff --git a/src/SbmlMath.cpp b/src/SbmlMath.cpp new file mode 100644 index 0000000..4a05778 --- /dev/null +++ b/src/SbmlMath.cpp @@ -0,0 +1,190 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "SbmlMath.hpp" + +// Arithmetic =================================== + +// divide +double sbmlmath::divide(double x, double y) +{ + return x / y; +} + +// minus +double sbmlmath::minus(double x, double y) +{ + return x - y; +} + +// plus +// times + +// Logs and exponents =========================== + +// log +double sbmlmath::log(double x) +{ + return std::log(x); +} + +double sbmlmath::log(double b, double x) +{ + return std::log(x) / std::log(b); +} + +// root +double sbmlmath::root(double n, double x) +{ + return std::pow(x, 1.0 / n); +} + +// sqr +double sbmlmath::sqr(double x) +{ + return x * x; +} + +// Logical ====================================== + +// and_ +// or_ + +// not_ +bool sbmlmath::not_(bool x) +{ + return !x; +} + +// xor_ + +// Relational =================================== + +// eq +// geq +// gt +// leq +// lt + +// neq +bool sbmlmath::neq(double x, double y) +{ + return x != y; +} + +// Trigonometry ================================= + +// cot, coth, acot, acoth +double sbmlmath::cot(double x) +{ + return 1.0 / std::tan(x); +} + +double sbmlmath::coth(double x) +{ + return 1.0 / std::tanh(x); +} + +double sbmlmath::acot(double x) +{ + return std::atan(1.0 / x); +} + +double sbmlmath::acoth(double x) +{ + return std::atanh(1.0 / x); +} + +// csc, csch, acsc, acsch +double sbmlmath::csc(double x) +{ + return 1.0 / std::sin(x); +} + +double sbmlmath::csch(double x) +{ + return 1.0 / std::sinh(x); +} + +double sbmlmath::acsc(double x) +{ + return std::asin(1.0 / x); +} + +double sbmlmath::acsch(double x) +{ + return std::asinh(1.0 / x); +} + +// sec, sech, asec, asech +double sbmlmath::sec(double x) +{ + return 1.0 / std::cos(x); +} + +double sbmlmath::sech(double x) +{ + return 1.0 / std::cosh(x); +} + +double sbmlmath::asec(double x) +{ + return std::acos(1.0 / x); +} + +double sbmlmath::asech(double x) +{ + return std::acosh(1.0 / x); +} + +// Other functions ============================== + +// factorial +double sbmlmath::factorial(double x) +{ + return std::tgamma(x + 1.0); +} + +// max +// min +// piecewise + +// quotient +double sbmlmath::quotient(double numer, double denom) +{ + return std::trunc(numer / denom); +} diff --git a/src/SbmlMath.hpp b/src/SbmlMath.hpp new file mode 100644 index 0000000..28422c8 --- /dev/null +++ b/src/SbmlMath.hpp @@ -0,0 +1,310 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SBML_MATH_HPP_ +#define SBML_MATH_HPP_ + +/** + * SBML math functions. + */ + +#include + +namespace sbmlmath +{ +// Constants ================================== + +// SBML Level 3 recommended avogadro value: +// https://sbml.org/documents/specifications/level-3/ +inline constexpr double AVOGADRO = 6.02214179E23; + +// Note: Avogadro value has been updated in the most recent SI Brochure. +// Bureau International des Poids et Mesures (2019): +// The International System of Units (SI), 9th edition + +// Arithmetic ================================= + +// divide +double divide(double x, double y); + +// minus +double minus(double x, double y); + +// plus +template +constexpr double plus(Args... args); + +// times +template +constexpr double times(Args... args); + +// Logs and exponents ========================= + +// log +double log(double x); +double log(double b, double x); + +// root +double root(double n, double x); + +// sqr +double sqr(double x); + +// Logical ==================================== + +// and_ +template +constexpr bool and_(Args... args); + +// or_ +template +constexpr bool or_(Args... args); + +// not_ +bool not_(bool x); + +// xor_ +template +constexpr bool xor_(Args... args); + +// Relational ================================= + +// eq +template +constexpr bool eq(double first, double second, Args... rest); + +// geq +template +constexpr bool geq(double first, double second, Args... rest); + +// gt +template +constexpr bool gt(double first, double second, Args... rest); + +// leq +template +constexpr bool leq(double first, double second, Args... rest); + +// lt +template +constexpr bool lt(double first, double second, Args... rest); + +// neq +bool neq(double x, double y); + +// Trigonometry =============================== + +// cot, coth, acot, acoth +double cot(double x); +double coth(double x); +double acot(double x); +double acoth(double x); + +// csc, csch, acsc, acsch +double csc(double x); +double csch(double x); +double acsc(double x); +double acsch(double x); + +// sec, sech, asec, asech +double sec(double x); +double sech(double x); +double asec(double x); +double asech(double x); + +// Other functions ============================ + +// factorial +double factorial(double x); + +// max +template +constexpr double max(double first, double second, Args... rest); + +// min +template +constexpr double min(double first, double second, Args... rest); + +// piecewise +constexpr double piecewise(double otherwise); + +constexpr double piecewise(double value, bool condition, double otherwise); + +template +constexpr double piecewise(double value, bool condition, Args... rest); + +// quotient +double quotient(double numer, double denom); + +} // namespace sbmlmath + +// Arithmetic (variadic templates) ============ + +// plus +template +constexpr double sbmlmath::plus(Args... args) +{ + return (0.0 + ... + args); +} + +// times +template +constexpr double sbmlmath::times(Args... args) +{ + return (1.0 * ... * args); +} + +// Logical (variadic templates) ================= + +// and_ +template +constexpr bool sbmlmath::and_(Args... args) +{ + return (true && ... && args); +} + +// or_ +template +constexpr bool sbmlmath::or_(Args... args) +{ + return (false || ... || args); +} + +// xor_ +template +constexpr bool sbmlmath::xor_(Args... args) +{ + return (false ^ ... ^ args); +} + +// Relational (variadic templates) ============== + +// eq +template +constexpr bool sbmlmath::eq(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return (first == second) && sbmlmath::eq(second, rest...); + } + return first == second; +} + +// geq +template +constexpr bool sbmlmath::geq(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return (first >= second) && sbmlmath::geq(second, rest...); + } + return first >= second; +} + +// gt +template +constexpr bool sbmlmath::gt(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return (first > second) && sbmlmath::gt(second, rest...); + } + return first > second; +} + +// leq +template +constexpr bool sbmlmath::leq(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return (first <= second) && sbmlmath::leq(second, rest...); + } + return first <= second; +} + +// lt +template +constexpr bool sbmlmath::lt(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return (first < second) && sbmlmath::lt(second, rest...); + } + return first < second; +} + +// Other (variadic templates) =================== + +// max +template +constexpr double sbmlmath::max(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return std::fmax(first, sbmlmath::max(second, rest...)); + } + return std::fmax(first, second); +} + +// min +template +constexpr double sbmlmath::min(double first, double second, Args... rest) +{ + if constexpr (sizeof...(rest) > 0) + { + return std::fmin(first, sbmlmath::min(second, rest...)); + } + return std::fmin(first, second); +} + +// piecewise +constexpr double sbmlmath::piecewise(double otherwise) +{ + return otherwise; +} + +constexpr double sbmlmath::piecewise(double value, bool condition, double otherwise) +{ + return condition ? value : otherwise; +} + +template +constexpr double sbmlmath::piecewise(double value, bool condition, Args... rest) +{ + return condition ? value : sbmlmath::piecewise(rest...); +} + +#endif // SBML_MATH_HPP_ diff --git a/src/fortests/SbmlTestHelpers.cpp b/src/fortests/SbmlTestHelpers.cpp new file mode 100644 index 0000000..a01ab3f --- /dev/null +++ b/src/fortests/SbmlTestHelpers.cpp @@ -0,0 +1,143 @@ + +#include +#include +#include +#include + +#include "AbstractOdeSystem.hpp" +#include "OdeSolution.hpp" +#include "OutputFileHandler.hpp" + +#include "SbmlTestHelpers.hpp" + +void sbmltesthelpers::ExportCsv(const std::string& rFilename, + OdeSolution& rOdeSolution, + AbstractOdeSystem& rOdeSystem, + const std::vector >* pParamsPerStep) +{ + OutputFileHandler handler(""); + out_stream file = handler.OpenOutputFile(rFilename); + + // Times + const std::vector& time_data = rOdeSolution.rGetTimes(); + + // State variables + const std::vector& svar_names = rOdeSystem.rGetStateVariableNames(); + const std::vector >& svar_data = rOdeSolution.rGetSolutions(); + + // Derived quantities + rOdeSolution.CalculateDerivedQuantitiesAndParameters(&rOdeSystem); + const std::vector& dq_names = rOdeSystem.rGetDerivedQuantityNames(); + const std::vector >& dq_data = rOdeSolution.rGetDerivedQuantities(&rOdeSystem); + + // Parameters + const std::vector& param_names = rOdeSystem.rGetParameterNames(); + const std::vector& param_data = rOdeSolution.rGetParameters(&rOdeSystem); + + // Sanity checks + if (time_data.empty()) + { + throw std::invalid_argument("OdeSolution contains no time points."); + } + + if (svar_data.empty() && dq_data.empty()) + { + throw std::invalid_argument("OdeSolution contains no state variables or derived quantities."); + } + + if (!svar_data.empty() && (svar_data.size() != time_data.size())) + { + throw std::length_error("Number of state variable data rows do not match time points."); + } + + if (!dq_data.empty() && (dq_data.size() != time_data.size())) + { + throw std::length_error("Number of derived quantity data rows do not match time points."); + } + + if ((svar_data.empty() && !svar_names.empty()) || (!svar_data.empty() && svar_data[0].size() != svar_names.size())) + { + throw std::length_error("Number of state variable names do not match data."); + } + + if ((dq_data.empty() && !dq_names.empty()) || (!dq_data.empty() && dq_data[0].size() != dq_names.size())) + { + throw std::length_error("Number of derived quantity names do not match data."); + } + + if ((param_data.empty() && !param_names.empty()) || (!param_data.empty() && param_data.size() != param_names.size())) + { + throw std::length_error("Number of parameter names do not match data."); + } + + // Write column headings + (*file) << "time"; + if (!svar_names.empty()) + { + for (unsigned i = 0; i < svar_names.size(); i++) + { + (*file) << "," << svar_names[i]; + } + } + if (!dq_names.empty()) + { + for (unsigned i = 0; i < dq_names.size(); i++) + { + (*file) << "," << dq_names[i]; + } + } + if (!param_names.empty()) + { + for (unsigned i = 0; i < param_names.size(); i++) + { + (*file) << "," << param_names[i]; + } + } + (*file) << std::endl + << std::flush; + + // Write data + for (unsigned i = 0; i < time_data.size(); i++) + { + (*file) << time_data[i]; + if (!svar_data.empty()) + { + for (unsigned j = 0; j < svar_data[i].size(); j++) + { + (*file) << "," << svar_data[i][j]; + } + } + if (!dq_data.empty()) + { + for (unsigned j = 0; j < dq_data[i].size(); j++) + { + (*file) << "," << dq_data[i][j]; + } + } + if (pParamsPerStep != nullptr && i < pParamsPerStep->size()) + { + // Time-resolved parameters (e.g. a parameter changed by an event). + for (unsigned j = 0; j < (*pParamsPerStep)[i].size(); j++) + { + (*file) << "," << (*pParamsPerStep)[i][j]; + } + } + else if (!param_data.empty()) + { + for (unsigned j = 0; j < param_data.size(); j++) + { + (*file) << "," << param_data[j]; + } + } + (*file) << std::endl + << std::flush; + } + file->close(); +} + +std::string sbmltesthelpers::ToString(double value, unsigned precision) +{ + std::ostringstream oss; + oss << std::fixed << std::setprecision(precision) << value; + return oss.str(); +} diff --git a/src/fortests/SbmlTestHelpers.hpp b/src/fortests/SbmlTestHelpers.hpp new file mode 100644 index 0000000..6485aae --- /dev/null +++ b/src/fortests/SbmlTestHelpers.hpp @@ -0,0 +1,195 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SBML_TEST_HELPERS_HPP_ +#define SBML_TEST_HELPERS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AbstractOdeSystem.hpp" +#include "OdeSolution.hpp" + +namespace sbmltesthelpers +{ +/** Export results to a CSV file. + * + * The first column is time, followed by the state variables, derived quantities and + * parameters. + * + * @param rFilename The name of the file to create. + * @param rOdeSolution The OdeSolution containing the results. + * @param rOdeSystem The ODE system (for variable names and derived quantities). + * @param pParamsPerStep Optional time-resolved parameter values (one row per time step, + * as recorded by SbmlTestOdeSolution). When null the parameters are written from the + * system's single current snapshot, repeated on every row. + */ +void ExportCsv(const std::string& rFilename, + OdeSolution& rOdeSolution, + AbstractOdeSystem& rOdeSystem, + const std::vector >* pParamsPerStep = nullptr); + +/** Calculate the maximum of a vector of doubles. + * @param vec The vector of doubles. + * @return The maximum value. + */ +inline double Max(const std::vector& vec); + +/** Calculate the mean of a vector of doubles. + * @param vec The vector of doubles. + * @return The mean value. + */ +inline double Mean(const std::vector& vec); + +/** Calculate the minimum of a vector of doubles. + * @param vec The vector of doubles. + * @return The minimum value. + */ +inline double Min(const std::vector& vec); + +/** Calculate the standard deviation of a vector of doubles. + * @param vec The vector of doubles. + * @return The standard deviation value. + */ +inline double Stdev(const std::vector& vec); + +/** Convert a double to a string. + * @param value The double value. + * @return The string representation. + */ +std::string ToString(double value, unsigned precision = 9); + +/** Calculate the qth quantile of a vector of doubles. + * @param vec The vector of doubles, assumed to be sorted. + * @param q The quantile to calculate (between 0 and 1). + * @return The qth quantile value. + */ +inline double Quantile(const std::vector& vec, double q); + +/** Calculate the variance of a vector of doubles. + * @param vec The vector of doubles. + * @return The variance value. + */ +inline double Variance(const std::vector& vec); +} // namespace sbmltesthelpers + +// max +inline double sbmltesthelpers::Max(const std::vector& vec) +{ + if (vec.empty()) + { + throw std::invalid_argument("Cannot calculate maximum of an empty vector."); + } + return *std::max_element(vec.begin(), vec.end()); +} + +// mean +inline double sbmltesthelpers::Mean(const std::vector& vec) +{ + if (vec.empty()) + { + throw std::invalid_argument("Cannot calculate mean of an empty vector."); + } + return std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size(); +} + +// min +inline double sbmltesthelpers::Min(const std::vector& vec) +{ + if (vec.empty()) + { + throw std::invalid_argument("Cannot calculate minimum of an empty vector."); + } + return *std::min_element(vec.begin(), vec.end()); +} + +// stdev +inline double sbmltesthelpers::Stdev(const std::vector& vec) +{ + return std::sqrt(sbmltesthelpers::Variance(vec)); +} + +// quantile +inline double sbmltesthelpers::Quantile(const std::vector& vec, double q) +{ + if (vec.empty()) + { + throw std::invalid_argument("Cannot calculate quantile of an empty vector."); + } + + if (q < 0.0 || q > 1.0) + { + throw std::out_of_range("Quantile must be between 0.0 and 1.0"); + } + + std::vector sorted_vec = vec; + std::sort(sorted_vec.begin(), sorted_vec.end()); + + size_t n = sorted_vec.size(); + size_t index = static_cast(q * (n - 1)); + + // Even number of elements + if (n % 2 == 0) + { + return (sorted_vec[index] + sorted_vec[index - 1]) / 2.0; + } + + // Odd number of elements + return sorted_vec[index]; +} + +// variance +inline double sbmltesthelpers::Variance(const std::vector& vec) +{ + if (vec.size() < 2) + { + throw std::invalid_argument("Variance requires at least two data points."); + } + double mean_val = sbmltesthelpers::Mean(vec); + double accum = 0.0; + for (double val : vec) + { + accum += (val - mean_val) * (val - mean_val); + } + return accum / (vec.size() - 1); +} + +#endif // SBML_TEST_HELPERS_HPP_ diff --git a/src/fortests/SbmlTestOdeSolution.cpp b/src/fortests/SbmlTestOdeSolution.cpp new file mode 100644 index 0000000..7162743 --- /dev/null +++ b/src/fortests/SbmlTestOdeSolution.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "SbmlTestOdeSolution.hpp" + +void SbmlTestOdeSolution::RecordPoint(double time, const std::vector& rY, AbstractOdeSystem* pSystem) +{ + if (rGetTimes().empty()) + { + SetOdeSystemInformation(pSystem->GetSystemInformation()); + } + + // Snapshot the system's current parameters for this point. + std::vector params(pSystem->GetNumberOfParameters()); + for (unsigned i = 0; i < params.size(); ++i) + { + params[i] = pSystem->GetParameter(i); + } + + rGetTimes().push_back(time); + rGetSolutions().push_back(rY); + mParametersPerStep.push_back(params); + + SetNumberOfTimeSteps(rGetTimes().size()); +} + +std::vector SbmlTestOdeSolution::GetParameterSeries(const std::string& rName, AbstractOdeSystem* pSystem) const +{ + unsigned index = pSystem->GetParameterIndex(rName); + std::vector series(mParametersPerStep.size()); + for (unsigned i = 0; i < series.size(); ++i) + { + series[i] = mParametersPerStep[i][index]; + } + return series; +} + +std::vector SbmlTestOdeSolution::GetDerivedQuantitySeries(const std::string& rName, AbstractOdeSystem* pSystem) const +{ + unsigned index = pSystem->GetSystemInformation()->GetDerivedQuantityIndex(rName); + + // Save the system's current parameters so they can be restored afterwards. + std::vector saved(pSystem->GetNumberOfParameters()); + for (unsigned p = 0; p < saved.size(); ++p) + { + saved[p] = pSystem->GetParameter(p); + } + + std::vector series(rGetTimes().size()); + for (unsigned i = 0; i < series.size(); ++i) + { + // Restore this step's parameters so parameter-dependent derived quantities are time-resolved. + for (unsigned p = 0; p < mParametersPerStep[i].size(); ++p) + { + pSystem->SetParameter(p, mParametersPerStep[i][p]); + } + series[i] = pSystem->ComputeDerivedQuantities(rGetTimes()[i], rGetSolutions()[i])[index]; + } + + for (unsigned p = 0; p < saved.size(); ++p) + { + pSystem->SetParameter(p, saved[p]); + } + return series; +} diff --git a/src/fortests/SbmlTestOdeSolution.hpp b/src/fortests/SbmlTestOdeSolution.hpp new file mode 100644 index 0000000..ba1b8e7 --- /dev/null +++ b/src/fortests/SbmlTestOdeSolution.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2005-2025, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SBML_TEST_ODE_SOLUTION_HPP_ +#define SBML_TEST_ODE_SOLUTION_HPP_ + +#include +#include + +#include "AbstractOdeSystem.hpp" +#include "OdeSolution.hpp" + +/** + * An OdeSolution that additionally records the model parameters at each time step. + * + * Chaste's OdeSolution stores only a single parameter snapshot - GetVariableAtIndex + * returns mParameters[i] with no time index, and the header notes it "assumes that + * mParameters is constant through time". A parameter changed by an event (e.g. a + * compartment resized by an event assignment) is therefore reported with its final + * value at every time step. + * + * An SBML model is solved one sample-grid point at a time, stopping at each event; the + * parameters are constant between events and change (via AdjustParameters) at them. + * RecordPoint stores the system's current parameters alongside each grid point, so + * GetParameterSeries can return a genuinely time-resolved series. + */ +class SbmlTestOdeSolution : public OdeSolution +{ +private: + /** Parameter values for each stored time step. */ + std::vector > mParametersPerStep; + +public: + /** + * Append a single solution point, recording the system's current parameter values for it. + * Building the solution one grid point at a time (rather than one segment at a time) keeps + * the output on the requested sample grid even when an event fires between grid points. + * + * @param time the time of the point + * @param rY the state variables at this point + * @param pSystem the ODE system, queried for its current parameter values + */ + void RecordPoint(double time, const std::vector& rY, AbstractOdeSystem* pSystem); + + /** + * @param rName the parameter name + * @param pSystem the ODE system, used to resolve the parameter's index + * @return the parameter's value at each stored time step + */ + std::vector GetParameterSeries(const std::string& rName, AbstractOdeSystem* pSystem) const; + + /** + * Evaluate a derived quantity at every stored step with that step's recorded parameters + * restored first. A derived quantity that depends on an event-modified parameter (e.g. the + * amount conversion amt__X = X * compartment for a boundary species X changed by an event) + * is otherwise computed with the parameter's final value at every point, losing the time + * resolution. Restoring the per-step parameters fixes this. + * + * @param rName the derived quantity name + * @param pSystem the ODE system, used to compute the derived quantities + * @return the derived quantity's value at each stored time step + */ + std::vector GetDerivedQuantitySeries(const std::string& rName, AbstractOdeSystem* pSystem) const; + + /** @return the parameter values recorded for each stored time step. */ + const std::vector >& rGetParametersPerStep() const + { + return mParametersPerStep; + } +}; + +#endif // SBML_TEST_ODE_SOLUTION_HPP_ From 4b35e12022125815f30993f51356aabfad2bfcb6 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Tue, 30 Jun 2026 13:26:50 +0100 Subject: [PATCH 2/5] #3 Update workflow prompts and dependencies --- .github/workflows/test-force-example.yml | 4 ++-- .github/workflows/test-project.yml | 4 ++-- .github/workflows/test-python-project.yml | 4 ++-- .github/workflows/test-sbml-project.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-force-example.yml b/.github/workflows/test-force-example.yml index a6c2417..b41b10d 100644 --- a/.github/workflows/test-force-example.yml +++ b/.github/workflows/test-force-example.yml @@ -34,8 +34,8 @@ jobs: run: | cp -r . ${{ env.PROJECT_DIR }} cd ${{ env.PROJECT_DIR }} - # Yes to cell_based component and Python bindings, defaults otherwise, yes to confirm - printf '\ny\n\n\n\ny\ny\n' | python3 setup_project.py + # Yes to cell_based component and Python bindings, no to SBML, defaults otherwise, yes to confirm + printf '\ny\n\n\n\ny\nn\ny\n' | python3 setup_project.py - name: Add MyForce to the project bindings run: | diff --git a/.github/workflows/test-project.yml b/.github/workflows/test-project.yml index 8402c3b..0c5679e 100644 --- a/.github/workflows/test-project.yml +++ b/.github/workflows/test-project.yml @@ -34,8 +34,8 @@ jobs: run: | cp -r . ${{ env.PROJECT_DIR }} cd ${{ env.PROJECT_DIR }} - # Use prompt defaults, yes to final confirmation - printf '\n\n\n\n\nn\ny\n' | python3 setup_project.py + # Prompt defaults (no components, no Python bindings, no SBML), yes to confirm + printf '\n\n\n\n\nn\nn\ny\n' | python3 setup_project.py - name: Configure run: ${{ env.PROJECT_DIR }}/scripts/configure.sh diff --git a/.github/workflows/test-python-project.yml b/.github/workflows/test-python-project.yml index faabc24..0dc629e 100644 --- a/.github/workflows/test-python-project.yml +++ b/.github/workflows/test-python-project.yml @@ -34,8 +34,8 @@ jobs: run: | cp -r . ${{ env.PROJECT_DIR }} cd ${{ env.PROJECT_DIR }} - # Yes to python bindings prompt and final confirmation prompt - printf '\n\n\n\n\ny\ny\n' | python3 setup_project.py + # Yes to Python bindings, no to SBML, yes to final confirmation + printf '\n\n\n\n\ny\nn\ny\n' | python3 setup_project.py - name: Configure run: | diff --git a/.github/workflows/test-sbml-project.yml b/.github/workflows/test-sbml-project.yml index eb97927..b70ad86 100644 --- a/.github/workflows/test-sbml-project.yml +++ b/.github/workflows/test-sbml-project.yml @@ -30,8 +30,8 @@ jobs: - name: Mark Chaste source as safe for git run: git config --global --add safe.directory "${CHASTE_SOURCE_DIR}" - - name: Install clang-format - run: apt-get update && apt-get install -y clang-format + - name: Install clang-format and curl + run: apt-get update && apt-get install -y clang-format curl - name: Create test project run: | From 061cf3ab293b4dfd19308593ed08f64edea41aa9 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Tue, 30 Jun 2026 14:29:42 +0100 Subject: [PATCH 3/5] #3 Include Goldbeter example --- .github/workflows/test-sbml-project.yml | 5 +- examples/goldbeter_1991/Goldbeter1991.xml | 464 ++++++++++++++++++++++ examples/goldbeter_1991/README.md | 15 +- 3 files changed, 479 insertions(+), 5 deletions(-) create mode 100644 examples/goldbeter_1991/Goldbeter1991.xml diff --git a/.github/workflows/test-sbml-project.yml b/.github/workflows/test-sbml-project.yml index b70ad86..b65d0de 100644 --- a/.github/workflows/test-sbml-project.yml +++ b/.github/workflows/test-sbml-project.yml @@ -46,8 +46,9 @@ jobs: - name: Import the Goldbeter 1991 model run: | cd ${{ env.PROJECT_DIR }} - curl -L "https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000003?filename=BIOMD0000000003_url.xml" \ - -o Goldbeter1991.xml + # Use the vendored model (examples/goldbeter_1991/Goldbeter1991.xml) so the + # job does not depend on reaching BioModels at run time. + cp examples/goldbeter_1991/Goldbeter1991.xml . .virtualenv/bin/chaste_codegen_sbml Goldbeter1991.xml --model-type srn --output-dir src/ cp examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp test/ echo "TestGoldbeter1991SbmlSrnModel.hpp" >> test/ContinuousTestPack.txt diff --git a/examples/goldbeter_1991/Goldbeter1991.xml b/examples/goldbeter_1991/Goldbeter1991.xml new file mode 100644 index 0000000..74d9201 --- /dev/null +++ b/examples/goldbeter_1991/Goldbeter1991.xml @@ -0,0 +1,464 @@ + + + + + +
Goldbeter - Min Mit Oscil
+

Minimal cascade model for the mitotic oscillator involving cyclin and cdc2 kinase.

+
+

This model has been generated by MathSBML 2.4.6 (14-January-2005) 14-January-2005 18:33:39.806932.

+
+

This model is described in the article:

+ +
Goldbeter A.
+
Proc. Natl. Acad. Sci. U.S.A. 1991; 88(20):9107-11
+

Abstract:

+

A minimal model for the mitotic oscillator is presented. The model, built on recent experimental advances, is based on the cascade of post-translational modification that modulates the activity of cdc2 kinase during the cell cycle. The model pertains to the situation encountered in early amphibian embryos, where the accumulation of cyclin suffices to trigger the onset of mitosis. In the first cycle of the bicyclic cascade model, cyclin promotes the activation of cdc2 kinase through reversible dephosphorylation, and in the second cycle, cdc2 kinase activates a cyclin protease by reversible phosphorylation. That cyclin activates cdc2 kinase while the kinase triggers the degradation of cyclin has suggested that oscillations may originate from such a negative feedback loop [Félix, M. A., Labbé, J. C., Dorée, M., Hunt, T. & Karsenti, E. (1990) Nature (London) 346, 379-382]. This conjecture is corroborated by the model, which indicates that sustained oscillations of the limit cycle type can arise in the cascade, provided that a threshold exists in the activation of cdc2 kinase by cyclin and in the activation of cyclin proteolysis by cdc2 kinase. The analysis shows how miototic oscillations may readily arise from time lags associated with these thresholds and from the delayed negative feedback provided by cdc2-induced cyclin degradation. A mechanism for the origin of the thresholds is proposed in terms of the phenomenon of zero-order ultrasensitivity previously described for biochemical systems regulated by covalent modification.

+
+
+

This model is hosted on BioModels Database + and identified by: BIOMD0000000003 + .

+

To cite BioModels Database, please use: BioModels Database: An enhanced, curated and annotated resource for published quantitative kinetic models + .

+
+

To the extent possible under law, all copyright and related or neighbouring rights to this encoded model have been dedicated to the public domain worldwide. Please refer to CC0 Public Domain Dedication + for more information.

+
+ + +
+ + + + + + + + Shapiro + Bruce + + bshapiro@jpl.nasa.gov + + NASA Jet Propulsion Laboratory + + + + + Chelliah + Vijayalakshmi + + viji@ebi.ac.uk + + EMBL-EBI + + + + + + 2005-02-06T23:39:40Z + + + 2013-05-16T14:38:01Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + VM1 + + + + + C + Kc + + -1 + + + + + + + + + M + VM3 + + + + + + + + + + + + + + + + + + + + + + + + + cell + vi + + + + + + + + + + + + + + + + + + + + + + + + + + + C + cell + kd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + cell + vd + X + + + + + C + Kd + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cell + + + 1 + + + -1 + M + + + V1 + + + + + K1 + + + -1 + M + + 1 + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cell + M + V2 + + + + + K2 + M + + -1 + + + + + + + + + + + + + + + + + + cell + V3 + + + 1 + + + -1 + X + + + + + + + K3 + + + -1 + X + + 1 + + -1 + + + + + + + + + + + + + + + + + cell + V4 + X + + + + + K4 + X + + -1 + + + + + + + + + + +
+
\ No newline at end of file diff --git a/examples/goldbeter_1991/README.md b/examples/goldbeter_1991/README.md index 9dbeffe..f152957 100644 --- a/examples/goldbeter_1991/README.md +++ b/examples/goldbeter_1991/README.md @@ -19,10 +19,19 @@ Run every command below from your project's root directory. source .virtualenv/bin/activate ``` -## 2. Download the SBML model and give it a clean name +## 2. Get the SBML model with a clean name -The generated C++ class names come from the **file name**, so download the model and -rename it to something C++-friendly. Here we use `Goldbeter1991`: +The generated C++ class names come from the **file name**, so the model needs a +C++-friendly name. Here we use `Goldbeter1991`. + +A copy of the model is included next to this walkthrough, so just copy it in: + +```sh +cp examples/goldbeter_1991/Goldbeter1991.xml . +``` + +Alternatively, download it from BioModels and rename it yourself (this needs network +access to `www.ebi.ac.uk`): ```sh curl -L "https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000003?filename=BIOMD0000000003_url.xml" -o Goldbeter1991.xml From a2dc66ab2ee36cda6cd94c287a0e58f27650fe56 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Tue, 30 Jun 2026 17:16:46 +0100 Subject: [PATCH 4/5] #3 Fix Goldbeter1991 example --- .github/workflows/test-sbml-project.yml | 8 ++-- examples/goldbeter_1991/README.md | 45 ++++++++++++------- .../TestGoldbeter1991SbmlSrnModel.hpp | 43 +++++++++++------- test/ContinuousTestPack.txt | 2 +- 4 files changed, 61 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test-sbml-project.yml b/.github/workflows/test-sbml-project.yml index b65d0de..998f4da 100644 --- a/.github/workflows/test-sbml-project.yml +++ b/.github/workflows/test-sbml-project.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest container: - image: chaste/develop + image: chaste/base options: --user root env: @@ -27,8 +27,10 @@ jobs: - name: Checkout template uses: actions/checkout@v7 - - name: Mark Chaste source as safe for git - run: git config --global --add safe.directory "${CHASTE_SOURCE_DIR}" + - name: Checkout Chaste source + run: | + git clone --depth 1 --branch develop https://github.com/Chaste/Chaste.git "${CHASTE_SOURCE_DIR}" + git config --global --add safe.directory "${CHASTE_SOURCE_DIR}" - name: Install clang-format and curl run: apt-get update && apt-get install -y clang-format curl diff --git a/examples/goldbeter_1991/README.md b/examples/goldbeter_1991/README.md index f152957..c56e0d4 100644 --- a/examples/goldbeter_1991/README.md +++ b/examples/goldbeter_1991/README.md @@ -67,10 +67,11 @@ active cyclin protease (`X`). #include "AbstractCellBasedTestSuite.hpp" #include "Cell.hpp" -#include "NoCellCycleModel.hpp" +#include "CellPropertyRegistry.hpp" +#include "FixedG1GenerationalCellCycleModel.hpp" #include "SimulationTime.hpp" #include "SmartPointers.hpp" -#include "StemCellProliferativeType.hpp" +#include "TransitCellProliferativeType.hpp" #include "WildTypeCellMutationState.hpp" // The header generated from Goldbeter1991.xml in step 3. @@ -84,33 +85,43 @@ class TestGoldbeter1991SbmlSrnModel : public AbstractCellBasedTestSuite public: void TestSteadyStateSimulation() { - // Integrate to t = 1000 in 1000 steps (AbstractCellBasedTestSuite has set the start time to 0). + // Run until t = 100 with dt = 0.001, by which time the mitotic oscillator + // has settled to the reference steady state. SimulationTime* p_simulation_time = SimulationTime::Instance(); - p_simulation_time->SetEndTimeAndNumberOfTimeSteps(1000.0, 1000); - - // Create the SRN model from the imported SBML model and attach it to a cell. + double end_time = 100; + double dt = 0.001; + unsigned num_steps = (unsigned)(end_time / dt); + p_simulation_time->SetEndTimeAndNumberOfTimeSteps(end_time, num_steps + 1); + + // Create a cell carrying the imported SRN model. + boost::shared_ptr p_healthy_state( + CellPropertyRegistry::Instance()->Get()); + boost::shared_ptr p_transit_type( + CellPropertyRegistry::Instance()->Get()); + + FixedG1GenerationalCellCycleModel* p_cell_model = new FixedG1GenerationalCellCycleModel(); Goldbeter1991SbmlSrnModel* p_srn_model = new Goldbeter1991SbmlSrnModel(); - MAKE_PTR(WildTypeCellMutationState, p_state); - MAKE_PTR(StemCellProliferativeType, p_stem_type); - NoCellCycleModel* p_cc_model = new NoCellCycleModel(); - - CellPtr p_cell(new Cell(p_state, p_cc_model, p_srn_model)); - p_cell->SetCellProliferativeType(p_stem_type); + CellPtr p_cell(new Cell(p_healthy_state, p_cell_model, p_srn_model, false, CellPropertyCollection())); + p_cell->SetCellProliferativeType(p_transit_type); p_cell->InitialiseCellCycleModel(); p_cell->InitialiseSrnModel(); - // Step the simulation to the end time, advancing the SRN model each step. + // Step the simulation to the end time. while (!p_simulation_time->IsFinished()) { p_simulation_time->IncrementTimeOneStep(); - p_srn_model->SimulateToCurrentTime(); + if (p_cell->ReadyToDivide()) + { + p_cell->Divide(); + } } // Check the steady state of the mitotic oscillator. - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("C"), 0.5470, 1e-2); - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("M"), 0.2936, 1e-2); - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("X"), 0.0067, 1e-3); + Goldbeter1991SbmlSrnModel* p_srn = dynamic_cast(p_cell->GetSrnModel()); + TS_ASSERT_DELTA(p_srn->GetStateVariable("C"), 0.5470, 1e-2); + TS_ASSERT_DELTA(p_srn->GetStateVariable("M"), 0.2936, 1e-2); + TS_ASSERT_DELTA(p_srn->GetStateVariable("X"), 0.0067, 1e-3); } }; diff --git a/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp b/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp index a3d8592..9029202 100644 --- a/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp +++ b/examples/goldbeter_1991/TestGoldbeter1991SbmlSrnModel.hpp @@ -5,10 +5,11 @@ #include "AbstractCellBasedTestSuite.hpp" #include "Cell.hpp" -#include "NoCellCycleModel.hpp" +#include "CellPropertyRegistry.hpp" +#include "FixedG1GenerationalCellCycleModel.hpp" #include "SimulationTime.hpp" #include "SmartPointers.hpp" -#include "StemCellProliferativeType.hpp" +#include "TransitCellProliferativeType.hpp" #include "WildTypeCellMutationState.hpp" // The header generated from Goldbeter1991.xml by chaste_codegen_sbml. @@ -22,33 +23,43 @@ class TestGoldbeter1991SbmlSrnModel : public AbstractCellBasedTestSuite public: void TestSteadyStateSimulation() { - // Integrate to t = 1000 in 1000 steps (AbstractCellBasedTestSuite has set the start time to 0). + // Run until t = 100 with dt = 0.001, by which time the mitotic oscillator + // has settled to the reference steady state. SimulationTime* p_simulation_time = SimulationTime::Instance(); - p_simulation_time->SetEndTimeAndNumberOfTimeSteps(1000.0, 1000); + double end_time = 100; + double dt = 0.001; + unsigned num_steps = (unsigned)(end_time / dt); + p_simulation_time->SetEndTimeAndNumberOfTimeSteps(end_time, num_steps + 1); - // Create the SRN model from the imported SBML model and attach it to a cell. - Goldbeter1991SbmlSrnModel* p_srn_model = new Goldbeter1991SbmlSrnModel(); + // Create a cell carrying the imported SRN model. + boost::shared_ptr p_healthy_state( + CellPropertyRegistry::Instance()->Get()); + boost::shared_ptr p_transit_type( + CellPropertyRegistry::Instance()->Get()); - MAKE_PTR(WildTypeCellMutationState, p_state); - MAKE_PTR(StemCellProliferativeType, p_stem_type); - NoCellCycleModel* p_cc_model = new NoCellCycleModel(); + FixedG1GenerationalCellCycleModel* p_cell_model = new FixedG1GenerationalCellCycleModel(); + Goldbeter1991SbmlSrnModel* p_srn_model = new Goldbeter1991SbmlSrnModel(); - CellPtr p_cell(new Cell(p_state, p_cc_model, p_srn_model)); - p_cell->SetCellProliferativeType(p_stem_type); + CellPtr p_cell(new Cell(p_healthy_state, p_cell_model, p_srn_model, false, CellPropertyCollection())); + p_cell->SetCellProliferativeType(p_transit_type); p_cell->InitialiseCellCycleModel(); p_cell->InitialiseSrnModel(); - // Step the simulation to the end time, advancing the SRN model each step. + // Step the simulation to the end time. while (!p_simulation_time->IsFinished()) { p_simulation_time->IncrementTimeOneStep(); - p_srn_model->SimulateToCurrentTime(); + if (p_cell->ReadyToDivide()) + { + p_cell->Divide(); + } } // Check the steady state of the mitotic oscillator. - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("C"), 0.5470, 1e-2); - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("M"), 0.2936, 1e-2); - TS_ASSERT_DELTA(p_srn_model->GetStateVariable("X"), 0.0067, 1e-3); + Goldbeter1991SbmlSrnModel* p_srn = dynamic_cast(p_cell->GetSrnModel()); + TS_ASSERT_DELTA(p_srn->GetStateVariable("C"), 0.5470, 1e-2); + TS_ASSERT_DELTA(p_srn->GetStateVariable("M"), 0.2936, 1e-2); + TS_ASSERT_DELTA(p_srn->GetStateVariable("X"), 0.0067, 1e-3); } }; diff --git a/test/ContinuousTestPack.txt b/test/ContinuousTestPack.txt index 0e80dc8..0608b55 100644 --- a/test/ContinuousTestPack.txt +++ b/test/ContinuousTestPack.txt @@ -1 +1 @@ -TestHello.hpp \ No newline at end of file +TestHello.hpp From 7f2ebac998c24ecb539ce559a0b7621ecd966067 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 3 Jul 2026 21:20:42 +0100 Subject: [PATCH 5/5] #3 Add create_venv.sh --- scripts/bindings_install.sh | 18 ++++++------- scripts/common.sh | 16 +++++++----- scripts/create_venv.sh | 24 +++++++++++++++++ scripts/sbml_install.sh | 27 ++++++++------------ setup_project.py | 51 ++++++++++++++++++++++--------------- 5 files changed, 81 insertions(+), 55 deletions(-) create mode 100755 scripts/create_venv.sh diff --git a/scripts/bindings_install.sh b/scripts/bindings_install.sh index 3debc4c..7da7848 100755 --- a/scripts/bindings_install.sh +++ b/scripts/bindings_install.sh @@ -37,18 +37,16 @@ if [[ ! -d "${pychaste_pkg}" ]]; then exit 1 fi -# Create the virtualenv if it does not already exist. -# Use --system-site-packages so the venv can see PyChaste's native runtime -# dependencies (petsc4py, mpi4py, vtk), which are provided by the system Python -# and are not pip-installable here. -venv_dir="${PROJECT_ROOT}/.virtualenv" -python3 -m venv --system-site-packages "${venv_dir}" +# Create the virtualenv (with --system-site-packages) if it does not already exist, so it +# can see PyChaste's native runtime dependencies (petsc4py, mpi4py, vtk) from the system +# Python. These are not pip-installable here. +"${common_dir}/create_venv.sh" # Install pychaste first (it is a dependency of the project bindings). -"${venv_dir}/bin/pip" install "${pychaste_pkg}" +"${VENV_DIR}/bin/pip" install "${pychaste_pkg}" # Install the project Python bindings. -"${venv_dir}/bin/pip" install "${project_pkg}" +"${VENV_DIR}/bin/pip" install "${project_pkg}" -echo "Installed Python bindings to '${venv_dir}'." -echo "Activate with: source '${venv_dir}/bin/activate'" +echo "Installed Python bindings to '${VENV_DIR}'." +echo "Activate with: source '${VENV_DIR}/bin/activate'" diff --git a/scripts/common.sh b/scripts/common.sh index 7555d2c..acc54d6 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -6,7 +6,13 @@ common_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd -- "${common_dir}/.." && pwd)" +# The project virtualenv, shared by the Python bindings and SBML install scripts. +# Keep this path in sync with VENV_DIR in setup_project.py, which defines it independently. +VENV_DIR="${PROJECT_ROOT}/.virtualenv" + # If the Chaste source directory is not set, try to find it in a few common locations. +# It is left empty if not found: sourcing this file must not require Chaste (create_venv.sh +# uses it before the build is set up). Scripts that need it call require_source to enforce it. if [[ -z "${CHASTE_SOURCE_DIR:-}" ]]; then for _candidate in \ "${PROJECT_ROOT}/../Chaste" \ @@ -19,12 +25,8 @@ if [[ -z "${CHASTE_SOURCE_DIR:-}" ]]; then break fi done - if [[ -z "${CHASTE_SOURCE_DIR:-}" ]]; then - echo "Error: could not find Chaste source directory." >&2 - echo "Set CHASTE_SOURCE_DIR to the location of your Chaste source." >&2 - exit 1 - fi fi +CHASTE_SOURCE_DIR="${CHASTE_SOURCE_DIR:-}" CHASTE_PROJECTS_DIR="${CHASTE_SOURCE_DIR}/projects" @@ -53,9 +55,9 @@ else BUILD_PROJECT_PYTHON_BINDINGS=OFF fi -# Set the number of parallel jobs for building and testing. +# Set the number of parallel jobs for building and testing if ! [[ "${NCORES:-}" =~ ^[1-9][0-9]*$ ]]; then - NCORES="$(nproc)" + NCORES="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)" fi # Abort with an error if the given command is not on PATH. diff --git a/scripts/create_venv.sh b/scripts/create_venv.sh new file mode 100755 index 0000000..1e2c85e --- /dev/null +++ b/scripts/create_venv.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Create the project virtualenv if it does not already exist. +# +# Usage: create_venv.sh +# +# Creates virtual environment with --system-site-packages so it can see native +# packages provided by the system Python (e.g. petsc4py, mpi4py and vtk) + +if [[ $# -ne 0 ]]; then + echo "Usage: $(basename "$0")" >&2 + exit 1 +fi + +# Import common variables and helpers. +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +source "${script_dir}/common.sh" + +require_command python3 + +if [[ ! -d "${VENV_DIR}" ]]; then + python3 -m venv --system-site-packages "${VENV_DIR}" +fi diff --git a/scripts/sbml_install.sh b/scripts/sbml_install.sh index eacb584..762d115 100755 --- a/scripts/sbml_install.sh +++ b/scripts/sbml_install.sh @@ -9,17 +9,9 @@ if [[ $# -ne 0 ]]; then exit 1 fi -# The project root is the parent of this script's directory. +# Import common variables and helpers. script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd -- "${script_dir}/.." && pwd)" - -# Abort with an error if the given command is not on PATH. -require_command() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "Error: $1 is not available on PATH." >&2 - exit 1 - fi -} +source "${script_dir}/common.sh" require_command python3 @@ -28,15 +20,16 @@ if ! command -v clang-format >/dev/null 2>&1; then echo "Warning: clang-format is not on PATH; chaste_codegen_sbml needs it to format generated code." >&2 fi -# Create the project virtualenv (idempotent; shared with Python bindings if both are set up). -venv_dir="${PROJECT_ROOT}/.virtualenv" -python3 -m venv "${venv_dir}" +# Create the project virtualenv if it does not already exist (shared with the Python +# bindings, hence --system-site-packages inside create_venv.sh so it never hides PyChaste's +# native packages when a project has both SBML and Python bindings). +"${common_dir}/create_venv.sh" # Install the SBML code generator from GitHub. -"${venv_dir}/bin/pip" install --upgrade pip -"${venv_dir}/bin/pip" install "git+https://github.com/Chaste/chaste-codegen-sbml@develop" +"${VENV_DIR}/bin/pip" install --upgrade pip +"${VENV_DIR}/bin/pip" install "git+https://github.com/Chaste/chaste-codegen-sbml@develop" echo "" -echo "Installed chaste-codegen-sbml into '${venv_dir}'." -echo "Activate the virtualenv with: source '${venv_dir}/bin/activate'" +echo "Installed chaste-codegen-sbml into '${VENV_DIR}'." +echo "Activate the virtualenv with: source '${VENV_DIR}/bin/activate'" echo "Then convert an SBML model with: chaste_codegen_sbml --help" diff --git a/setup_project.py b/setup_project.py index 63aa7de..60db8c9 100644 --- a/setup_project.py +++ b/setup_project.py @@ -106,6 +106,11 @@ def __init__(self) -> None: # Python package template directory (renamed to / during setup). self.PYTHON_PKG_TEMPLATE_DIR = os.path.join(self.PROJECT_ROOT, "src", "py", "template_project") + # The project virtualenv and the script that creates it (shared by the bindings and SBML paths). + # Keep VENV_DIR in sync with VENV_DIR in scripts/common.sh, which defines it independently. + self.VENV_DIR = os.path.join(self.PROJECT_ROOT, ".virtualenv") + self.CREATE_VENV_SCRIPT = os.path.join(self.PROJECT_ROOT, "scripts", "create_venv.sh") + # SBML support files vendored from chaste-codegen-sbml (kept if SBML opted in, deleted otherwise). # Their names are kept unchanged: generated model code #includes and subclasses them by name. self.SBML_SUPPORT_FILES = [ @@ -180,29 +185,31 @@ def print_banner(*lines: str) -> None: print(border) -def create_virtualenv(settings: Settings) -> None: - """Create the project virtualenv for the Python bindings. +def create_virtualenv(settings: Settings) -> bool: + """Create the project virtualenv, shared by the Python bindings and SBML paths. - Creates .virtualenv/ with --system-site-packages so it can see PyChaste's native - runtime dependencies (petsc4py, mpi4py, vtk) provided by the system Python. On any - failure this is non-fatal: it prints the manual command so the user can create it - themselves. The compiled bindings are installed into this virtualenv later by - scripts/bindings_install.sh, once the project has been built. + Runs scripts/create_venv.sh, which creates .virtualenv/ with --system-site-packages so + it can see native pip packages provided by the system Python (e.g. petsc4py, mpi4py and vtk ). """ - venv_dir = os.path.join(settings.PROJECT_ROOT, ".virtualenv") try: - subprocess.run(["python3", "-m", "venv", "--system-site-packages", venv_dir], check=True) + subprocess.run([settings.CREATE_VENV_SCRIPT], check=True) except (subprocess.CalledProcessError, OSError) as error: print("") print(f"WARNING: could not create the project virtualenv automatically ({error}).") print("Create it manually with:") - print(f" python3 -m venv --system-site-packages {venv_dir}") - return + print(f" python3 -m venv --system-site-packages {settings.VENV_DIR}") + return False + return True + - # Warn (non-fatal) if PyChaste's native runtime dependencies are not visible to the - # venv. They are provided by the system Python (e.g. in the chaste/base Docker image), - # not pip-installed; the bindings will fail to import at runtime without them. - venv_python = os.path.join(venv_dir, "bin", "python") +def warn_missing_pychaste_deps(settings: Settings) -> None: + """Warn (non-fatal) if PyChaste's native runtime dependencies are not visible. + + petsc4py, mpi4py and vtk are provided by the system Python (e.g. in the chaste/base + Docker image), not pip-installed; the bindings will fail to import at runtime without + them. Only relevant to the Python bindings, so it is not run for the SBML path. + """ + venv_python = os.path.join(settings.VENV_DIR, "bin", "python") missing = [ module for module in ("petsc4py", "mpi4py", "vtk") @@ -218,19 +225,20 @@ def create_virtualenv(settings: Settings) -> None: def install_sbml_codegen(settings: Settings) -> None: """Create the project virtualenv and install chaste-codegen-sbml into it. - Runs scripts/sbml_install.sh. On any failure this is non-fatal: it prints the - manual commands so the user can finish the install themselves. + Creates the shared virtualenv via create_virtualenv(), then runs scripts/sbml_install.sh + to install the code generator. On any failure this is non-fatal: it prints the manual + commands so the user can finish the install themselves. """ + if not create_virtualenv(settings): + return try: subprocess.run([settings.SBML_INSTALL_SCRIPT], check=True) except (subprocess.CalledProcessError, OSError) as error: - venv_dir = os.path.join(settings.PROJECT_ROOT, ".virtualenv") print("") print(f"WARNING: could not install chaste-codegen-sbml automatically ({error}).") print("Install it manually with:") - print(f" python3 -m venv {venv_dir}") print( - f" {os.path.join(venv_dir, 'bin', 'pip')} install " + f" {os.path.join(settings.VENV_DIR, 'bin', 'pip')} install " "'git+https://github.com/Chaste/chaste-codegen-sbml@develop'" ) @@ -341,7 +349,8 @@ def setup(settings: Settings) -> None: os.rename(settings.PYTHON_PKG_TEMPLATE_DIR, new_pkg_dir) # Create the project virtualenv (the compiled bindings are installed later # by scripts/bindings_install.sh). - create_virtualenv(settings) + if create_virtualenv(settings): + warn_missing_pychaste_deps(settings) else: # Remove the Python binding template files shutil.rmtree(os.path.join(settings.PROJECT_ROOT, "dynamic"))