Skip to content

Commit

Permalink
0.2.5
Browse files Browse the repository at this point in the history
  • Loading branch information
longxf21 committed Apr 26, 2024
1 parent 82e3d1b commit 8486cf6
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 59 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/

# test taking too long to run
tests/biomodels/*
tests/test_biomodels.py

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
23 changes: 12 additions & 11 deletions ratesb_python/common/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand All @@ -409,15 +405,15 @@ 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"]

# 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:
Expand Down Expand Up @@ -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(" ", ",")
Expand All @@ -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
Expand All @@ -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

Expand Down
80 changes: 51 additions & 29 deletions ratesb_python/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""


for symbol in ids_list:
# assert symbol does not start with "___"
assert symbol[:3] != "___"
kinetics = kinetics.replace("___" + symbol, symbol)
return kinetics
51 changes: 33 additions & 18 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()

0 comments on commit 8486cf6

Please sign in to comment.