From 8486cf6d71e33c5c8714f4904dbe5e9a6cce65ab Mon Sep 17 00:00:00 2001 From: Aldrich Fan Date: Thu, 25 Apr 2024 18:04:01 -0700 Subject: [PATCH] 0.2.5 --- .gitignore | 2 + README.md | 3 +- ratesb_python/common/analyzer.py | 23 ++++----- ratesb_python/common/util.py | 80 ++++++++++++++++++++------------ tests/test_util.py | 51 +++++++++++++------- 5 files changed, 100 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 516bd2e..9be0fa0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,8 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ + +# test taking too long to run tests/biomodels/* tests/test_biomodels.py diff --git a/README.md b/README.md index fc4f3d7..b065bee 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ python -m unittest * Separated model reading from analysis * Tested on 1054 biomodels and fixed bugs * Added check_model method to allow user to use the package with one line +* Solved when running sympy with sympy builtin symbols that raise error such as "S", a reaction like "S->P;k1*S" would work now ## Contributing @@ -214,7 +215,7 @@ Contributions to `ratesb_python` are welcomed! Whether it's bug reports, feature ## Known Issues -There are some sympy builtin symbols that will raise error such as "S", so a reaction like "S->P; k1*S" would not work. +N/A ## Contact diff --git a/ratesb_python/common/analyzer.py b/ratesb_python/common/analyzer.py index a2129d9..4af1ddb 100644 --- a/ratesb_python/common/analyzer.py +++ b/ratesb_python/common/analyzer.py @@ -381,16 +381,12 @@ def _check_flux_increasing_with_reactant(self, **kwargs): A warning message to results specifying if the flux is not increasing as reactant increases. """ reaction_id = kwargs["reaction_id"] + ids_list = kwargs["ids_list"] reactant_list = kwargs["reactant_list"] kinetics = kwargs["kinetics"] - # first check if kinetics can be sympified, TODO: support functions in MathML - try: - sympified_kinetics = sp.sympify(kinetics) - except: - return - if not util.check_symbols_derivative(sympified_kinetics, reactant_list): + if not util.check_kinetics_derivative(kinetics, ids_list, reactant_list): self.data.results.add_message( reaction_id, 1003, "Flux is not increasing as reactant increases.") @@ -409,6 +405,7 @@ def _check_flux_decreasing_with_product(self, **kwargs): A warning message to results specifying if the flux is not decreasing as product increases. """ reaction_id = kwargs["reaction_id"] + ids_list = kwargs["ids_list"] is_reversible = kwargs["is_reversible"] product_list = kwargs["product_list"] kinetics = kwargs["kinetics"] @@ -416,8 +413,7 @@ def _check_flux_decreasing_with_product(self, **kwargs): # first check if kinetics can be sympified, TODO: support functions in MathML if is_reversible: try: - sympified_kinetics = sp.sympify(kinetics) - if not util.check_symbols_derivative(sympified_kinetics, product_list, False): + if not util.check_kinetics_derivative(kinetics, ids_list, product_list, is_positive_derivative=False): self.data.results.add_message( reaction_id, 1004, "Flux is not decreasing as product increases.") except: @@ -587,8 +583,10 @@ def _numerator_denominator(self, kinetics_sim, ids_list): strange_func = 0 pre_symbols = '' - for i in range(len(ids_list)): - pre_symbols += ids_list[i] + ids_with_underscore = [] + kinetics_with_underscore = util.add_underscore_to_ids(ids_list, kinetics_sim, ids_with_underscore) + for i in range(len(ids_with_underscore)): + pre_symbols += ids_with_underscore[i] pre_symbols += ' ' pre_symbols = pre_symbols[:-1] # remove the space at the end pre_symbols_comma = pre_symbols.replace(" ", ",") @@ -600,7 +598,7 @@ def _numerator_denominator(self, kinetics_sim, ids_list): try: # check if there is strange func (i.e. delay) in kinetic law; # sometimes ids_list is not enough for all the ids in kinetics - eq_stat = "kinetics_eq = " + kinetics_sim + eq_stat = "kinetics_eq = " + kinetics_with_underscore exec(eq_stat, globals()) except: strange_func = 1 @@ -615,6 +613,9 @@ def _numerator_denominator(self, kinetics_sim, ids_list): eq[1] = denominator except: pass + + eq[0] = util.remove_underscore_from_ids(ids_list, eq[0]) + eq[1] = util.remove_underscore_from_ids(ids_list, eq[1]) return eq diff --git a/ratesb_python/common/util.py b/ratesb_python/common/util.py index dc850ff..969f1b6 100644 --- a/ratesb_python/common/util.py +++ b/ratesb_python/common/util.py @@ -78,52 +78,74 @@ def check_equal(expr1, expr2, n=4, sample_min=1, sample_max=10): return False return True -def check_symbols_derivative(expr, symbols, is_positive_derivative=True): - """check if the derivative to symbols of a expression are always strictly positive where variables are positive +def check_kinetics_derivative(kinetics, ids_list, species_list, is_positive_derivative=True): + """check if the derivative to ids_list of a kinetics are always strictly positive where variables are positive Args: - expr (sympy.Expr): sympy expression to evaluate - symbols (List): list of symbols to check derivative - ids_list (List): all symbols in expression + kinetics (str): kinetics expression + ids_list (List): all ids in expression + species_list (List): all species in expression is_positive_derivative (bool, optional): if we are checking the derivative is positive, checking negative otherwise. Defaults to True. Returns: bool: if every symbol's derivative if strictly positive if is_positive_derivative, strictively negative if not is_positive_derivative """ - # Numeric (brute force) equality testing n-times - for symbol in symbols: - temp_expr = expr - for other_symbol in expr.free_symbols: - if str(other_symbol) != symbol: - temp_expr = temp_expr.subs(other_symbol, sp.Float(1)) + for symbol in ids_list: + if symbol not in species_list: + kinetics = kinetics.replace(symbol, "1") + for species in species_list: + curr_kinetics = kinetics + for other_species in species_list: + if other_species != species: + curr_kinetics = curr_kinetics.replace(other_species, "1") if is_positive_derivative: prev = -math.inf else: prev = math.inf for i in range(1, 1001, 10): - try: - curr = sp.Float(temp_expr.subs(symbol, sp.Float(i/100))) - if is_positive_derivative: - if curr <= prev: - return False - else: - if curr >= prev: - return False - prev = curr - except: - return False + expr = curr_kinetics.replace(species, str(i/100)) + curr = eval(expr) + if is_positive_derivative: + if curr <= prev: + return False + else: + if curr >= prev: + return False + prev = curr return True -def substitute_sympy_builtin_symbols_with_underscore(symbol): +def add_underscore_to_ids(ids_list, kinetics, ids_to_add): + """Add "___" to the ids in the ids_list, without changing the original ids_list + add the new ids to the ids_to_add list + change the ids in the kinetics to the new ids + Args: + ids_list (List): list of ids + kinetics (str): kinetics expression + ids_to_add (List): empty list to add the new ids + + Returns: + str: kinetics expression with new ids """ - Substitute sympy built-in symbols with underscored symbols - This method recognizes which symbols are built-in sympy symbols and substitutes them with underscored symbols + for symbol in ids_list: + new_id = "___" + symbol + ids_to_add.append(new_id) + kinetics = kinetics.replace(symbol, new_id) + return kinetics +def remove_underscore_from_ids(ids_list, kinetics): + """Remove "___" from the ids in the kinetics + ids_list should not contain "___" Args: - symbol (sympy.Symbol): symbol to substitute - + ids_list (List): list of ids without "___" + kinetics (str): kinetics expression + Returns: - str: symbol name with underscore in front of the symbol + str: kinetics expression with new ids """ - \ No newline at end of file + + for symbol in ids_list: + # assert symbol does not start with "___" + assert symbol[:3] != "___" + kinetics = kinetics.replace("___" + symbol, symbol) + return kinetics \ No newline at end of file diff --git a/tests/test_util.py b/tests/test_util.py index 948fbf3..636b960 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -8,7 +8,7 @@ common_dir = os.path.join(parent_dir, 'ratesb_python', 'common') sys.path.append(common_dir) -from util import get_model_str, get_json_str, check_equal, check_symbols_derivative +from util import get_model_str, get_json_str, check_equal, check_kinetics_derivative, add_underscore_to_ids, remove_underscore_from_ids class TestUtil(unittest.TestCase): def test_get_model_str(self): @@ -68,25 +68,40 @@ def test_check_equal(self): self.assertFalse(check_equal(expr5, expr7)) self.assertFalse(check_equal(expr6, expr7)) - def test_check_symbols_derivative(self): - x, y, z = sp.symbols('x y z') - expr1 = x + y + z - expr2 = x + z - 2 * y - expr3 = 1 / x - expr4 = x * y * z - expr5 = x * y / (1 + y) + def test_check_kinetics_derivative(self): + expr1 = "x + y + z" + expr2 = "x + z - 2 * y" + expr3 = "1 / x" + expr4 = "x * y * z" + expr5 = "x * y / (1 + y)" symbols = ["x", "y", "z"] - self.assertTrue(check_symbols_derivative(expr1, symbols)) - self.assertFalse(check_symbols_derivative(expr1, symbols, False)) - self.assertFalse(check_symbols_derivative(expr2, symbols)) - self.assertFalse(check_symbols_derivative(expr2, symbols, False)) - self.assertTrue(check_symbols_derivative(expr3, ["x"], False)) - self.assertFalse(check_symbols_derivative(expr3, ["x"])) - self.assertTrue(check_symbols_derivative(expr4, symbols)) - self.assertFalse(check_symbols_derivative(expr4, symbols, False)) - self.assertTrue(check_symbols_derivative(expr5, ["x", "y"])) - self.assertFalse(check_symbols_derivative(expr5, ["x", "y"], False)) + self.assertTrue(check_kinetics_derivative(expr1, symbols, symbols)) + self.assertFalse(check_kinetics_derivative(expr1, symbols, symbols, False)) + self.assertFalse(check_kinetics_derivative(expr2, symbols, symbols)) + self.assertFalse(check_kinetics_derivative(expr2, symbols, symbols, False)) + self.assertTrue(check_kinetics_derivative(expr3, symbols, ["x"], False)) + self.assertFalse(check_kinetics_derivative(expr3, symbols, ["x"])) + self.assertTrue(check_kinetics_derivative(expr4, symbols, symbols)) + self.assertFalse(check_kinetics_derivative(expr4, symbols, symbols, False)) + self.assertTrue(check_kinetics_derivative(expr5, symbols, ["x", "y"])) + self.assertFalse(check_kinetics_derivative(expr5, symbols, ["x", "y"], False)) + + def test_add_underscore_to_ids(self): + ids_list = ["x", "y", "z"] + kinetics = "x + y + z" + ids_to_add = [] + kinetics = add_underscore_to_ids(ids_list, kinetics, ids_to_add) + self.assertEqual(kinetics, "___x + ___y + ___z") + self.assertEqual(ids_to_add, ["___x", "___y", "___z"]) + self.assertEqual(ids_list, ["x", "y", "z"]) + + def test_remove_underscore_from_ids(self): + ids_list = ["x", "y", "z"] + kinetics = "___x + ___y + ___z" + kinetics = remove_underscore_from_ids(ids_list, kinetics) + self.assertEqual(kinetics, "x + y + z") + self.assertEqual(ids_list, ["x", "y", "z"]) if __name__ == "__main__": unittest.main() \ No newline at end of file