From 7e9a776646819f3cd8596a73014e9e0261c606b3 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 09:23:47 -0700 Subject: [PATCH 01/56] Add mathml -> sympy parser using sbmlmath package --- mira/metamodel/io.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mira/metamodel/io.py b/mira/metamodel/io.py index b268bd557..6b994aa55 100644 --- a/mira/metamodel/io.py +++ b/mira/metamodel/io.py @@ -2,6 +2,7 @@ import json import sympy +from sbmlmath import SBMLMathMLParser from .template_model import TemplateModel, SympyExprStr @@ -53,3 +54,12 @@ def expression_to_mathml(expression: sympy.Expr, *args, **kwargs) -> str: for old_symbol, new_symbol in mappings.items(): mml = mml.replace(new_symbol, old_symbol) return mml + + +def mathml_to_expression(xml_str: str) -> sympy.Expr: + """Convert a MathML string to a sympy expression.""" + template = """ + + {xml_str}""" + xml_str = template.format(xml_str=xml_str) + return SBMLMathMLParser().parse_str(xml_str) From 080506ceff574b696f677cde05798e040cad9944 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 10:24:02 -0700 Subject: [PATCH 02/56] Ensure sbmlmath is installed for tests --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a03b0a322..1a0473fdd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,8 @@ jobs: sudo apt-get install graphviz libgraphviz-dev pip install --upgrade pip setuptools wheel pip install "tox<4.0.0" + # Install sbmlmath from github, but ignore the python version since its 3.10+ only + pip install --ignore-requires-python git+https://github.com/dweindl/sbmlmath.git - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 From 3c39830ca7e7cb1f332fa378757637c824677af6 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 10:27:53 -0700 Subject: [PATCH 03/56] Add sbmlmath in setup.cfg --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index e117d479d..bd4a5d62f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,6 +34,7 @@ ode = numpy scipy sympy + sbmlmath @ git+https://github.com/dweindl/sbmlmath.git tests = pytest coverage From 6fb0b0267ac17b4732e00df27769337dba6f4c64 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 10:58:10 -0700 Subject: [PATCH 04/56] Add function to __all__ --- mira/metamodel/io.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mira/metamodel/io.py b/mira/metamodel/io.py index 6b994aa55..fb1d24d95 100644 --- a/mira/metamodel/io.py +++ b/mira/metamodel/io.py @@ -1,4 +1,5 @@ -__all__ = ["model_from_json_file", "model_to_json_file", "expression_to_mathml"] +__all__ = ["model_from_json_file", "model_to_json_file", + "expression_to_mathml", "mathml_to_expression"] import json import sympy From 0ca85059722ca0f38f5bd294a06d4a2a9c4039fd Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 10:58:27 -0700 Subject: [PATCH 05/56] Add closing in template --- mira/metamodel/io.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mira/metamodel/io.py b/mira/metamodel/io.py index fb1d24d95..c58ba60fe 100644 --- a/mira/metamodel/io.py +++ b/mira/metamodel/io.py @@ -61,6 +61,7 @@ def mathml_to_expression(xml_str: str) -> sympy.Expr: """Convert a MathML string to a sympy expression.""" template = """ - {xml_str}""" + {xml_str} + """ xml_str = template.format(xml_str=xml_str) return SBMLMathMLParser().parse_str(xml_str) From 730d3b520e2b555786be62dfe0ff84f250173e06 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 11:51:42 -0700 Subject: [PATCH 06/56] Write test for some expressions in scenario1a json --- tests/test_io.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_io.py diff --git a/tests/test_io.py b/tests/test_io.py new file mode 100644 index 000000000..385f40c7b --- /dev/null +++ b/tests/test_io.py @@ -0,0 +1,86 @@ +"""Test functions in the mira.metamodel.io module.""" +import sympy + +from mira.metamodel.io import mathml_to_expression, expression_to_mathml + + +def test_sympy_to_mathml(): + pass + + +def test_mathml_to_sympy(): + # 1 + xml_str = """ + + + + x + y + + + + x + z + + + """ + expected = sympy.parse_expr("x*y + x*z") + assert mathml_to_expression(xml_str) == expected + + # 2 + expression_str = ( + "I*S*kappa*(beta_c + (-beta_c + beta_s)/(1 + exp(-k*(-t + t_0))))/N" + ) + sympy_expr = sympy.parse_expr( + expression_str, + local_dict={"I": sympy.Symbol("I"), + "S": sympy.Symbol("S"), + "kappa": sympy.Symbol("kappa"), + "beta_c": sympy.Symbol("beta_c"), + "beta_s": sympy.Symbol("beta_s"), + "k": sympy.Symbol("k"), + "t_0": sympy.Symbol("t_0"), + "t": sympy.Symbol("t"), + "N": sympy.Symbol("N")} + ) + parsed_sympy = expression_to_mathml(sympy_expr) + expression_mathml = ( + "ISkappa" + "beta_c" + "beta_cbeta_s" + "1k" + "t_0t" + "N" + ) + assert parsed_sympy == expression_mathml + parsed_mathml = mathml_to_expression(expression_mathml) + assert parsed_mathml == sympy_expr + + # 3 + sympy_expr = sympy.parse_expr("E*delta", + local_dict={"E": sympy.Symbol("E")}) + + expression_mathml = "Edelta" + assert expression_to_mathml(sympy_expr) == expression_mathml + + # 4 + sympy_expr = sympy.parse_expr("I*gamma*(1 - alpha)", + local_dict={"I": sympy.Symbol("I"), + "gamma": sympy.Symbol("gamma"), + "alpha": sympy.Symbol("alpha")}) + expression_mathml = ( + "Igamma1" + "alpha" + ) + assert expression_to_mathml(sympy_expr) == expression_mathml + + # 5 + sympy_expr = sympy.parse_expr("I*alpha*rho", + local_dict={"I": sympy.Symbol("I"), + "alpha": sympy.Symbol("alpha"), + "rho": sympy.Symbol("rho")}) + expression_mathml = ( + "Ialpharho" + ) + assert expression_to_mathml(sympy_expr) == expression_mathml + From 8b23a33085a134cd68e73baf729edef6219a9d33 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:19:43 -0700 Subject: [PATCH 07/56] Handle mathml -> sympy in state_to_concept --- mira/sources/askenet/petrinet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 393234705..eb755f49f 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -243,11 +243,16 @@ def state_to_concept(state): units = state.get('units') units_obj = None if units: - # TODO: if sympy expression isn't given, parse MathML expr = units.get('expression') if expr: units_expr = safe_parse_expr(expr, local_dict=UNIT_SYMBOLS) units_obj = Unit(expression=units_expr) + else: + # Parse the units from MathML + expr_mathml = units.get('expression_mathml') + if expr_mathml: + units_expr = mathml_to_expression(expr_mathml) + units_obj = Unit(expression=units_expr) return Concept(name=name, display_name=display_name, identifiers=identifiers, From f3335f9064704d8cce685969b85f993e240d2518 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:20:55 -0700 Subject: [PATCH 08/56] Handle sympy -> mathml for initials --- mira/sources/askenet/petrinet.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index eb755f49f..533e4479e 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -113,16 +113,29 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # Next we process initial conditions initials = {} for initial_state in ode_semantics.get("initials", []): - initial_expression = initial_state.get("expression") - if initial_expression: - initial_sympy = safe_parse_expr(initial_expression, - local_dict=symbols) - initial_sympy = initial_sympy.subs(param_values) + # If there is a sympy expression, use it + initial_val = None + if initial_state.get("expression"): + initial_expr = sympy.parse_expr(initial_state["expression"], + local_dict=symbols) + initial_expr = initial_expr.subs(param_values) try: - initial_val = float(initial_sympy) + initial_val = float(initial_expr) except TypeError: continue + # If there is no sympy expression, try mathml + elif initial_state.get("expression_mathml"): + initial_expr = mathml_to_expression( + initial_state["expression_mathml"] + ) + initial_expr = initial_expr.subs(param_values) + try: + initial_val = float(initial_expr) + except TypeError: + continue + + if initial_val is not None: initial = Initial( concept=concepts[initial_state['target']].copy(deep=True), value=initial_val From 4eec718a8b6ff671ff1a1d587363b18e9b55f7ef Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:27:28 -0700 Subject: [PATCH 09/56] More clear logic for state_to_concept mathml handling --- mira/sources/askenet/petrinet.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 533e4479e..0acad53ab 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -117,7 +117,7 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: initial_val = None if initial_state.get("expression"): initial_expr = sympy.parse_expr(initial_state["expression"], - local_dict=symbols) + local_dict=symbols) initial_expr = initial_expr.subs(param_values) try: initial_val = float(initial_expr) @@ -260,12 +260,10 @@ def state_to_concept(state): if expr: units_expr = safe_parse_expr(expr, local_dict=UNIT_SYMBOLS) units_obj = Unit(expression=units_expr) - else: - # Parse the units from MathML - expr_mathml = units.get('expression_mathml') - if expr_mathml: - units_expr = mathml_to_expression(expr_mathml) - units_obj = Unit(expression=units_expr) + elif units.get("expression_mathml"): + units_expr = mathml_to_expression(units["expression_mathml"]) + units_obj = Unit(expression=units_expr) + return Concept(name=name, display_name=display_name, identifiers=identifiers, From 9d4e59922ddcd59f8e72d9d3da9174450882bf6a Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:35:00 -0700 Subject: [PATCH 10/56] Clearer logic for mathml handling for initals --- mira/sources/askenet/petrinet.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 0acad53ab..1d27286ec 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -114,33 +114,29 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: initials = {} for initial_state in ode_semantics.get("initials", []): # If there is a sympy expression, use it - initial_val = None if initial_state.get("expression"): initial_expr = sympy.parse_expr(initial_state["expression"], local_dict=symbols) - initial_expr = initial_expr.subs(param_values) - try: - initial_val = float(initial_expr) - except TypeError: - continue - # If there is no sympy expression, try mathml elif initial_state.get("expression_mathml"): initial_expr = mathml_to_expression( initial_state["expression_mathml"] ) - initial_expr = initial_expr.subs(param_values) - try: - initial_val = float(initial_expr) - except TypeError: - continue + else: + # If there is no expression, skip this initial condition + continue - if initial_val is not None: + # If we have an expression, try to evaluate it + initial_expr = initial_expr.subs(param_values) + try: + initial_val = float(initial_expr) initial = Initial( concept=concepts[initial_state['target']].copy(deep=True), value=initial_val ) initials[initial.concept.name] = initial + except TypeError: + continue # We get observables from the semantics observables = {} From b576070e2aa41912ce72e4bc057bc0d6997943f0 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:36:35 -0700 Subject: [PATCH 11/56] Add mathml support for observables --- mira/sources/askenet/petrinet.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 1d27286ec..25cdad51e 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -141,13 +141,19 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # We get observables from the semantics observables = {} for observable in ode_semantics.get("observables", []): - observable_expression = observable.get("expression") - if observable_expression: - observable_sympy = safe_parse_expr(observable_expression, + if observable.get("expression"): + observable_expr = sympy.parse_expr(observable["expression"], local_dict=symbols) - observable = Observable(name=observable['id'], - expression=observable_sympy) - observables[observable.name] = observable + elif observable.get("expression_mathml"): + observable_expr = mathml_to_expression( + observable["expression_mathml"] + ) + else: + continue + + observable = Observable(name=observable['id'], + expression=observable_expr) + observables[observable.name] = observable # We get the time variable from the semantics time = ode_semantics.get("time") From fb0c5656cc51ef238b1546b04aa66b6541704319 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:43:01 -0700 Subject: [PATCH 12/56] Handle mathml for time units --- mira/sources/askenet/petrinet.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 25cdad51e..615df0d09 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -161,9 +161,16 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: time_units = time.get('units') time_units_obj = None if time_units: - time_expr = time_units.get('expression') - time_units_expr = safe_parse_expr(time_expr, - local_dict=UNIT_SYMBOLS) + if time_units.get('expression'): + time_units_expr = sympy.parse_expr( + time_units['expression'], local_dict=UNIT_SYMBOLS + ) + elif time_units.get('expression_mathml'): + time_units_expr = mathml_to_expression( + time_units['expression_mathml'] + ) + else: + time_units_expr = None time_units_obj = Unit(expression=time_units_expr) model_time = Time(name=time['id'], units=time_units_obj) else: From 0ceedc2e8067cf3b685b54ae34dd34000ba4b69a Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 14:48:01 -0700 Subject: [PATCH 13/56] Handle mathml for rates --- mira/sources/askenet/petrinet.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 615df0d09..8f81cdc4c 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -296,10 +296,15 @@ def parameter_to_mira(parameter): def transition_to_templates(transition_rate, input_concepts, output_concepts, controller_concepts, symbols, transition_id): """Return a list of templates from a transition""" - rate_law_expression = transition_rate.get('expression') - rate_law = safe_parse_expr(rate_law_expression, - local_dict=symbols) \ - if rate_law_expression else None + if transition_rate.get("expression"): + rate_law = sympy.parse_expr( + clean_formula(transition_rate["expression"]), local_dict=symbols + ) + elif transition_rate.get("expression_mathml"): + rate_law = mathml_to_expression(transition_rate["expression_mathml"]) + else: + rate_law = None + if not controller_concepts: if not input_concepts: for output_concept in output_concepts: From 4702fe008dcc1552d12079b5b939b8ac5a435c89 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 15:01:35 -0700 Subject: [PATCH 14/56] DRY implementation of sympy and mathml parsing --- mira/sources/askenet/petrinet.py | 69 ++++++++++---------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 8f81cdc4c..5f8c4fde2 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -11,6 +11,7 @@ __all__ = ["model_from_url", "model_from_json_file", "template_model_from_askenet_json"] import json +from typing import Optional from copy import deepcopy import sympy @@ -113,17 +114,8 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # Next we process initial conditions initials = {} for initial_state in ode_semantics.get("initials", []): - # If there is a sympy expression, use it - if initial_state.get("expression"): - initial_expr = sympy.parse_expr(initial_state["expression"], - local_dict=symbols) - # If there is no sympy expression, try mathml - elif initial_state.get("expression_mathml"): - initial_expr = mathml_to_expression( - initial_state["expression_mathml"] - ) - else: - # If there is no expression, skip this initial condition + initial_expr = _get_sympy(initial_state, symbols) + if initial_expr is None: continue # If we have an expression, try to evaluate it @@ -141,14 +133,8 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # We get observables from the semantics observables = {} for observable in ode_semantics.get("observables", []): - if observable.get("expression"): - observable_expr = sympy.parse_expr(observable["expression"], - local_dict=symbols) - elif observable.get("expression_mathml"): - observable_expr = mathml_to_expression( - observable["expression_mathml"] - ) - else: + observable_expr = _get_sympy(observable, symbols) + if observable_expr is None: continue observable = Observable(name=observable['id'], @@ -161,16 +147,7 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: time_units = time.get('units') time_units_obj = None if time_units: - if time_units.get('expression'): - time_units_expr = sympy.parse_expr( - time_units['expression'], local_dict=UNIT_SYMBOLS - ) - elif time_units.get('expression_mathml'): - time_units_expr = mathml_to_expression( - time_units['expression_mathml'] - ) - else: - time_units_expr = None + time_units_expr = _get_sympy(time_units, UNIT_SYMBOLS) time_units_obj = Unit(expression=time_units_expr) model_time = Time(name=time['id'], units=time_units_obj) else: @@ -263,16 +240,7 @@ def state_to_concept(state): identifiers = grounding.get('identifiers', {}) context = grounding.get('modifiers', {}) units = state.get('units') - units_obj = None - if units: - expr = units.get('expression') - if expr: - units_expr = safe_parse_expr(expr, local_dict=UNIT_SYMBOLS) - units_obj = Unit(expression=units_expr) - elif units.get("expression_mathml"): - units_expr = mathml_to_expression(units["expression_mathml"]) - units_obj = Unit(expression=units_expr) - + units_obj = _get_sympy(units, UNIT_SYMBOLS) return Concept(name=name, display_name=display_name, identifiers=identifiers, @@ -296,14 +264,7 @@ def parameter_to_mira(parameter): def transition_to_templates(transition_rate, input_concepts, output_concepts, controller_concepts, symbols, transition_id): """Return a list of templates from a transition""" - if transition_rate.get("expression"): - rate_law = sympy.parse_expr( - clean_formula(transition_rate["expression"]), local_dict=symbols - ) - elif transition_rate.get("expression_mathml"): - rate_law = mathml_to_expression(transition_rate["expression_mathml"]) - else: - rate_law = None + rate_law = _get_sympy(transition_rate, local_dict=symbols) if not controller_concepts: if not input_concepts: @@ -361,3 +322,17 @@ def transition_to_templates(transition_rate, input_concepts, output_concepts, subject=input_concepts[0], outcome=output_concepts[0], rate_law=rate_law) + + +def _get_sympy(expr_data, local_dict=None) -> Optional[sympy.Expr]: + # Sympy + if expr_data.get("expression"): + expr = sympy.parse_expr(clean_formula(expr_data["expression"]), + local_dict=local_dict) + # MathML + elif expr_data.get("expression_mathml"): + expr = mathml_to_expression(expr_data["expression_mathml"]) + # No expression found + else: + expr = None + return expr From aa13ae79aad26f87779a4a2954939f98f3ecab0b Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 15:03:59 -0700 Subject: [PATCH 15/56] Clean up mathml->sympy tests --- tests/test_io.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_io.py b/tests/test_io.py index 385f40c7b..c240441f5 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -43,7 +43,6 @@ def test_mathml_to_sympy(): "t": sympy.Symbol("t"), "N": sympy.Symbol("N")} ) - parsed_sympy = expression_to_mathml(sympy_expr) expression_mathml = ( "ISkappa" "beta_c" @@ -52,7 +51,6 @@ def test_mathml_to_sympy(): "t_0t" "N" ) - assert parsed_sympy == expression_mathml parsed_mathml = mathml_to_expression(expression_mathml) assert parsed_mathml == sympy_expr @@ -83,4 +81,3 @@ def test_mathml_to_sympy(): "Ialpharho" ) assert expression_to_mathml(sympy_expr) == expression_mathml - From 1b7c5499bc41ee351a8a846f8156cd1dd319e0fc Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 15:05:11 -0700 Subject: [PATCH 16/56] Add test for sympy -> mathml --- tests/test_io.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_io.py b/tests/test_io.py index c240441f5..8013ae73c 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -5,7 +5,23 @@ def test_sympy_to_mathml(): - pass + expression_str = "x*y + x*z" + sympy_expr = sympy.parse_expr(expression_str) + expected = """ + + + + x + y + + + + x + z + + + """.replace("\n", "").replace(" ", "") + assert expression_to_mathml(sympy_expr) == expected def test_mathml_to_sympy(): From 03c2fa7d090151fb0845cb537893648e77614bb1 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 17:11:19 -0700 Subject: [PATCH 17/56] Fix bug --- mira/sources/askenet/petrinet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 5f8c4fde2..afb8f99a0 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -240,7 +240,8 @@ def state_to_concept(state): identifiers = grounding.get('identifiers', {}) context = grounding.get('modifiers', {}) units = state.get('units') - units_obj = _get_sympy(units, UNIT_SYMBOLS) + units_expr = _get_sympy(units, UNIT_SYMBOLS) + units_obj = Unit(expression=units_expr) if units_expr else None return Concept(name=name, display_name=display_name, identifiers=identifiers, From bf28d4806a266b93d5303d80c4e3155aff57b4d9 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 17:27:03 -0700 Subject: [PATCH 18/56] Test askenet petrinet sir example --- tests/test_io.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_io.py b/tests/test_io.py index 8013ae73c..14c210583 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -1,8 +1,12 @@ """Test functions in the mira.metamodel.io module.""" +import requests import sympy +from mira.metamodel import UNIT_SYMBOLS from mira.metamodel.io import mathml_to_expression, expression_to_mathml +from mira.sources.askenet.petrinet import state_to_concept + def test_sympy_to_mathml(): expression_str = "x*y + x*z" @@ -97,3 +101,49 @@ def test_mathml_to_sympy(): "Ialpharho" ) assert expression_to_mathml(sympy_expr) == expression_mathml + + +def _expression_yielder(model_json, is_unit=False): + # Recursively yield all (sympy, mathml) string pairs in the model json + if isinstance(model_json, list): + for item in model_json: + yield from _expression_yielder(item) + elif isinstance(model_json, dict): + if "expression" in model_json and "expression_mathml" in model_json: + yield (model_json["expression"], + model_json["expression_mathml"], + is_unit) + + # Otherwise, check if 'units' key is in the dict, indicating that + # the expression is a unit + is_units = "units" in model_json + for value in model_json.values(): + # Otherwise, recursively yield from the value + yield from _expression_yielder(value, is_units) + # Otherwise, do nothing since we only care about the expression and + # expression_mathml fields in a dict + + +def test_from_askenet_petri(): + source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ + "-Representations/main/petrinet/examples/sir.json" + resp = requests.get(source_url) + assert resp.status_code == 200 + model_json = resp.json() + + # Create symbols dict like in petrinet + model = model_json['model'] + concepts = {} + for state in model.get('states', []): + concepts[state['id']] = state_to_concept(state) + symbols = {state_id: sympy.Symbol(state_id) for state_id in concepts} + ode_semantics = model_json.get("semantics", {}).get("ode", {}) + for parameter in ode_semantics.get('parameters', []): + symbols[parameter['id']] = sympy.Symbol(parameter['id']) + + for expression_str, expression_mathml, is_unit in _expression_yielder( + model_json): + # if checking units, use the UNIT_SYMBOLS dict + local_dict = UNIT_SYMBOLS if "units" in expression_str else symbols + assert mathml_to_expression(expression_mathml) == \ + sympy.parse_expr(expression_str, local_dict=local_dict) From 1370e081a8fdc81f39bd5365101a70b612aafdcc Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 17:40:32 -0700 Subject: [PATCH 19/56] Fix(?) sbmlmath install --- .github/workflows/tests.yml | 2 -- setup.cfg | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a0473fdd..a03b0a322 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,8 +20,6 @@ jobs: sudo apt-get install graphviz libgraphviz-dev pip install --upgrade pip setuptools wheel pip install "tox<4.0.0" - # Install sbmlmath from github, but ignore the python version since its 3.10+ only - pip install --ignore-requires-python git+https://github.com/dweindl/sbmlmath.git - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 diff --git a/setup.cfg b/setup.cfg index bd4a5d62f..f837db268 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,6 +14,7 @@ license_files = install_requires = pydantic>=1.10,<2.0.0 sympy + sbmlmath @ git+https://github.com/dweindl/sbmlmath.git typing_extensions networkx tqdm @@ -34,7 +35,6 @@ ode = numpy scipy sympy - sbmlmath @ git+https://github.com/dweindl/sbmlmath.git tests = pytest coverage From 9b078c02f104d0db1b8326d55dd3e4aa195da707 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 18:09:26 -0700 Subject: [PATCH 20/56] Check for None in _get_sympy --- mira/sources/askenet/petrinet.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index afb8f99a0..9da894c3d 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -326,6 +326,9 @@ def transition_to_templates(transition_rate, input_concepts, output_concepts, def _get_sympy(expr_data, local_dict=None) -> Optional[sympy.Expr]: + if expr_data is None: + return None + # Sympy if expr_data.get("expression"): expr = sympy.parse_expr(clean_formula(expr_data["expression"]), From d5a60cab25b6aca140f18acecf86d842ad0c1f5a Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 5 Jul 2023 18:09:34 -0700 Subject: [PATCH 21/56] Simplify logic --- mira/sources/askenet/petrinet.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 9da894c3d..7ddb64f6e 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -145,10 +145,9 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: time = ode_semantics.get("time") if time: time_units = time.get('units') - time_units_obj = None - if time_units: - time_units_expr = _get_sympy(time_units, UNIT_SYMBOLS) - time_units_obj = Unit(expression=time_units_expr) + time_units_expr = _get_sympy(time_units, UNIT_SYMBOLS) + time_units_obj = Unit(expression=time_units_expr) \ + if time_units_expr else None model_time = Time(name=time['id'], units=time_units_obj) else: model_time = None From 373f35c2631d32a2fc5b9775b1f976f7f790b43f Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 6 Jul 2023 12:33:56 -0700 Subject: [PATCH 22/56] Move sorted_json_str to tests.__init__.py --- tests/__init__.py | 30 ++++++++++++++++++++++++++++++ tests/test_model_api.py | 1 - 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index ae3a24c02..5af58956d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,31 @@ """Tests for MIRA.""" +import json + +from mira.metamodel import SympyExprStr + + +def sorted_json_str(json_dict, ignore_key=None) -> str: + if isinstance(json_dict, str): + return json_dict + elif isinstance(json_dict, (int, float, SympyExprStr)): + return str(json_dict) + elif isinstance(json_dict, (tuple, list, set)): + return "[%s]" % ( + ",".join(sorted(sorted_json_str(s, ignore_key) for s in json_dict)) + ) + elif isinstance(json_dict, dict): + if ignore_key is not None: + dict_gen = ( + str(k) + sorted_json_str(v, ignore_key) + for k, v in json_dict.items() + if k != ignore_key + ) + else: + dict_gen = ( + str(k) + sorted_json_str(v, ignore_key) for k, v in json_dict.items() + ) + return "{%s}" % (",".join(sorted(dict_gen))) + elif json_dict is None: + return json.dumps(json_dict) + else: + raise TypeError("Invalid type: %s" % type(json_dict)) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 14fb83ff6..93fc15e25 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -18,7 +18,6 @@ from mira.metamodel import Concept, ControlledConversion, NaturalConversion, \ TemplateModel, Distribution, Annotations, Time, Observable from mira.metamodel.ops import stratify -from mira.metamodel.templates import SympyExprStr from mira.metamodel.comparison import TemplateModelComparison, \ TemplateModelDelta, RefinementClosure, ModelComparisonGraphdata from mira.modeling import Model From 5483076ae4a620d4b3a65a2c61117b5a53f3dd03 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 6 Jul 2023 12:36:58 -0700 Subject: [PATCH 23/56] Add test for recovering expressions from mathml --- tests/test_io.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_io.py b/tests/test_io.py index 14c210583..28c2a3647 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -1,11 +1,15 @@ """Test functions in the mira.metamodel.io module.""" +from copy import deepcopy + import requests import sympy from mira.metamodel import UNIT_SYMBOLS from mira.metamodel.io import mathml_to_expression, expression_to_mathml +from mira.sources.askenet import petrinet from mira.sources.askenet.petrinet import state_to_concept +from tests import sorted_json_str def test_sympy_to_mathml(): @@ -147,3 +151,42 @@ def test_from_askenet_petri(): local_dict = UNIT_SYMBOLS if "units" in expression_str else symbols assert mathml_to_expression(expression_mathml) == \ sympy.parse_expr(expression_str, local_dict=local_dict) + + +def _remove_all_sympy(json_data): + # Recursively remove all sympy expressions + if isinstance(json_data, list): + for item in json_data: + _remove_all_sympy(item) + elif isinstance(json_data, dict): + if "expression" in json_data: + # Remove value + json_data.pop("expression") + else: + # Recursive call + for val in json_data.values(): + _remove_all_sympy(val) + + +def test_from_askenet_petri_mathml(): + # Get model + source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ + "-Representations/main/petrinet/examples/sir.json" + resp = requests.get(source_url) + assert resp.status_code == 200 + model_json_mathml = resp.json() + + # Make a deepcopy of the model + model_json = deepcopy(model_json_mathml) + + # Remove sympy data from one copy + _remove_all_sympy(model_json_mathml) + + # Create models + mathml_tm = petrinet.template_model_from_askenet_json(model_json_mathml) + tm = petrinet.template_model_from_askenet_json(model_json) + + # Check equality + mathml_str = sorted_json_str(mathml_tm.dict()) + org_str = sorted_json_str(tm.dict()) + assert mathml_str == org_str From f93a5be6ed71da933b054cb8c563272d48b87dc4 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 11:27:18 -0500 Subject: [PATCH 24/56] Move helpers to tests/__init__.py --- tests/__init__.py | 36 +++++++++++++++++++++++++++++ tests/test_io.py | 58 +---------------------------------------------- 2 files changed, 37 insertions(+), 57 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 5af58956d..c24e0e90d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -29,3 +29,39 @@ def sorted_json_str(json_dict, ignore_key=None) -> str: return json.dumps(json_dict) else: raise TypeError("Invalid type: %s" % type(json_dict)) + + +def _expression_yielder(model_json, is_unit=False): + # Recursively yield all (sympy, mathml) string pairs in the model json + if isinstance(model_json, list): + for item in model_json: + yield from _expression_yielder(item) + elif isinstance(model_json, dict): + if "expression" in model_json and "expression_mathml" in model_json: + yield (model_json["expression"], + model_json["expression_mathml"], + is_unit) + + # Otherwise, check if 'units' key is in the dict, indicating that + # the expression is a unit + is_units = "units" in model_json + for value in model_json.values(): + # Otherwise, recursively yield from the value + yield from _expression_yielder(value, is_units) + # Otherwise, do nothing since we only care about the expression and + # expression_mathml fields in a dict + + +def _remove_all_sympy(json_data): + # Recursively remove all sympy expressions + if isinstance(json_data, list): + for item in json_data: + _remove_all_sympy(item) + elif isinstance(json_data, dict): + if "expression" in json_data: + # Remove value + json_data.pop("expression") + else: + # Recursive call + for val in json_data.values(): + _remove_all_sympy(val) diff --git a/tests/test_io.py b/tests/test_io.py index 28c2a3647..7305f7189 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -9,27 +9,7 @@ from mira.sources.askenet import petrinet from mira.sources.askenet.petrinet import state_to_concept -from tests import sorted_json_str - - -def test_sympy_to_mathml(): - expression_str = "x*y + x*z" - sympy_expr = sympy.parse_expr(expression_str) - expected = """ - - - - x - y - - - - x - z - - - """.replace("\n", "").replace(" ", "") - assert expression_to_mathml(sympy_expr) == expected +from tests import sorted_json_str, _expression_yielder, _remove_all_sympy def test_mathml_to_sympy(): @@ -107,27 +87,6 @@ def test_mathml_to_sympy(): assert expression_to_mathml(sympy_expr) == expression_mathml -def _expression_yielder(model_json, is_unit=False): - # Recursively yield all (sympy, mathml) string pairs in the model json - if isinstance(model_json, list): - for item in model_json: - yield from _expression_yielder(item) - elif isinstance(model_json, dict): - if "expression" in model_json and "expression_mathml" in model_json: - yield (model_json["expression"], - model_json["expression_mathml"], - is_unit) - - # Otherwise, check if 'units' key is in the dict, indicating that - # the expression is a unit - is_units = "units" in model_json - for value in model_json.values(): - # Otherwise, recursively yield from the value - yield from _expression_yielder(value, is_units) - # Otherwise, do nothing since we only care about the expression and - # expression_mathml fields in a dict - - def test_from_askenet_petri(): source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ "-Representations/main/petrinet/examples/sir.json" @@ -153,21 +112,6 @@ def test_from_askenet_petri(): sympy.parse_expr(expression_str, local_dict=local_dict) -def _remove_all_sympy(json_data): - # Recursively remove all sympy expressions - if isinstance(json_data, list): - for item in json_data: - _remove_all_sympy(item) - elif isinstance(json_data, dict): - if "expression" in json_data: - # Remove value - json_data.pop("expression") - else: - # Recursive call - for val in json_data.values(): - _remove_all_sympy(val) - - def test_from_askenet_petri_mathml(): # Get model source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ From f3b1d641f3e757e3c6185ae6119bb1714eab1825 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 11:30:10 -0500 Subject: [PATCH 25/56] Move tests --- tests/test_io.py | 133 ---------------------------------------- tests/test_metamodel.py | 131 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 133 deletions(-) diff --git a/tests/test_io.py b/tests/test_io.py index 7305f7189..9bf0edf1a 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -1,136 +1,3 @@ """Test functions in the mira.metamodel.io module.""" -from copy import deepcopy -import requests -import sympy -from mira.metamodel import UNIT_SYMBOLS -from mira.metamodel.io import mathml_to_expression, expression_to_mathml -from mira.sources.askenet import petrinet - -from mira.sources.askenet.petrinet import state_to_concept -from tests import sorted_json_str, _expression_yielder, _remove_all_sympy - - -def test_mathml_to_sympy(): - # 1 - xml_str = """ - - - - x - y - - - - x - z - - - """ - expected = sympy.parse_expr("x*y + x*z") - assert mathml_to_expression(xml_str) == expected - - # 2 - expression_str = ( - "I*S*kappa*(beta_c + (-beta_c + beta_s)/(1 + exp(-k*(-t + t_0))))/N" - ) - sympy_expr = sympy.parse_expr( - expression_str, - local_dict={"I": sympy.Symbol("I"), - "S": sympy.Symbol("S"), - "kappa": sympy.Symbol("kappa"), - "beta_c": sympy.Symbol("beta_c"), - "beta_s": sympy.Symbol("beta_s"), - "k": sympy.Symbol("k"), - "t_0": sympy.Symbol("t_0"), - "t": sympy.Symbol("t"), - "N": sympy.Symbol("N")} - ) - expression_mathml = ( - "ISkappa" - "beta_c" - "beta_cbeta_s" - "1k" - "t_0t" - "N" - ) - parsed_mathml = mathml_to_expression(expression_mathml) - assert parsed_mathml == sympy_expr - - # 3 - sympy_expr = sympy.parse_expr("E*delta", - local_dict={"E": sympy.Symbol("E")}) - - expression_mathml = "Edelta" - assert expression_to_mathml(sympy_expr) == expression_mathml - - # 4 - sympy_expr = sympy.parse_expr("I*gamma*(1 - alpha)", - local_dict={"I": sympy.Symbol("I"), - "gamma": sympy.Symbol("gamma"), - "alpha": sympy.Symbol("alpha")}) - expression_mathml = ( - "Igamma1" - "alpha" - ) - assert expression_to_mathml(sympy_expr) == expression_mathml - - # 5 - sympy_expr = sympy.parse_expr("I*alpha*rho", - local_dict={"I": sympy.Symbol("I"), - "alpha": sympy.Symbol("alpha"), - "rho": sympy.Symbol("rho")}) - expression_mathml = ( - "Ialpharho" - ) - assert expression_to_mathml(sympy_expr) == expression_mathml - - -def test_from_askenet_petri(): - source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ - "-Representations/main/petrinet/examples/sir.json" - resp = requests.get(source_url) - assert resp.status_code == 200 - model_json = resp.json() - - # Create symbols dict like in petrinet - model = model_json['model'] - concepts = {} - for state in model.get('states', []): - concepts[state['id']] = state_to_concept(state) - symbols = {state_id: sympy.Symbol(state_id) for state_id in concepts} - ode_semantics = model_json.get("semantics", {}).get("ode", {}) - for parameter in ode_semantics.get('parameters', []): - symbols[parameter['id']] = sympy.Symbol(parameter['id']) - - for expression_str, expression_mathml, is_unit in _expression_yielder( - model_json): - # if checking units, use the UNIT_SYMBOLS dict - local_dict = UNIT_SYMBOLS if "units" in expression_str else symbols - assert mathml_to_expression(expression_mathml) == \ - sympy.parse_expr(expression_str, local_dict=local_dict) - - -def test_from_askenet_petri_mathml(): - # Get model - source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ - "-Representations/main/petrinet/examples/sir.json" - resp = requests.get(source_url) - assert resp.status_code == 200 - model_json_mathml = resp.json() - - # Make a deepcopy of the model - model_json = deepcopy(model_json_mathml) - - # Remove sympy data from one copy - _remove_all_sympy(model_json_mathml) - - # Create models - mathml_tm = petrinet.template_model_from_askenet_json(model_json_mathml) - tm = petrinet.template_model_from_askenet_json(model_json) - - # Check equality - mathml_str = sorted_json_str(mathml_tm.dict()) - org_str = sorted_json_str(tm.dict()) - assert mathml_str == org_str diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index 8b6e3e52d..ac0f8d3e6 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -3,10 +3,17 @@ from copy import deepcopy as _d import json import unittest +from copy import deepcopy +import requests import sympy from mira.metamodel import * +from mira.metamodel import mathml_to_expression, expression_to_mathml, \ + UNIT_SYMBOLS +from mira.sources.askenet import petrinet +from mira.sources.askenet.petrinet import state_to_concept +from tests import _expression_yielder, _remove_all_sympy, sorted_json_str class TestMetaModel(unittest.TestCase): @@ -95,3 +102,127 @@ def test_rate_law_to_mathml(): mathml = expression_to_mathml(expr) assert mathml == ('I_uS_u' 'b1') + + +def test_mathml_to_sympy(): + # 1 + xml_str = """ + + + + x + y + + + + x + z + + + """ + expected = sympy.parse_expr("x*y + x*z") + assert mathml_to_expression(xml_str) == expected + + # 2 + expression_str = ( + "I*S*kappa*(beta_c + (-beta_c + beta_s)/(1 + exp(-k*(-t + t_0))))/N" + ) + sympy_expr = sympy.parse_expr( + expression_str, + local_dict={"I": sympy.Symbol("I"), + "S": sympy.Symbol("S"), + "kappa": sympy.Symbol("kappa"), + "beta_c": sympy.Symbol("beta_c"), + "beta_s": sympy.Symbol("beta_s"), + "k": sympy.Symbol("k"), + "t_0": sympy.Symbol("t_0"), + "t": sympy.Symbol("t"), + "N": sympy.Symbol("N")} + ) + expression_mathml = ( + "ISkappa" + "beta_c" + "beta_cbeta_s" + "1k" + "t_0t" + "N" + ) + parsed_mathml = mathml_to_expression(expression_mathml) + assert parsed_mathml == sympy_expr + + # 3 + sympy_expr = sympy.parse_expr("E*delta", + local_dict={"E": sympy.Symbol("E")}) + + expression_mathml = "Edelta" + assert expression_to_mathml(sympy_expr) == expression_mathml + + # 4 + sympy_expr = sympy.parse_expr("I*gamma*(1 - alpha)", + local_dict={"I": sympy.Symbol("I"), + "gamma": sympy.Symbol("gamma"), + "alpha": sympy.Symbol("alpha")}) + expression_mathml = ( + "Igamma1" + "alpha" + ) + assert expression_to_mathml(sympy_expr) == expression_mathml + + # 5 + sympy_expr = sympy.parse_expr("I*alpha*rho", + local_dict={"I": sympy.Symbol("I"), + "alpha": sympy.Symbol("alpha"), + "rho": sympy.Symbol("rho")}) + expression_mathml = ( + "Ialpharho" + ) + assert expression_to_mathml(sympy_expr) == expression_mathml + + +def test_from_askenet_petri(): + source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ + "-Representations/main/petrinet/examples/sir.json" + resp = requests.get(source_url) + assert resp.status_code == 200 + model_json = resp.json() + + # Create symbols dict like in petrinet + model = model_json['model'] + concepts = {} + for state in model.get('states', []): + concepts[state['id']] = state_to_concept(state) + symbols = {state_id: sympy.Symbol(state_id) for state_id in concepts} + ode_semantics = model_json.get("semantics", {}).get("ode", {}) + for parameter in ode_semantics.get('parameters', []): + symbols[parameter['id']] = sympy.Symbol(parameter['id']) + + for expression_str, expression_mathml, is_unit in _expression_yielder( + model_json): + # if checking units, use the UNIT_SYMBOLS dict + local_dict = UNIT_SYMBOLS if "units" in expression_str else symbols + assert mathml_to_expression(expression_mathml) == \ + sympy.parse_expr(expression_str, local_dict=local_dict) + + +def test_from_askenet_petri_mathml(): + # Get model + source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ + "-Representations/main/petrinet/examples/sir.json" + resp = requests.get(source_url) + assert resp.status_code == 200 + model_json_mathml = resp.json() + + # Make a deepcopy of the model + model_json = deepcopy(model_json_mathml) + + # Remove sympy data from one copy + _remove_all_sympy(model_json_mathml) + + # Create models + mathml_tm = petrinet.template_model_from_askenet_json(model_json_mathml) + tm = petrinet.template_model_from_askenet_json(model_json) + + # Check equality + mathml_str = sorted_json_str(mathml_tm.dict()) + org_str = sorted_json_str(tm.dict()) + assert mathml_str == org_str From 41d367a505fae1527e0e2847f2450cb10ae7bfa7 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 11:31:03 -0500 Subject: [PATCH 26/56] Delete unused file --- tests/test_io.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tests/test_io.py diff --git a/tests/test_io.py b/tests/test_io.py deleted file mode 100644 index 9bf0edf1a..000000000 --- a/tests/test_io.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Test functions in the mira.metamodel.io module.""" - - From c72877769ada20aa99ebb3b30d5598a7be55c407 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 12:18:46 -0500 Subject: [PATCH 27/56] Add method and inplace args in _remove_all_sympy --- tests/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index c24e0e90d..b7be8b5b1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -52,7 +52,9 @@ def _expression_yielder(model_json, is_unit=False): # expression_mathml fields in a dict -def _remove_all_sympy(json_data): +def _remove_all_sympy(json_data, method="pop", inplace: bool = True): + if method not in ("pop", "clear"): + raise ValueError(f"Invalid method: {method}, must be 'pop' or 'clear'") # Recursively remove all sympy expressions if isinstance(json_data, list): for item in json_data: @@ -60,8 +62,14 @@ def _remove_all_sympy(json_data): elif isinstance(json_data, dict): if "expression" in json_data: # Remove value - json_data.pop("expression") + if method == "pop": + json_data.pop("expression") + elif method == "clear": + json_data["expression"] = "" else: # Recursive call for val in json_data.values(): _remove_all_sympy(val) + + if not inplace: + return json_data From cfdde5633c5fa0c64d942ff101cb78c28bc62e12 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 12:19:02 -0500 Subject: [PATCH 28/56] Add docstring to helpers --- tests/__init__.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index b7be8b5b1..24c507e77 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,6 +5,20 @@ def sorted_json_str(json_dict, ignore_key=None) -> str: + """Return a sorted JSON string. + + Parameters + ---------- + json_dict : + A JSON dictionary. + ignore_key : + A key to ignore when sorting. + + Returns + ------- + : + A sorted JSON string. + """ if isinstance(json_dict, str): return json_dict elif isinstance(json_dict, (int, float, SympyExprStr)): @@ -32,7 +46,20 @@ def sorted_json_str(json_dict, ignore_key=None) -> str: def _expression_yielder(model_json, is_unit=False): - # Recursively yield all (sympy, mathml) string pairs in the model json + """Recursively yield all (sympy, mathml) string pairs in the model json + + Parameters + ---------- + model_json : + The model json to yield from + is_unit : + Whether the current expression is a unit + + Yields + ------ + : + A (sympy, mathml) string pair + """ if isinstance(model_json, list): for item in model_json: yield from _expression_yielder(item) @@ -53,6 +80,18 @@ def _expression_yielder(model_json, is_unit=False): def _remove_all_sympy(json_data, method="pop", inplace: bool = True): + """Remove all sympy expressions from the model json by either popping or + clearing the expression field. + + Parameters + ---------- + json_data : + The data to check completion for + method : + The method to use to remove the sympy expression. Either "pop" or + "clear" (default: "pop"). If "pop", the expression is removed from + the dict. If "clear", the expression is set to an empty string. + """ if method not in ("pop", "clear"): raise ValueError(f"Invalid method: {method}, must be 'pop' or 'clear'") # Recursively remove all sympy expressions From 6824ae0f93905b099a54320605ee315e33128ed6 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 7 Jul 2023 12:20:19 -0500 Subject: [PATCH 29/56] Rename helpers --- tests/__init__.py | 12 ++++++------ tests/test_metamodel.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 24c507e77..260532cda 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -45,7 +45,7 @@ def sorted_json_str(json_dict, ignore_key=None) -> str: raise TypeError("Invalid type: %s" % type(json_dict)) -def _expression_yielder(model_json, is_unit=False): +def expression_yielder(model_json, is_unit=False): """Recursively yield all (sympy, mathml) string pairs in the model json Parameters @@ -62,7 +62,7 @@ def _expression_yielder(model_json, is_unit=False): """ if isinstance(model_json, list): for item in model_json: - yield from _expression_yielder(item) + yield from expression_yielder(item) elif isinstance(model_json, dict): if "expression" in model_json and "expression_mathml" in model_json: yield (model_json["expression"], @@ -74,12 +74,12 @@ def _expression_yielder(model_json, is_unit=False): is_units = "units" in model_json for value in model_json.values(): # Otherwise, recursively yield from the value - yield from _expression_yielder(value, is_units) + yield from expression_yielder(value, is_units) # Otherwise, do nothing since we only care about the expression and # expression_mathml fields in a dict -def _remove_all_sympy(json_data, method="pop", inplace: bool = True): +def remove_all_sympy(json_data, method="pop", inplace: bool = True): """Remove all sympy expressions from the model json by either popping or clearing the expression field. @@ -97,7 +97,7 @@ def _remove_all_sympy(json_data, method="pop", inplace: bool = True): # Recursively remove all sympy expressions if isinstance(json_data, list): for item in json_data: - _remove_all_sympy(item) + remove_all_sympy(item) elif isinstance(json_data, dict): if "expression" in json_data: # Remove value @@ -108,7 +108,7 @@ def _remove_all_sympy(json_data, method="pop", inplace: bool = True): else: # Recursive call for val in json_data.values(): - _remove_all_sympy(val) + remove_all_sympy(val) if not inplace: return json_data diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index ac0f8d3e6..21c958172 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -13,7 +13,7 @@ UNIT_SYMBOLS from mira.sources.askenet import petrinet from mira.sources.askenet.petrinet import state_to_concept -from tests import _expression_yielder, _remove_all_sympy, sorted_json_str +from tests import expression_yielder, remove_all_sympy, sorted_json_str class TestMetaModel(unittest.TestCase): @@ -196,7 +196,7 @@ def test_from_askenet_petri(): for parameter in ode_semantics.get('parameters', []): symbols[parameter['id']] = sympy.Symbol(parameter['id']) - for expression_str, expression_mathml, is_unit in _expression_yielder( + for expression_str, expression_mathml, is_unit in expression_yielder( model_json): # if checking units, use the UNIT_SYMBOLS dict local_dict = UNIT_SYMBOLS if "units" in expression_str else symbols @@ -216,7 +216,7 @@ def test_from_askenet_petri_mathml(): model_json = deepcopy(model_json_mathml) # Remove sympy data from one copy - _remove_all_sympy(model_json_mathml) + remove_all_sympy(model_json_mathml) # Create models mathml_tm = petrinet.template_model_from_askenet_json(model_json_mathml) From ddf29fb7c8551ec2c122247cd0cb3dc5aaaf5856 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 10 Jul 2023 09:52:51 -0400 Subject: [PATCH 30/56] Remove inplace option --- tests/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 260532cda..8f21bd89b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -79,7 +79,7 @@ def expression_yielder(model_json, is_unit=False): # expression_mathml fields in a dict -def remove_all_sympy(json_data, method="pop", inplace: bool = True): +def remove_all_sympy(json_data, method="pop"): """Remove all sympy expressions from the model json by either popping or clearing the expression field. @@ -109,6 +109,3 @@ def remove_all_sympy(json_data, method="pop", inplace: bool = True): # Recursive call for val in json_data.values(): remove_all_sympy(val) - - if not inplace: - return json_data From d6088b13d0e4ef9978688b80f1ec56d84a4197a1 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 10 Jul 2023 09:53:31 -0400 Subject: [PATCH 31/56] Add option to 'null' values in sympy deleter --- tests/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 8f21bd89b..1ab3da4c5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -93,7 +93,8 @@ def remove_all_sympy(json_data, method="pop"): the dict. If "clear", the expression is set to an empty string. """ if method not in ("pop", "clear"): - raise ValueError(f"Invalid method: {method}, must be 'pop' or 'clear'") + raise ValueError(f"Invalid method: {method}, must be 'pop', 'clear' " + f"or 'null'") # Recursively remove all sympy expressions if isinstance(json_data, list): for item in json_data: @@ -105,6 +106,8 @@ def remove_all_sympy(json_data, method="pop"): json_data.pop("expression") elif method == "clear": json_data["expression"] = "" + elif method == "null": + json_data["expression"] = None else: # Recursive call for val in json_data.values(): From a5ebbc42adc05f187857628f6e080ec2cc4655e2 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 10 Jul 2023 09:58:17 -0400 Subject: [PATCH 32/56] Add test for no sympy in model api tests --- tests/test_model_api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 93fc15e25..d15924d47 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -223,6 +223,15 @@ def test_askenet_to_template_model(self): template_model = TemplateModel.from_json(response.json()) self.assertIsInstance(template_model, TemplateModel) + def test_askenet_to_template_model_no_sympy(self): + askenet_json = AskeNetPetriNetModel(Model(sir_parameterized)).to_json() + # Remove sympy expressions and leave the mathml expressions + remove_all_sympy(askenet_json, method="pop") + response = self.client.post("/api/from_petrinet", json=askenet_json) + self.assertEqual(200, response.status_code, msg=response.content) + template_model = TemplateModel.from_json(response.json()) + self.assertIsInstance(template_model, TemplateModel) + def test_askenet_from_template_model(self): response = self.client.post("/api/to_petrinet", json=json.loads(sir_parameterized.json())) self.assertEqual(200, response.status_code, msg=response.content) From 5b45061270a8a19c879dc596a82e51bdd79ac97e Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 14:37:12 -0400 Subject: [PATCH 33/56] Use safe_parse_expr in sympy helper --- mira/sources/askenet/petrinet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 7ddb64f6e..27fb1236a 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -330,8 +330,7 @@ def _get_sympy(expr_data, local_dict=None) -> Optional[sympy.Expr]: # Sympy if expr_data.get("expression"): - expr = sympy.parse_expr(clean_formula(expr_data["expression"]), - local_dict=local_dict) + expr = safe_parse_expr(expr_data["expression"], local_dict=local_dict) # MathML elif expr_data.get("expression_mathml"): expr = mathml_to_expression(expr_data["expression_mathml"]) From 536ddad4e92e4e9b70102dc18c889c98fd829b68 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 14:42:43 -0400 Subject: [PATCH 34/56] Clean up after rebase conflicts --- tests/__init__.py | 58 +++++++++++++++++++++++++------------ tests/test_model_api.py | 63 +---------------------------------------- 2 files changed, 41 insertions(+), 80 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 1ab3da4c5..7b820dd3b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,41 +4,63 @@ from mira.metamodel import SympyExprStr -def sorted_json_str(json_dict, ignore_key=None) -> str: - """Return a sorted JSON string. +def sorted_json_str(json_dict, ignore_key=None, skip_empty: bool = False) -> str: + """Create a sorted json string from a json compliant object Parameters ---------- json_dict : - A JSON dictionary. + A json compliant object ignore_key : - A key to ignore when sorting. + Key to ignore in dictionaries + skip_empty : + Skip values that evaluates to False, except for 0, 0.0, and False Returns ------- : - A sorted JSON string. + A sorted string representation of the json_dict object """ if isinstance(json_dict, str): + if skip_empty and not json_dict: + return "" return json_dict elif isinstance(json_dict, (int, float, SympyExprStr)): + if skip_empty and not json_dict and json_dict != 0 and json_dict != 0.0: + return "" return str(json_dict) elif isinstance(json_dict, (tuple, list, set)): - return "[%s]" % ( - ",".join(sorted(sorted_json_str(s, ignore_key) for s in json_dict)) + if skip_empty and not json_dict: + return "" + out_str = "[%s]" % ( + ",".join(sorted(sorted_json_str(s, ignore_key, skip_empty) for s in + json_dict)) ) + if skip_empty and out_str == "[]": + return "" + return out_str elif isinstance(json_dict, dict): - if ignore_key is not None: - dict_gen = ( - str(k) + sorted_json_str(v, ignore_key) - for k, v in json_dict.items() - if k != ignore_key - ) - else: - dict_gen = ( - str(k) + sorted_json_str(v, ignore_key) for k, v in json_dict.items() - ) - return "{%s}" % (",".join(sorted(dict_gen))) + if skip_empty and not json_dict: + return "" + + # Here skip the key value pair if skip_empty is True and the value + # is empty + def _k_v_gen(d): + for k, v in d.items(): + if ignore_key is not None and k == ignore_key: + continue + if skip_empty and not v and v != 0 and v != 0.0 and v is not False: + continue + yield k, v + + dict_gen = ( + str(k) + sorted_json_str(v, ignore_key, skip_empty) + for k, v in _k_v_gen(json_dict) + ) + out_str = "{%s}" % (",".join(sorted(dict_gen))) + if skip_empty and out_str == "{}": + return "" + return out_str elif json_dict is None: return json.dumps(json_dict) else: diff --git a/tests/test_model_api.py b/tests/test_model_api.py index d15924d47..d4428fe22 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -30,68 +30,7 @@ from mira.sources.biomodels import get_sbml_model from mira.sources.petri import template_model_from_petri_json from mira.sources.sbml import template_model_from_sbml_string - - -def sorted_json_str(json_dict, ignore_key=None, skip_empty: bool = False) -> str: - """Create a sorted json string from a json compliant object - - Parameters - ---------- - json_dict : - A json compliant object - ignore_key : - Key to ignore in dictionaries - skip_empty : - Skip values that evaluates to False, except for 0, 0.0, and False - - Returns - ------- - : - A sorted string representation of the json_dict object - """ - if isinstance(json_dict, str): - if skip_empty and not json_dict: - return "" - return json_dict - elif isinstance(json_dict, (int, float, SympyExprStr)): - if skip_empty and not json_dict and json_dict != 0 and json_dict != 0.0: - return "" - return str(json_dict) - elif isinstance(json_dict, (tuple, list, set)): - if skip_empty and not json_dict: - return "" - out_str = "[%s]" % ( - ",".join(sorted(sorted_json_str(s, ignore_key, skip_empty) for s in - json_dict)) - ) - if skip_empty and out_str == "[]": - return "" - return out_str - elif isinstance(json_dict, dict): - if skip_empty and not json_dict: - return "" - - # Here skip the key value pair if skip_empty is True and the value is empty - def _k_v_gen(d): - for k, v in d.items(): - if ignore_key is not None and k == ignore_key: - continue - if skip_empty and not v and v != 0 and v != 0.0 and v is not False: - continue - yield k, v - - dict_gen = ( - str(k) + sorted_json_str(v, ignore_key, skip_empty) - for k, v in _k_v_gen(json_dict) - ) - out_str = "{%s}" % (",".join(sorted(dict_gen))) - if skip_empty and out_str == "{}": - return "" - return out_str - elif json_dict is None: - return json.dumps(json_dict) - else: - raise TypeError("Invalid type: %s" % type(json_dict)) +from tests import sorted_json_str, remove_all_sympy def _get_sir_templatemodel() -> TemplateModel: From 212a88af17062e0a2f25027a57173ef471161cf8 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 14:57:35 -0400 Subject: [PATCH 35/56] Rename _get_sympy -> get_sympy, add docstring --- mira/sources/askenet/petrinet.py | 35 +++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/mira/sources/askenet/petrinet.py b/mira/sources/askenet/petrinet.py index 27fb1236a..ae941c71b 100644 --- a/mira/sources/askenet/petrinet.py +++ b/mira/sources/askenet/petrinet.py @@ -8,7 +8,12 @@ - Initials only have a value, cannot be expressions so information on initial condition parameter relationship is lost """ -__all__ = ["model_from_url", "model_from_json_file", "template_model_from_askenet_json"] +__all__ = [ + "model_from_url", + "model_from_json_file", + "template_model_from_askenet_json", + "get_sympy" +] import json from typing import Optional @@ -114,7 +119,7 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # Next we process initial conditions initials = {} for initial_state in ode_semantics.get("initials", []): - initial_expr = _get_sympy(initial_state, symbols) + initial_expr = get_sympy(initial_state, symbols) if initial_expr is None: continue @@ -133,7 +138,7 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: # We get observables from the semantics observables = {} for observable in ode_semantics.get("observables", []): - observable_expr = _get_sympy(observable, symbols) + observable_expr = get_sympy(observable, symbols) if observable_expr is None: continue @@ -145,7 +150,7 @@ def template_model_from_askenet_json(model_json) -> TemplateModel: time = ode_semantics.get("time") if time: time_units = time.get('units') - time_units_expr = _get_sympy(time_units, UNIT_SYMBOLS) + time_units_expr = get_sympy(time_units, UNIT_SYMBOLS) time_units_obj = Unit(expression=time_units_expr) \ if time_units_expr else None model_time = Time(name=time['id'], units=time_units_obj) @@ -239,7 +244,7 @@ def state_to_concept(state): identifiers = grounding.get('identifiers', {}) context = grounding.get('modifiers', {}) units = state.get('units') - units_expr = _get_sympy(units, UNIT_SYMBOLS) + units_expr = get_sympy(units, UNIT_SYMBOLS) units_obj = Unit(expression=units_expr) if units_expr else None return Concept(name=name, display_name=display_name, @@ -264,7 +269,7 @@ def parameter_to_mira(parameter): def transition_to_templates(transition_rate, input_concepts, output_concepts, controller_concepts, symbols, transition_id): """Return a list of templates from a transition""" - rate_law = _get_sympy(transition_rate, local_dict=symbols) + rate_law = get_sympy(transition_rate, local_dict=symbols) if not controller_concepts: if not input_concepts: @@ -324,7 +329,23 @@ def transition_to_templates(transition_rate, input_concepts, output_concepts, rate_law=rate_law) -def _get_sympy(expr_data, local_dict=None) -> Optional[sympy.Expr]: +def get_sympy(expr_data, local_dict=None) -> Optional[sympy.Expr]: + """Return a sympy expression from a dict with an expression or MathML + + Sympy string expressions are prioritized over MathML. + + Parameters + ---------- + expr_data : + A dict with an expression and/or MathML + local_dict : + A dict of local variables to use when parsing the expression + + Returns + ------- + : + A sympy expression or None if no expression was found + """ if expr_data is None: return None From 36f9e827cab1b1e10f5180a2de24321eb3b0862b Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 15:30:42 -0400 Subject: [PATCH 36/56] Use askenet.petrinet's get_sympy --- mira/metamodel/units.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mira/metamodel/units.py b/mira/metamodel/units.py index ffa30a051..de2c9b1c0 100644 --- a/mira/metamodel/units.py +++ b/mira/metamodel/units.py @@ -47,12 +47,9 @@ class Config: @classmethod def from_json(cls, data: Dict[str, Any]) -> "Unit": - expr_str = data.get('expression') - if expr_str: - data['expression'] = sympy.parse_expr( - expr_str, local_dict=UNIT_SYMBOLS - ) - + # Use get_sympy from askenet.petrinet, but avoid circular import + from mira.sources.askenet.petrinet import get_sympy + data["expression"] = get_sympy(data, local_dict=UNIT_SYMBOLS) assert data.get('expression') is None or not isinstance( data['expression'], str ) From 32935d784f4de0900304eacbabe445cc247985cc Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 15:31:19 -0400 Subject: [PATCH 37/56] Clean up import --- tests/test_metamodel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index 21c958172..d6188e658 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -11,8 +11,8 @@ from mira.metamodel import * from mira.metamodel import mathml_to_expression, expression_to_mathml, \ UNIT_SYMBOLS -from mira.sources.askenet import petrinet -from mira.sources.askenet.petrinet import state_to_concept +from mira.sources.askenet.petrinet import state_to_concept, \ + template_model_from_askenet_json from tests import expression_yielder, remove_all_sympy, sorted_json_str @@ -219,8 +219,8 @@ def test_from_askenet_petri_mathml(): remove_all_sympy(model_json_mathml) # Create models - mathml_tm = petrinet.template_model_from_askenet_json(model_json_mathml) - tm = petrinet.template_model_from_askenet_json(model_json) + mathml_tm = template_model_from_askenet_json(model_json_mathml) + tm = template_model_from_askenet_json(model_json) # Check equality mathml_str = sorted_json_str(mathml_tm.dict()) From 08657b37df9eaf405be2cb1a576781e3040a68d2 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 16:04:14 -0400 Subject: [PATCH 38/56] Initial try to install sbmlmath for tests --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a03b0a322..46ba6e3b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,11 @@ jobs: sudo apt-get install graphviz libgraphviz-dev pip install --upgrade pip setuptools wheel pip install "tox<4.0.0" + # Set up sbmlmath + pip install python-libsbml --no-dependencies + pip install pint --no-dependencies + pip install "lxml>=4.6.4" --no-dependencies + pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 From caca2542a8b3d9c021e4314ce266ff6a9b6e5bc7 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 16:10:12 -0400 Subject: [PATCH 39/56] Force install despite wrong python version --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46ba6e3b5..d71e66c53 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,7 @@ jobs: pip install python-libsbml --no-dependencies pip install pint --no-dependencies pip install "lxml>=4.6.4" --no-dependencies - pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies + pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies --ignore-requires-python - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 From 85b47189adb9641c4ab9d0ab9aa7ea89b8c974d4 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 16:13:37 -0400 Subject: [PATCH 40/56] Revert --ignore-requires-python, test bump lower python version --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d71e66c53..323761cf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.8", "3.10" ] + python-version: [ "3.9", "3.10" ] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -24,7 +24,7 @@ jobs: pip install python-libsbml --no-dependencies pip install pint --no-dependencies pip install "lxml>=4.6.4" --no-dependencies - pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies --ignore-requires-python + pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 From fc5c07061a5af64f25a03415faa9471a74858c80 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 16:54:11 -0400 Subject: [PATCH 41/56] Import sbmlmath locally to avoid missing package --- mira/metamodel/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mira/metamodel/io.py b/mira/metamodel/io.py index c58ba60fe..0bc099de6 100644 --- a/mira/metamodel/io.py +++ b/mira/metamodel/io.py @@ -3,7 +3,6 @@ import json import sympy -from sbmlmath import SBMLMathMLParser from .template_model import TemplateModel, SympyExprStr @@ -59,6 +58,7 @@ def expression_to_mathml(expression: sympy.Expr, *args, **kwargs) -> str: def mathml_to_expression(xml_str: str) -> sympy.Expr: """Convert a MathML string to a sympy expression.""" + from sbmlmath import SBMLMathMLParser template = """ {xml_str} From 2f988701f000b93453b80eb71fbbc2ff0c3e560c Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 16:55:30 -0400 Subject: [PATCH 42/56] Skip tests that use sbmlmath --- tests/test_metamodel.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index d6188e658..7c2d0475a 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -5,6 +5,7 @@ import unittest from copy import deepcopy +import pytest import requests import sympy @@ -104,6 +105,7 @@ def test_rate_law_to_mathml(): 'b1') +@pytest.mark.skip("sbmlmath is not installed") def test_mathml_to_sympy(): # 1 xml_str = """ @@ -155,7 +157,7 @@ def test_mathml_to_sympy(): local_dict={"E": sympy.Symbol("E")}) expression_mathml = "Edelta" - assert expression_to_mathml(sympy_expr) == expression_mathml + assert sympy_expr.equals(mathml_to_expression(expression_mathml)) # 4 sympy_expr = sympy.parse_expr("I*gamma*(1 - alpha)", @@ -179,6 +181,7 @@ def test_mathml_to_sympy(): assert expression_to_mathml(sympy_expr) == expression_mathml +@pytest.mark.skip("sbmlmath is not installed") def test_from_askenet_petri(): source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ "-Representations/main/petrinet/examples/sir.json" @@ -204,6 +207,7 @@ def test_from_askenet_petri(): sympy.parse_expr(expression_str, local_dict=local_dict) +@pytest.mark.skip("sbmlmath is not installed") def test_from_askenet_petri_mathml(): # Get model source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ From fdae5b93fb441253c4b9efe101d0351cc623dc87 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 17:22:01 -0400 Subject: [PATCH 43/56] Put sbmlmath under its own extra --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f837db268..0e7bb065c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,6 @@ license_files = install_requires = pydantic>=1.10,<2.0.0 sympy - sbmlmath @ git+https://github.com/dweindl/sbmlmath.git typing_extensions networkx tqdm @@ -96,6 +95,10 @@ docs = sbml = python-libsbml lxml +sbmlmath = + pint + lxml>=4.6.4 + sbmlmath @ git+https://github.com/dweindl/sbmlmath.git [mypy] plugins = pydantic.mypy From 59c4b61b150dadba105f986d0e3836c6eefc606c Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 12 Jul 2023 17:27:34 -0400 Subject: [PATCH 44/56] Revert to python 3.8, skip sbmlmath install --- .github/workflows/tests.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 323761cf2..a03b0a322 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.9", "3.10" ] + python-version: [ "3.8", "3.10" ] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -20,11 +20,6 @@ jobs: sudo apt-get install graphviz libgraphviz-dev pip install --upgrade pip setuptools wheel pip install "tox<4.0.0" - # Set up sbmlmath - pip install python-libsbml --no-dependencies - pip install pint --no-dependencies - pip install "lxml>=4.6.4" --no-dependencies - pip install git+https://github.com/dweindl/sbmlmath.git --no-dependencies - name: Test with pytest run: | export MIRA_REST_URL=http://34.230.33.149:8771 From 0b5f08c707238cf4da39065850fcdddd78d68986 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 08:57:11 -0400 Subject: [PATCH 45/56] Update skip message --- tests/test_metamodel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index 7c2d0475a..e7ac8a4bd 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -105,7 +105,7 @@ def test_rate_law_to_mathml(): 'b1') -@pytest.mark.skip("sbmlmath is not installed") +@pytest.mark.skip("sbmlmath is not installed, run locally, run locally") def test_mathml_to_sympy(): # 1 xml_str = """ @@ -181,7 +181,7 @@ def test_mathml_to_sympy(): assert expression_to_mathml(sympy_expr) == expression_mathml -@pytest.mark.skip("sbmlmath is not installed") +@pytest.mark.skip("sbmlmath is not installed, run locally") def test_from_askenet_petri(): source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ "-Representations/main/petrinet/examples/sir.json" @@ -207,7 +207,7 @@ def test_from_askenet_petri(): sympy.parse_expr(expression_str, local_dict=local_dict) -@pytest.mark.skip("sbmlmath is not installed") +@pytest.mark.skip("sbmlmath is not installed, run locally") def test_from_askenet_petri_mathml(): # Get model source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ From b797b3b57f4616cc59c566cfc2617238a0bd5426 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:16:22 -0400 Subject: [PATCH 46/56] Use sir model with expressions filled out --- tests/test_model_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index d4428fe22..0c51e26f4 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -163,7 +163,8 @@ def test_askenet_to_template_model(self): self.assertIsInstance(template_model, TemplateModel) def test_askenet_to_template_model_no_sympy(self): - askenet_json = AskeNetPetriNetModel(Model(sir_parameterized)).to_json() + askenet_json = AskeNetPetriNetModel(Model( + sir_parameterized_init)).to_json() # Remove sympy expressions and leave the mathml expressions remove_all_sympy(askenet_json, method="pop") response = self.client.post("/api/from_petrinet", json=askenet_json) From f1b78e127bbe9e530e845ef4a680897593e03248 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:41:41 -0400 Subject: [PATCH 47/56] Add functionality to test a docker running locally --- tests/test_model_api.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 0c51e26f4..75fbb7379 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -31,6 +31,19 @@ from mira.sources.petri import template_model_from_petri_json from mira.sources.sbml import template_model_from_sbml_string from tests import sorted_json_str, remove_all_sympy +import requests + + +class RequestsWrapper: + base_url = "http://localhost:8771" + + def get(self, url: str, **kwargs) -> requests.Response: + url = self.base_url + url + return requests.get(url, **kwargs) + + def post(self, url: str, **kwargs) -> requests.Response: + url = self.base_url + url + return requests.post(url, **kwargs) def _get_sir_templatemodel() -> TemplateModel: @@ -92,7 +105,10 @@ def setUp(self) -> None: self.test_app = FastAPI() self.test_app.state = State() self.test_app.include_router(model_blueprint, prefix="/api") + # Comment out the TestClient and uncomment the RequestsWrapper to + # use a local instance of the API self.client = TestClient(self.test_app) + # self.client = RequestsWrapper() self.temp_files: List[Path] = [] def tearDown(self) -> None: @@ -295,6 +311,9 @@ def test_biomodels_id_to_template_model(self): local = template_model_from_sbml_string( xml_string, model_id=model_id ) + # This assert print a decently readable diff if it fails, but is + # less restrictive than the string comparison below + assert tm == local self.assertEqual( sorted_json_str(tm.dict()), sorted_json_str(local.dict()) ) @@ -349,6 +368,9 @@ def test_xml_str_to_template_model(self): tm_res = TemplateModel.from_json(response.json()) local = template_model_from_sbml_string(xml_string) + # This assert print a decently readable diff if it fails, but is + # less restrictive than the string comparison below + assert tm_res == local self.assertEqual( sorted_json_str(tm_res.dict()), sorted_json_str(local.dict()) ) From d57aeb5daac1a822d3e0b4d433548ceeec5bfe24 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:44:19 -0400 Subject: [PATCH 48/56] Add comment on how to install sbmlmath locally --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 0e7bb065c..937753e9c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -96,6 +96,9 @@ sbml = python-libsbml lxml sbmlmath = + # sbmlmath is py3.9+ only. For version below 3.9, use the following install command locally: + # pip install sbmlmath@git+https://github.com/dweindl/sbmlmath.git --python-version py39 --ignore-requires-python + # It might be necessary to install sbmlmath, pint and lxml using --no-deps as well. pint lxml>=4.6.4 sbmlmath @ git+https://github.com/dweindl/sbmlmath.git From fcfece4f1ed0873ec92e85cbd1b48df7834d34b9 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:44:40 -0400 Subject: [PATCH 49/56] Update dkg docker file --- docker/Dockerfile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d6039abcd..737b10bc7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 WORKDIR /sw @@ -10,6 +10,8 @@ RUN apt-get update && \ apt-get install -y neo4j && \ apt-get install -y git zip unzip bzip2 gcc graphviz graphviz-dev \ pkg-config python3 python3-pip && \ + # purge blinker here to avoid pip uninstall error below + apt-get purge -y python3-blinker && \ ln -s /usr/bin/python3 /usr/bin/python ARG version=2023-07-07 @@ -25,10 +27,14 @@ RUN wget -O /sw/nodes.tsv.gz https://askem-mira.s3.amazonaws.com/dkg/$domain/bui neo4j-admin import --delimiter='TAB' --skip-duplicate-nodes=true --skip-bad-relationships=true --nodes /sw/nodes.tsv.gz --relationships /sw/edges.tsv.gz # Python packages -RUN python -m pip install git+https://github.com/indralab/mira.git@main#egg=mira[web,uvicorn,dkg-client] && \ +RUN python -m pip install --upgrade pip && \ + python -m pip install git+https://github.com/indralab/mira.git@mathml-sympy#egg=mira[web,uvicorn,dkg-client] && \ python -m pip uninstall -y flask_bootstrap && \ python -m pip uninstall -y bootstrap_flask && \ - python -m pip install bootstrap_flask + python -m pip install bootstrap_flask && \ + python -m pip install --no-dependencies pint && \ + python -m pip install --no-dependencies "lxml>=4.6.4" && \ + python -m pip install --no-dependencies git+https://github.com/dweindl/sbmlmath.git # Copy the example json for reconstructing the ode semantics RUN wget -O /sw/sir_flux_span.json https://raw.githubusercontent.com/indralab/mira/main/tests/sir_flux_span.json From 7fdc3ceffb6488a2505a44dbfd97bc97bae24c7d Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:45:53 -0400 Subject: [PATCH 50/56] Fix grammar --- tests/test_metamodel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index e7ac8a4bd..13ba169cf 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -105,7 +105,7 @@ def test_rate_law_to_mathml(): 'b1') -@pytest.mark.skip("sbmlmath is not installed, run locally, run locally") +@pytest.mark.skip("sbmlmath is not installed, run locally") def test_mathml_to_sympy(): # 1 xml_str = """ From ddbbd06a8519329ed7aafc1fdd1d13794159c111 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:54:12 -0400 Subject: [PATCH 51/56] Try disabling codecov --- .github/workflows/tests.yml | 10 +++++----- tox.ini | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a03b0a322..4eaa3470c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,8 +24,8 @@ jobs: run: | export MIRA_REST_URL=http://34.230.33.149:8771 tox -e py - - name: Upload coverage report to codecov - uses: codecov/codecov-action@v1 - if: success() - with: - file: coverage.xml +# - name: Upload coverage report to codecov +# uses: codecov/codecov-action@v1 +# if: success() +# with: +# file: coverage.xml diff --git a/tox.ini b/tox.ini index 844667cef..be7b0891d 100644 --- a/tox.ini +++ b/tox.ini @@ -16,8 +16,8 @@ extras = web commands = coverage run -p -m pytest --durations=20 {posargs:tests} -m "not slow" - coverage combine - coverage xml +; coverage combine +; coverage xml [testenv:docs] extras = From 6bfd26258e503e8c7225147d40c2172bbe3ed123 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 11:56:55 -0400 Subject: [PATCH 52/56] Skip another sbmlmath dependent test --- tests/test_model_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 75fbb7379..8ccfa1394 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import List, Union +import pytest import sympy from fastapi import FastAPI from fastapi.testclient import TestClient @@ -178,6 +179,7 @@ def test_askenet_to_template_model(self): template_model = TemplateModel.from_json(response.json()) self.assertIsInstance(template_model, TemplateModel) + @pytest.mark.skip(reason="sbmlmath is not installed, run locally") def test_askenet_to_template_model_no_sympy(self): askenet_json = AskeNetPetriNetModel(Model( sir_parameterized_init)).to_json() From e30399c58801bb789d39a6a368fd1ea1a6ea6614 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 16:56:51 -0400 Subject: [PATCH 53/56] Mark sbmlmath tests and skip them on github --- tests/test_metamodel.py | 7 ++++--- tests/test_model_api.py | 2 +- tox.ini | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index 13ba169cf..4514760a0 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -105,7 +105,7 @@ def test_rate_law_to_mathml(): 'b1') -@pytest.mark.skip("sbmlmath is not installed, run locally") +@pytest.mark.sbmlmath def test_mathml_to_sympy(): # 1 xml_str = """ @@ -181,7 +181,8 @@ def test_mathml_to_sympy(): assert expression_to_mathml(sympy_expr) == expression_mathml -@pytest.mark.skip("sbmlmath is not installed, run locally") + +@pytest.mark.sbmlmath def test_from_askenet_petri(): source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ "-Representations/main/petrinet/examples/sir.json" @@ -207,7 +208,7 @@ def test_from_askenet_petri(): sympy.parse_expr(expression_str, local_dict=local_dict) -@pytest.mark.skip("sbmlmath is not installed, run locally") +@pytest.mark.sbmlmath def test_from_askenet_petri_mathml(): # Get model source_url = "https://raw.githubusercontent.com/DARPA-ASKEM/Model" \ diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 8ccfa1394..f19b53bca 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -179,7 +179,7 @@ def test_askenet_to_template_model(self): template_model = TemplateModel.from_json(response.json()) self.assertIsInstance(template_model, TemplateModel) - @pytest.mark.skip(reason="sbmlmath is not installed, run locally") + @pytest.mark.sbmlmath def test_askenet_to_template_model_no_sympy(self): askenet_json = AskeNetPetriNetModel(Model( sir_parameterized_init)).to_json() diff --git a/tox.ini b/tox.ini index be7b0891d..0eb59af35 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ extras = tests web commands = - coverage run -p -m pytest --durations=20 {posargs:tests} -m "not slow" + coverage run -p -m pytest --durations=20 {posargs:tests} -m "not slow and not sbmlmath" ; coverage combine ; coverage xml From 6c28d3941aa11269c49f6e5a29773f22ae7909c0 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jul 2023 16:58:47 -0400 Subject: [PATCH 54/56] Import SympyExprStr --- tests/test_model_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index f19b53bca..0d370dfa8 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -17,7 +17,7 @@ from mira.dkg.api import RelationQuery from mira.dkg.web_client import is_ontological_child_web, get_relations_web from mira.metamodel import Concept, ControlledConversion, NaturalConversion, \ - TemplateModel, Distribution, Annotations, Time, Observable + TemplateModel, Distribution, Annotations, Time, Observable, SympyExprStr from mira.metamodel.ops import stratify from mira.metamodel.comparison import TemplateModelComparison, \ TemplateModelDelta, RefinementClosure, ModelComparisonGraphdata From ca20063f7a90b3a59f02d0493c896919154ed2f2 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 21 Jul 2023 08:37:38 -0700 Subject: [PATCH 55/56] Restore docker build branch --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 737b10bc7..2dd40bb35 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,7 +28,7 @@ RUN wget -O /sw/nodes.tsv.gz https://askem-mira.s3.amazonaws.com/dkg/$domain/bui # Python packages RUN python -m pip install --upgrade pip && \ - python -m pip install git+https://github.com/indralab/mira.git@mathml-sympy#egg=mira[web,uvicorn,dkg-client] && \ + python -m pip install git+https://github.com/indralab/mira.git@main#egg=mira[web,uvicorn,dkg-client] && \ python -m pip uninstall -y flask_bootstrap && \ python -m pip uninstall -y bootstrap_flask && \ python -m pip install bootstrap_flask && \ From 37ee300b37a3d477014a3d9d7d751c2f17d4b0bb Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 14 Aug 2023 15:14:43 -0700 Subject: [PATCH 56/56] pip install sbmlmath --- docker/Dockerfile | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2dd40bb35..cc4610465 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,7 +34,7 @@ RUN python -m pip install --upgrade pip && \ python -m pip install bootstrap_flask && \ python -m pip install --no-dependencies pint && \ python -m pip install --no-dependencies "lxml>=4.6.4" && \ - python -m pip install --no-dependencies git+https://github.com/dweindl/sbmlmath.git + python -m pip install --no-dependencies sbmlmath # Copy the example json for reconstructing the ode semantics RUN wget -O /sw/sir_flux_span.json https://raw.githubusercontent.com/indralab/mira/main/tests/sir_flux_span.json diff --git a/setup.cfg b/setup.cfg index 937753e9c..b3c3b4a2a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -101,7 +101,7 @@ sbmlmath = # It might be necessary to install sbmlmath, pint and lxml using --no-deps as well. pint lxml>=4.6.4 - sbmlmath @ git+https://github.com/dweindl/sbmlmath.git + sbmlmath [mypy] plugins = pydantic.mypy