From 8e15c56ef1c05eed9f8d32d2e68ca4e91936c702 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Thu, 8 Oct 2026 19:19:37 +1300 Subject: [PATCH 1/3] New version. --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 49a3b77d5..5fe4d4334 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.20261008.0 +1.20261008.1 From e5aacf1325b5f75e44922d99584c3f3ef6c773b5 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Thu, 8 Oct 2026 19:31:23 +1300 Subject: [PATCH 2/3] Require a uniform time course simulation for ODE/DAE models. --- src/sed/sedsimulation.cpp | 23 ++++++++++- src/sed/sedsimulation_p.h | 2 +- src/sed/sedtask.cpp | 3 +- tests/api/sed/instancetests.cpp | 35 ++++++++++++++++ .../bindings/javascript/sed.instance.test.js | 40 +++++++++++++++++++ tests/bindings/python/test_sed_instance.py | 40 +++++++++++++++++++ 6 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/sed/sedsimulation.cpp b/src/sed/sedsimulation.cpp index a17d9a953..99b95a2d3 100644 --- a/src/sed/sedsimulation.cpp +++ b/src/sed/sedsimulation.cpp @@ -30,7 +30,7 @@ SedSimulation::Impl::Impl(const SedDocumentPtr &pDocument) { } -bool SedSimulation::Impl::isValid(const SedModelPtr &pModel) +bool SedSimulation::Impl::isValid(const SedModelPtr &pModel, bool pUniformTimeCourse) { auto modelType {pModel->pimpl()->mFile->pimpl()->mCellmlFile->type()}; const auto &modelId = pModel->pimpl()->mId; @@ -51,6 +51,27 @@ bool SedSimulation::Impl::isValid(const SedModelPtr &pModel) addError(error); }; + // Make sure that we are a uniform time course simulation if our model is an ODE/DAE model. + //---GRY--- WE DON'T CURRENTLY SUPPORT OTHER TYPES OF SIMULATION FOR ODE/DAE MODELS. + + if (((modelType == libcellml::AnalyserModel::Type::ODE) + || (modelType == libcellml::AnalyserModel::Type::DAE)) + && !pUniformTimeCourse) { + std::string error; + + error.reserve(mId.size() + modelId.size() + 96); // NOLINT + + error += "Simulation '"; + error += mId; + error += "' is to be used with model '"; + error += modelId; + error += "' which (currently) requires a uniform time course simulation."; + + addError(error); + } + + // Make sure that we have the solver(s) required by our model. + if ((modelType == libcellml::AnalyserModel::Type::ODE) && (mOdeSolver == nullptr)) { addMissingSolverError("ODE"); } else if ((modelType == libcellml::AnalyserModel::Type::NLA) && (mNlaSolver == nullptr)) { diff --git a/src/sed/sedsimulation_p.h b/src/sed/sedsimulation_p.h index a357dab58..9ed4786c5 100644 --- a/src/sed/sedsimulation_p.h +++ b/src/sed/sedsimulation_p.h @@ -30,7 +30,7 @@ class SedSimulation::Impl: public SedBase::Impl explicit Impl(const SedDocumentPtr &pDocument); - bool isValid(const SedModelPtr &pModel); + bool isValid(const SedModelPtr &pModel, bool pUniformTimeCourse); const SolverOdePtr &odeSolver() const; void setOdeSolver(const SolverOdePtr &pOdeSolver); diff --git a/src/sed/sedtask.cpp b/src/sed/sedtask.cpp index acd7632e3..d9ed47b66 100644 --- a/src/sed/sedtask.cpp +++ b/src/sed/sedtask.cpp @@ -18,6 +18,7 @@ limitations under the License. #include "sedmodel_p.h" #include "sedsimulation_p.h" #include "sedtask_p.h" +#include "seduniformtimecourse_p.h" #include "utils.h" @@ -78,7 +79,7 @@ bool SedTask::Impl::isValid() // Make sure that the simulation is valid for the model. - if (!mSimulation->pimpl()->isValid(mModel)) { + if (!mSimulation->pimpl()->isValid(mModel, std::dynamic_pointer_cast(mSimulation) != nullptr)) { addIssues(mSimulation, "Simulation"); } diff --git a/tests/api/sed/instancetests.cpp b/tests/api/sed/instancetests.cpp index 9d14cb13b..adbbaac2b 100644 --- a/tests/api/sed/instancetests.cpp +++ b/tests/api/sed/instancetests.cpp @@ -599,6 +599,41 @@ TEST(InstanceSedTest, odeModelWithNoOdeSolver) EXPECT_EQ_ISSUES(instance, EXPECTED_ISSUES); } +TEST(InstanceSedTest, odeModelWithNonUniformTimeCourseSimulation) +{ + static const libOpenCOR::ExpectedIssues ONE_STEP_EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation."}, + }}; + static const libOpenCOR::ExpectedIssues STEADY_STATE_EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "Task | Simulation: simulation 'simulation2' is to be used with model 'model1' which (currently) requires a uniform time course simulation."}, + }}; + + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("cellml_2.cellml"))}; + auto document {libOpenCOR::SedDocument::create()}; + auto model {libOpenCOR::SedModel::create(document, file)}; + auto oneStep {libOpenCOR::SedOneStep::create(document)}; + auto steadyState {libOpenCOR::SedSteadyState::create(document)}; + auto task {libOpenCOR::SedTask::create(document, model, oneStep)}; + + oneStep->setOdeSolver(libOpenCOR::SolverCvode::create()); + steadyState->setOdeSolver(libOpenCOR::SolverCvode::create()); + + document->addModel(model); + document->addSimulation(oneStep); + document->addSimulation(steadyState); + document->addTask(task); + + auto instance {document->instantiate()}; + + EXPECT_EQ_ISSUES(instance, ONE_STEP_EXPECTED_ISSUES); + + task->setSimulation(steadyState); + + instance = document->instantiate(); + + EXPECT_EQ_ISSUES(instance, STEADY_STATE_EXPECTED_ISSUES); +} + TEST(InstanceSedTest, nlaModel) { static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ diff --git a/tests/bindings/javascript/sed.instance.test.js b/tests/bindings/javascript/sed.instance.test.js index 460281c5c..f609075ce 100644 --- a/tests/bindings/javascript/sed.instance.test.js +++ b/tests/bindings/javascript/sed.instance.test.js @@ -653,6 +653,46 @@ test.describe('Sed instance tests', () => { ]); }); + test('ODE model with non-uniform time course simulation', () => { + const file = new loc.File(utils.resourcePath('cellml_2.cellml')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(); + const model = new loc.SedModel(document, file); + const oneStep = new loc.SedOneStep(document); + const steadyState = new loc.SedSteadyState(document); + const task = new loc.SedTask(document, model, oneStep); + + oneStep.odeSolver = new loc.SolverCvode(); + steadyState.odeSolver = new loc.SolverCvode(); + + document.addModel(model); + document.addSimulation(oneStep); + document.addSimulation(steadyState); + document.addTask(task); + + let instance = document.instantiate(); + + assertIssues(loc, instance, [ + [ + loc.Issue.Type.ERROR, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation." + ] + ]); + + task.simulation = steadyState; + + instance = document.instantiate(); + + assertIssues(loc, instance, [ + [ + loc.Issue.Type.ERROR, + "Task | Simulation: simulation 'simulation2' is to be used with model 'model1' which (currently) requires a uniform time course simulation." + ] + ]); + }); + test('NLA model', () => { const file = new loc.File(utils.resourcePath('api/sed/nla.cellml')); diff --git a/tests/bindings/python/test_sed_instance.py b/tests/bindings/python/test_sed_instance.py index cf1732048..bbfbfd327 100644 --- a/tests/bindings/python/test_sed_instance.py +++ b/tests/bindings/python/test_sed_instance.py @@ -580,6 +580,46 @@ def test_ode_model_with_no_ode_solver(): assert_issues(instance, expected_issues) +def test_ode_model_with_non_uniform_time_course_simulation(): + one_step_expected_issues = [ + [ + loc.Issue.Type.Error, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation.", + ], + ] + steady_state_expected_issues = [ + [ + loc.Issue.Type.Error, + "Task | Simulation: simulation 'simulation2' is to be used with model 'model1' which (currently) requires a uniform time course simulation.", + ], + ] + + file = loc.File(utils.resource_path("cellml_2.cellml")) + document = loc.SedDocument() + model = loc.SedModel(document, file) + one_step = loc.SedOneStep(document) + steady_state = loc.SedSteadyState(document) + task = loc.SedTask(document, model, one_step) + + one_step.ode_solver = loc.SolverCvode() + steady_state.ode_solver = loc.SolverCvode() + + document.add_model(model) + document.add_simulation(one_step) + document.add_simulation(steady_state) + document.add_task(task) + + instance = document.instantiate() + + assert_issues(instance, one_step_expected_issues) + + task.simulation = steady_state + + instance = document.instantiate() + + assert_issues(instance, steady_state_expected_issues) + + def test_nla_model(): expected_issues = [ [ From 50515642ab36b428d6bb11d0136de14bb87732bf Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Thu, 8 Oct 2026 19:44:52 +1300 Subject: [PATCH 3/3] Fixed DAE non-uniform simulation validation. --- src/sed/sedinstancetask.cpp | 4 +++ tests/api/sed/instancetests.cpp | 23 +++++++++++++ .../bindings/javascript/sed.instance.test.js | 32 +++++++++++++++++++ tests/bindings/python/test_sed_instance.py | 31 ++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/src/sed/sedinstancetask.cpp b/src/sed/sedinstancetask.cpp index 9e70df69c..34c55800d 100644 --- a/src/sed/sedinstancetask.cpp +++ b/src/sed/sedinstancetask.cpp @@ -92,6 +92,10 @@ SedInstanceTask::Impl::Impl(const SedAbstractTaskPtr &pTask) mSimulation = task->pimpl()->mSimulation; mSedUniformTimeCourse = mDifferentialModel ? std::dynamic_pointer_cast(mSimulation) : nullptr; + // Note: SedTask::Impl::isValid() ensures that an ODE/DAE model is used with a uniform time course simulation. + + ASSERT_EQ(mDifferentialModel, (mSedUniformTimeCourse != nullptr)); + const auto &odeSolver {mSimulation->odeSolver()}; const auto &nlaSolver {mSimulation->nlaSolver()}; diff --git a/tests/api/sed/instancetests.cpp b/tests/api/sed/instancetests.cpp index adbbaac2b..94cd95d6e 100644 --- a/tests/api/sed/instancetests.cpp +++ b/tests/api/sed/instancetests.cpp @@ -725,6 +725,29 @@ TEST(InstanceSedTest, daeModelWithNoOdeOrNlaSolver) EXPECT_EQ_ISSUES(instance, EXPECTED_ISSUES); } +TEST(InstanceSedTest, daeModelWithNonUniformTimeCourseSimulation) +{ + static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation."}, + {libOpenCOR::Issue::Type::ERROR, "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an ODE solver but none is provided."}, + {libOpenCOR::Issue::Type::ERROR, "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an NLA solver but none is provided."}, + }}; + + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/dae.cellml"))}; + auto document {libOpenCOR::SedDocument::create()}; + auto model {libOpenCOR::SedModel::create(document, file)}; + auto oneStep {libOpenCOR::SedOneStep::create(document)}; + auto task {libOpenCOR::SedTask::create(document, model, oneStep)}; + + document->addModel(model); + document->addSimulation(oneStep); + document->addTask(task); + + auto instance {document->instantiate()}; + + EXPECT_EQ_ISSUES(instance, EXPECTED_ISSUES); +} + TEST(InstanceSedTest, daeModelWithFailingOdeSolver) { static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ diff --git a/tests/bindings/javascript/sed.instance.test.js b/tests/bindings/javascript/sed.instance.test.js index f609075ce..37e9c76a9 100644 --- a/tests/bindings/javascript/sed.instance.test.js +++ b/tests/bindings/javascript/sed.instance.test.js @@ -804,6 +804,38 @@ test.describe('Sed instance tests', () => { ]); }); + test('DAE model with non-uniform time course simulation', () => { + const file = new loc.File(utils.resourcePath('api/sed/dae.cellml')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(); + const model = new loc.SedModel(document, file); + const oneStep = new loc.SedOneStep(document); + const task = new loc.SedTask(document, model, oneStep); + + document.addModel(model); + document.addSimulation(oneStep); + document.addTask(task); + + const instance = document.instantiate(); + + assertIssues(loc, instance, [ + [ + loc.Issue.Type.ERROR, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation." + ], + [ + loc.Issue.Type.ERROR, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an ODE solver but none is provided." + ], + [ + loc.Issue.Type.ERROR, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an NLA solver but none is provided." + ] + ]); + }); + test('DAE model with failing ODE solver', () => { const file = new loc.File(utils.resourcePath('api/sed/dae.cellml')); diff --git a/tests/bindings/python/test_sed_instance.py b/tests/bindings/python/test_sed_instance.py index bbfbfd327..286cd5790 100644 --- a/tests/bindings/python/test_sed_instance.py +++ b/tests/bindings/python/test_sed_instance.py @@ -722,6 +722,37 @@ def test_dae_model_with_no_ode_or_nla_solver(): assert_issues(instance, expected_issues) +def test_dae_model_with_non_uniform_time_course_simulation(): + expected_issues = [ + [ + loc.Issue.Type.Error, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which (currently) requires a uniform time course simulation.", + ], + [ + loc.Issue.Type.Error, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an ODE solver but none is provided.", + ], + [ + loc.Issue.Type.Error, + "Task | Simulation: simulation 'simulation1' is to be used with model 'model1' which requires an NLA solver but none is provided.", + ], + ] + + file = loc.File(utils.resource_path("api/sed/dae.cellml")) + document = loc.SedDocument() + model = loc.SedModel(document, file) + one_step = loc.SedOneStep(document) + task = loc.SedTask(document, model, one_step) + + document.add_model(model) + document.add_simulation(one_step) + document.add_task(task) + + instance = document.instantiate() + + assert_issues(instance, expected_issues) + + def test_dae_model_with_failing_ode_solver(): expected_issues = [ [