Skip to content

Commit

Permalink
Implement BNG boolean treatment within expressions (#494)
Browse files Browse the repository at this point in the history
* Implement BNG boolean treatment within expressions

BNG treats booleans as integers within expressions (1=True,
0=False), so an expression like `(2 > 1) * 4` will get evaluated
to `4`. Sometimes these expressions get produced by BNG
local functions and may be present in other imported models as
well. However, doesn't implement multiplication of
BooleanAtoms, which causes a TypeError.

* Implement Boolean multiplication fix proposed by @jmuhlich

Co-authored-by: Jeremy Muhlich <jmuhlich@bitflood.org>
  • Loading branch information
alubbock and jmuhlich committed Jun 4, 2020
1 parent 7391df8 commit fe2c0e4
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 4 deletions.
23 changes: 21 additions & 2 deletions pysb/bng.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import pysb.pathfinder as pf
import tokenize
from pysb.logging import get_logger, EXTENDED_DEBUG

from sympy.logic.boolalg import BooleanTrue, BooleanFalse, BooleanAtom
try:
from cStringIO import StringIO
except ImportError:
Expand Down Expand Up @@ -933,6 +933,19 @@ def _convert_tokens(tokens, local_dict, global_dict):
return tokens


def _is_bool_expr(e):
return isinstance(e, sympy.boolalg.Boolean) and not \
isinstance(e, sympy.AtomicExpr)


def _fix_boolean_multiplication(*args):
args = [
sympy.Piecewise((1, a), (0, True)) if _is_bool_expr(a) else a
for a in args
]
return sympy.Mul(*args)


def parse_bngl_expr(text, *args, **kwargs):
"""Convert a BNGL math expression string to a sympy Expr."""

Expand All @@ -944,7 +957,13 @@ def parse_bngl_expr(text, *args, **kwargs):
sympy_parser.standard_transformations
+ (sympy_parser.convert_equals_signs, _convert_tokens)
)
expr = sympy_parser.parse_expr(text, *args, transformations=trans, **kwargs)
expr = sympy_parser.parse_expr(text, *args, transformations=trans,
evaluate=False, **kwargs)

# Replace Boolean multiplications, e.g. `2 * (3 > 0)`
# See https://github.com/pysb/pysb/pull/494
expr = expr.replace(sympy.Mul, _fix_boolean_multiplication)

# Transforming 'if' to Piecewise requires subexpression rearrangement, so we
# use sympy's replace functionality rather than attempt it using text
# replacements above.
Expand Down
3 changes: 1 addition & 2 deletions pysb/importers/bngl.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ def _eval_in_model_env(self, expression):

# Quick security check on the expression
if re.match(r'^[\w\s()/+\-._*^]*$', expression):
return parse_bngl_expr(expression, local_dict=self._model_env,
evaluate=False)
return parse_bngl_expr(expression, local_dict=self._model_env)
else:
self._warn_or_except('Security check on expression "%s" failed' %
expression)
Expand Down
5 changes: 5 additions & 0 deletions pysb/tests/test_bng.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,8 @@ def test_parse_bngl_expression_and_or_equals():
assert parse_bngl_expr('x and y') == sympy.And(x, y)
assert parse_bngl_expr('x or y') == sympy.Or(x, y)
assert parse_bngl_expr('x == y') == sympy.Eq(x, y)


def test_bng_boolean_multiply_number():
assert parse_bngl_expr('(2 > 1) * 4') == 4
assert parse_bngl_expr('4 * (2 > 1)') == 4

0 comments on commit fe2c0e4

Please sign in to comment.