Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions petab/v1/math/SympyVisitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,21 @@ def bool2num(x: sp.Basic | sp.Expr) -> sp.Basic | sp.Expr:


def num2bool(x: sp.Basic | sp.Expr) -> sp.Basic | sp.Expr:
"""Convert sympy Floats to booleans."""
if isinstance(x, BooleanTrue | BooleanFalse):
"""Convert sympy numeric expressions to booleans.

Zero is converted to false, any other value to true.
"""
# sp.Symbol is both Boolean and Expr; PEtab symbols are numeric
if isinstance(x, Boolean) and not isinstance(x, sp.Expr):
return x
# Note: sp.Float(0) == 0 is False in sympy>=1.13
if x.is_zero is True:
return sp.false
if x.is_zero is False:
return sp.true
Comment on lines 338 to 341

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be deleted? Since we now have return sp.Ne(x, 0)?

if isinstance(x, Boolean):
return x
return sp.Piecewise((True, x != 0.0), (False, True))
if isinstance(x, sp.Piecewise):
# Boolean-valued pieces have to be converted to numbers first
x = sp.Piecewise(
*((bool2num(expr), cond) for expr, cond in x.args),
)
return sp.Ne(x, 0)
32 changes: 32 additions & 0 deletions tests/v1/math/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,38 @@ def test_print_parse_round_trip(expr_str, evaluate):
assert np.isclose(actual, expected, rtol=1e-15, atol=0), printed


@pytest.mark.parametrize(
"expr_str, expected",
[
# expected values for x = -1, 0, 1 (y = 3)
("(x + 1) && (y > 2)", [False, True, True]),
("(x + 1) || (y < 2)", [False, True, True]),
("!(x + 1)", [True, False, False]),
("piecewise(1, x + 1, 0)", [0.0, 1.0, 1.0]),
("x && y", [True, False, True]),
("!x", [False, True, False]),
("piecewise(1, x > 0, 0) && true", [False, False, True]),
("!piecewise(1, x < 0, 0)", [False, True, True]),
("piecewise(true, x > 0, false) && true", [False, False, True]),
("!piecewise(true, x < 0, false)", [False, True, True]),
("piecewise(1, piecewise(true, x > 0, false), 0)", [0.0, 0.0, 1.0]),
],
)
@pytest.mark.parametrize("evaluate", [True, False])
def test_num2bool(expr_str, expected, evaluate):
"""Non-constant numbers in Boolean context: 0 is false, else true."""
x, y = sp.symbols("x y", real=True)
expr = sympify_petab(expr_str, evaluate=evaluate)
reparsed = sympify_petab(petab_math_str(expr), evaluate=evaluate)
for e in (expr, reparsed):
assert {x, y} & e.free_symbols, e
actual = [
e.subs({x: sp.Float(x_val), y: sp.Float(3.0)})
for x_val in (-1.0, 0.0, 1.0)
]
assert actual == expected, e


def read_cases():
"""Read test cases from YAML file in the petab_test_suite package."""
yaml_file = importlib.resources.files("petabtests.cases").joinpath(
Expand Down
Loading