I am looking for a way to automatically determine, e.g., that (a < 12) & (a < 3) & (c >= 4)
is the same as (a < 3) & (c >= 4)
. I looked into Matlab's symbolic toolbox and SymPy in Python, but these are apparently only capable of simplifying purely Boolean logic (e.g., simplify(a & b | b & a) -> ans=(a & b)
)
Is there a way of using these symbolic math tools like described above?
Edit
As noted in the comment to @user12750353's answer, I would like to also simplify systems of relations that are concatenated with a Boolean OR, e.g., ((a < 12) & (a < 3) & (c >= 4)) | (a < 1)
.
SymPy sets can be used to do univariate simplification, e.g. ((x < 3) & (x < 5)).as_set() -> Interval.open(-oo, 3)
and sets can be converted back to relationals. The following converts a complex expression to cnf form, separates args with respect to free symbols and simplifies those that are univariate while leaving multivariate arguments unchanged.
def f(eq):
from collections import defaultdict
from sympy import to_cnf, ordered
cnf = to_cnf(eq)
args = defaultdict(list)
for a in cnf.args:
args[tuple(ordered(a.free_symbols))].append(a)
_args = []
for k in args:
if len(k) == 1:
_args.append(cnf.func(*args[k]).as_set().as_relational(k[0]))
else:
_args.append(cnf.func(*args[k]))
return cnf.func(*_args)
For example:
>>> from sympy.abc import a, c
>>> f((a < 1) | ((c >= 4) & (a < 3) & (a < 12)))
(a < 3) & ((c >= 4) | (a < 1))
In case you have oo
or -oo
showing up in the results and you want such relationals to be True, you can just replace all such relationals with True or else make your variables real:
>>> from sympy import Dummy
>>> from sympy.abc import x
>>> (x < 3).as_set().as_relational(x)
(-oo < x) & (x < 3)
>>> _.replace(
... lambda x: x.is_Relational and x.has(
... S.Infinity, S.NegativeInfinity),
... lambda x: True)
...
x < 3
>>> r = Dummy(real=True)
>>> (x < 3).as_set().as_relational(r).subs(r, x)
x < 3