From 41599e2aa26d09ea765f8b22dc77df742a21a8ca Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 17 Feb 2026 13:38:22 -0800 Subject: [PATCH 01/30] Update version number for minor release. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6a8f5da9e..ed0965b560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16) set(ROADRUNNER_VERSION_MAJOR 2) set(ROADRUNNER_VERSION_MINOR 9) -set(ROADRUNNER_VERSION_PATCH 0) +set(ROADRUNNER_VERSION_PATCH 1) set(ROADRUNNER_VERSION "${ROADRUNNER_VERSION_MAJOR}.${ROADRUNNER_VERSION_MINOR}.${ROADRUNNER_VERSION_PATCH}") From 1ca005ed9e5aa5f76d086e160fad1d6f29dfb66d Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Fri, 20 Feb 2026 11:07:32 -0800 Subject: [PATCH 02/30] As a first pass at fixing named stoichiometries, throw on invalid name strings. Also, don't log errors and then throw that same error; that's redundant. --- source/llvm/LLVMExecutableModel.cpp | 13 ++- source/rrRoadRunner.cpp | 12 ++- test/cxx_api_tests/SelectionRecordTests.cpp | 105 ++++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/source/llvm/LLVMExecutableModel.cpp b/source/llvm/LLVMExecutableModel.cpp index 21050df008..fda304ad59 100644 --- a/source/llvm/LLVMExecutableModel.cpp +++ b/source/llvm/LLVMExecutableModel.cpp @@ -1485,8 +1485,8 @@ double LLVMExecutableModel::getValue(const std::string& id) } break; default: - rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " - << sel.to_repr(); + //rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " + //<< sel.to_repr(); throw LLVMException("Invalid selection '" + id + "' for setting value"); break; } @@ -1631,11 +1631,16 @@ const rr::SelectionRecord& LLVMExecutableModel::getSelection(const std::string& break; case SelectionRecord::STOICHIOMETRY: sel.index = getStoichiometryIndex(sel.p1, sel.p2); + if (sel.index == -1) { + throw LLVMException("Invalid id '" + str + "': could not find a species with the ID '" + sel.p1 + "', and/or a reaction with the ID '" + sel.p2 + "'"); + break; + } + break; default: - rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " - << sel.to_repr(); + //rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " + // << sel.to_repr(); throw LLVMException("Invalid selection '" + str + "' for setting value"); break; } diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 71346f9ddc..ea75951299 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4719,7 +4719,8 @@ namespace rr { void RoadRunner::setValue(const std::string &sId, double dValue) { check_model(); - SelectionRecord sel(sId); + //Run 'createSelection' so it throws if it's an invalid ID right away. + SelectionRecord sel = createSelection(sId); if (sel.selectionType & SelectionRecord::INITIAL && sel.p2 == "") { if (sel.selectionType & SelectionRecord::CONCENTRATION) { @@ -4839,7 +4840,7 @@ namespace rr { break; } else { std::string msg = "No sbml element exists for concentration selection '" + str + "'"; - rrLog(Logger::LOG_ERROR) << msg; + //rrLog(Logger::LOG_ERROR) << msg; throw Exception(msg); break; } @@ -4956,9 +4957,10 @@ namespace rr { break; } default: - rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " - << sel.to_repr(); - break; + //rrLog(Logger::LOG_ERROR) << "A new SelectionRecord should not have this value: " + // << sel.to_repr(); + throw Exception("Invalid selection '" + str + "' for selecting an element of this model."); + break; } return sel; diff --git a/test/cxx_api_tests/SelectionRecordTests.cpp b/test/cxx_api_tests/SelectionRecordTests.cpp index a053bfa84e..ec6bdebc60 100644 --- a/test/cxx_api_tests/SelectionRecordTests.cpp +++ b/test/cxx_api_tests/SelectionRecordTests.cpp @@ -44,6 +44,111 @@ TEST_F(SelectionRecordTests, REACTION_RATE) { delete testModel; } +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT) { + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(SelectionRecord record = rr.createSelection("foo"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("[foo]"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("[S1"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("init(S1"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("init([S1]"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("init(foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("eigen(foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("eigenReal(foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("eigenImag(foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("uec(foo, bar)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ec(foo, bar)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ucc(foo, bar)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("cc(foo, bar)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("stoich(foo, bar)"), Exception); + delete testModel; +} + +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT_COMBINATION) { +// test selection records that require two inputs, only one of which doesn't exist. + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(SelectionRecord record = rr.createSelection("uec(foo, S1)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("uec(_J1, foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ec(foo, S1)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ec(_J1, foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ucc(foo, S1)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("ucc(_J1, foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("cc(foo, S1)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("cc(_J1, foo)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("stoich(foo, _J1)"), Exception); + EXPECT_THROW(SelectionRecord record = rr.createSelection("stoich(S1, foo)"), Exception); + delete testModel; +} + +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT_SET) { + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(rr.setValue("foo", 5), Exception); + EXPECT_THROW(rr.setValue("[foo]", 5), Exception); + EXPECT_THROW(rr.setValue("init(foo)", 5), Exception); + EXPECT_THROW(rr.setValue("eigen(foo)", 5), Exception); + EXPECT_THROW(rr.setValue("eigenReal(foo)", 5), Exception); + EXPECT_THROW(rr.setValue("eigenImag(foo)", 5), Exception); + EXPECT_THROW(rr.setValue("uec(foo, bar)", 5), Exception); + EXPECT_THROW(rr.setValue("ec(foo, bar)", 5), Exception); + EXPECT_THROW(rr.setValue("ucc(foo, bar)", 5), Exception); + EXPECT_THROW(rr.setValue("cc(foo, bar)", 5), Exception); + EXPECT_THROW(rr.setValue("stoich(foo, bar)", 5), Exception); + delete testModel; +} + +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT_COMBINATION_SET) { + // test selection records that require two inputs, only one of which doesn't exist. + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(rr.setValue("uec(foo, S1)", 5), Exception); + EXPECT_THROW(rr.setValue("uec(_J1, foo)", 5), Exception); + EXPECT_THROW(rr.setValue("ec(foo, S1)", 5), Exception); + EXPECT_THROW(rr.setValue("ec(_J1, foo)", 5), Exception); + EXPECT_THROW(rr.setValue("ucc(foo, S1)", 5), Exception); + EXPECT_THROW(rr.setValue("ucc(_J1, foo)", 5), Exception); + EXPECT_THROW(rr.setValue("cc(foo, S1)", 5), Exception); + EXPECT_THROW(rr.setValue("cc(_J1, foo)", 5), Exception); + EXPECT_THROW(rr.setValue("stoich(foo, _J1)", 5), Exception); + EXPECT_THROW(rr.setValue("stoich(S1, foo)", 5), Exception); + delete testModel; +} + +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT_GET) { + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(rr.getValue("foo"), Exception); + EXPECT_THROW(rr.getValue("[foo]"), Exception); + EXPECT_THROW(rr.getValue("init(foo)"), Exception); + EXPECT_THROW(rr.getValue("eigen(foo)"), Exception); + EXPECT_THROW(rr.getValue("eigenReal(foo)"), Exception); + EXPECT_THROW(rr.getValue("eigenImag(foo)"), Exception); + EXPECT_THROW(rr.getValue("uec(foo, bar)"), Exception); + EXPECT_THROW(rr.getValue("ec(foo, bar)"), Exception); + EXPECT_THROW(rr.getValue("ucc(foo, bar)"), Exception); + EXPECT_THROW(rr.getValue("cc(foo, bar)"), Exception); + EXPECT_THROW(rr.getValue("stoich(foo, bar)"), Exception); + delete testModel; +} + +TEST_F(SelectionRecordTests, NO_SUCH_ELEMENT_COMBINATION_GET) { + // test selection records that require two inputs, only one of which doesn't exist. + TestModel* testModel = TestModelFactory("SimpleFlux"); + RoadRunner rr(testModel->str()); + EXPECT_THROW(rr.getValue("uec(foo, S1)"), Exception); + EXPECT_THROW(rr.getValue("uec(_J1, foo)"), Exception); + EXPECT_THROW(rr.getValue("ec(foo, S1)"), Exception); + EXPECT_THROW(rr.getValue("ec(_J1, foo)"), Exception); + EXPECT_THROW(rr.getValue("ucc(foo, S1)"), Exception); + EXPECT_THROW(rr.getValue("ucc(_J1, foo)"), Exception); + EXPECT_THROW(rr.getValue("cc(foo, S1)"), Exception); + EXPECT_THROW(rr.getValue("cc(_J1, foo)"), Exception); + EXPECT_THROW(rr.getValue("stoich(foo, _J1)"), Exception); + EXPECT_THROW(rr.getValue("stoich(S1, foo)"), Exception); + delete testModel; +} + TEST_F(SelectionRecordTests, BOUNDARY_CONCENTRATION) { TestModel* testModel = TestModelFactory("SimpleFlux"); RoadRunner rr(testModel->str()); From c2eb664f7ddf66ecf1d971df0fc2e67f00c1600a Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 4 Mar 2026 15:59:13 -0800 Subject: [PATCH 03/30] Add more test. --- source/rrRoadRunner.cpp | 1 - test/model_analysis/model_analysis.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index ea75951299..f4194b2e60 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4840,7 +4840,6 @@ namespace rr { break; } else { std::string msg = "No sbml element exists for concentration selection '" + str + "'"; - //rrLog(Logger::LOG_ERROR) << msg; throw Exception(msg); break; } diff --git a/test/model_analysis/model_analysis.cpp b/test/model_analysis/model_analysis.cpp index b4b7691b54..cd8e2bb621 100644 --- a/test/model_analysis/model_analysis.cpp +++ b/test/model_analysis/model_analysis.cpp @@ -25,6 +25,8 @@ class ModelAnalysisTests : public RoadRunnerTest { TEST_F(ModelAnalysisTests, issue1306_named_stoich_steadyState) { rr::RoadRunner rr((modelAnalysisModelsDir / "named_stoic_in_kinetic_law.xml").string()); rr.steadyState();// , CoreException); + rr.setValue("stoich(A, _J0)", 3); + rr.setValue("n", 3); } From 5fb073bd5f355b66c290bc6b09fa79e50888e18a Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 4 Mar 2026 16:06:43 -0800 Subject: [PATCH 04/30] Update swig version. Hopefully fix for https://github.com/sbmlteam/python-libsbml/issues/40 , since at least one of the linked issues claims that the underlying swig bug is 'fixed'. Hopefully this means 'with 4.4.1'. --- .github/workflows/main.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1cc5438471..afaf2c6f5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -300,11 +300,11 @@ jobs: brew install pcre pcre2 mkdir -p ${RUNNER_WORKSPACE}/swig cd swig - curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.4.0/swig-4.4.0.tar.gz/download > swig.tar.gz + curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.4.1/swig-4.4.1.tar.gz/download > swig.tar.gz tar -zxf swig.tar.gz rm swig.tar.gz mkdir -p install-swig - cd swig-4.4.0/ + cd swig-4.4.1/ ./configure --prefix=${RUNNER_WORKSPACE}/swig/install-swig make make install @@ -312,19 +312,19 @@ jobs: elif [ "${{ matrix.platform.os_type }}" == 'windows' ]; then mkdir -p swig cd swig - curl -L https://sourceforge.net/projects/swig/files/swigwin/swigwin-4.4.0/swigwin-4.4.0.zip/download > swig.zip + curl -L https://sourceforge.net/projects/swig/files/swigwin/swigwin-4.4.1/swigwin-4.4.1.zip/download > swig.zip unzip -q swig.zip -d install-swig rm swig.zip - echo SWIG_DIR="-DSWIG_EXECUTABLE=${RUNNER_WORKSPACE}/swig/install-swig/swigwin-4.4.0/" >> "${GITHUB_PATH}" + echo SWIG_DIR="-DSWIG_EXECUTABLE=${RUNNER_WORKSPACE}/swig/install-swig/swigwin-4.4.1/" >> "${GITHUB_PATH}" elif [ "${{ matrix.platform.os_type }}" == 'manylinux' ]; then dnf install -y pcre-devel pcre2-devel mkdir -p swig cd swig - curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.4.0/swig-4.4.0.tar.gz/download > swig.tar.gz + curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.4.1/swig-4.4.1.tar.gz/download > swig.tar.gz tar -zxf swig.tar.gz rm swig.tar.gz mkdir -p install-swig - cd swig-4.4.0/ + cd swig-4.4.1/ ./configure --disable-dependency-tracking --prefix=${RUNNER_WORKSPACE}/swig/install-swig make make install @@ -334,11 +334,11 @@ jobs: sudo sed -i "681,684d;686d" /usr/share/swig4.0/swig.swg # mkdir -p swig # cd swig - # curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.3.0/swig-4.3.0.tar.gz/download > swig.tar.gz + # curl -L https://sourceforge.net/projects/swig/files/swig/swig-4.4.1/swig-4.4.1.tar.gz/download > swig.tar.gz # tar -zxf swig.tar.gz # rm swig.tar.gz # mkdir -p install-swig - # cd swig-4.3.0/ + # cd swig-4.4.1/ # sed -i "677,680d;682d" Lib/swig.swg # ./configure --disable-dependency-tracking --prefix=${RUNNER_WORKSPACE}/swig/install-swig # make From a06aa5659ad36248c95399ae46b1d2885a5eae76 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Thu, 19 Mar 2026 17:35:47 -0700 Subject: [PATCH 05/30] Just the efficiency fixes in roadrunner. The stoichiometry branch was failing all over the place; try to recreate the changes one by one instead. --- source/rrRoadRunner.cpp | 197 ++++++++++++++++++++++------------------ source/rrRoadRunner.h | 10 ++ 2 files changed, 120 insertions(+), 87 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index f4194b2e60..3ffa84a284 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4554,45 +4554,47 @@ namespace rr { } } - static std::string convertSBMLVersion(const std::string &str, int level, int version) { - libsbml::SBMLReader reader; - std::stringstream stream; - libsbml::SBMLDocument *doc = 0; + void convertSBMLVersionDocument(libsbml::SBMLDocument* doc, int level, int version) { + // this does an in-place conversion, at least for the time being + libsbml::SBMLLevelVersionConverter versionConverter; + libsbml::ConversionProperties versionProps = versionConverter.getDefaultProperties(); - try { - // new doc - doc = reader.readSBMLFromString(str); - - // this does an in-place conversion, at least for the time being - libsbml::SBMLLevelVersionConverter versionConverter; + // this is how the target version is set + libsbml::SBMLNamespaces targetNamespace(level, version); - libsbml::ConversionProperties versionProps = versionConverter.getDefaultProperties(); + // clones the ns + versionProps.setTargetNamespaces(&targetNamespace); - // this is how the target version is set - libsbml::SBMLNamespaces targetNamespace(level, version); + versionConverter.setProperties(&versionProps); - // clones the ns - versionProps.setTargetNamespaces(&targetNamespace); + // converter does an in-place conversion + doc->setApplicableValidators((unsigned char)Config::getInt( + Config::SBML_APPLICABLEVALIDATORS)); - versionConverter.setProperties(&versionProps); + versionConverter.setDocument(doc); - // converter does an in-place conversion - doc->setApplicableValidators((unsigned char) Config::getInt( - Config::SBML_APPLICABLEVALIDATORS)); + if (versionConverter.convert() != libsbml::LIBSBML_OPERATION_SUCCESS) { + rrLog(rr::Logger::LOG_ERROR) << "could not change source sbml level or version"; - versionConverter.setDocument(doc); + const libsbml::SBMLErrorLog* log = doc->getErrorLog(); + std::string errors = log ? log->toString() : std::string(" NULL SBML Error Log"); + rrLog(rr::Logger::LOG_ERROR) << "Conversion Errors: " + errors; - if (versionConverter.convert() != libsbml::LIBSBML_OPERATION_SUCCESS) { - rrLog(rr::Logger::LOG_ERROR) << "could not change source sbml level or version"; + throw std::logic_error("Error version converting sbml: " + errors); + } + } - const libsbml::SBMLErrorLog *log = doc->getErrorLog(); - std::string errors = log ? log->toString() : std::string(" NULL SBML Error Log"); - rrLog(rr::Logger::LOG_ERROR) << "Conversion Errors: " + errors; + static std::string convertSBMLVersion(const std::string &str, int level, int version) { + libsbml::SBMLReader reader; + std::stringstream stream; + libsbml::SBMLDocument *doc = 0; - throw std::logic_error("Error version converting sbml: " + errors); - } + try { + // new doc + doc = reader.readSBMLFromString(str); + convertSBMLVersionDocument(doc, level, version); libsbml::SBMLWriter writer; writer.writeSBML(doc, stream); delete doc; @@ -4611,83 +4613,95 @@ namespace rr { std::stringstream stream; libsbml::SBMLWriter writer; - writer.writeSBML(impl->document.get(), stream); - - if (level > 0) { - return convertSBMLVersion(stream.str(), level, version); + if (level > 0 && version > 0 && level != impl->document->getLevel() && version != impl->document->getVersion()) { + //Need to convert the level/version + libsbml::SBMLDocument doc(*impl->document.get()); + convertSBMLVersionDocument(&doc, level, version); + writer.writeSBML(impl->document.get(), stream); + } + else { + writer.writeSBML(impl->document.get(), stream); } + return stream.str(); } - std::string RoadRunner::getCurrentSBML(int level, int version) { - check_model(); - get_self(); + libsbml::SBMLDocument* RoadRunner::getCurrentSBMLDocument(int level, int version) + { + check_model(); + get_self(); - std::stringstream stream; - libsbml::SBMLDocument doc(*impl->document); - libsbml::Model *model = doc.getModel(); + libsbml::SBMLDocument* doc = impl->document->clone(); + libsbml::Model* model = doc->getModel(); - while (model->getNumInitialAssignments() > 0) { - delete model->removeInitialAssignment(0); - } + while (model->getNumInitialAssignments() > 0) { + delete model->removeInitialAssignment(0); + } - std::vector array = getFloatingSpeciesIds(); - for (int i = 0; i < array.size(); i++) { - double value = 0; - if (model->getSpecies(array[i])->isSetInitialConcentration()) { - impl->model->getFloatingSpeciesConcentrations(1, &i, &value); - setSBMLValue(model, array[i], value, true); - } - else { - impl->model->getFloatingSpeciesAmounts(1, &i, &value); - setSBMLValue(model, array[i], value, false); - } + std::vector array = getFloatingSpeciesIds(); + for (int i = 0; i < array.size(); i++) { + double value = 0; + if (model->getSpecies(array[i])->isSetInitialConcentration()) { + impl->model->getFloatingSpeciesConcentrations(1, &i, &value); + setSBMLValue(model, array[i], value, true); } - - array = getBoundarySpeciesIds(); - for (int i = 0; i < array.size(); i++) { - double value = 0; - if (model->getSpecies(array[i])->isSetInitialConcentration()) { - impl->model->getBoundarySpeciesConcentrations(1, &i, &value); - setSBMLValue(model, array[i], value, true); - } - else { - impl->model->getBoundarySpeciesAmounts(1, &i, &value); - setSBMLValue(model, array[i], value, false); - } + else { + impl->model->getFloatingSpeciesAmounts(1, &i, &value); + setSBMLValue(model, array[i], value, false); } + } - array = getCompartmentIds(); - for (int i = 0; i < array.size(); i++) { - double value = 0; - impl->model->getCompartmentVolumes(1, &i, &value); - setSBMLValue(model, array[i], value); + array = getBoundarySpeciesIds(); + for (int i = 0; i < array.size(); i++) { + double value = 0; + if (model->getSpecies(array[i])->isSetInitialConcentration()) { + impl->model->getBoundarySpeciesConcentrations(1, &i, &value); + setSBMLValue(model, array[i], value, true); } + else { + impl->model->getBoundarySpeciesAmounts(1, &i, &value); + setSBMLValue(model, array[i], value, false); + } + } - array = getGlobalParameterIds(); - for (int i = 0; i < impl->model->getNumGlobalParameters(); i++) { - double value = 0; - impl->model->getGlobalParameterValues(1, &i, &value); + array = getCompartmentIds(); + for (int i = 0; i < array.size(); i++) { + double value = 0; + impl->model->getCompartmentVolumes(1, &i, &value); + setSBMLValue(model, array[i], value); + } - libsbml::Parameter *param = model->getParameter(array[i]); - if (param != NULL) { - param->setValue(value); - } else { - // sanity check, just make sure that this is a conserved moeity - if (self.model->getConservedMoietyIndex(array[i]) < 0) { - throw std::logic_error("The global parameter name " - + array[i] + " could not be found in the SBML model, " - " and it is not a conserved moiety"); - } - } + array = getGlobalParameterIds(); + for (int i = 0; i < impl->model->getNumGlobalParameters(); i++) { + double value = 0; + impl->model->getGlobalParameterValues(1, &i, &value); + + libsbml::Parameter* param = model->getParameter(array[i]); + if (param != NULL) { + param->setValue(value); } + else { + // sanity check, just make sure that this is a conserved moeity + if (self.model->getConservedMoietyIndex(array[i]) < 0) { + throw std::logic_error("The global parameter name " + + array[i] + " could not be found in the SBML model, " + " and it is not a conserved moiety"); + } + } + } + convertSBMLVersionDocument(doc, level, version); + return doc; + } + + std::string RoadRunner::getCurrentSBML(int level, int version) { + + std::stringstream stream; + + libsbml::SBMLDocument* doc = getCurrentSBMLDocument(level, version); libsbml::SBMLWriter writer; - writer.writeSBML(&doc, stream); + writer.writeSBML(doc, stream); - if (level > 0) { - return convertSBMLVersion(stream.str(), level, version); - } return stream.str(); } @@ -5443,6 +5457,15 @@ namespace rr { return std::vector(list.begin(), list.end()); } + std::vector RoadRunner::getStoichiometryIds() + { + std::list list; + + getIds(SelectionRecord::STOICHIOMETRY, list); + + return std::vector(list.begin(), list.end()); + } + /************************ End Selection Ids Species Section *******************/ /******************************************************************************/ diff --git a/source/rrRoadRunner.h b/source/rrRoadRunner.h index 0b550e2833..7d69cc0412 100644 --- a/source/rrRoadRunner.h +++ b/source/rrRoadRunner.h @@ -877,6 +877,11 @@ namespace rr { */ std::vector getEigenValueIds(); + /** + * returns the list of stoichiometry ids, either their names (if present), or 'stoich(species, rxn)'. + */ + std::vector getStoichiometryIds(); + /** * Returns the unscaled elasticity for a named reaction with respect to a * named parameter @@ -1976,6 +1981,11 @@ namespace rr { void loadSelectionVector(std::istream &, std::vector &); ls::DoubleMatrix getSubStoichiometryMatrix(bool reactants, bool products, bool boundary); + + protected: + libsbml::SBMLDocument* getCurrentSBMLDocument(int level = 0, int version = 0); + + }; } From 2dc37367117988a06a3b748fccbe76c047a8207e Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Thu, 19 Mar 2026 17:55:32 -0700 Subject: [PATCH 06/30] Don't try to convert when we don't need to. --- source/rrRoadRunner.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 3ffa84a284..6b0901fb4e 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4689,7 +4689,9 @@ namespace rr { } } } - convertSBMLVersionDocument(doc, level, version); + if (level > 0 && version > 0 && level != doc->getLevel() && version != doc->getVersion()) { + convertSBMLVersionDocument(doc, level, version); + } return doc; } @@ -5908,7 +5910,7 @@ namespace rr { m.setRowNames(spec_ids); libsbml::SBMLReader reader; - libsbml::SBMLDocument* doc = reader.readSBMLFromString(getCurrentSBML()); + libsbml::SBMLDocument* doc = getCurrentSBMLDocument(); libsbml::Model* model = doc->getModel(); set > nonStandardStoichs; From 287bd3684b639a271130f1d88a0e1754af3b9fbc Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Fri, 20 Mar 2026 10:41:57 -0700 Subject: [PATCH 07/30] Fix getting changed stoichs in getCurrentSBML. --- source/rrRoadRunner.cpp | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 6b0901fb4e..1062d6eff9 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4689,6 +4689,64 @@ namespace rr { } } } + + //Set any changed stoichiometries: + vector rxnids = getReactionIds(); + for (int nr = 0; nr < rxnids.size(); nr++) { + string rxnid = rxnids[nr]; + libsbml::Reaction* rxn = model->getReaction(rxnid); + int rxnindex = impl->model->getReactionIndex(rxnid); + + // First pass: sum stoichiometries across both reactants and products per species. + // We use the same sign convention as getStoichiometry(): negative for reactants, + // positive for products. + std::map totalStoich; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + totalStoich[sr->getSpecies()] -= sr->getStoichiometry(); + } + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + totalStoich[sp->getSpecies()] += sp->getStoichiometry(); + } + + // Second pass: for each species, apply any delta to the first species reference + // seen across both reactants and products. + set seen; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + string specid = sr->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); + + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; + + double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Reactant stoichiometries are positive in SBML, so subtract delta + sr->setStoichiometry(sr->getStoichiometry() - delta); + } + } + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + string specid = sp->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); + + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; + + double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Product stoichiometries are positive in SBML, so add delta + sp->setStoichiometry(sp->getStoichiometry() + delta); + } + } + } + if (level > 0 && version > 0 && level != doc->getLevel() && version != doc->getVersion()) { convertSBMLVersionDocument(doc, level, version); } From 74afe6535280ef3e005001ae31a791456adfc6a0 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Fri, 20 Mar 2026 13:03:40 -0700 Subject: [PATCH 08/30] Don't reset stoichiometries when conserved moiety analysis is on. --- source/rrRoadRunner.cpp | 103 +++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 1062d6eff9..6abdacdbb7 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4690,59 +4690,62 @@ namespace rr { } } - //Set any changed stoichiometries: - vector rxnids = getReactionIds(); - for (int nr = 0; nr < rxnids.size(); nr++) { - string rxnid = rxnids[nr]; - libsbml::Reaction* rxn = model->getReaction(rxnid); - int rxnindex = impl->model->getReactionIndex(rxnid); - - // First pass: sum stoichiometries across both reactants and products per species. - // We use the same sign convention as getStoichiometry(): negative for reactants, - // positive for products. - std::map totalStoich; - for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { - libsbml::SpeciesReference* sr = rxn->getReactant(r); - totalStoich[sr->getSpecies()] -= sr->getStoichiometry(); - } - for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { - libsbml::SpeciesReference* sp = rxn->getProduct(p); - totalStoich[sp->getSpecies()] += sp->getStoichiometry(); - } - - // Second pass: for each species, apply any delta to the first species reference - // seen across both reactants and products. - set seen; - for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { - libsbml::SpeciesReference* sr = rxn->getReactant(r); - string specid = sr->getSpecies(); - if (seen.find(specid) != seen.end()) continue; - seen.insert(specid); - - int specindex = impl->model->getFloatingSpeciesIndex(specid); - if (specindex < 0) continue; - - double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative - double delta = desired - totalStoich[specid]; - if (std::abs(delta) > 1e-15) { - // Reactant stoichiometries are positive in SBML, so subtract delta - sr->setStoichiometry(sr->getStoichiometry() - delta); + //Set any changed stoichiometries, but only if conserved moiety analysis is OFF: + if (!self.loadOpt.getConservedMoietyConversion()) { + + vector rxnids = getReactionIds(); + for (int nr = 0; nr < rxnids.size(); nr++) { + string rxnid = rxnids[nr]; + libsbml::Reaction* rxn = model->getReaction(rxnid); + int rxnindex = impl->model->getReactionIndex(rxnid); + + // First pass: sum stoichiometries across both reactants and products per species. + // We use the same sign convention as getStoichiometry(): negative for reactants, + // positive for products. + std::map totalStoich; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + totalStoich[sr->getSpecies()] -= sr->getStoichiometry(); } - } - for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { - libsbml::SpeciesReference* sp = rxn->getProduct(p); - string specid = sp->getSpecies(); - if (seen.find(specid) != seen.end()) continue; - seen.insert(specid); + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + totalStoich[sp->getSpecies()] += sp->getStoichiometry(); + } + + // Second pass: for each species, apply any delta to the first species reference + // seen across both reactants and products. + set seen; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + string specid = sr->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); + + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; - int specindex = impl->model->getFloatingSpeciesIndex(specid); - if (specindex < 0) continue; + double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Reactant stoichiometries are positive in SBML, so subtract delta + sr->setStoichiometry(sr->getStoichiometry() - delta); + } + } + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + string specid = sp->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); - double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive - double delta = desired - totalStoich[specid]; - if (std::abs(delta) > 1e-15) { - // Product stoichiometries are positive in SBML, so add delta - sp->setStoichiometry(sp->getStoichiometry() + delta); + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; + + double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Product stoichiometries are positive in SBML, so add delta + sp->setStoichiometry(sp->getStoichiometry() + delta); + } } } } From e28849d713d21896162991e5b5a583c16b7494f9 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Fri, 20 Mar 2026 17:21:45 -0700 Subject: [PATCH 09/30] Named stoichs still not quite working for steady state, but closer. It would work if we sent 'regenerateModel' the current SBML... but that breaks other things. --- source/llvm/LLVMModelGenerator.cpp | 21 +++ source/rrRoadRunner.cpp | 175 ++++++++++-------- .../named_stoic_in_kinetic_law.xml | 41 ++-- test/sbml_features/named_stoich.cpp | 50 ++++- 4 files changed, 176 insertions(+), 111 deletions(-) rename test/models/{ModelAnalysis => SBMLFeatures}/named_stoic_in_kinetic_law.xml (55%) diff --git a/source/llvm/LLVMModelGenerator.cpp b/source/llvm/LLVMModelGenerator.cpp index 215ec1c5f9..c2f984eff3 100644 --- a/source/llvm/LLVMModelGenerator.cpp +++ b/source/llvm/LLVMModelGenerator.cpp @@ -381,6 +381,27 @@ namespace rrllvm { } } } + + ////Copy any changed stoichiometries. Make sure we know if we're in 'conserved moieties' mode. + //for (int r = 0; r < oldModel->getNumReactions(); r++) { + // string rID = oldModel->getReactionId(r); + // int new_r = newModel->getReactionIndex(rID); + // if (new_r < 0) continue; + // int s_max = oldModel->getNumIndFloatingSpecies(); + // for (int s = 0; s < s_max; s++) { + // string sID = oldModel->getFloatingSpeciesId(s); + // int new_s = newModel->getFloatingSpeciesIndex(sID); + // if (new_s < 0) continue; + // if (new_s < newModel->getNumIndFloatingSpecies()) continue; + // double stoich = oldModel->getStoichiometry(s, r); + // newModel->setStoichiometry(new_s, new_r, stoich); + // } + // + //} + + + + newModel->setTime(oldModel->getTime()); } diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 6abdacdbb7..53fd7bd158 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -4628,7 +4628,11 @@ namespace rr { libsbml::SBMLDocument* RoadRunner::getCurrentSBMLDocument(int level, int version) { - check_model(); + if (!impl->model) { + libsbml::SBMLDocument* empty = new libsbml::SBMLDocument(level, version); + empty->createModel(); + return empty; + } get_self(); libsbml::SBMLDocument* doc = impl->document->clone(); @@ -4641,26 +4645,30 @@ namespace rr { std::vector array = getFloatingSpeciesIds(); for (int i = 0; i < array.size(); i++) { double value = 0; - if (model->getSpecies(array[i])->isSetInitialConcentration()) { - impl->model->getFloatingSpeciesConcentrations(1, &i, &value); - setSBMLValue(model, array[i], value, true); - } - else { - impl->model->getFloatingSpeciesAmounts(1, &i, &value); - setSBMLValue(model, array[i], value, false); + if (model->getSpecies(array[i])) { + if (model->getSpecies(array[i])->isSetInitialConcentration()) { + impl->model->getFloatingSpeciesConcentrations(1, &i, &value); + setSBMLValue(model, array[i], value, true); + } + else { + impl->model->getFloatingSpeciesAmounts(1, &i, &value); + setSBMLValue(model, array[i], value, false); + } } } array = getBoundarySpeciesIds(); for (int i = 0; i < array.size(); i++) { double value = 0; - if (model->getSpecies(array[i])->isSetInitialConcentration()) { - impl->model->getBoundarySpeciesConcentrations(1, &i, &value); - setSBMLValue(model, array[i], value, true); - } - else { - impl->model->getBoundarySpeciesAmounts(1, &i, &value); - setSBMLValue(model, array[i], value, false); + if (model->getSpecies(array[i])) { + if (model->getSpecies(array[i])->isSetInitialConcentration()) { + impl->model->getBoundarySpeciesConcentrations(1, &i, &value); + setSBMLValue(model, array[i], value, true); + } + else { + impl->model->getBoundarySpeciesAmounts(1, &i, &value); + setSBMLValue(model, array[i], value, false); + } } } @@ -4668,7 +4676,12 @@ namespace rr { for (int i = 0; i < array.size(); i++) { double value = 0; impl->model->getCompartmentVolumes(1, &i, &value); - setSBMLValue(model, array[i], value); + try { + setSBMLValue(model, array[i], value); + } + catch(std::exception e) { + continue; + } } array = getGlobalParameterIds(); @@ -4681,71 +4694,82 @@ namespace rr { param->setValue(value); } else { - // sanity check, just make sure that this is a conserved moeity + //Could be a species reference that we converted to a global parameter. if (self.model->getConservedMoietyIndex(array[i]) < 0) { - throw std::logic_error("The global parameter name " - + array[i] + " could not be found in the SBML model, " - " and it is not a conserved moiety"); + libsbml::SBase* ref = model->getElementBySId(array[i]); + if (ref == NULL || ref->getTypeCode() != libsbml::SBML_SPECIES_REFERENCE) { + continue; + // sanity check, just make sure that this is a conserved moeity + //throw std::logic_error("The global parameter name " + // + array[i] + " could not be found in the SBML model, " + // " and it is not a conserved moiety"); + } + libsbml::SpeciesReference* sref = static_cast(ref); + sref->setStoichiometry(value); + } } } //Set any changed stoichiometries, but only if conserved moiety analysis is OFF: - if (!self.loadOpt.getConservedMoietyConversion()) { - - vector rxnids = getReactionIds(); - for (int nr = 0; nr < rxnids.size(); nr++) { - string rxnid = rxnids[nr]; - libsbml::Reaction* rxn = model->getReaction(rxnid); - int rxnindex = impl->model->getReactionIndex(rxnid); - - // First pass: sum stoichiometries across both reactants and products per species. - // We use the same sign convention as getStoichiometry(): negative for reactants, - // positive for products. - std::map totalStoich; - for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { - libsbml::SpeciesReference* sr = rxn->getReactant(r); - totalStoich[sr->getSpecies()] -= sr->getStoichiometry(); - } - for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { - libsbml::SpeciesReference* sp = rxn->getProduct(p); - totalStoich[sp->getSpecies()] += sp->getStoichiometry(); - } - - // Second pass: for each species, apply any delta to the first species reference - // seen across both reactants and products. - set seen; - for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { - libsbml::SpeciesReference* sr = rxn->getReactant(r); - string specid = sr->getSpecies(); - if (seen.find(specid) != seen.end()) continue; - seen.insert(specid); - - int specindex = impl->model->getFloatingSpeciesIndex(specid); - if (specindex < 0) continue; - - double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative - double delta = desired - totalStoich[specid]; - if (std::abs(delta) > 1e-15) { - // Reactant stoichiometries are positive in SBML, so subtract delta - sr->setStoichiometry(sr->getStoichiometry() - delta); - } + bool conservedMoieties = self.loadOpt.getConservedMoietyConversion(); + + vector rxnids = getReactionIds(); + int n_ind = impl->model->getNumIndFloatingSpecies(); + for (int nr = 0; nr < rxnids.size(); nr++) { + string rxnid = rxnids[nr]; + libsbml::Reaction* rxn = model->getReaction(rxnid); + if (!rxn) continue; + int rxnindex = impl->model->getReactionIndex(rxnid); + + // First pass: sum stoichiometries across both reactants and products per species. + // We use the same sign convention as getStoichiometry(): negative for reactants, + // positive for products. + std::map totalStoich; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + totalStoich[sr->getSpecies()] -= sr->getStoichiometry(); + } + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + totalStoich[sp->getSpecies()] += sp->getStoichiometry(); + } + + // Second pass: for each species, apply any delta to the first species reference + // seen across both reactants and products. + set seen; + for (unsigned int r = 0; r < rxn->getNumReactants(); r++) { + libsbml::SpeciesReference* sr = rxn->getReactant(r); + string specid = sr->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); + + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; + if (specindex >= n_ind) continue; + + double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Reactant stoichiometries are positive in SBML, so subtract delta + sr->setStoichiometry(sr->getStoichiometry() - delta); } - for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { - libsbml::SpeciesReference* sp = rxn->getProduct(p); - string specid = sp->getSpecies(); - if (seen.find(specid) != seen.end()) continue; - seen.insert(specid); - - int specindex = impl->model->getFloatingSpeciesIndex(specid); - if (specindex < 0) continue; - - double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive - double delta = desired - totalStoich[specid]; - if (std::abs(delta) > 1e-15) { - // Product stoichiometries are positive in SBML, so add delta - sp->setStoichiometry(sp->getStoichiometry() + delta); - } + } + for (unsigned int p = 0; p < rxn->getNumProducts(); p++) { + libsbml::SpeciesReference* sp = rxn->getProduct(p); + string specid = sp->getSpecies(); + if (seen.find(specid) != seen.end()) continue; + seen.insert(specid); + + int specindex = impl->model->getFloatingSpeciesIndex(specid); + if (specindex < 0) continue; + if (specindex >= n_ind) continue; + + double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive + double delta = desired - totalStoich[specid]; + if (std::abs(delta) > 1e-15) { + // Product stoichiometries are positive in SBML, so add delta + sp->setStoichiometry(sp->getStoichiometry() + delta); } } } @@ -7273,10 +7297,13 @@ namespace rr { // regenerate the model + libsbml::SBMLDocument* currdoc = getCurrentSBMLDocument(); impl->model.reset(ExecutableModelFactory::regenerateModel( impl->model.get(), + //currdoc, impl->document.get(), impl->loadOpt.modelGeneratorOpt)); + delete currdoc; impl->syncAllSolversWithModel(impl->model.get()); diff --git a/test/models/ModelAnalysis/named_stoic_in_kinetic_law.xml b/test/models/SBMLFeatures/named_stoic_in_kinetic_law.xml similarity index 55% rename from test/models/ModelAnalysis/named_stoic_in_kinetic_law.xml rename to test/models/SBMLFeatures/named_stoic_in_kinetic_law.xml index d36f8a69da..bf94871c6a 100644 --- a/test/models/ModelAnalysis/named_stoic_in_kinetic_law.xml +++ b/test/models/SBMLFeatures/named_stoic_in_kinetic_law.xml @@ -1,22 +1,22 @@ - - + + - + - - + + - + @@ -28,31 +28,14 @@ - + - - k1 - A - - - B - n - - - - - k2 - - - C - m - - - - D - q - + + n + m + q + A diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index cf2ba74940..0b35ba2fa6 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -16,15 +16,49 @@ using std::filesystem::path; class SBMLFeatures : public RoadRunnerTest { public: - path SBMLFeaturesDir = rrTestModelsDir_ / "SBMLFeatures"; - SBMLFeatures() = default; + path SBMLFeaturesDir = rrTestModelsDir_ / "SBMLFeatures"; + SBMLFeatures() = default; }; -TEST_F(SBMLFeatures, NAMED_BOUNDARY_SPECIES) -{ - //Logger::enableConsoleLogging(); - //Logger::setLevel(Logger::LOG_DEBUG); +TEST_F(SBMLFeatures, named_stoich_list) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + vector stoichs = rr.getStoichiometryIds(); + EXPECT_STREQ(stoichs[0].c_str(), "stoich(A, J0)"); + //EXPECT_STREQ(stoichs[1].c_str(), "n"); + //EXPECT_STREQ(stoichs[2].c_str(), "m"); + //EXPECT_STREQ(stoichs[3].c_str(), "q"); +} + + +TEST_F(SBMLFeatures, issue1306_named_stoich_value) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + rr.setValue("n", 3); + rr.setValue("m", 5); + rr.setValue("q", 7); + EXPECT_EQ(rr.getValue("n"), 3.0); + EXPECT_EQ(rr.getValue("m"), 5.0); + EXPECT_EQ(rr.getValue("q"), 7.0); + EXPECT_EQ(rr.getValue("stoich(B, J0)"), 3.0); + EXPECT_EQ(rr.getValue("stoich(C, J0)"), 5.0); + EXPECT_EQ(rr.getValue("stoich(D, J0)"), 7.0); + EXPECT_EQ(rr.getValue("J0"), 15.0); + rr.oneStep(0, 0.01); + EXPECT_NEAR(rr.getValue("A"), 0.8607083324139845, 0.00001); + string sbml = rr.getCurrentSBML(); + +} + - RoadRunner rri((SBMLFeaturesDir / "named_boundary_species.xml").string()); - rri.simulate(); +TEST_F(SBMLFeatures, issue1306_named_stoich_steadyState) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + //rr.steadyState(); + //EXPECT_NEAR(rr.getValue("B"), 6.0, 0.00001); + //rr.reset(); + rr.setValue("n", 3); + rr.setValue("m", 5); + rr.setValue("q", 7); + rr.steadyState(); + //EXPECT_NEAR(rr.getValue("B"), 4.0, 0.00001); } + + From 7ae9c1ca6fe4708678343442327641eca91930f4 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Mon, 23 Mar 2026 10:15:04 -0700 Subject: [PATCH 10/30] The test issue1306_named_stoich_steadyState was moved. --- test/model_analysis/model_analysis.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/model_analysis/model_analysis.cpp b/test/model_analysis/model_analysis.cpp index cd8e2bb621..22c055aa8e 100644 --- a/test/model_analysis/model_analysis.cpp +++ b/test/model_analysis/model_analysis.cpp @@ -22,14 +22,6 @@ class ModelAnalysisTests : public RoadRunnerTest { }; -TEST_F(ModelAnalysisTests, issue1306_named_stoich_steadyState) { - rr::RoadRunner rr((modelAnalysisModelsDir / "named_stoic_in_kinetic_law.xml").string()); - rr.steadyState();// , CoreException); - rr.setValue("stoich(A, _J0)", 3); - rr.setValue("n", 3); -} - - TEST_F(ModelAnalysisTests, issue1259) { BasicDictionary opt; opt.setItem("allow_approx", true); From 1e19e9594fd7a4bb66c6b1475831b9ef73177b76 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Mon, 23 Mar 2026 12:54:10 -0700 Subject: [PATCH 11/30] Clean things up in preparation of actual fix. The fix has to go in LLVMModelGenerator.cpp, and the now-uncommented code has to go earlier. --- source/llvm/LLVMModelGenerator.cpp | 36 +++++++++++++---------------- source/rrRoadRunner.cpp | 3 --- test/sbml_features/named_stoich.cpp | 10 +++++++- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/source/llvm/LLVMModelGenerator.cpp b/source/llvm/LLVMModelGenerator.cpp index c2f984eff3..bfdcf582f6 100644 --- a/source/llvm/LLVMModelGenerator.cpp +++ b/source/llvm/LLVMModelGenerator.cpp @@ -286,10 +286,10 @@ namespace rrllvm { { SharedModelResourcesPtr modelResources = std::make_shared(); - char* docSBML = doc->toSBML(); ModelGeneratorContext modelGeneratorContext(doc, options, JitFactory::makeJitEngine(options)); + char* docSBML = doc->toSBML(); std::string sbmlMD5 = getSBMLMD5(std::string((const char*)docSBML), options); if (modelResources->sbmlMD5.empty()) { modelResources->sbmlMD5 = sbmlMD5; @@ -382,25 +382,21 @@ namespace rrllvm { } } - ////Copy any changed stoichiometries. Make sure we know if we're in 'conserved moieties' mode. - //for (int r = 0; r < oldModel->getNumReactions(); r++) { - // string rID = oldModel->getReactionId(r); - // int new_r = newModel->getReactionIndex(rID); - // if (new_r < 0) continue; - // int s_max = oldModel->getNumIndFloatingSpecies(); - // for (int s = 0; s < s_max; s++) { - // string sID = oldModel->getFloatingSpeciesId(s); - // int new_s = newModel->getFloatingSpeciesIndex(sID); - // if (new_s < 0) continue; - // if (new_s < newModel->getNumIndFloatingSpecies()) continue; - // double stoich = oldModel->getStoichiometry(s, r); - // newModel->setStoichiometry(new_s, new_r, stoich); - // } - // - //} - - - + //Copy any changed stoichiometries. Make sure we know if we're in 'conserved moieties' mode. + for (int r = 0; r < oldModel->getNumReactions(); r++) { + string rID = oldModel->getReactionId(r); + int new_r = newModel->getReactionIndex(rID); + if (new_r < 0) continue; + int s_max = oldModel->getNumIndFloatingSpecies(); + for (int s = 0; s < s_max; s++) { + string sID = oldModel->getFloatingSpeciesId(s); + int new_s = newModel->getFloatingSpeciesIndex(sID); + if (new_s < 0) continue; + if (new_s >= newModel->getNumIndFloatingSpecies()) continue; + double stoich = oldModel->getStoichiometry(s, r); + newModel->setStoichiometry(new_s, new_r, stoich); + } + } newModel->setTime(oldModel->getTime()); } diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 53fd7bd158..efd70180a0 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -7297,13 +7297,10 @@ namespace rr { // regenerate the model - libsbml::SBMLDocument* currdoc = getCurrentSBMLDocument(); impl->model.reset(ExecutableModelFactory::regenerateModel( impl->model.get(), - //currdoc, impl->document.get(), impl->loadOpt.modelGeneratorOpt)); - delete currdoc; impl->syncAllSolversWithModel(impl->model.get()); diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index 0b35ba2fa6..c99b1da670 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -44,6 +44,9 @@ TEST_F(SBMLFeatures, issue1306_named_stoich_value) { EXPECT_EQ(rr.getValue("J0"), 15.0); rr.oneStep(0, 0.01); EXPECT_NEAR(rr.getValue("A"), 0.8607083324139845, 0.00001); + EXPECT_NEAR(rr.getValue("B"), 6.582124997241953, 0.00001); + EXPECT_NEAR(rr.getValue("C"), 0.6964583379300783, 0.00001); + EXPECT_NEAR(rr.getValue("D"), 0.9750416731021092, 0.00001); string sbml = rr.getCurrentSBML(); } @@ -57,8 +60,13 @@ TEST_F(SBMLFeatures, issue1306_named_stoich_steadyState) { rr.setValue("n", 3); rr.setValue("m", 5); rr.setValue("q", 7); + rr.setConservedMoietyAnalysis(true); + EXPECT_EQ(rr.getValue("n"), 3.0); + EXPECT_EQ(rr.getValue("m"), 5.0); + EXPECT_EQ(rr.getValue("q"), 7.0); + EXPECT_EQ(rr.getValue("J0"), 0.0); rr.steadyState(); - //EXPECT_NEAR(rr.getValue("B"), 4.0, 0.00001); + EXPECT_NEAR(rr.getValue("B"), 4.0, 0.00001); } From f238d99b604ef8cca87d17d77e6d69bc9c8ebf0d Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Mon, 23 Mar 2026 16:58:13 -0700 Subject: [PATCH 12/30] Reset the model when turning on conserved moieties only! --- source/rrRoadRunner.cpp | 45 ++++++++++++++++++----------- source/rrRoadRunner.h | 1 + test/sbml_features/named_stoich.cpp | 4 +-- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index efd70180a0..d0ce7233f8 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -1725,8 +1725,11 @@ namespace rr { // have to reload self.loadOpt.modelGeneratorOpt |= LoadSBMLOptions::RECOMPILE; - //load(getSBML()); + //We have to update the stoichiometries manually here: + libsbml::SBMLDocument* olddoc = impl->document->clone(); + updateStoichiometriesWith(impl->document.get(), impl->model.get(), getReactionIds()); regenerateModel(true); + impl->document.reset(olddoc); // restore original reload value self.loadOpt.modelGeneratorOpt = savedOpt; @@ -4711,16 +4714,31 @@ namespace rr { } } - //Set any changed stoichiometries, but only if conserved moiety analysis is OFF: - bool conservedMoieties = self.loadOpt.getConservedMoietyConversion(); + //Set any changed stoichiometries + updateStoichiometriesWith(doc, impl->model.get(), getReactionIds()); - vector rxnids = getReactionIds(); - int n_ind = impl->model->getNumIndFloatingSpecies(); + + if (level > 0 && version > 0 && level != doc->getLevel() && version != doc->getVersion()) { + convertSBMLVersionDocument(doc, level, version); + } + return doc; + } + + void RoadRunner::updateStoichiometriesWith(libsbml::SBMLDocument* doc, ExecutableModel* ex_model, vector rxnids) + { + if (!doc || !ex_model) { + return; + } + libsbml::Model* model = doc->getModel(); + if (!model) { + return; + } + int n_ind = ex_model->getNumIndFloatingSpecies(); for (int nr = 0; nr < rxnids.size(); nr++) { string rxnid = rxnids[nr]; libsbml::Reaction* rxn = model->getReaction(rxnid); if (!rxn) continue; - int rxnindex = impl->model->getReactionIndex(rxnid); + int rxnindex = ex_model->getReactionIndex(rxnid); // First pass: sum stoichiometries across both reactants and products per species. // We use the same sign convention as getStoichiometry(): negative for reactants, @@ -4744,11 +4762,11 @@ namespace rr { if (seen.find(specid) != seen.end()) continue; seen.insert(specid); - int specindex = impl->model->getFloatingSpeciesIndex(specid); + int specindex = ex_model->getFloatingSpeciesIndex(specid); if (specindex < 0) continue; if (specindex >= n_ind) continue; - double desired = impl->model->getStoichiometry(specindex, rxnindex); // negative + double desired = ex_model->getStoichiometry(specindex, rxnindex); // negative double delta = desired - totalStoich[specid]; if (std::abs(delta) > 1e-15) { // Reactant stoichiometries are positive in SBML, so subtract delta @@ -4761,11 +4779,11 @@ namespace rr { if (seen.find(specid) != seen.end()) continue; seen.insert(specid); - int specindex = impl->model->getFloatingSpeciesIndex(specid); + int specindex = ex_model->getFloatingSpeciesIndex(specid); if (specindex < 0) continue; if (specindex >= n_ind) continue; - double desired = impl->model->getStoichiometry(specindex, rxnindex); // positive + double desired = ex_model->getStoichiometry(specindex, rxnindex); // positive double delta = desired - totalStoich[specid]; if (std::abs(delta) > 1e-15) { // Product stoichiometries are positive in SBML, so add delta @@ -4773,11 +4791,6 @@ namespace rr { } } } - - if (level > 0 && version > 0 && level != doc->getLevel() && version != doc->getVersion()) { - convertSBMLVersionDocument(doc, level, version); - } - return doc; } std::string RoadRunner::getCurrentSBML(int level, int version) { @@ -7295,14 +7308,12 @@ namespace rr { } } - // regenerate the model impl->model.reset(ExecutableModelFactory::regenerateModel( impl->model.get(), impl->document.get(), impl->loadOpt.modelGeneratorOpt)); - impl->syncAllSolversWithModel(impl->model.get()); if (auto v1 = absTol.get_if>()) { diff --git a/source/rrRoadRunner.h b/source/rrRoadRunner.h index 7d69cc0412..cebe3572a1 100644 --- a/source/rrRoadRunner.h +++ b/source/rrRoadRunner.h @@ -1985,6 +1985,7 @@ namespace rr { protected: libsbml::SBMLDocument* getCurrentSBMLDocument(int level = 0, int version = 0); + void updateStoichiometriesWith(libsbml::SBMLDocument* doc, ExecutableModel* ex_model, std::vector rxnids); }; diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index c99b1da670..f02dc936ef 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -63,8 +63,8 @@ TEST_F(SBMLFeatures, issue1306_named_stoich_steadyState) { rr.setConservedMoietyAnalysis(true); EXPECT_EQ(rr.getValue("n"), 3.0); EXPECT_EQ(rr.getValue("m"), 5.0); - EXPECT_EQ(rr.getValue("q"), 7.0); - EXPECT_EQ(rr.getValue("J0"), 0.0); + //EXPECT_EQ(rr.getValue("q"), 7.0); + //EXPECT_EQ(rr.getValue("J0"), 0.0); rr.steadyState(); EXPECT_NEAR(rr.getValue("B"), 4.0, 0.00001); } From c5221083f8fda6c4bc76d934d3a95ff9739a2fe6 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Mon, 23 Mar 2026 18:15:25 -0700 Subject: [PATCH 13/30] Remove long commented bit. --- source/rrRoadRunner.cpp | 119 ---------------------------------------- 1 file changed, 119 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index d0ce7233f8..cb163fa378 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -3147,125 +3147,6 @@ namespace rr { ls::DoubleMatrix RoadRunner::getExtendedStoichiometryMatrix() { return getSubStoichiometryMatrix(true, true, true); - //check_model(); - //get_self(); - //ls::LibStructural *ls = getLibStruct(); - - //if (self.loadOpt.getConservedMoietyConversion()) { - // // pointer to mat owned by ls - // ls::DoubleMatrix m = *ls->getReorderedStoichiometryMatrix(); - // ls->getReorderedStoichiometryMatrixLabels( - // m.getRowNames(), m.getColNames()); - // return m; - //} - - //// pointer to owned matrix - //ls::DoubleMatrix *mptr = ls->getStoichiometryMatrix(); - //if (!mptr) { - // throw CoreException("Error: Stoichiometry matrix does not exist for this model"); - //} - //ls::DoubleMatrix m = *mptr; - //ls->getStoichiometryMatrixLabels(m.getRowNames(), m.getColNames()); - - //libsbml::SBMLReader reader; - //libsbml::SBMLDocument *doc = reader.readSBMLFromString(getSBML()); - //libsbml::Model *model = doc->getModel(); - //typedef cxx11_ns::unordered_map RxnIdxToRowMap; - //typedef cxx11_ns::unordered_map RxnIdxToRxnMap; - //typedef cxx11_ns::unordered_map SpcToRowMap; - //RxnIdxToRowMap missing_rct_row_map; - //RxnIdxToRowMap missing_prd_row_map; - //RxnIdxToRxnMap rxn_map; - //SpcToRowMap boundary_spc_row_map; - - //int n_rows = m.numRows(); - - //for (int i = 0; i < m.getColNames().size(); ++i) { - // libsbml::Reaction *r = model->getReaction(m.getColNames().at(i)); - // assert(r); - // rxn_map[i] = r; - // if (!r->getNumReactants()) { - // missing_rct_row_map[i] = n_rows++; - // } else { - // for (int j = 0; j < r->getNumReactants(); ++j) { - // libsbml::SpeciesReference *ref = r->getReactant(j); - // assert(ref); - // libsbml::Species *s = model->getSpecies(ref->getSpecies()); - // assert(s); - // if (s->getBoundaryCondition() && boundary_spc_row_map.find(s) == boundary_spc_row_map.end()) - // boundary_spc_row_map[s] = n_rows++; - // } - // } - // if (!r->getNumProducts()) { - // missing_prd_row_map[i] = n_rows++; - // } else { - // for (int j = 0; j < r->getNumProducts(); ++j) { - // libsbml::SpeciesReference *ref = r->getProduct(j); - // assert(ref); - // libsbml::Species *s = model->getSpecies(ref->getSpecies()); - // assert(s); - // if (s->getBoundaryCondition() && boundary_spc_row_map.find(s) == boundary_spc_row_map.end()) - // boundary_spc_row_map[s] = n_rows++; - // } - // } - //} - - //ls::DoubleMatrix extended_matrix(n_rows, m.numCols()); - //extended_matrix.setRowNames(m.getRowNames()); - //extended_matrix.setColNames(m.getColNames()); - //extended_matrix.getRowNames().resize(n_rows); - //for (int i = 0; i < m.numRows(); ++i) { - // for (int j = 0; j < m.numCols(); ++j) { - // extended_matrix(i, j) = m(i, j); - // } - //} - //for (int i = m.numRows(); i < n_rows; ++i) { - // for (int j = 0; j < m.numCols(); ++j) { - // extended_matrix(i, j) = 0; - // } - //} - //for (RxnIdxToRowMap::const_iterator i = missing_rct_row_map.begin(); i != missing_rct_row_map.end(); ++i) { - // extended_matrix(i->second, i->first) = -1; - // RxnIdxToRxnMap::const_iterator r = rxn_map.find(i->first); - // if (r != rxn_map.end()) - // extended_matrix.getRowNames().at(i->second) = r->second->getId() + "_source"; - //} - //for (RxnIdxToRowMap::const_iterator i = missing_prd_row_map.begin(); i != missing_prd_row_map.end(); ++i) { - // extended_matrix(i->second, i->first) = 1; - // RxnIdxToRxnMap::const_iterator r = rxn_map.find(i->first); - // if (r != rxn_map.end()) - // extended_matrix.getRowNames().at(i->second) = r->second->getId() + "_sink"; - //} - //for (SpcToRowMap::const_iterator i = boundary_spc_row_map.begin(); i != boundary_spc_row_map.end(); ++i) { - // for (int j = 0; j < m.getColNames().size(); ++j) { - // libsbml::Reaction *r = model->getReaction(m.getColNames().at(j)); - // assert(r); - // for (int k = 0; k < r->getNumReactants(); ++k) { - // libsbml::SpeciesReference *ref = r->getReactant(k); - // assert(ref); - // libsbml::Species *s = model->getSpecies(ref->getSpecies()); - // assert(s); - // if (s == i->first) { - // extended_matrix(i->second, j) = -1; - // extended_matrix.getRowNames().at(i->second) = s->getId(); - // } - // } - // for (int k = 0; k < r->getNumProducts(); ++k) { - // libsbml::SpeciesReference *ref = r->getProduct(k); - // assert(ref); - // libsbml::Species *s = model->getSpecies(ref->getSpecies()); - // assert(s); - // if (s == i->first) { - // extended_matrix(i->second, j) = 1; - // extended_matrix.getRowNames().at(i->second) = s->getId(); - // } - // } - // } - //} - - //delete doc; - - //return extended_matrix; } ls::DoubleMatrix RoadRunner::getReactantsStoichiometryMatrix(bool boundary) From 25d494794468798c032238de5f64c0a00ae4495e Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 08:20:41 -0700 Subject: [PATCH 14/30] Re-enable test! Last time I got this close to working, calling steadyState twice didn't work. Now it does! --- test/sbml_features/named_stoich.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index f02dc936ef..8005cd5ec5 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -54,9 +54,9 @@ TEST_F(SBMLFeatures, issue1306_named_stoich_value) { TEST_F(SBMLFeatures, issue1306_named_stoich_steadyState) { rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); - //rr.steadyState(); - //EXPECT_NEAR(rr.getValue("B"), 6.0, 0.00001); - //rr.reset(); + rr.steadyState(); + EXPECT_NEAR(rr.getValue("B"), 6.0, 0.00001); + rr.reset(); rr.setValue("n", 3); rr.setValue("m", 5); rr.setValue("q", 7); From 9591c09ea4fd999f435400fefed25a2fa095763c Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 13:56:54 -0700 Subject: [PATCH 15/30] More support for increasingly-obscure things to do with named stoichs: * Put stubs for init stoichiometries * Throw error on variable stoichiometries * Put stoichiometry names in ID list. * Allow 'init(n)' to set/get initial stoichiometries (which are actually unchanging: setting 'init' or not does the same thing) * Drop the function that claimed to set multiple stoichiometries, since it actually did not. * Add convenience function to get a particular stoichiometry id (either its name or "stoich(sp, rxn)") * Stoichiometry seletion record names fixed. --- source/llvm/LLVMExecutableModel.cpp | 120 +++++++++++++++++++++++---- source/llvm/LLVMExecutableModel.h | 22 ++++- source/llvm/LLVMModelData.cpp | 2 + source/llvm/LLVMModelData.h | 27 +++--- source/llvm/LLVMModelDataSymbols.cpp | 32 +++++++ source/llvm/LLVMModelDataSymbols.h | 1 + source/llvm/ModelDataIRBuilder.cpp | 2 +- source/rrExecutableModel.h | 8 +- source/rrRoadRunner.cpp | 12 ++- source/rrSelectionRecord.cpp | 7 +- source/rrSelectionRecord.h | 5 ++ test/sbml_features/named_stoich.cpp | 30 ++++++- 12 files changed, 224 insertions(+), 44 deletions(-) diff --git a/source/llvm/LLVMExecutableModel.cpp b/source/llvm/LLVMExecutableModel.cpp index fda304ad59..2024fd9648 100644 --- a/source/llvm/LLVMExecutableModel.cpp +++ b/source/llvm/LLVMExecutableModel.cpp @@ -1246,7 +1246,7 @@ void LLVMExecutableModel::getIds(int types, std::list &ids) if (getStoichiometry(s, r) != 0) { string rid = getReactionId(r); - ids.push_back("stoich(" + sid + ", " + rid + ")"); + ids.push_back(symbols->getStoichiometryIdFor(sid, rid)); } } } @@ -1603,6 +1603,10 @@ const rr::SelectionRecord& LLVMExecutableModel::getSelection(const std::string& sel.selectionType = SelectionRecord::INITIAL_GLOBAL_PARAMETER; sel.index = index; break; + case LLVMModelDataSymbols::STOICHIOMETRY: + sel.selectionType = SelectionRecord::INITIAL_STOICHIOMETRY; + sel.index = index; + break; default: std::string msg = "Invalid Id for initial value: '" + str + "'"; throw LLVMException(msg); @@ -1744,7 +1748,8 @@ void LLVMExecutableModel::setValue(const std::string& id, double value) setGlobalParameterInitValues(1, &index, &value); break; case SelectionRecord::STOICHIOMETRY: - setStoichiometries(1, &index, &value); + case SelectionRecord::INITIAL_STOICHIOMETRY: + setStoichiometry(index, value); break; default: throw LLVMException("Invalid selection '" + sel.to_string() + "' for setting value"); @@ -2319,25 +2324,73 @@ int LLVMExecutableModel::setCompartmentVolumes(size_t len, const int* indx, return result; } -int LLVMExecutableModel::setStoichiometries(size_t len, const int* indx, - const double* values) -{ - return setStoichiometries(len, indx, values, true); -} +//int LLVMExecutableModel::setStoichiometries(size_t len, const int* indx, +// const double* values) +//{ +// return setStoichiometries(len, indx, values, true); +//} + +//int LLVMExecutableModel::setStoichiometries(size_t len, const int* indx, +// const double* values, bool strict) +//{ +// if (len == 1) { +// int index = *indx; +// double value = *values; +// return setStoichiometry(index, value); +// } +// +// return -1; +//} -int LLVMExecutableModel::setStoichiometries(size_t len, const int* indx, - const double* values, bool strict) +int LLVMExecutableModel::setStoichiometry(int index, double value) { - if (len == 1) { - int index = *indx; - double value = *values; - return setStoichiometry(index, value); - } + if (std::signbit(value)) + throw LLVMException("Invalid stoichiometry value"); + + if (symbols->isConservedMoietyAnalysis()) + throw LLVMException("Unable to set stoichiometries when conserved moieties are on"); + + std::list stoichiometryIndx = symbols->getStoichiometryList(); + std::list::const_iterator stoichiometry = stoichiometryIndx.begin(); + for (int i = 0; i < index; i++) + ++stoichiometry; + if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Product) + return setStoichiometry(stoichiometry->row, stoichiometry->column, value); + else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Reactant) + return setStoichiometry(stoichiometry->row, stoichiometry->column, -1 * value); + else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::MultiReactantProduct) + throw LLVMException("Cannot set stoichiometry for a MultiReactantProduct"); + else + throw LLVMException("Cannot set stoichiometry for a Modifier"); return -1; } -int LLVMExecutableModel::setStoichiometry(int index, double value) +int LLVMExecutableModel::setStoichiometry(int speciesIndex, int reactionIndex, double value) +{ + double result = csr_matrix_set_nz(modelData->stoichiometry, speciesIndex, reactionIndex, value); + return isnan(result) ? 0 : result; +} + +//int LLVMExecutableModel::setInitStoichiometries(size_t len, const int* indx, +// const double* values) +//{ +// return setInitStoichiometries(len, indx, values, true); +//} + +//int LLVMExecutableModel::setInitStoichiometries(size_t len, const int* indx, +// const double* values, bool strict) +//{ +// if (len == 1) { +// int index = *indx; +// double value = *values; +// return setInitStoichiometry(index, value); +// } +// +// return -1; +//} + +int LLVMExecutableModel::setInitStoichiometry(int index, double value) { if (std::signbit(value)) throw LLVMException("Invalid stoichiometry value"); @@ -2350,9 +2403,9 @@ int LLVMExecutableModel::setStoichiometry(int index, double value) for (int i = 0; i < index; i++) ++stoichiometry; if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Product) - return setStoichiometry(stoichiometry->row, stoichiometry->column, value); + return setInitStoichiometry(stoichiometry->row, stoichiometry->column, value); else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Reactant) - return setStoichiometry(stoichiometry->row, stoichiometry->column, -1 * value); + return setInitStoichiometry(stoichiometry->row, stoichiometry->column, -1 * value); else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::MultiReactantProduct) throw LLVMException("Cannot set stoichiometry for a MultiReactantProduct"); else @@ -2361,8 +2414,10 @@ int LLVMExecutableModel::setStoichiometry(int index, double value) return -1; } -int LLVMExecutableModel::setStoichiometry(int speciesIndex, int reactionIndex, double value) +int LLVMExecutableModel::setInitStoichiometry(int speciesIndex, int reactionIndex, double value) { + //For now, we don't store 'initStoichiometry' separately. Essentially, every stoichiometry set is to the initial value. + //double result = csr_matrix_set_nz(modelData->initStoichiometry, speciesIndex, reactionIndex, value); double result = csr_matrix_set_nz(modelData->stoichiometry, speciesIndex, reactionIndex, value); return isnan(result) ? 0 : result; } @@ -2394,6 +2449,35 @@ double LLVMExecutableModel::getStoichiometry(int speciesIndex, int reactionIndex return isnan(result) ? 0 : result; } +double LLVMExecutableModel::getInitStoichiometry(int index) +{ + if (symbols->isConservedMoietyAnalysis()) + throw LLVMException("Unable to get stoichiometries when conserved moieties are on"); + + if (index < 0) + throw LLVMException("The stoichiometry index is not valid"); + std::list stoichiometryIndx = symbols->getStoichiometryList(); + std::list::const_iterator stoichiometry = stoichiometryIndx.begin(); + for (int i = 0; i < index; i++) + ++stoichiometry; + if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Reactant) + return -1 * getInitStoichiometry(stoichiometry->row, stoichiometry->column); + else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::Product) + return getInitStoichiometry(stoichiometry->row, stoichiometry->column); + else if (stoichiometry->type == LLVMModelDataSymbols::SpeciesReferenceType::MultiReactantProduct) + throw LLVMException("Cannot return stoichiometry for a MultiReactantProduct"); + else + throw LLVMException("Cannot return stoichiometry for a Modifier"); +} + +double LLVMExecutableModel::getInitStoichiometry(int speciesIndex, int reactionIndex) +{ + //For now, we don't store 'initStoichiometry' separately. Essentially, every stoichiometry set is to the initial value. + //double result = csr_matrix_get_nz(modelData->initStoichiometry, speciesIndex, reactionIndex); + double result = csr_matrix_get_nz(modelData->stoichiometry, speciesIndex, reactionIndex); + return isnan(result) ? 0 : result; +} + /******************************* Events Section *******************************/ #if (1) /**********************************************************************/ /******************************************************************************/ diff --git a/source/llvm/LLVMExecutableModel.h b/source/llvm/LLVMExecutableModel.h index 30b4b26cc2..9add0ce22f 100644 --- a/source/llvm/LLVMExecutableModel.h +++ b/source/llvm/LLVMExecutableModel.h @@ -348,20 +348,34 @@ class RR_DECLSPEC LLVMExecutableModel: public rr::ExecutableModel virtual int setCompartmentVolumes(size_t len, int const* indx, const double* values, bool strict); - virtual int setStoichiometries(size_t len, int const* indx, - const double* values); + //virtual int setStoichiometries(size_t len, int const* indx, + // const double* values); - virtual int setStoichiometries(size_t len, int const* indx, - const double* values, bool strict); + //virtual int setStoichiometries(size_t len, int const* indx, + // const double* values, bool strict); virtual int setStoichiometry(int index, double value); virtual int setStoichiometry(int speciesIndex, int reactionIndex, double value); + //virtual int setInitStoichiometries(size_t len, int const* indx, + // const double* values); + + //virtual int setInitStoichiometries(size_t len, int const* indx, + // const double* values, bool strict); + + virtual int setInitStoichiometry(int index, double value); + + virtual int setInitStoichiometry(int speciesIndex, int reactionIndex, double value); + virtual double getStoichiometry(int index); virtual double getStoichiometry(int speciesIndex, int reactionIndex); + virtual double getInitStoichiometry(int index); + + virtual double getInitStoichiometry(int speciesIndex, int reactionIndex); + /******************************* Initial Conditions Section *******************/ #if (1) /**********************************************************************/ /******************************************************************************/ diff --git a/source/llvm/LLVMModelData.cpp b/source/llvm/LLVMModelData.cpp index 73ab8f1a20..7a48650aaa 100644 --- a/source/llvm/LLVMModelData.cpp +++ b/source/llvm/LLVMModelData.cpp @@ -125,6 +125,7 @@ void LLVMModelData_save(LLVMModelData *data, std::ostream& out) rr::saveBinary(out, data->stateVectorSize); //Save the stoichiometry matrix rr::csr_matrix_dump_binary(data->stoichiometry, out); + //rr::csr_matrix_dump_binary(data->initStoichiometry, out); //We do not need to save random because LLVMExecutableModel will make a new one if it is null @@ -205,6 +206,7 @@ LLVMModelData* LLVMModelData_from_save(std::istream& in) //Load the stoichiometry matrix data->stoichiometry = rr::csr_matrix_new_from_binary(in); + //data->initStoichiometry = rr::csr_matrix_new_from_binary(in); //Alias pointer offsets diff --git a/source/llvm/LLVMModelData.h b/source/llvm/LLVMModelData.h index 0a789a262f..4c6ad58d6b 100644 --- a/source/llvm/LLVMModelData.h +++ b/source/llvm/LLVMModelData.h @@ -223,25 +223,30 @@ struct LLVMModelData */ double* floatingSpeciesAmountsAlias; // 31 + /* + * initial stoichiometry matrix + */ + //rr::csr_matrix* initStoichiometry; // 32 + /** * binary data layout: * - * compartmentVolumes [numIndCompartmentVolumes] // 32 - * initCompartmentVolumes [numInitCompartmentVolumes] // 33 - * initFloatingSpeciesAmounts [numInitFloatingSpecies] // 34 - * boundarySpeciesAmounts [numIndBoundarySpecies] // 35 - * initBoundarySpeciesAmounts [numInitBoundarySpecies] // 36 - * globalParameters [numIndGlobalParameters] // 37 - * initGlobalParameters [numInitGlobalParameters] // 38 - * reactionRates [numReactions] // 39 + * compartmentVolumes [numIndCompartmentVolumes] + * initCompartmentVolumes [numInitCompartmentVolumes] + * initFloatingSpeciesAmounts [numInitFloatingSpecies] + * boundarySpeciesAmounts [numIndBoundarySpecies] + * initBoundarySpeciesAmounts [numInitBoundarySpecies] + * globalParameters [numIndGlobalParameters] + * initGlobalParameters [numInitGlobalParameters] + * reactionRates [numReactions] * - * rateRuleValues [numRateRules] // 40 - * floatingSpeciesAmounts [numIndFloatingSpecies] // 41 + * rateRuleValues [numRateRules] + * floatingSpeciesAmounts [numIndFloatingSpecies] */ /** * This dynamic-sized array will be allocated while this ModelData is allocated. - * Ten array in the permanent data section is stored contiously in this chunck. + * Ten array in the permanent data section is stored contiously in this chunk. * Size of each array is defined by ten unsigned integer above. * Values can be accessed using ten alias pointers defined above. * diff --git a/source/llvm/LLVMModelDataSymbols.cpp b/source/llvm/LLVMModelDataSymbols.cpp index 304e8b1d4c..abc8bcfc9b 100644 --- a/source/llvm/LLVMModelDataSymbols.cpp +++ b/source/llvm/LLVMModelDataSymbols.cpp @@ -402,6 +402,19 @@ int LLVMModelDataSymbols::getStoichiometryIndex(const std::string& speciesId, co return -1; } +std::string LLVMModelDataSymbols::getStoichiometryIdFor(const std::string& speciesId, const std::string& reactionId) const +{ + int index = getStoichiometryIndex(speciesId, reactionId); + if (index == -1) { + return ""; + } + string stoichid = stoichIds[index]; + if (stoichid == "") { + return "stoich(" + speciesId + ", " + reactionId + ")"; + } + return stoichid; +} + std::vector LLVMModelDataSymbols::getStoichiometryIds() const { return stoichIds; @@ -1328,6 +1341,7 @@ typedef cxx11_ns::unordered_map UIntUMap; void LLVMModelDataSymbols::initReactions(const libsbml::Model* model) { // get the reactions + std::vector namedStoichiometryIds; const ListOfReactions *reactions = model->getListOfReactions(); for (size_t i = 0; i < reactions->size(); i++) { @@ -1357,6 +1371,7 @@ void LLVMModelDataSymbols::initReactions(const libsbml::Model* model) SpeciesReferenceInfo info = { static_cast(speciesIdx), static_cast(i), Reactant, r->getId() }; namedSpeciesReferenceInfo[r->getId()] = info; + namedStoichiometryIds.push_back(r->getId()); } else { @@ -1419,6 +1434,7 @@ void LLVMModelDataSymbols::initReactions(const libsbml::Model* model) SpeciesReferenceInfo info = { static_cast(speciesIdx), static_cast(i), Product, p->getId() }; namedSpeciesReferenceInfo[p->getId()] = info; + namedStoichiometryIds.push_back(p->getId()); } else { @@ -1462,6 +1478,22 @@ void LLVMModelDataSymbols::initReactions(const libsbml::Model* model) } } } + for (size_t ns = 0; ns < namedStoichiometryIds.size(); ns++) { + const Rule* rule = model->getRule(namedStoichiometryIds[ns]); + if (rule != NULL) { + std::string msg = "The named stoichiometry '" + namedStoichiometryIds[ns] + "' (also called a speciesReference) has "; + if (rule->isAssignment()) { + msg += "an assignment rule"; + } + else { + assert(rule->isRate()); + msg += "a rate rule"; + } + msg += ", which means that the stoichiometry of its reaction varies in time. Variable stoichiometries are not supported by roadrunner."; + throw_llvm_exception(msg); + } + } + } diff --git a/source/llvm/LLVMModelDataSymbols.h b/source/llvm/LLVMModelDataSymbols.h index dfef0fd66b..d6c117e965 100644 --- a/source/llvm/LLVMModelDataSymbols.h +++ b/source/llvm/LLVMModelDataSymbols.h @@ -248,6 +248,7 @@ class LLVMModelDataSymbols int getStoichiometryIndex(std::string const&) const; int getStoichiometryIndex(const std::string&, const std::string&) const; + std::string getStoichiometryIdFor(const std::string&, const std::string&) const; std::vector getStoichiometryIds() const; size_t getStoichiometrySize() const; diff --git a/source/llvm/ModelDataIRBuilder.cpp b/source/llvm/ModelDataIRBuilder.cpp index 920706cdfc..709caa5fdc 100644 --- a/source/llvm/ModelDataIRBuilder.cpp +++ b/source/llvm/ModelDataIRBuilder.cpp @@ -744,7 +744,7 @@ llvm::StructType *ModelDataIRBuilder::getStructType(llvm::Module *module) unsigned ModelDataIRBuilder::getModelDataSize(llvm::Module *module, const DataLayout& dl) { StructType *structType = getStructType(module); - uint64_t llvm_size = dl.getTypeStoreSize(structType); + uint64_t llvm_size = dl.getTypeStoreSize(structType); // the model data struct will NEVER be bigger than a 32 bit pointer! return (unsigned)llvm_size; diff --git a/source/rrExecutableModel.h b/source/rrExecutableModel.h index 273f5f173d..1c9cd5984d 100644 --- a/source/rrExecutableModel.h +++ b/source/rrExecutableModel.h @@ -605,8 +605,8 @@ namespace rr { * @param[in] values an array of at least length len which store the * stoichiometries. */ - virtual int setStoichiometries(size_t len, int const *indx, - const double *values) = 0; + //virtual int setStoichiometries(size_t len, int const *indx, + // const double *values) = 0; /* * Set the stoichiometries. @@ -617,8 +617,8 @@ namespace rr { * stoichiometries. * @param[in] strict whether to throw if the value cannot be set. */ - virtual int setStoichiometries(size_t len, int const* indx, - const double* values, bool strict) = 0; + //virtual int setStoichiometries(size_t len, int const* indx, + // const double* values, bool strict) = 0; /* * Set the setStoichiometry. diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index cb163fa378..a7591c89c2 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -1351,7 +1351,8 @@ namespace rr { case SelectionRecord::INITIAL_COMPARTMENT: impl->model->getCompartmentInitVolumes(1, &record.index, &dResult); break; - case SelectionRecord::STOICHIOMETRY: { + case SelectionRecord::STOICHIOMETRY: + case SelectionRecord::INITIAL_STOICHIOMETRY: { // in case it is entered in the form of stoich(SpeciesId, ReactionId) if (impl->model->getFloatingSpeciesIndex(record.p1) != -1 && impl->model->getReactionIndex(record.p2) != -1) dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1, record.p2)); @@ -4946,7 +4947,14 @@ namespace rr { } else if ((sel.index = impl->model->getCompartmentIndex(sel.p1)) >= 0) { sel.selectionType = SelectionRecord::INITIAL_COMPARTMENT; break; - } else { + } else if ((sel.index = impl->model->getStoichiometryIndex(sel.p1)) >= 0) { + sel.selectionType = SelectionRecord::INITIAL_STOICHIOMETRY; + break; + } else if ((sel.index = impl->model->getStoichiometryIndex(sel.p1, sel.p2)) >= 0) { + sel.selectionType = SelectionRecord::INITIAL_STOICHIOMETRY; + break; + } + else { throw Exception("Invalid id '" + sel.p1 + "' for initial amount"); break; } diff --git a/source/rrSelectionRecord.cpp b/source/rrSelectionRecord.cpp index f0d9c41ded..7262b0fcf8 100644 --- a/source/rrSelectionRecord.cpp +++ b/source/rrSelectionRecord.cpp @@ -457,7 +457,12 @@ std::string rr::SelectionRecord::to_string() const result = "init([" + p1 + "])"; break; case STOICHIOMETRY: - result = "stoich(" + p1 + ", " + p2 + ")"; + if (p2.empty()) { + result = p1; + } + else { + result = "stoich(" + p1 + ", " + p2 + ")"; + } break; case UNKNOWN: result = "UNKNOWN"; diff --git a/source/rrSelectionRecord.h b/source/rrSelectionRecord.h index ed3f1d2927..d327cd8c0b 100644 --- a/source/rrSelectionRecord.h +++ b/source/rrSelectionRecord.h @@ -298,6 +298,11 @@ class RR_DECLSPEC SelectionRecord */ INITIAL_GLOBAL_PARAMETER = INITIAL | _GLOBAL_PARAMETER | INDEPENDENT | DEPENDENT, + /** + * SelectionType for initial compartment values. + */ + INITIAL_STOICHIOMETRY = INITIAL | STOICHIOMETRY | INDEPENDENT | DEPENDENT, + /** * SelectionType for global parameters that have initial assignment rules. */ diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index 8005cd5ec5..59e3fd768d 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -8,6 +8,7 @@ #include "../test_util.h" #include #include "RoadRunnerTest.h" +#include "llvm/LLVMException.h" using namespace testing; using namespace rr; @@ -24,9 +25,9 @@ TEST_F(SBMLFeatures, named_stoich_list) { rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); vector stoichs = rr.getStoichiometryIds(); EXPECT_STREQ(stoichs[0].c_str(), "stoich(A, J0)"); - //EXPECT_STREQ(stoichs[1].c_str(), "n"); - //EXPECT_STREQ(stoichs[2].c_str(), "m"); - //EXPECT_STREQ(stoichs[3].c_str(), "q"); + EXPECT_STREQ(stoichs[1].c_str(), "n"); + EXPECT_STREQ(stoichs[2].c_str(), "m"); + EXPECT_STREQ(stoichs[3].c_str(), "q"); } @@ -70,3 +71,26 @@ TEST_F(SBMLFeatures, issue1306_named_stoich_steadyState) { } +TEST_F(SBMLFeatures, named_stoich_init_value) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + rr.setValue("init(n)", 3); + rr.setValue("init(m)", 5); + rr.setValue("init(q)", 7); + EXPECT_EQ(rr.getValue("n"), 3.0); + EXPECT_EQ(rr.getValue("m"), 5.0); + EXPECT_EQ(rr.getValue("q"), 7.0); + EXPECT_EQ(rr.getValue("init(n)"), 3.0); + EXPECT_EQ(rr.getValue("init(m)"), 5.0); + EXPECT_EQ(rr.getValue("init(q)"), 7.0); +} + + +TEST_F(SBMLFeatures, variable_named_stoich) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + EXPECT_THROW(rr.addAssignmentRule("n", "5", true), rrllvm::LLVMException); + + rr::RoadRunner rr2((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + EXPECT_THROW(rr2.addRateRule("q", "5", true), rrllvm::LLVMException); +} + + From ca31b2d133a57cebb9829067ac3ac3b2d04fa777 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 14:07:20 -0700 Subject: [PATCH 16/30] Comment out now-missing functions. --- test/mockups/MockExecutableModel.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mockups/MockExecutableModel.h b/test/mockups/MockExecutableModel.h index 5554e9bbfe..f62519ce49 100644 --- a/test/mockups/MockExecutableModel.h +++ b/test/mockups/MockExecutableModel.h @@ -67,8 +67,8 @@ class MockExecutableModel : public rr::ExecutableModel{ MOCK_METHOD(int, getSupportedIdTypes, (), (override)); MOCK_METHOD(double, getValue, (const std::string &id), (override)); MOCK_METHOD(void, setValue, (const std::string &id, double value), (override)); - MOCK_METHOD(int, setStoichiometries, (size_t len, int const *indx, const double *values), (override)); - MOCK_METHOD(int, setStoichiometries, (size_t len, int const* indx, const double* values, bool strict), (override)); + //MOCK_METHOD(int, setStoichiometries, (size_t len, int const *indx, const double *values), (override)); + //MOCK_METHOD(int, setStoichiometries, (size_t len, int const* indx, const double* values, bool strict), (override)); MOCK_METHOD(int, setStoichiometry, (int index, double value), (override)); MOCK_METHOD(int, setStoichiometry, (int speciesIndex, int reactionIndex, double value), (override)); MOCK_METHOD(double, getStoichiometry, (int index), (override)); From 8978ca5285afdc06429fcc64dac757f8cf8321b1 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 15:04:11 -0700 Subject: [PATCH 17/30] Don't test models with assignment rules to named stoichiometries. SAVE_STATE_21 was simply incorrect: it never should have worked. Technically, the test suite models that have an assignment rule that never changes will 'work' in roadrunner, but only coincidentally: the assignment rule is never evaluated in an context other than as an effective initial assignment. Model 1121 was another model like that; just switch to 1122 instead for the test (which wasn't supposed to be 'about' assigned stoichiometries anyway.) --- source/rrUtils.cpp | 1 + .../SAVE_STATE_21/SAVE_STATE_21-results.csv | 154 ----------------- .../SAVE_STATE_21/SAVE_STATE_21-sbml-l3v1.xml | 160 ------------------ .../SAVE_STATE_21/SAVE_STATE_21-settings.txt | 9 - test/serialization/state_saving.cpp | 18 +- 5 files changed, 4 insertions(+), 338 deletions(-) delete mode 100644 test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-results.csv delete mode 100644 test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-sbml-l3v1.xml delete mode 100644 test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-settings.txt diff --git a/source/rrUtils.cpp b/source/rrUtils.cpp index 34ff998347..1740b00c6f 100644 --- a/source/rrUtils.cpp +++ b/source/rrUtils.cpp @@ -469,6 +469,7 @@ bool hasUnimplementedTags(const std::string& descriptionFileName, const string& badtags.push_back("VolumeConcentrationRate"); //badtags.push_back("RateOf"); badtags.push_back("AssignedVariableStoichiometry"); + badtags.push_back("AssignedConstantStoichiometry"); if (integrator == "rk4" || integrator == "rk45") { diff --git a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-results.csv b/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-results.csv deleted file mode 100644 index 285e7c28f5..0000000000 --- a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-results.csv +++ /dev/null @@ -1,154 +0,0 @@ -time, S1, S2, S3, S4, k1, k2, k3, k4, k5, kavo, comp, comp2 -1.504918033,-18.582684,-26.79335718,4,2,1.1,8.12,2.5,2.132389137,2.8,1.000023545,6.504918033,2.132389137 -1.514754098,-18.6501299,-26.98623451,4,2,1.1,8.12,2.5,2.147239983,2.8,1.000023545,6.514754098,2.147239983 -1.524590164,-18.71764185,-27.17970821,4,2,1.1,8.12,2.5,2.162187578,2.8,1.000023545,6.524590164,2.162187578 -1.53442623,-18.78522445,-27.37379175,4,2,1.1,8.12,2.5,2.177231921,2.8,1.000023545,6.53442623,2.177231921 -1.544262295,-18.85288215,-27.56849834,4,2,1.1,8.12,2.5,2.192373012,2.8,1.000023545,6.544262295,2.192373012 -1.554098361,-18.92061928,-27.76384092,4,2,1.1,8.12,2.5,2.207610851,2.8,1.000023545,6.554098361,2.207610851 -1.563934426,-18.98844002,-27.95983214,4,2,1.1,8.12,2.5,2.222945439,2.8,1.000023545,6.563934426,2.222945439 -1.573770492,-19.05634842,-28.15648444,4,2,1.1,8.12,2.5,2.238376774,2.8,1.000023545,6.573770492,2.238376774 -1.583606557,-19.12434843,-28.35380997,4,2,1.1,8.12,2.5,2.253904858,2.8,1.000023545,6.583606557,2.253904858 -1.593442623,-19.19244384,-28.55182066,4,2,1.1,8.12,2.5,2.26952969,2.8,1.000023545,6.593442623,2.26952969 -1.603278689,-19.26063835,-28.75052821,4,2,1.1,8.12,2.5,2.28525127,2.8,1.000023545,6.603278689,2.28525127 -1.613114754,-19.32893555,-28.94994407,4,2,1.1,8.12,2.5,2.301069599,2.8,1.000023545,6.613114754,2.301069599 -1.62295082,-19.3973389,-29.15007949,4,2,1.1,8.12,2.5,2.316984675,2.8,1.000023545,6.62295082,2.316984675 -1.632786885,-19.46585176,-29.35094551,4,2,1.1,8.12,2.5,2.3329965,2.8,1.000023545,6.632786885,2.3329965 -1.642622951,-19.53447739,-29.55255293,4,2,1.1,8.12,2.5,2.349105073,2.8,1.000023545,6.642622951,2.349105073 -1.652459016,-19.60321896,-29.75491238,4,2,1.1,8.12,2.5,2.365310394,2.8,1.000023545,6.652459016,2.365310394 -1.662295082,-19.67207953,-29.95803428,4,2,1.1,8.12,2.5,2.381612464,2.8,1.000023545,6.662295082,2.381612464 -1.672131148,-19.74106207,-30.16192885,4,2,1.1,8.12,2.5,2.398011281,2.8,1.000023545,6.672131148,2.398011281 -1.681967213,-19.81016945,-30.36660615,4,2,1.1,8.12,2.5,2.414506847,2.8,1.000023545,6.681967213,2.414506847 -1.691803279,-19.87940447,-30.57207604,4,2,1.1,8.12,2.5,2.431099161,2.8,1.000023545,6.691803279,2.431099161 -1.701639344,-19.94876983,-30.7783482,4,2,1.1,8.12,2.5,2.447788223,2.8,1.000023545,6.701639344,2.447788223 -1.71147541,-20.01826816,-30.98543216,4,2,1.1,8.12,2.5,2.464574033,2.8,1.000023545,6.71147541,2.464574033 -1.721311475,-20.08790199,-31.19333728,4,2,1.1,8.12,2.5,2.481456592,2.8,1.000023545,6.721311475,2.481456592 -1.731147541,-20.15767381,-31.40207275,4,2,1.1,8.12,2.5,2.498435898,2.8,1.000023545,6.731147541,2.498435898 -1.740983607,-20.22758598,-31.61164762,4,2,1.1,8.12,2.5,2.515511953,2.8,1.000023545,6.740983607,2.515511953 -1.750819672,-20.29764085,-31.82207078,4,2,1.1,8.12,2.5,2.532684756,2.8,1.000023545,6.750819672,2.532684756 -1.760655738,-20.36784065,-32.03335098,4,2,1.1,8.12,2.5,2.549954307,2.8,1.000023545,6.760655738,2.549954307 -1.770491803,-20.43818757,-32.24549684,4,2,1.1,8.12,2.5,2.567320607,2.8,1.000023545,6.770491803,2.567320607 -1.780327869,-20.50868372,-32.45851681,4,2,1.1,8.12,2.5,2.584783654,2.8,1.000023545,6.780327869,2.584783654 -1.790163934,-20.57933115,-32.67241925,4,2,1.1,8.12,2.5,2.60234345,2.8,1.000023545,6.790163934,2.60234345 -1.8,-20.65013185,-32.88721237,4,2,1.1,8.12,2.5,2.619999994,2.8,1.000023545,6.8,2.619999994 -1.809836066,-20.72108777,-33.10290425,4,2,1.1,8.12,2.5,2.637753286,2.8,1.000023545,6.809836066,2.637753286 -1.819672131,-20.79220076,-33.31950286,4,2,1.1,8.12,2.5,2.655603326,2.8,1.000023545,6.819672131,2.655603326 -1.829508197,-20.86347264,-33.53701606,4,2,1.1,8.12,2.5,2.673550115,2.8,1.000023545,6.829508197,2.673550115 -1.839344262,-20.93490519,-33.75545158,4,2,1.1,8.12,2.5,2.691593651,2.8,1.000023545,6.839344262,2.691593651 -1.849180328,-21.0065001,-33.97481707,4,2,1.1,8.12,2.5,2.709733936,2.8,1.000023545,6.849180328,2.709733936 -1.859016393,-21.07825905,-34.19512003,4,2,1.1,8.12,2.5,2.727970969,2.8,1.000023545,6.859016393,2.727970969 -1.868852459,-21.15018363,-34.4163679,4,2,1.1,8.12,2.5,2.746304751,2.8,1.000023545,6.868852459,2.746304751 -1.878688525,-21.22227543,-34.63856801,4,2,1.1,8.12,2.5,2.76473528,2.8,1.000023545,6.878688525,2.76473528 -1.88852459,-21.29453595,-34.86172757,4,2,1.1,8.12,2.5,2.783262558,2.8,1.000023545,6.88852459,2.783262558 -1.898360656,-21.36696667,-35.08585374,4,2,1.1,8.12,2.5,2.801886583,2.8,1.000023545,6.898360656,2.801886583 -1.908196721,-21.43956902,-35.31095355,4,2,1.1,8.12,2.5,2.820607357,2.8,1.000023545,6.908196721,2.820607357 -1.918032787,-21.51234439,-35.53703397,4,2,1.1,8.12,2.5,2.83942488,2.8,1.000023545,6.918032787,2.83942488 -1.927868852,-21.58529413,-35.76410188,4,2,1.1,8.12,2.5,2.85833915,2.8,1.000023545,6.927868852,2.85833915 -1.937704918,-21.65841955,-35.99216407,4,2,1.1,8.12,2.5,2.877350169,2.8,1.000023545,6.937704918,2.877350169 -1.947540984,-21.73172192,-36.22122726,4,2,1.1,8.12,2.5,2.896457935,2.8,1.000023545,6.947540984,2.896457935 -1.957377049,-21.80520249,-36.45129811,4,2,1.1,8.12,2.5,2.91566245,2.8,1.000023545,6.957377049,2.91566245 -1.967213115,-21.87886245,-36.68238317,4,2,1.1,8.12,2.5,2.934963713,2.8,1.000023545,6.967213115,2.934963713 -1.97704918,-21.95270297,-36.91448895,4,2,1.1,8.12,2.5,2.954361725,2.8,1.000023545,6.97704918,2.954361725 -1.986885246,-22.02672519,-37.14762189,4,2,1.1,8.12,2.5,2.973856484,2.8,1.000023545,6.986885246,2.973856484 -1.996721311,-22.1009302,-37.38178835,4,2,1.1,8.12,2.5,2.993447992,2.8,1.000023545,6.996721311,2.993447992 -2.006557377,-22.17531909,-37.61699463,4,2,1.1,8.12,2.5,3.013136248,2.8,1.000023545,7.006557377,3.013136248 -2.016393443,-22.24989289,-37.85324699,4,2,1.1,8.12,2.5,3.032921252,2.8,1.000023545,7.016393443,3.032921252 -2.026229508,-22.32465261,-38.0905516,4,2,1.1,8.12,2.5,3.052803004,2.8,1.000023545,7.026229508,3.052803004 -2.036065574,-22.39959926,-38.32891459,4,2,1.1,8.12,2.5,3.072781504,2.8,1.000023545,7.036065574,3.072781504 -2.045901639,-22.47473377,-38.56834205,4,2,1.1,8.12,2.5,3.092856753,2.8,1.000023545,7.045901639,3.092856753 -2.055737705,-22.5500571,-38.80883998,4,2,1.1,8.12,2.5,3.11302875,2.8,1.000023545,7.055737705,3.11302875 -2.06557377,-22.62557013,-39.05041436,4,2,1.1,8.12,2.5,3.133297494,2.8,1.000023545,7.06557377,3.133297494 -2.075409836,-22.70127377,-39.29307111,4,2,1.1,8.12,2.5,3.153662988,2.8,1.000023545,7.075409836,3.153662988 -2.085245902,-22.77716887,-39.53681611,4,2,1.1,8.12,2.5,3.174125229,2.8,1.000023545,7.085245902,3.174125229 -2.095081967,-22.85325626,-39.78165519,4,2,1.1,8.12,2.5,3.194684218,2.8,1.000023545,7.095081967,3.194684218 -2.104918033,-22.92953677,-40.02759412,4,2,1.1,8.12,2.5,3.215339956,2.8,1.000023545,7.104918033,3.215339956 -2.114754098,-23.00601119,-40.27463867,4,2,1.1,8.12,2.5,3.236092442,2.8,1.000023545,7.114754098,3.236092442 -2.124590164,-23.08268029,-40.52279452,4,2,1.1,8.12,2.5,3.256941676,2.8,1.000023545,7.124590164,3.256941676 -2.13442623,-23.15954482,-40.77206735,4,2,1.1,8.12,2.5,3.277887658,2.8,1.000023545,7.13442623,3.277887658 -2.144262295,-23.23660553,-41.02246279,4,2,1.1,8.12,2.5,3.298930389,2.8,1.000023545,7.144262295,3.298930389 -2.154098361,-23.31386313,-41.27398643,4,2,1.1,8.12,2.5,3.320069868,2.8,1.000023545,7.154098361,3.320069868 -2.163934426,-23.39131833,-41.52664382,4,2,1.1,8.12,2.5,3.341306094,2.8,1.000023545,7.163934426,3.341306094 -2.173770492,-23.46897179,-41.78044051,4,2,1.1,8.12,2.5,3.362639069,2.8,1.000023545,7.173770492,3.362639069 -2.183606557,-23.54682419,-42.03538197,4,2,1.1,8.12,2.5,3.384068793,2.8,1.000023545,7.183606557,3.384068793 -2.193442623,-23.62487618,-42.29147368,4,2,1.1,8.12,2.5,3.405595264,2.8,1.000023545,7.193442623,3.405595264 -2.203278689,-23.70312839,-42.54872107,4,2,1.1,8.12,2.5,3.427218483,2.8,1.000023545,7.203278689,3.427218483 -2.213114754,-23.78158144,-42.80712956,4,2,1.1,8.12,2.5,3.448938451,2.8,1.000023545,7.213114754,3.448938451 -2.22295082,-23.86023593,-43.06670453,4,2,1.1,8.12,2.5,3.470755167,2.8,1.000023545,7.22295082,3.470755167 -2.232786885,-23.93909247,-43.32745134,4,2,1.1,8.12,2.5,3.492668631,2.8,1.000023545,7.232786885,3.492668631 -2.242622951,-24.01815162,-43.58937531,4,2,1.1,8.12,2.5,3.514678844,2.8,1.000023545,7.242622951,3.514678844 -2.252459016,-24.09741394,-43.85248178,4,2,1.1,8.12,2.5,3.536785804,2.8,1.000023545,7.252459016,3.536785804 -2.262295082,-24.17688,-44.11677602,4,2,1.1,8.12,2.5,3.558989513,2.8,1.000023545,7.262295082,3.558989513 -2.272131148,-24.25655033,-44.38226331,4,2,1.1,8.12,2.5,3.58128997,2.8,1.000023545,7.272131148,3.58128997 -2.281967213,-24.33642546,-44.6489489,4,2,1.1,8.12,2.5,3.603687175,2.8,1.000023545,7.281967213,3.603687175 -2.291803279,-24.41650591,-44.91683802,4,2,1.1,8.12,2.5,3.626181128,2.8,1.000023545,7.291803279,3.626181128 -2.301639344,-24.49679218,-45.18593589,4,2,1.1,8.12,2.5,3.648771829,2.8,1.000023545,7.301639344,3.648771829 -2.31147541,-24.57728478,-45.45624772,4,2,1.1,8.12,2.5,3.671459279,2.8,1.000023545,7.31147541,3.671459279 -2.321311475,-24.65798418,-45.72777868,4,2,1.1,8.12,2.5,3.694243477,2.8,1.000023545,7.321311475,3.694243477 -2.331147541,-24.73889086,-46.00053394,4,2,1.1,8.12,2.5,3.717124423,2.8,1.000023545,7.331147541,3.717124423 -2.340983607,-24.82000529,-46.27451867,4,2,1.1,8.12,2.5,3.740102117,2.8,1.000023545,7.340983607,3.740102117 -2.350819672,-24.90132794,-46.54973799,4,2,1.1,8.12,2.5,3.763176559,2.8,1.000023545,7.350819672,3.763176559 -2.360655738,-24.98285924,-46.82619706,4,2,1.1,8.12,2.5,3.78634775,2.8,1.000023545,7.360655738,3.78634775 -2.370491803,-25.06459964,-47.10390098,4,2,1.1,8.12,2.5,3.809615689,2.8,1.000023545,7.370491803,3.809615689 -2.380327869,-25.14654958,-47.38285488,4,2,1.1,8.12,2.5,3.832980375,2.8,1.000023545,7.380327869,3.832980375 -2.390163934,-25.22870948,-47.66306384,4,2,1.1,8.12,2.5,3.856441811,2.8,1.000023545,7.390163934,3.856441811 -2.4,-25.31107976,-47.94453296,4,2,1.1,8.12,2.5,3.879999994,2.8,1.000023545,7.4,3.879999994 -2.409836066,-25.39366083,-48.22726732,4,2,1.1,8.12,2.5,3.903654925,2.8,1.000023545,7.409836066,3.903654925 -2.419672131,-25.4764531,-48.51127201,4,2,1.1,8.12,2.5,3.927406605,2.8,1.000023545,7.419672131,3.927406605 -2.429508197,-25.55945695,-48.79655209,4,2,1.1,8.12,2.5,3.951255033,2.8,1.000023545,7.429508197,3.951255033 -2.439344262,-25.6426728,-49.08311263,4,2,1.1,8.12,2.5,3.975200209,2.8,1.000023545,7.439344262,3.975200209 -2.449180328,-25.72610101,-49.37095869,4,2,1.1,8.12,2.5,3.999242133,2.8,1.000023545,7.449180328,3.999242133 -2.459016393,-25.80974198,-49.66009531,4,2,1.1,8.12,2.5,4.023380805,2.8,1.000023545,7.459016393,4.023380805 -2.468852459,-25.89359608,-49.95052756,4,2,1.1,8.12,2.5,4.047616226,2.8,1.000023545,7.468852459,4.047616226 -2.478688525,-25.97766367,-50.24226047,4,2,1.1,8.12,2.5,4.071948395,2.8,1.000023545,7.478688525,4.071948395 -2.48852459,-26.06194512,-50.5352991,4,2,1.1,8.12,2.5,4.096377312,2.8,1.000023545,7.48852459,4.096377312 -2.498360656,-26.14644079,-50.82964848,4,2,1.1,8.12,2.5,4.120902977,2.8,1.000023545,7.498360656,4.120902977 -2.508196721,-26.23115103,-51.12531365,4,2,1.1,8.12,2.5,4.14552539,2.8,1.000023545,7.508196721,4.14552539 -2.518032787,-26.3160762,-51.42229966,4,2,1.1,8.12,2.5,4.170244552,2.8,1.000023545,7.518032787,4.170244552 -2.527868852,-26.40121664,-51.72061154,4,2,1.1,8.12,2.5,4.195060461,2.8,1.000023545,7.527868852,4.195060461 -2.537704918,-26.48657269,-52.02025433,4,2,1.1,8.12,2.5,4.219973119,2.8,1.000023545,7.537704918,4.219973119 -2.547540984,-26.57214468,-52.32123307,4,2,1.1,8.12,2.5,4.244982525,2.8,1.000023545,7.547540984,4.244982525 -2.557377049,-26.65793297,-52.62355279,4,2,1.1,8.12,2.5,4.27008868,2.8,1.000023545,7.557377049,4.27008868 -2.567213115,-26.74393786,-52.92721855,4,2,1.1,8.12,2.5,4.295291582,2.8,1.000023545,7.567213115,4.295291582 -2.57704918,-26.8301597,-53.23223539,4,2,1.1,8.12,2.5,4.320591233,2.8,1.000023545,7.57704918,4.320591233 -2.586885246,-26.9165988,-53.53860834,4,2,1.1,8.12,2.5,4.345987632,2.8,1.000023545,7.586885246,4.345987632 -2.596721311,-27.00325549,-53.84634246,4,2,1.1,8.12,2.5,4.371480779,2.8,1.000023545,7.596721311,4.371480779 -2.606557377,-27.09013008,-54.1554428,4,2,1.1,8.12,2.5,4.397070674,2.8,1.000023545,7.606557377,4.397070674 -2.616393443,-27.1772229,-54.46591441,4,2,1.1,8.12,2.5,4.422757317,2.8,1.000023545,7.616393443,4.422757317 -2.626229508,-27.26453424,-54.77776235,4,2,1.1,8.12,2.5,4.448540709,2.8,1.000023545,7.626229508,4.448540709 -2.636065574,-27.35206442,-55.09099169,4,2,1.1,8.12,2.5,4.474420848,2.8,1.000023545,7.636065574,4.474420848 -2.645901639,-27.43981375,-55.4056075,4,2,1.1,8.12,2.5,4.500397736,2.8,1.000023545,7.645901639,4.500397736 -2.655737705,-27.52778254,-55.72161484,4,2,1.1,8.12,2.5,4.526471372,2.8,1.000023545,7.655737705,4.526471372 -2.66557377,-27.61597108,-56.0390188,4,2,1.1,8.12,2.5,4.552641757,2.8,1.000023545,7.66557377,4.552641757 -2.675409836,-27.70437968,-56.35782446,4,2,1.1,8.12,2.5,4.578908889,2.8,1.000023545,7.675409836,4.578908889 -2.685245902,-27.79300864,-56.67803692,4,2,1.1,8.12,2.5,4.60527277,2.8,1.000023545,7.685245902,4.60527277 -2.695081967,-27.88185825,-56.99966127,4,2,1.1,8.12,2.5,4.631733399,2.8,1.000023545,7.695081967,4.631733399 -2.704918033,-27.97092881,-57.32270261,4,2,1.1,8.12,2.5,4.658290776,2.8,1.000023545,7.704918033,4.658290776 -2.714754098,-28.06022061,-57.64716606,4,2,1.1,8.12,2.5,4.684944901,2.8,1.000023545,7.714754098,4.684944901 -2.724590164,-28.14973395,-57.97305674,4,2,1.1,8.12,2.5,4.711695775,2.8,1.000023545,7.724590164,4.711695775 -2.73442623,-28.23946912,-58.30037978,4,2,1.1,8.12,2.5,4.738543396,2.8,1.000023545,7.73442623,4.738543396 -2.744262295,-28.3294264,-58.62914031,4,2,1.1,8.12,2.5,4.765487766,2.8,1.000023545,7.744262295,4.765487766 -2.754098361,-28.41960609,-58.95934348,4,2,1.1,8.12,2.5,4.792528884,2.8,1.000023545,7.754098361,4.792528884 -2.763934426,-28.51000847,-59.29099443,4,2,1.1,8.12,2.5,4.81966675,2.8,1.000023545,7.763934426,4.81966675 -2.773770492,-28.60063383,-59.62409834,4,2,1.1,8.12,2.5,4.846901364,2.8,1.000023545,7.773770492,4.846901364 -2.783606557,-28.69148246,-59.95866038,4,2,1.1,8.12,2.5,4.874232727,2.8,1.000023545,7.783606557,4.874232727 -2.793442623,-28.78255464,-60.29468571,4,2,1.1,8.12,2.5,4.901660838,2.8,1.000023545,7.793442623,4.901660838 -2.803278689,-28.87385065,-60.63217955,4,2,1.1,8.12,2.5,4.929185697,2.8,1.000023545,7.803278689,4.929185697 -2.813114754,-28.96537077,-60.97114709,4,2,1.1,8.12,2.5,4.956807304,2.8,1.000023545,7.813114754,4.956807304 -2.82295082,-29.0571153,-61.31159354,4,2,1.1,8.12,2.5,4.984525659,2.8,1.000023545,7.82295082,4.984525659 -2.832786885,-29.1490845,-61.65352412,4,2,1.1,8.12,2.5,5.012340762,2.8,1.000023545,7.832786885,5.012340762 -2.842622951,-29.24127867,-61.99694406,4,2,1.1,8.12,2.5,5.040252614,2.8,1.000023545,7.842622951,5.040252614 -2.852459016,-29.33369808,-62.34185863,4,2,1.1,8.12,2.5,5.068261214,2.8,1.000023545,7.852459016,5.068261214 -2.862295082,-29.42634301,-62.68827305,4,2,1.1,8.12,2.5,5.096366562,2.8,1.000023545,7.862295082,5.096366562 -2.872131148,-29.51921375,-63.03619262,4,2,1.1,8.12,2.5,5.124568658,2.8,1.000023545,7.872131148,5.124568658 -2.881967213,-29.61231057,-63.3856226,4,2,1.1,8.12,2.5,5.152867503,2.8,1.000023545,7.881967213,5.152867503 -2.891803279,-29.70563375,-63.73656829,4,2,1.1,8.12,2.5,5.181263095,2.8,1.000023545,7.891803279,5.181263095 -2.901639344,-29.79918357,-64.08903499,4,2,1.1,8.12,2.5,5.209755436,2.8,1.000023545,7.901639344,5.209755436 -2.91147541,-29.89296031,-64.44302801,4,2,1.1,8.12,2.5,5.238344525,2.8,1.000023545,7.91147541,5.238344525 -2.921311475,-29.98696426,-64.7985527,4,2,1.1,8.12,2.5,5.267030362,2.8,1.000023545,7.921311475,5.267030362 -2.931147541,-30.08119568,-65.15561438,4,2,1.1,8.12,2.5,5.295812947,2.8,1.000023545,7.931147541,5.295812947 -2.940983607,-30.17565486,-65.51421841,4,2,1.1,8.12,2.5,5.324692281,2.8,1.000023545,7.940983607,5.324692281 -2.950819672,-30.27034208,-65.87437017,4,2,1.1,8.12,2.5,5.353668363,2.8,1.000023545,7.950819672,5.353668363 -2.960655738,-30.36525762,-66.23607502,4,2,1.1,8.12,2.5,5.382741192,2.8,1.000023545,7.960655738,5.382741192 -2.970491803,-30.46040175,-66.59933837,4,2,1.1,8.12,2.5,5.41191077,2.8,1.000023545,7.970491803,5.41191077 -2.980327869,-30.55577476,-66.96416563,4,2,1.1,8.12,2.5,5.441177097,2.8,1.000023545,7.980327869,5.441177097 -2.990163934,-30.65137692,-67.33056222,4,2,1.1,8.12,2.5,5.470540171,2.8,1.000023545,7.990163934,5.470540171 -3,-30.74720853,-67.69853357,4,2,1.1,8.12,2.5,5.499999994,2.8,1.000023545,8,5.499999994 diff --git a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-sbml-l3v1.xml b/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-sbml-l3v1.xml deleted file mode 100644 index 8150fc04c6..0000000000 --- a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-sbml-l3v1.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - a - - - b - - - c - - - d - - - e - - - f - - - g - - - h - - - i - - - j - - - - - - - - a - - b - c - d - e - f - g - h - i - j - - 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - avogadro - 6.022 23 - - - - - - k1 - - - - - - - 1 - - - - - k4 - - - - - time - - - - - - - k1 - S1 - - - - - - - - - - - - - - - - - - - - kinetics - k1 - k2 - k3 - k4 - k5 - S1 - S1ref - S3 - S4 - S2 - - - - - - - - - - diff --git a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-settings.txt b/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-settings.txt deleted file mode 100644 index 0d5cd1843f..0000000000 --- a/test/models/StateSavingTests/SAVE_STATE_21/SAVE_STATE_21-settings.txt +++ /dev/null @@ -1,9 +0,0 @@ -start: 0 -duration: 3 -steps: 305 -variables: S1, S2, S3, S4, k1, k2, k3, k4, k5, kavo, comp, comp2 -absolute: 0.0001 -relative: 0.0001 -amount: S1, S2, S3, S4 -concentration: - diff --git a/test/serialization/state_saving.cpp b/test/serialization/state_saving.cpp index 742a9147f8..6cbe36153e 100644 --- a/test/serialization/state_saving.cpp +++ b/test/serialization/state_saving.cpp @@ -609,14 +609,14 @@ TEST_F(StateSavingTests, SaveThenLoadTwice) { } TEST_F(StateSavingTests, SaveThenLoadWithADifferentModel) { - ASSERT_TRUE(RunStateSavingTest(1121, [](RoadRunner *rri, std::string fname) { + ASSERT_TRUE(RunStateSavingTest(1122, [](RoadRunner *rri, std::string fname) { rri->saveState(fname); rri->loadState(fname); }, "l3v1")); } TEST_F(StateSavingTests, SaveThenLoadWithADiffentModelTwice) { - ASSERT_TRUE(RunStateSavingTest(1121, [](RoadRunner *rri, std::string fname) { + ASSERT_TRUE(RunStateSavingTest(1122, [](RoadRunner *rri, std::string fname) { rri->saveState(fname); rri->loadState(fname); rri->saveState(fname); @@ -634,7 +634,7 @@ TEST_F(StateSavingTests, SimulateThenSaveLoadThenReset) { } TEST_F(StateSavingTests, SimulateThenSaveLoadThenResetWithDifferentModel) { - ASSERT_TRUE(RunStateSavingTest(1121, [](RoadRunner *rri, std::string fname) { + ASSERT_TRUE(RunStateSavingTest(1122, [](RoadRunner *rri, std::string fname) { rri->simulate(); rri->saveState(fname); rri->loadState(fname); @@ -689,18 +689,6 @@ TEST_F(StateSavingTests, SAVE_STATE_20) { })); } -TEST_F(StateSavingTests, SAVE_STATE_21) { - ASSERT_TRUE(RunStateSavingTest([](RoadRunner *rri, std::string fname) { - rri->getSimulateOptions().duration = 1.50492; - rri->getSimulateOptions().steps /= 2; - rri->simulate(); - rri->saveState(fname); - rri->loadState(fname); - rri->getSimulateOptions().start = rri->getSimulateOptions().duration; - rri->getSimulateOptions().duration = 3.0 - rri->getSimulateOptions().duration; - }, "l3v1")); -} - TEST_F(StateSavingTests, RETAIN_ABSOLUTE_TOLERANCES_1) { path f = rrTestSbmlTestSuiteDir_ / "semantic" / "00001" / "00001-sbml-l2v4.xml"; path stateFname = fs::current_path() / "save-state-tests.rr"; From 20a62c80d6f076e5a2913412c4e8f83d969bd352 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 15:50:57 -0700 Subject: [PATCH 18/30] Update workflow versions. We're getting github complaints about node20 vs. node24. --- .github/workflows/main.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index afaf2c6f5d..486a974034 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,6 @@ name: BuildRoadRunner +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' #for seanmiddleditch/gha-setup-ninja and ilammy/msvc-dev-cmd. on: push: @@ -90,11 +92,11 @@ jobs: echo "python_v4_name=v4" >> $GITHUB_ENV - name: Checkout RoadRunner - uses: actions/checkout@v4 + uses: actions/checkout@main - name: Setup Python for non-Manylinux platforms if: matrix.platform.build_python == 'ON' && matrix.platform.os_type != 'manylinux' - uses: actions/setup-python@v5 + uses: actions/setup-python@main id: four_pythons with: python-version: | @@ -230,6 +232,7 @@ jobs: fi - name: Setup Ninja + if: matrix.platform.os_type == 'manylinux' uses: seanmiddleditch/gha-setup-ninja@master - name: Get Host Architecture @@ -283,7 +286,7 @@ jobs: - name: Cache ccache files on non-Windows if: matrix.platform.os_type != 'windows' - uses: actions/cache@v4 + uses: actions/cache@main with: path: ${RUNNER_WORKSPACE}/.ccache key: @@ -483,7 +486,7 @@ jobs: echo "artifacts_path=${RUNNER_WORKSPACE}/install-roadrunner" >> $GITHUB_ENV - name: Upload roadrunner binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@main with: name: ${{ env.artifacts_name }} path: ${{ env.artifacts_path }} @@ -622,7 +625,7 @@ jobs: - name: Upload RoadRunner Python wheel artifacts if: matrix.platform.build_type == 'Release' && matrix.platform.build_python == 'ON' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@main with: name: ${{ env.roadrunner_python_wheel_artifacts_name }} path: ${{ env.roadrunner_python_wheel_artifacts_file }} From 4c4daf3c63601d175d8eacc2f92a7181059b6290 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Tue, 24 Mar 2026 23:42:10 -0700 Subject: [PATCH 19/30] Fixes using a stoichiometry name as an ID on a roadrunner object. It turns out 'r.n' didn't actually work. This hopefully fixes it! Also, update the version number. --- CMakeLists.txt | 2 +- test/python/named_stoic_in_kinetic_law.xml | 45 ++++++++++++++++++++++ test/python/test_python_api.py | 15 ++++++++ wrappers/Python/roadrunner/roadrunner.i | 4 ++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 test/python/named_stoic_in_kinetic_law.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index ed0965b560..a9a9428434 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16) set(ROADRUNNER_VERSION_MAJOR 2) set(ROADRUNNER_VERSION_MINOR 9) -set(ROADRUNNER_VERSION_PATCH 1) +set(ROADRUNNER_VERSION_PATCH 2) set(ROADRUNNER_VERSION "${ROADRUNNER_VERSION_MAJOR}.${ROADRUNNER_VERSION_MINOR}.${ROADRUNNER_VERSION_PATCH}") diff --git a/test/python/named_stoic_in_kinetic_law.xml b/test/python/named_stoic_in_kinetic_law.xml new file mode 100644 index 0000000000..bf94871c6a --- /dev/null +++ b/test/python/named_stoic_in_kinetic_law.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + n + m + q + + A + + + + + + + diff --git a/test/python/test_python_api.py b/test/python/test_python_api.py index 57d4a888be..313cc3138c 100644 --- a/test/python/test_python_api.py +++ b/test/python/test_python_api.py @@ -397,6 +397,21 @@ def test_getFullStoichiometryMatrix(self): def test_getGlobalParameterByName(self): self.assertEqual(self.rr.getGlobalParameterByName("kf"), 0.1) + + def test_getSetNamedStoichiometries(self): + rr = RoadRunner("named_stoic_in_kinetic_law.xml") + self.assertEqual(rr.n, 1) + self.assertEqual(rr.m, 2) + self.assertEqual(rr.q, 3) + self.assertEqual(rr['q'], 3) + self.assertEqual(rr.getValue('q'), 3) + rr.n = 3 + rr.m = 5 + rr.q = 7 + self.assertEqual(rr['n'], 3) + self.assertEqual(rr['m'], 5) + self.assertEqual(rr['q'], 7) + self.assertEqual(rr.getValue('q'), 7) @unittest.skip("No way to use this method from Python - it " "requires an out parameter as input") diff --git a/wrappers/Python/roadrunner/roadrunner.i b/wrappers/Python/roadrunner/roadrunner.i index e443d6f744..d631eebe8d 100644 --- a/wrappers/Python/roadrunner/roadrunner.i +++ b/wrappers/Python/roadrunner/roadrunner.i @@ -1401,6 +1401,10 @@ namespace std { class ostream{}; } for s in model.getGlobalParameterIds() + model.getCompartmentIds() + model.getReactionIds() + model.getConservedMoietyIds(): makeProperty(s, s) + for s in model.getStoihiometryIds(): + if "(" not in s: + makeProperty(s, s) + def __getstate__(self): return self.saveStateS() From 01563f3a7279f362d6632a4795ba87a84597e2a7 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 07:41:03 -0700 Subject: [PATCH 20/30] Fix misspelling (sigh) --- wrappers/Python/roadrunner/roadrunner.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/Python/roadrunner/roadrunner.i b/wrappers/Python/roadrunner/roadrunner.i index d631eebe8d..503439951b 100644 --- a/wrappers/Python/roadrunner/roadrunner.i +++ b/wrappers/Python/roadrunner/roadrunner.i @@ -1401,7 +1401,7 @@ namespace std { class ostream{}; } for s in model.getGlobalParameterIds() + model.getCompartmentIds() + model.getReactionIds() + model.getConservedMoietyIds(): makeProperty(s, s) - for s in model.getStoihiometryIds(): + for s in model.getStoichiometryIds(): if "(" not in s: makeProperty(s, s) From a2830b8b6bd21656e12037cc9df392060a36e628 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 07:45:00 -0700 Subject: [PATCH 21/30] Some python tests were named 'Python'. --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 486a974034..f08819b987 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -454,21 +454,21 @@ jobs: shell: bash run: | cd ${RUNNER_WORKSPACE}/build-roadrunner - ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex python_tests + ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex ython - name: Run C RoadRunner tests (Ubuntu; skip plugins) if: matrix.platform.build_type == 'Release' && matrix.build_tests == 'ON' && matrix.platform.os_type == 'ubuntu' shell: bash run: | cd ${RUNNER_WORKSPACE}/build-roadrunner - ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex python_tests --exclude-regex Plugin + ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex ython --exclude-regex Plugin - name: Run Python Roadrunner tests (first Python version) if: matrix.platform.build_python == 'ON' && matrix.build_tests == 'ON' shell: bash run: | cd ${RUNNER_WORKSPACE}/build-roadrunner - ctest -C ${{ matrix.platform.build_type }} --output-on-failure --verbose --extra-verbose --progress --tests-regex python_tests + ctest -C ${{ matrix.platform.build_type }} --output-on-failure --verbose --extra-verbose --progress --tests-regex ython - name: Save roadrunner version shell: bash @@ -591,7 +591,7 @@ jobs: shell: bash run: | cd ${RUNNER_WORKSPACE}/build-roadrunner - ctest -C ${{ matrix.platform.build_type }} --output-on-failure --verbose --extra-verbose --progress --tests-regex python_tests + ctest -C ${{ matrix.platform.build_type }} --output-on-failure --verbose --extra-verbose --progress --tests-regex ython - name: Create fourth Python wheel artifacts and rename From e69702f7ec521e3302f3033408c5a73270713b14 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 08:18:58 -0700 Subject: [PATCH 22/30] Add functions to support named stoichiometries. In python, r['n'] was working, but not r.model['n']. Hopefully this fixes the latter. --- source/llvm/LLVMExecutableModel.cpp | 10 ++++++++++ source/llvm/LLVMExecutableModel.h | 1 + source/rrExecutableModel.h | 2 ++ test/mockups/MockExecutableModel.h | 1 + 4 files changed, 14 insertions(+) diff --git a/source/llvm/LLVMExecutableModel.cpp b/source/llvm/LLVMExecutableModel.cpp index 2024fd9648..bfc157dbb5 100644 --- a/source/llvm/LLVMExecutableModel.cpp +++ b/source/llvm/LLVMExecutableModel.cpp @@ -1478,6 +1478,10 @@ double LLVMExecutableModel::getValue(const std::string& id) case SelectionRecord::INITIAL_GLOBAL_PARAMETER: getGlobalParameterInitValues(1, &index, &result); break; + case SelectionRecord::STOICHIOMETRY: + case SelectionRecord::INITIAL_STOICHIOMETRY: + getStoichiometry(index, result); + break; case SelectionRecord::EVENT: { bool trigger = getEventTrigger(index); @@ -1909,6 +1913,12 @@ void LLVMExecutableModel::getInitialAssignmentIds(std::list& out) std::copy(rrIds.begin(), rrIds.end(), std::back_inserter(out)); } +void LLVMExecutableModel::getStoichiometryIds(std::list& out) +{ + std::vector rrIds = symbols->getStoichiometryIds(); + std::copy(rrIds.begin(), rrIds.end(), std::back_inserter(out)); +} + void LLVMExecutableModel::setEventListener(size_t index, rr::EventListenerPtr eventHandler) { diff --git a/source/llvm/LLVMExecutableModel.h b/source/llvm/LLVMExecutableModel.h index 9add0ce22f..bf39a4a8ac 100644 --- a/source/llvm/LLVMExecutableModel.h +++ b/source/llvm/LLVMExecutableModel.h @@ -586,6 +586,7 @@ class RR_DECLSPEC LLVMExecutableModel: public rr::ExecutableModel virtual void getAssignmentRuleIds(std::list& out); virtual void getRateRuleIds(std::list& out); virtual void getInitialAssignmentIds(std::list& out); + virtual void getStoichiometryIds(std::list& out); virtual void setEventListener(size_t index, rr::EventListenerPtr eventHandler); virtual rr::EventListenerPtr getEventListener(size_t index); diff --git a/source/rrExecutableModel.h b/source/rrExecutableModel.h index 1c9cd5984d..e98d280383 100644 --- a/source/rrExecutableModel.h +++ b/source/rrExecutableModel.h @@ -890,6 +890,8 @@ namespace rr { virtual void getInitialAssignmentIds(std::list&) = 0; + virtual void getStoichiometryIds(std::list&) = 0; + virtual void setEventListener(size_t index, EventListenerPtr eventHandler) = 0; virtual EventListenerPtr getEventListener(size_t index) = 0; diff --git a/test/mockups/MockExecutableModel.h b/test/mockups/MockExecutableModel.h index f62519ce49..4d2f5dc294 100644 --- a/test/mockups/MockExecutableModel.h +++ b/test/mockups/MockExecutableModel.h @@ -112,6 +112,7 @@ class MockExecutableModel : public rr::ExecutableModel{ MOCK_METHOD(void, getAssignmentRuleIds, (std::list&), (override)); MOCK_METHOD(void, getRateRuleIds, (std::list&), (override)); MOCK_METHOD(void, getInitialAssignmentIds, (std::list&), (override)); + MOCK_METHOD(void, getStoichiometryIds, (std::list&), (override)); MOCK_METHOD(void, setEventListener, (size_t index, EventListenerPtr eventHandler), (override)); MOCK_METHOD(EventListenerPtr, getEventListener, (size_t index), (override)); MOCK_METHOD(double, getFloatingSpeciesAmountRate, (size_t index,const double *reactionRates), (override)); From 41b940819d4399d17f69f50c478ee99e725159c8 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 09:44:32 -0700 Subject: [PATCH 23/30] Don't imitate getInitialAssignmentIds, which is completely different, imitate getConservedMoietyIds. --- source/llvm/LLVMExecutableModel.cpp | 6 ------ source/llvm/LLVMExecutableModel.h | 1 - source/rrExecutableModel.h | 2 -- test/mockups/MockExecutableModel.h | 1 - wrappers/Python/roadrunner/roadrunner.i | 4 ++++ wrappers/Python/roadrunner/rr_docstrings.txt | 17 +++++++++++++++-- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/source/llvm/LLVMExecutableModel.cpp b/source/llvm/LLVMExecutableModel.cpp index bfc157dbb5..dab5a3e1cb 100644 --- a/source/llvm/LLVMExecutableModel.cpp +++ b/source/llvm/LLVMExecutableModel.cpp @@ -1913,12 +1913,6 @@ void LLVMExecutableModel::getInitialAssignmentIds(std::list& out) std::copy(rrIds.begin(), rrIds.end(), std::back_inserter(out)); } -void LLVMExecutableModel::getStoichiometryIds(std::list& out) -{ - std::vector rrIds = symbols->getStoichiometryIds(); - std::copy(rrIds.begin(), rrIds.end(), std::back_inserter(out)); -} - void LLVMExecutableModel::setEventListener(size_t index, rr::EventListenerPtr eventHandler) { diff --git a/source/llvm/LLVMExecutableModel.h b/source/llvm/LLVMExecutableModel.h index bf39a4a8ac..9add0ce22f 100644 --- a/source/llvm/LLVMExecutableModel.h +++ b/source/llvm/LLVMExecutableModel.h @@ -586,7 +586,6 @@ class RR_DECLSPEC LLVMExecutableModel: public rr::ExecutableModel virtual void getAssignmentRuleIds(std::list& out); virtual void getRateRuleIds(std::list& out); virtual void getInitialAssignmentIds(std::list& out); - virtual void getStoichiometryIds(std::list& out); virtual void setEventListener(size_t index, rr::EventListenerPtr eventHandler); virtual rr::EventListenerPtr getEventListener(size_t index); diff --git a/source/rrExecutableModel.h b/source/rrExecutableModel.h index e98d280383..1c9cd5984d 100644 --- a/source/rrExecutableModel.h +++ b/source/rrExecutableModel.h @@ -890,8 +890,6 @@ namespace rr { virtual void getInitialAssignmentIds(std::list&) = 0; - virtual void getStoichiometryIds(std::list&) = 0; - virtual void setEventListener(size_t index, EventListenerPtr eventHandler) = 0; virtual EventListenerPtr getEventListener(size_t index) = 0; diff --git a/test/mockups/MockExecutableModel.h b/test/mockups/MockExecutableModel.h index 4d2f5dc294..f62519ce49 100644 --- a/test/mockups/MockExecutableModel.h +++ b/test/mockups/MockExecutableModel.h @@ -112,7 +112,6 @@ class MockExecutableModel : public rr::ExecutableModel{ MOCK_METHOD(void, getAssignmentRuleIds, (std::list&), (override)); MOCK_METHOD(void, getRateRuleIds, (std::list&), (override)); MOCK_METHOD(void, getInitialAssignmentIds, (std::list&), (override)); - MOCK_METHOD(void, getStoichiometryIds, (std::list&), (override)); MOCK_METHOD(void, setEventListener, (size_t index, EventListenerPtr eventHandler), (override)); MOCK_METHOD(EventListenerPtr, getEventListener, (size_t index), (override)); MOCK_METHOD(double, getFloatingSpeciesAmountRate, (size_t index,const double *reactionRates), (override)); diff --git a/wrappers/Python/roadrunner/roadrunner.i b/wrappers/Python/roadrunner/roadrunner.i index 503439951b..291fa52136 100644 --- a/wrappers/Python/roadrunner/roadrunner.i +++ b/wrappers/Python/roadrunner/roadrunner.i @@ -2502,6 +2502,10 @@ namespace std { class ostream{}; } return rr_ExecutableModel_getIds($self, rr::SelectionRecord::REACTION_RATE); } + PyObject *getStoichiometryIds() { + return rr_ExecutableModel_getIds($self, rr::SelectionRecord::STOICHIOMETRY); + } + PyObject *getFloatingSpeciesInitAmountIds() { return rr_ExecutableModel_getIds($self, rr::SelectionRecord::INITIAL_FLOATING_AMOUNT); } diff --git a/wrappers/Python/roadrunner/rr_docstrings.txt b/wrappers/Python/roadrunner/rr_docstrings.txt index e0a609d4ab..446488b027 100644 --- a/wrappers/Python/roadrunner/rr_docstrings.txt +++ b/wrappers/Python/roadrunner/rr_docstrings.txt @@ -508,9 +508,22 @@ ExecutableModel.getConservedMoietyIds([index]) Returns a vector of conserved moiety identifier symbols. -:param index: A array of compartment indices indicating which compartment ids to return. +:param index: A array of conserved moiety indices indicating which conserved moiety ids to return. :type index: None or numpy.ndarray -:returns: a list of compartment ids. +:returns: a list of conserved moiety ids. +"; + + + +%feature("docstring") rr::ExecutableModel::getStoichiometryIds " +ExecutableModel.getStoichiometryIds([index]) + +Returns a vector of stoichiometry identifier symbols. + + +:param index: A array of stoichiometry indices indicating which stoichiometry ids to return. +:type index: None or numpy.ndarray +:returns: a list of stoichiometry ids. "; From fa1b939fa0dc5ef426000a6b88d9fe52067cc6dc Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 13:57:34 -0700 Subject: [PATCH 24/30] Fixed the bug! The python bindings were getting 'n' from r.model['n'], which didn't work. This was because I was calling 'getStoichiometry' wrong in LLVMExecutableModel. Added tests for this on the C++ side. --- source/llvm/LLVMExecutableModel.cpp | 19 +++++--- source/rrRoadRunner.cpp | 37 ++++++++++----- test/cxx_api_tests/SelectionRecordTests.cpp | 2 +- test/sbml_features/named_stoich.cpp | 50 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 19 deletions(-) diff --git a/source/llvm/LLVMExecutableModel.cpp b/source/llvm/LLVMExecutableModel.cpp index dab5a3e1cb..85d37fb10f 100644 --- a/source/llvm/LLVMExecutableModel.cpp +++ b/source/llvm/LLVMExecutableModel.cpp @@ -1480,7 +1480,7 @@ double LLVMExecutableModel::getValue(const std::string& id) break; case SelectionRecord::STOICHIOMETRY: case SelectionRecord::INITIAL_STOICHIOMETRY: - getStoichiometry(index, result); + result = getStoichiometry(index); break; case SelectionRecord::EVENT: { @@ -1638,10 +1638,19 @@ const rr::SelectionRecord& LLVMExecutableModel::getSelection(const std::string& } break; case SelectionRecord::STOICHIOMETRY: - sel.index = getStoichiometryIndex(sel.p1, sel.p2); - if (sel.index == -1) { - throw LLVMException("Invalid id '" + str + "': could not find a species with the ID '" + sel.p1 + "', and/or a reaction with the ID '" + sel.p2 + "'"); - break; + if (sel.p2.empty()) { + sel.index = getStoichiometryIndex(sel.p1); + if (sel.index == -1) { + throw LLVMException("Invalid id '" + str + "': could not find a stoichiometry with the ID '" + sel.p1 + "'."); + break; + } + } + else { + sel.index = getStoichiometryIndex(sel.p1, sel.p2); + if (sel.index == -1) { + throw LLVMException("Invalid id '" + str + "': could not find a species with the ID '" + sel.p1 + "', and/or a reaction with the ID '" + sel.p2 + "'"); + break; + } } break; diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index a7591c89c2..5bb1bf28f7 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -1353,12 +1353,13 @@ namespace rr { break; case SelectionRecord::STOICHIOMETRY: case SelectionRecord::INITIAL_STOICHIOMETRY: { - // in case it is entered in the form of stoich(SpeciesId, ReactionId) - if (impl->model->getFloatingSpeciesIndex(record.p1) != -1 && impl->model->getReactionIndex(record.p2) != -1) - dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1, record.p2)); - // in case it is entered in the form of a stoichiometry parameter - else - dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1)); + dResult = impl->model->getStoichiometry(record.index); + //// in case it is entered in the form of stoich(SpeciesId, ReactionId) + // if (impl->model->getFloatingSpeciesIndex(record.p1) != -1 && impl->model->getReactionIndex(record.p2) != -1) + // dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1, record.p2)); + // // in case it is entered in the form of a stoichiometry parameter + // else + // dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1)); break; } case SelectionRecord::TIME: @@ -4914,14 +4915,26 @@ namespace rr { } break; case SelectionRecord::STOICHIOMETRY: - if (impl->model->getFloatingSpeciesIndex(sel.p1) >= 0) { - if (impl->model->getReactionIndex(sel.p2) >= 0) { - break; + if (sel.p2.empty()) { + if (impl->model->getStoichiometryIndex(sel.p1) < 0) { + throw Exception("The id '" + sel.p1 + "' is not the id of a stoichiometry (speciesReference)."); + } + break; + } + else { + if (impl->model->getFloatingSpeciesIndex(sel.p1) >= 0) { + if (impl->model->getReactionIndex(sel.p2) >= 0) { + sel.index = impl->model->getStoichiometryIndex(sel.p1, sel.p2); + if (sel.index < 0) { + throw Exception("The species id '" + sel.p1 + "' and reaction id '" + sel.p2 + "' does not lead to a valid stoichiometry."); + } + break; + } else { + throw Exception("second argument to stoich '" + sel.p2 + "' is not a reaction id."); + } } else { - throw Exception("second argument to stoich '" + sel.p2 + "' is not a reaction id."); + throw Exception("first argument to stoich '" + sel.p1 + "' is not a floating species id."); } - } else { - throw Exception("first argument to stoich '" + sel.p1 + "' is not a floating species id."); } case SelectionRecord::INITIAL_CONCENTRATION: if ((sel.index = impl->model->getFloatingSpeciesIndex(sel.p1)) >= 0) { diff --git a/test/cxx_api_tests/SelectionRecordTests.cpp b/test/cxx_api_tests/SelectionRecordTests.cpp index ec6bdebc60..d433c4b3cc 100644 --- a/test/cxx_api_tests/SelectionRecordTests.cpp +++ b/test/cxx_api_tests/SelectionRecordTests.cpp @@ -301,7 +301,7 @@ TEST_F(SelectionRecordTests, STOICHIOMETRY){ SelectionRecord record = rr.createSelection("stoich(S1, _J1)"); EXPECT_EQ(record.selectionType, SelectionRecord::STOICHIOMETRY); EXPECT_STREQ(record.to_string().c_str(), "stoich(S1, _J1)"); - EXPECT_EQ(record.index, -1); + EXPECT_EQ(record.index, 3); EXPECT_EQ(record.p1, "S1"); EXPECT_EQ(record.p2, "_J1"); delete testModel; diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index 59e3fd768d..6c862a0bbb 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -9,6 +9,7 @@ #include #include "RoadRunnerTest.h" #include "llvm/LLVMException.h" +#include "llvm/LLVMExecutableModel.h" using namespace testing; using namespace rr; @@ -85,7 +86,21 @@ TEST_F(SBMLFeatures, named_stoich_init_value) { } -TEST_F(SBMLFeatures, variable_named_stoich) { +TEST_F(SBMLFeatures, named_stoich_values) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + rr.setValue("n", 3); + rr.setValue("m", 5); + rr.setValue("q", 7); + //EXPECT_EQ(rr.getModel()->getValue("n"), 3.0); + EXPECT_EQ(rr.getValue("m"), 5.0); + EXPECT_EQ(rr.getValue("q"), 7.0); + EXPECT_EQ(rr.getValue("init(n)"), 3.0); + EXPECT_EQ(rr.getValue("init(m)"), 5.0); + EXPECT_EQ(rr.getValue("init(q)"), 7.0); +} + + +TEST_F(SBMLFeatures, no_variable_named_stoich) { rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); EXPECT_THROW(rr.addAssignmentRule("n", "5", true), rrllvm::LLVMException); @@ -94,3 +109,36 @@ TEST_F(SBMLFeatures, variable_named_stoich) { } +TEST_F(SBMLFeatures, named_stoich_selectors) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + SelectionRecord record = rr.createSelection("n"); + EXPECT_EQ(record.selectionType, SelectionRecord::STOICHIOMETRY); + EXPECT_STREQ(record.to_string().c_str(), "n"); + EXPECT_EQ(record.index, 1); + EXPECT_EQ(record.p1, "n"); + EXPECT_EQ(record.p2, ""); + + record = rr.createSelection("stoich(A, J0)"); + EXPECT_EQ(record.selectionType, SelectionRecord::STOICHIOMETRY); + EXPECT_STREQ(record.to_string().c_str(), "stoich(A, J0)"); + EXPECT_EQ(record.index, 0); + EXPECT_EQ(record.p1, "A"); + EXPECT_EQ(record.p2, "J0"); +} + + +TEST_F(SBMLFeatures, get_named_stoich_value_from_model) { + rr::RoadRunner rr((SBMLFeaturesDir / "named_stoic_in_kinetic_law.xml").string()); + ExecutableModel* em = rr.getModel(); + rrllvm::LLVMExecutableModel* llem = static_cast(em); + + EXPECT_EQ(llem->getValue("n"), 1); + llem->setValue("n", 3); + EXPECT_EQ(llem->getValue("n"), 3); + + EXPECT_EQ(llem->getValue("stoich(A, J0)"), 1); + llem->setValue("stoich(A, J0)", 5); + EXPECT_EQ(llem->getValue("stoich(A, J0)"), 5); +} + + From 8ebac8a0220029b9ae2a542bc4fea5343031e8a6 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 16:05:54 -0700 Subject: [PATCH 25/30] Cleanup. Remove commented code; add better tests. --- source/rrRoadRunner.cpp | 6 ------ test/python/test_python_api.py | 2 ++ test/sbml_features/named_stoich.cpp | 12 ++++++------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/source/rrRoadRunner.cpp b/source/rrRoadRunner.cpp index 5bb1bf28f7..46fd741b43 100644 --- a/source/rrRoadRunner.cpp +++ b/source/rrRoadRunner.cpp @@ -1354,12 +1354,6 @@ namespace rr { case SelectionRecord::STOICHIOMETRY: case SelectionRecord::INITIAL_STOICHIOMETRY: { dResult = impl->model->getStoichiometry(record.index); - //// in case it is entered in the form of stoich(SpeciesId, ReactionId) - // if (impl->model->getFloatingSpeciesIndex(record.p1) != -1 && impl->model->getReactionIndex(record.p2) != -1) - // dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1, record.p2)); - // // in case it is entered in the form of a stoichiometry parameter - // else - // dResult = impl->model->getStoichiometry(impl->model->getStoichiometryIndex(record.p1)); break; } case SelectionRecord::TIME: diff --git a/test/python/test_python_api.py b/test/python/test_python_api.py index 313cc3138c..5c8d39cb45 100644 --- a/test/python/test_python_api.py +++ b/test/python/test_python_api.py @@ -404,6 +404,7 @@ def test_getSetNamedStoichiometries(self): self.assertEqual(rr.m, 2) self.assertEqual(rr.q, 3) self.assertEqual(rr['q'], 3) + self.assertEqual(rr.model['q'], 3) self.assertEqual(rr.getValue('q'), 3) rr.n = 3 rr.m = 5 @@ -411,6 +412,7 @@ def test_getSetNamedStoichiometries(self): self.assertEqual(rr['n'], 3) self.assertEqual(rr['m'], 5) self.assertEqual(rr['q'], 7) + self.assertEqual(rr.model['q'], 7) self.assertEqual(rr.getValue('q'), 7) @unittest.skip("No way to use this method from Python - it " diff --git a/test/sbml_features/named_stoich.cpp b/test/sbml_features/named_stoich.cpp index 6c862a0bbb..9b31aa36bc 100644 --- a/test/sbml_features/named_stoich.cpp +++ b/test/sbml_features/named_stoich.cpp @@ -91,12 +91,12 @@ TEST_F(SBMLFeatures, named_stoich_values) { rr.setValue("n", 3); rr.setValue("m", 5); rr.setValue("q", 7); - //EXPECT_EQ(rr.getModel()->getValue("n"), 3.0); - EXPECT_EQ(rr.getValue("m"), 5.0); - EXPECT_EQ(rr.getValue("q"), 7.0); - EXPECT_EQ(rr.getValue("init(n)"), 3.0); - EXPECT_EQ(rr.getValue("init(m)"), 5.0); - EXPECT_EQ(rr.getValue("init(q)"), 7.0); + EXPECT_EQ(rr.getModel()->getValue("n"), 3.0); + EXPECT_EQ(rr.getModel()->getValue("m"), 5.0); + EXPECT_EQ(rr.getModel()->getValue("q"), 7.0); + EXPECT_EQ(rr.getModel()->getValue("init(n)"), 3.0); + EXPECT_EQ(rr.getModel()->getValue("init(m)"), 5.0); + EXPECT_EQ(rr.getModel()->getValue("init(q)"), 7.0); } From cfd019e46fc2f90127017aed9fe0ae09bbbb4990 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 16:34:12 -0700 Subject: [PATCH 26/30] Fix ubuntu regex. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f08819b987..0c0451c418 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -461,7 +461,7 @@ jobs: shell: bash run: | cd ${RUNNER_WORKSPACE}/build-roadrunner - ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex ython --exclude-regex Plugin + ctest --output-on-failure --verbose --extra-verbose --progress --exclude-regex "ython|Plugin" - name: Run Python Roadrunner tests (first Python version) if: matrix.platform.build_python == 'ON' && matrix.build_tests == 'ON' From 0c36b0026f28b06dfd9a0b5d9662294d93824ec9 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 16:36:20 -0700 Subject: [PATCH 27/30] Try re-enabling python tests on mac. --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 200c8d3cf3..b124565daf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -315,7 +315,7 @@ if (BUILD_PYTHON AND BUILD_TESTS) endmacro() #For some reason, the new build of llvm makes importing TestModelFactory fail on MacOS (and causes Python to crash entirely!) - if (NOT APPLE) +# if (true) add_pytest_to_ctest(python_tests_AutomaticMoietyConservationAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_automaticMoietyConservationAnalysis.py") add_pytest_to_ctest(python_tests_ForwardSensitivitySolverTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_forwardSensitivitySolver.py") add_pytest_to_ctest(python_tests_IntegratorTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_integrator.py") @@ -327,7 +327,7 @@ if (BUILD_PYTHON AND BUILD_TESTS) add_pytest_to_ctest(python_tests_StructuralAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_structuralAnalysis.py") add_pytest_to_ctest(python_tests_RoadRunnerMap "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_roadrunner_map.py") add_pytest_to_ctest(python_tests_AutoPluginTest "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_auto_plugin.py") - endif() +# endif() #add_pytest_to_ctest(python_tests_PickleTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_pickle.py") add_pytest_to_ctest(python_tests_RRTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_rrtests.py") add_pytest_to_ctest(python_tests_SettingTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_settings.py") From bf028efb8068dd63369f7b028bafb09177c941e5 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 17:28:32 -0700 Subject: [PATCH 28/30] Try checking against named CSUM elements. --- test/python/test_python_api.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/test/python/test_python_api.py b/test/python/test_python_api.py index 57d4a888be..a7b67bc4b7 100644 --- a/test/python/test_python_api.py +++ b/test/python/test_python_api.py @@ -290,10 +290,29 @@ def test_getConservedMoietyValues(self): rr2 = RoadRunner(tmf.TestModelFactory("Brown2004").str()) rr2.conservedMoietyAnalysis = True print(rr2.getConservedMoietyValues()) - expected = [1.2000e+05, 6.0000e+05, 1.2000e+05, 1.2000e+05, 6.0000e+05, 1.2000e+05, 1.0000e+04, 1.2000e+05, - 1.2000e+05, 4.4600e+05, 1.2000e+05, 1.0002e+07, 1.2000e+05, 1.2000e+05, 8.0000e+04] - actual = rr2.getConservedMoietyValues() - for i in range(len(expected)): + expected = {'_CSUM0': 120000.0, + '_CSUM1': 600000.0, + '_CSUM2': 120000.0, + '_CSUM3': 120000.0, + '_CSUM4': 600000.0, + '_CSUM5': 120000.0, + '_CSUM6': 10000.0, + '_CSUM7': 120000.0, + '_CSUM8': 120000.0, + '_CSUM9': 446000.0, + '_CSUM10': 120000.0, + '_CSUM11': 10002000.0, + '_CSUM12': 120000.0, + '_CSUM13': 120000.0, + '_CSUM14': 80000.0, + } + results = rr2.getConservedMoietyValues() + ids = rr2.getConservedMoietyIds() + actual = {} + for n in range(len(results)): + actual[ids[n]] = results[n] + print(actual) + for i in expected: self.assertAlmostEqual(expected[i], actual[i]) def test_getCurrentSBML(self): From 2a48f20ba433eb0ec3fca04f5445f24f119ff627 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 17:54:17 -0700 Subject: [PATCH 29/30] Sort the list for comparison, so it'll work on MacOS. --- test/python/test_python_api.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/test/python/test_python_api.py b/test/python/test_python_api.py index a7b67bc4b7..a008eea216 100644 --- a/test/python/test_python_api.py +++ b/test/python/test_python_api.py @@ -290,29 +290,10 @@ def test_getConservedMoietyValues(self): rr2 = RoadRunner(tmf.TestModelFactory("Brown2004").str()) rr2.conservedMoietyAnalysis = True print(rr2.getConservedMoietyValues()) - expected = {'_CSUM0': 120000.0, - '_CSUM1': 600000.0, - '_CSUM2': 120000.0, - '_CSUM3': 120000.0, - '_CSUM4': 600000.0, - '_CSUM5': 120000.0, - '_CSUM6': 10000.0, - '_CSUM7': 120000.0, - '_CSUM8': 120000.0, - '_CSUM9': 446000.0, - '_CSUM10': 120000.0, - '_CSUM11': 10002000.0, - '_CSUM12': 120000.0, - '_CSUM13': 120000.0, - '_CSUM14': 80000.0, - } - results = rr2.getConservedMoietyValues() - ids = rr2.getConservedMoietyIds() - actual = {} - for n in range(len(results)): - actual[ids[n]] = results[n] - print(actual) - for i in expected: + expected = sorted([1.2000e+05, 6.0000e+05, 1.2000e+05, 1.2000e+05, 6.0000e+05, 1.2000e+05, 1.0000e+04, 1.2000e+05, + 1.2000e+05, 4.4600e+05, 1.2000e+05, 1.0002e+07, 1.2000e+05, 1.2000e+05, 8.0000e+04]) + actual = sorted(rr2.getConservedMoietyValues()) + for i in range(len(expected)): self.assertAlmostEqual(expected[i], actual[i]) def test_getCurrentSBML(self): From acd23c7b39d8ff3cfc783ad9c9f44615932708a2 Mon Sep 17 00:00:00 2001 From: Lucian Smith Date: Wed, 25 Mar 2026 20:29:40 -0700 Subject: [PATCH 30/30] Clean up indenting from fix. --- test/CMakeLists.txt | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b124565daf..2f39f92826 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -314,20 +314,17 @@ if (BUILD_PYTHON AND BUILD_TESTS) set(PyTestTests ${PyTestTests} ${TestName}) endmacro() - #For some reason, the new build of llvm makes importing TestModelFactory fail on MacOS (and causes Python to crash entirely!) -# if (true) - add_pytest_to_ctest(python_tests_AutomaticMoietyConservationAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_automaticMoietyConservationAnalysis.py") - add_pytest_to_ctest(python_tests_ForwardSensitivitySolverTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_forwardSensitivitySolver.py") - add_pytest_to_ctest(python_tests_IntegratorTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_integrator.py") - add_pytest_to_ctest(python_tests_JacobianTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_jacobian.py") - add_pytest_to_ctest(python_tests_TestModelFactoryTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_testModelFactory.py") - add_pytest_to_ctest(python_tests_NamedArrayTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_namedArray.py") - add_pytest_to_ctest(python_tests_Python_api_tests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_python_api.py") - add_pytest_to_ctest(python_tests_SteadyStateSolverTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_steadyStateSolver.py") - add_pytest_to_ctest(python_tests_StructuralAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_structuralAnalysis.py") - add_pytest_to_ctest(python_tests_RoadRunnerMap "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_roadrunner_map.py") - add_pytest_to_ctest(python_tests_AutoPluginTest "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_auto_plugin.py") -# endif() + add_pytest_to_ctest(python_tests_AutomaticMoietyConservationAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_automaticMoietyConservationAnalysis.py") + add_pytest_to_ctest(python_tests_ForwardSensitivitySolverTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_forwardSensitivitySolver.py") + add_pytest_to_ctest(python_tests_IntegratorTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_integrator.py") + add_pytest_to_ctest(python_tests_JacobianTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_jacobian.py") + add_pytest_to_ctest(python_tests_TestModelFactoryTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_testModelFactory.py") + add_pytest_to_ctest(python_tests_NamedArrayTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_namedArray.py") + add_pytest_to_ctest(python_tests_Python_api_tests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_python_api.py") + add_pytest_to_ctest(python_tests_SteadyStateSolverTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_steadyStateSolver.py") + add_pytest_to_ctest(python_tests_StructuralAnalysisTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_structuralAnalysis.py") + add_pytest_to_ctest(python_tests_RoadRunnerMap "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_roadrunner_map.py") + add_pytest_to_ctest(python_tests_AutoPluginTest "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_auto_plugin.py") #add_pytest_to_ctest(python_tests_PickleTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_pickle.py") add_pytest_to_ctest(python_tests_RRTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_rrtests.py") add_pytest_to_ctest(python_tests_SettingTests "${RR_PYTHON_TESTING_BUILD_PREFIX}/test_settings.py")