diff --git a/petabtests/__init__.py b/petabtests/__init__.py index 863c429..ae75034 100644 --- a/petabtests/__init__.py +++ b/petabtests/__init__.py @@ -6,3 +6,4 @@ from .file import * # noqa: F403, F401 from .evaluate import * # noqa: F403, F401 from .core import * # noqa: F403, F401 +from .antimony import * # noqa: F403, F401 diff --git a/petabtests/antimony.py b/petabtests/antimony.py new file mode 100644 index 0000000..73c649e --- /dev/null +++ b/petabtests/antimony.py @@ -0,0 +1,40 @@ +"""Antimony -> SBML""" + +import antimony as ant +from pathlib import Path + +__all__ = ["antimony_to_sbml_str"] + + +def antimony_to_sbml_str(ant_model: str | Path) -> str: + """Convert Antimony string to SBML model. + + Arguments: + ant_str: + Antimony model as string (model, not filename), or Path to file. + + Returns: + SBML model as string. + """ + + # Unload everything / free memory + ant.clearPreviousLoads() + ant.freeAll() + + if isinstance(ant_model, Path): + status = ant.loadAntimonyFile(str(ant_model)) + else: + status = ant.loadAntimonyString(ant_model) + if status < 0: + raise RuntimeError( + f"Antimony model could not be loaded: {ant.getLastError()}" + ) + + if (main_module_name := ant.getMainModuleName()) is None: + raise AssertionError("There is no Antimony module.") + + sbml_str = ant.getSBMLString(main_module_name) + if not sbml_str: + raise ValueError("Antimony model could not be converted to SBML.") + + return sbml_str diff --git a/petabtests/cases/v1.0.0/sbml/0018/0018.py b/petabtests/cases/v1.0.0/sbml/0018/0018.py index 4311553..853a30f 100644 --- a/petabtests/cases/v1.0.0/sbml/0018/0018.py +++ b/petabtests/cases/v1.0.0/sbml/0018/0018.py @@ -3,7 +3,12 @@ import pandas as pd from petab.v1.C import * from pathlib import Path -from petabtests import PetabTestCase, analytical_a, analytical_b +from petabtests import ( + PetabTestCase, + analytical_a, + analytical_b, + antimony_to_sbml_str, +) DESCRIPTION = cleandoc(""" ## Objective @@ -28,30 +33,23 @@ # problem -------------------------------------------------------------------- -model = str(Path(__file__).parent / "model.xml") - - -def get_model(): - import simplesbml - - model = simplesbml.SbmlModel() - model.addParameter("a0", 1) - model.addParameter("b0", 1) - model.addParameter("k1", 0) - model.addParameter("k2", 0) - model.addCompartment(comp_id="compartment") - model.addSpecies("[A]", 0, comp="compartment") - model.addParameter("B", 0) - model.addInitialAssignment("A", "a0") - model.addInitialAssignment("B", "b0") - model.addRateRule("A", "k2 * B - k1 * A") - model.addRateRule("B", "- compartment * k2 * B + compartment * k1 * A") - return model - +sbml_file = Path(__file__).parent / "model.xml" -with open(model, "w") as f: - f.write(get_model().toSBML()) +ant_model = """ +model petab_test_0018 + a0 = 1 + b0 = 1 + k1 = 0 + k2 = 0 + compartment default_compartment + species A in default_compartment = a0 + B = b0 + A' = k2 * B - k1 * A + B' = - default_compartment * k2 * B + default_compartment * k1 * A +end +""" +sbml_file.write_text(antimony_to_sbml_str(ant_model)) condition_df = pd.DataFrame( data={ @@ -114,7 +112,7 @@ def get_model(): "reinitialized, one not (NaN in condition table). InitialAssignment " "to species overridden.", description=DESCRIPTION, - model=model, + model=sbml_file, condition_dfs=[condition_df], observable_dfs=[observable_df], measurement_dfs=[measurement_df], diff --git a/petabtests/cases/v1.0.0/sbml/0018/_model.xml b/petabtests/cases/v1.0.0/sbml/0018/_model.xml index 9c57090..c3df580 100644 --- a/petabtests/cases/v1.0.0/sbml/0018/_model.xml +++ b/petabtests/cases/v1.0.0/sbml/0018/_model.xml @@ -1,26 +1,19 @@ - - - - - - - - - + + + - - + - + - - - - - + + + + + @@ -60,14 +53,14 @@ - compartment + default_compartment k2 B - compartment + default_compartment k1 A diff --git a/petabtests/cases/v1.0.0/sbml/0018/model.xml b/petabtests/cases/v1.0.0/sbml/0018/model.xml index 9c57090..c3df580 100644 --- a/petabtests/cases/v1.0.0/sbml/0018/model.xml +++ b/petabtests/cases/v1.0.0/sbml/0018/model.xml @@ -1,26 +1,19 @@ - - - - - - - - - + + + - - + - + - - - - - + + + + + @@ -60,14 +53,14 @@ - compartment + default_compartment k2 B - compartment + default_compartment k1 A diff --git a/petabtests/cases/v2.0.0/sbml/0018/0018.py b/petabtests/cases/v2.0.0/sbml/0018/0018.py index 4311553..2b3586f 100644 --- a/petabtests/cases/v2.0.0/sbml/0018/0018.py +++ b/petabtests/cases/v2.0.0/sbml/0018/0018.py @@ -3,7 +3,12 @@ import pandas as pd from petab.v1.C import * from pathlib import Path -from petabtests import PetabTestCase, analytical_a, analytical_b +from petabtests import ( + PetabTestCase, + analytical_a, + analytical_b, + antimony_to_sbml_str, +) DESCRIPTION = cleandoc(""" ## Objective @@ -28,30 +33,22 @@ # problem -------------------------------------------------------------------- -model = str(Path(__file__).parent / "model.xml") - - -def get_model(): - import simplesbml - - model = simplesbml.SbmlModel() - model.addParameter("a0", 1) - model.addParameter("b0", 1) - model.addParameter("k1", 0) - model.addParameter("k2", 0) - model.addCompartment(comp_id="compartment") - model.addSpecies("[A]", 0, comp="compartment") - model.addParameter("B", 0) - model.addInitialAssignment("A", "a0") - model.addInitialAssignment("B", "b0") - model.addRateRule("A", "k2 * B - k1 * A") - model.addRateRule("B", "- compartment * k2 * B + compartment * k1 * A") - return model - - -with open(model, "w") as f: - f.write(get_model().toSBML()) - +sbml_file = Path(__file__).parent / "model.xml" + +ant_model = """ +model petab_test_0018 + a0 = 1 + b0 = 1 + k1 = 0 + k2 = 0 + compartment default_compartment + species A in default_compartment = a0 + B = b0 + A' = k2 * B - k1 * A + B' = - default_compartment * k2 * B + default_compartment * k1 * A +end +""" +sbml_file.write_text(antimony_to_sbml_str(ant_model)) condition_df = pd.DataFrame( data={ @@ -114,7 +111,7 @@ def get_model(): "reinitialized, one not (NaN in condition table). InitialAssignment " "to species overridden.", description=DESCRIPTION, - model=model, + model=sbml_file, condition_dfs=[condition_df], observable_dfs=[observable_df], measurement_dfs=[measurement_df], diff --git a/petabtests/cases/v2.0.0/sbml/0018/_model.xml b/petabtests/cases/v2.0.0/sbml/0018/_model.xml index 9c57090..c3df580 100644 --- a/petabtests/cases/v2.0.0/sbml/0018/_model.xml +++ b/petabtests/cases/v2.0.0/sbml/0018/_model.xml @@ -1,26 +1,19 @@ - - - - - - - - - + + + - - + - + - - - - - + + + + + @@ -60,14 +53,14 @@ - compartment + default_compartment k2 B - compartment + default_compartment k1 A diff --git a/petabtests/cases/v2.0.0/sbml/0018/model.xml b/petabtests/cases/v2.0.0/sbml/0018/model.xml index 9c57090..c3df580 100644 --- a/petabtests/cases/v2.0.0/sbml/0018/model.xml +++ b/petabtests/cases/v2.0.0/sbml/0018/model.xml @@ -1,26 +1,19 @@ - - - - - - - - - + + + - - + - + - - - - - + + + + + @@ -60,14 +53,14 @@ - compartment + default_compartment k2 B - compartment + default_compartment k1 A diff --git a/pyproject.toml b/pyproject.toml index 1657d03..4a4f54b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,11 +26,11 @@ classifiers = [ ] keywords = ["PEtab", "testsuite"] dependencies = [ + "antimony>=2.14.0", "numpy>=1.22", "pandas>=2.0", "petab>=0.4.0", "pysb>=1.16.0", - "simplesbml", ] [project.urls] diff --git a/test/test_antimony.py b/test/test_antimony.py new file mode 100644 index 0000000..d18641f --- /dev/null +++ b/test/test_antimony.py @@ -0,0 +1,28 @@ +from petabtests.antimony import antimony_to_sbml_str +import libsbml +import tempfile +from pathlib import Path + + +def test_antimony_file_to_sbml_str(): + ant_model = """ + model test + S1 -> S2; k1*S1 + k1 = 0.1 + S1 = 10 + end + """ + + with tempfile.TemporaryDirectory() as tmpdirname: + ant_file = Path(tmpdirname, "test.ant") + ant_file.write_text(ant_model) + sbml_str = antimony_to_sbml_str(ant_model) + + assert sbml_str == antimony_to_sbml_str(ant_model) + assert sbml_str.startswith("