Skip to content

Commit 9ea6aef

Browse files
authored
Support XOR expressions (#2699)
Replace XOR by OR (-> #2685).
1 parent 078514d commit 9ea6aef

File tree

2 files changed

+28
-7
lines changed

2 files changed

+28
-7
lines changed

python/sdist/amici/import_utils.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,11 @@ def _parse_heaviside_trigger(trigger: sp.Expr) -> sp.Expr:
531531
# y >= x => not(x < y) => not(x - y < 0) => not r < 0
532532
return sp.Heaviside(root)
533533

534+
# rewrite n-ary XOR to OR to be handled below:
535+
trigger = trigger.replace(sp.Xor, _xor_to_or)
536+
537+
# TODO: x == y
538+
534539
# or(x,y) = not(and(not(x),not(y))
535540
if isinstance(trigger, sp.Or):
536541
return sp.Integer(1) - sp.Mul(
@@ -540,8 +545,6 @@ def _parse_heaviside_trigger(trigger: sp.Expr) -> sp.Expr:
540545
]
541546
)
542547

543-
# TODO: x XOR y = (A & ~B) | (~A & B)
544-
# TODO: x == y
545548
if isinstance(trigger, sp.And):
546549
return sp.Mul(*[_parse_heaviside_trigger(arg) for arg in trigger.args])
547550

@@ -551,6 +554,25 @@ def _parse_heaviside_trigger(trigger: sp.Expr) -> sp.Expr:
551554
)
552555

553556

557+
def _xor_to_or(*args):
558+
"""
559+
Replace XOR by OR expression.
560+
561+
``xor(x, y, ...) = (x & ~y & ...) | (~x & y & ...) | ...``.
562+
563+
to be used in ``trigger = trigger.replace(sp.Xor, _xor_to_or)``.
564+
"""
565+
res = sp.false
566+
for i in range(len(args)):
567+
res = sp.Or(
568+
res,
569+
sp.And(
570+
*(arg if i == j else sp.Not(arg) for j, arg in enumerate(args))
571+
),
572+
)
573+
return res.simplify()
574+
575+
554576
def grouper(
555577
iterable: Iterable, n: int, fillvalue: Any = None
556578
) -> Iterable[tuple[Any]]:

python/sdist/amici/sbml_import.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
_default_simplify,
5353
generate_flux_symbol,
5454
_parse_piecewise_to_heaviside,
55+
_xor_to_or,
5556
)
5657
from .logging import get_logger, log_execution_time, set_log_level
5758
from .sbml_utils import SBMLException
@@ -2976,11 +2977,6 @@ def subs_locals(expr: sp.Basic) -> sp.Basic:
29762977
f"Unsupported input: {var_or_math}, type: {type(var_or_math)}"
29772978
)
29782979

2979-
if expr.has(sp.Xor):
2980-
raise SBMLException(
2981-
"Xor is currently not supported as logical operation."
2982-
)
2983-
29842980
try:
29852981
_check_unsupported_functions_sbml(expr, expression_type=ele_name)
29862982
except SBMLException:
@@ -3218,6 +3214,9 @@ def _parse_event_trigger(trigger: sp.Expr) -> sp.Expr:
32183214
# y >= x or y > x
32193215
return root
32203216

3217+
# rewrite n-ary XOR to OR to be handled below:
3218+
trigger = trigger.replace(sp.Xor, _xor_to_or)
3219+
32213220
# or(x,y): any of {x,y} is > 0: sp.Max(x, y)
32223221
if isinstance(trigger, sp.Or):
32233222
return sp.Max(*[_parse_event_trigger(arg) for arg in trigger.args])

0 commit comments

Comments
 (0)