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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion source/rrSBMLReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ bool SBMLReader::is_sbml(const std::string& str)
/**
* extract the <sbml...> tag from an sbml document.
* This regex should stop at the first match.
* RE_DOTALL lets '.' match newlines, since the tag's attributes may be
* spread across multiple lines.
*/
static const Poco::RegularExpression sbml_re("<\\s*sbml\\s*.*?>",
RegularExpression::RE_UNGREEDY);
RegularExpression::RE_UNGREEDY | RegularExpression::RE_DOTALL);

/**
* Check if the given sbml std::string uses the composite extension.
Expand Down Expand Up @@ -255,6 +257,25 @@ std::string SBMLReader::read(const std::string& str)

if (is_sbml(str))
{
if (has_comp(str))
{
return flatten_comp(str, "");
}
if (has_qual(str))
{
rrLog(Logger::LOG_ERROR) << "Qual model discovered, but not supported.";
throw std::domain_error("This SBML model contains information from the 'qual' package (for qualitative or 'logical' modeling). These models are not supported by roadrunner or tellurium. A good source of software that supports qual is the COLOMOTO consortium. See http://www.colomoto.org/software/ for a list of other software packages that might work for you.");
}
if (has_spatial(str))
{
rrLog(Logger::LOG_ERROR) << "Spatial model discovered, but not supported.";
throw std::domain_error("This SBML model contains information from the 'spatial' package. These models are not supported by roadrunner or tellurium. A few software packages that do support spatial (as of 2023) include VCell (https://vcell.org/), XitoSBML (https://github.com/spatialsimulator/XitoSBML) and Spatial SBML (https://github.com/fbergmann/spatial-sbml).");
}
if (has_multi(str))
{
rrLog(Logger::LOG_ERROR) << "Multi model discovered, but not supported.";
throw std::domain_error("This SBML model contains information from the 'multi' package for multistate, multicomponent and multicompartment models, or 'rule-based' models. These models are not supported by roadrunner or tellurium. The best software package that supports multi (as of 2023) is Simmune (https://bioinformatics.niaid.nih.gov/simmune/).");
}
return str;
}

Expand Down
10 changes: 3 additions & 7 deletions source/rrSBMLReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,9 @@ class RR_DECLSPEC SBMLReader

/**
* read an SBML document from a local file path, a remote URI, or
* directlly from a sbml std::string. If the document is a local file,
* and has the comp extension, it is automatically flattened before
* begin returned.
*
* If the std::string is already a SBML std::string, it is passed through.
* If the std::string is a local file or URI, the document is read
* from the source and the contents returned (and flattened if comp).
* directly from a sbml std::string. If the document has the comp
* extension, it is automatically flattened before being returned,
* regardless of whether it came from a file, a URI, or a std::string.
*/
static std::string read(const std::string& sbml_or_uri);

Expand Down
41 changes: 41 additions & 0 deletions test/models/SBMLFeatures/comp_example.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<sbml xmlns="http://www.sbml.org/sbml/level3/version1/core"
xmlns:comp="http://www.sbml.org/sbml/level3/version1/comp/version1"
level="3" version="1" comp:required="true">
<model id="main_model">
<comp:listOfSubmodels>
<comp:submodel comp:id="sub1" comp:modelRef="submodel_def"/>
</comp:listOfSubmodels>
</model>
<comp:listOfModelDefinitions>
<comp:modelDefinition id="submodel_def">
<listOfCompartments>
<compartment id="C" size="1" constant="true"/>
</listOfCompartments>
<listOfSpecies>
<species id="S1" compartment="C" initialConcentration="1" hasOnlySubstanceUnits="false" boundaryCondition="false" constant="false"/>
<species id="S2" compartment="C" initialConcentration="0" hasOnlySubstanceUnits="false" boundaryCondition="false" constant="false"/>
</listOfSpecies>
<listOfReactions>
<reaction id="J0" reversible="false" fast="false">
<listOfReactants>
<speciesReference species="S1" stoichiometry="1" constant="true"/>
</listOfReactants>
<listOfProducts>
<speciesReference species="S2" stoichiometry="1" constant="true"/>
</listOfProducts>
<kineticLaw>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<apply>
<times/>
<ci>C</ci>
<cn type="real">0.1</cn>
<ci>S1</ci>
</apply>
</math>
</kineticLaw>
</reaction>
</listOfReactions>
</comp:modelDefinition>
</comp:listOfModelDefinitions>
</sbml>
1 change: 1 addition & 0 deletions test/sbml_features/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_test_executable(
${SharedTestFiles}
named_stoich.cpp
variable_stoich.cpp
unsupported_packages.cpp
)

# make test_targets list global to all tests
Expand Down
68 changes: 68 additions & 0 deletions test/sbml_features/unsupported_packages.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#include "gtest/gtest.h"
#include "rrRoadRunner.h"
#include "rrSBMLReader.h"
#include "rrException.h"
#include "rrUtils.h"
#include "rrTestSuiteModelSimulation.h"
#include "sbml/SBMLTypes.h"
#include "sbml/SBMLReader.h"
#include "../test_util.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include "RoadRunnerTest.h"

using namespace testing;
Expand All @@ -18,6 +21,13 @@ class SBMLFeatures : public RoadRunnerTest {
public:
path SBMLFeaturesDir = rrTestModelsDir_ / "SBMLFeatures";
SBMLFeatures() = default;

static std::string readFileToString(const path& p) {
std::ifstream in(p);
std::stringstream ss;
ss << in.rdbuf();
return ss.str();
}
};

TEST_F(SBMLFeatures, SBML_qual)
Expand All @@ -38,6 +48,64 @@ TEST_F(SBMLFeatures, SBML_multi)
EXPECT_THROW(rri.load((SBMLFeaturesDir / "simmune_Ecad.xml").string()), std::domain_error);
}

// A comp model read from a file path is flattened before being returned.
TEST_F(SBMLFeatures, SBML_comp)
{
std::string flattened = SBMLReader::read((SBMLFeaturesDir / "comp_example.xml").string());
EXPECT_EQ(flattened.find("level3/version1/comp"), std::string::npos);
EXPECT_NE(flattened.find("sub1__S1"), std::string::npos);
}

// A comp model read directly from a std::string (not a file path) is also flattened.
TEST_F(SBMLFeatures, SBML_comp_fromString)
{
std::string sbml = readFileToString(SBMLFeaturesDir / "comp_example.xml");
std::string flattened = SBMLReader::read(sbml);
EXPECT_EQ(flattened.find("level3/version1/comp"), std::string::npos);
EXPECT_NE(flattened.find("sub1__S1"), std::string::npos);
}

// qual/spatial/multi models read directly from a std::string are rejected,
// just like when read from a file path (see SBML_qual/SBML_spatial/SBML_multi above).
TEST_F(SBMLFeatures, SBML_qual_fromString)
{
std::string sbml = readFileToString(SBMLFeaturesDir / "BIOMD0000000562_url.xml");
try {
SBMLReader::read(sbml);
FAIL() << "Expected std::domain_error to be thrown for a qual model read from a string";
} catch (const std::domain_error& e) {
std::string msg = e.what();
EXPECT_NE(msg.find("qual"), std::string::npos);
EXPECT_NE(msg.find("COLOMOTO"), std::string::npos);
}
}

TEST_F(SBMLFeatures, SBML_spatial_fromString)
{
std::string sbml = readFileToString(SBMLFeaturesDir / "organelles.xml");
try {
SBMLReader::read(sbml);
FAIL() << "Expected std::domain_error to be thrown for a spatial model read from a string";
} catch (const std::domain_error& e) {
std::string msg = e.what();
EXPECT_NE(msg.find("spatial"), std::string::npos);
EXPECT_NE(msg.find("VCell"), std::string::npos);
}
}

TEST_F(SBMLFeatures, SBML_multi_fromString)
{
std::string sbml = readFileToString(SBMLFeaturesDir / "simmune_Ecad.xml");
try {
SBMLReader::read(sbml);
FAIL() << "Expected std::domain_error to be thrown for a multi model read from a string";
} catch (const std::domain_error& e) {
std::string msg = e.what();
EXPECT_NE(msg.find("multi"), std::string::npos);
EXPECT_NE(msg.find("Simmune"), std::string::npos);
}
}

TEST_F(SBMLFeatures, SBML_fbc)
{
RoadRunner rri((SBMLFeaturesDir / "fbc_example.xml").string());
Expand Down
Loading