Spaces:
Sleeping
Sleeping
| """ | |
| Boolean algebra module for SymPy | |
| """ | |
| from collections import defaultdict | |
| from itertools import chain, combinations, product, permutations | |
| from sympy.core.add import Add | |
| from sympy.core.basic import Basic | |
| from sympy.core.cache import cacheit | |
| from sympy.core.containers import Tuple | |
| from sympy.core.decorators import sympify_method_args, sympify_return | |
| from sympy.core.function import Application, Derivative | |
| from sympy.core.kind import BooleanKind, NumberKind | |
| from sympy.core.numbers import Number | |
| from sympy.core.operations import LatticeOp | |
| from sympy.core.singleton import Singleton, S | |
| from sympy.core.sorting import ordered | |
| from sympy.core.sympify import _sympy_converter, _sympify, sympify | |
| from sympy.utilities.iterables import sift, ibin | |
| from sympy.utilities.misc import filldedent | |
| def as_Boolean(e): | |
| """Like ``bool``, return the Boolean value of an expression, e, | |
| which can be any instance of :py:class:`~.Boolean` or ``bool``. | |
| Examples | |
| ======== | |
| >>> from sympy import true, false, nan | |
| >>> from sympy.logic.boolalg import as_Boolean | |
| >>> from sympy.abc import x | |
| >>> as_Boolean(0) is false | |
| True | |
| >>> as_Boolean(1) is true | |
| True | |
| >>> as_Boolean(x) | |
| x | |
| >>> as_Boolean(2) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: expecting bool or Boolean, not `2`. | |
| >>> as_Boolean(nan) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: expecting bool or Boolean, not `nan`. | |
| """ | |
| from sympy.core.symbol import Symbol | |
| if e == True: | |
| return true | |
| if e == False: | |
| return false | |
| if isinstance(e, Symbol): | |
| z = e.is_zero | |
| if z is None: | |
| return e | |
| return false if z else true | |
| if isinstance(e, Boolean): | |
| return e | |
| raise TypeError('expecting bool or Boolean, not `%s`.' % e) | |
| class Boolean(Basic): | |
| """A Boolean object is an object for which logic operations make sense.""" | |
| __slots__ = () | |
| kind = BooleanKind | |
| def __and__(self, other): | |
| return And(self, other) | |
| __rand__ = __and__ | |
| def __or__(self, other): | |
| return Or(self, other) | |
| __ror__ = __or__ | |
| def __invert__(self): | |
| """Overloading for ~""" | |
| return Not(self) | |
| def __rshift__(self, other): | |
| return Implies(self, other) | |
| def __lshift__(self, other): | |
| return Implies(other, self) | |
| __rrshift__ = __lshift__ | |
| __rlshift__ = __rshift__ | |
| def __xor__(self, other): | |
| return Xor(self, other) | |
| __rxor__ = __xor__ | |
| def equals(self, other): | |
| """ | |
| Returns ``True`` if the given formulas have the same truth table. | |
| For two formulas to be equal they must have the same literals. | |
| Examples | |
| ======== | |
| >>> from sympy.abc import A, B, C | |
| >>> from sympy import And, Or, Not | |
| >>> (A >> B).equals(~B >> ~A) | |
| True | |
| >>> Not(And(A, B, C)).equals(And(Not(A), Not(B), Not(C))) | |
| False | |
| >>> Not(And(A, Not(A))).equals(Or(B, Not(B))) | |
| False | |
| """ | |
| from sympy.logic.inference import satisfiable | |
| from sympy.core.relational import Relational | |
| if self.has(Relational) or other.has(Relational): | |
| raise NotImplementedError('handling of relationals') | |
| return self.atoms() == other.atoms() and \ | |
| not satisfiable(Not(Equivalent(self, other))) | |
| def to_nnf(self, simplify=True): | |
| # override where necessary | |
| return self | |
| def as_set(self): | |
| """ | |
| Rewrites Boolean expression in terms of real sets. | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol, Eq, Or, And | |
| >>> x = Symbol('x', real=True) | |
| >>> Eq(x, 0).as_set() | |
| {0} | |
| >>> (x > 0).as_set() | |
| Interval.open(0, oo) | |
| >>> And(-2 < x, x < 2).as_set() | |
| Interval.open(-2, 2) | |
| >>> Or(x < -2, 2 < x).as_set() | |
| Union(Interval.open(-oo, -2), Interval.open(2, oo)) | |
| """ | |
| from sympy.calculus.util import periodicity | |
| from sympy.core.relational import Relational | |
| free = self.free_symbols | |
| if len(free) == 1: | |
| x = free.pop() | |
| if x.kind is NumberKind: | |
| reps = {} | |
| for r in self.atoms(Relational): | |
| if periodicity(r, x) not in (0, None): | |
| s = r._eval_as_set() | |
| if s in (S.EmptySet, S.UniversalSet, S.Reals): | |
| reps[r] = s.as_relational(x) | |
| continue | |
| raise NotImplementedError(filldedent(''' | |
| as_set is not implemented for relationals | |
| with periodic solutions | |
| ''')) | |
| new = self.subs(reps) | |
| if new.func != self.func: | |
| return new.as_set() # restart with new obj | |
| else: | |
| return new._eval_as_set() | |
| return self._eval_as_set() | |
| else: | |
| raise NotImplementedError("Sorry, as_set has not yet been" | |
| " implemented for multivariate" | |
| " expressions") | |
| def binary_symbols(self): | |
| from sympy.core.relational import Eq, Ne | |
| return set().union(*[i.binary_symbols for i in self.args | |
| if i.is_Boolean or i.is_Symbol | |
| or isinstance(i, (Eq, Ne))]) | |
| def _eval_refine(self, assumptions): | |
| from sympy.assumptions import ask | |
| ret = ask(self, assumptions) | |
| if ret is True: | |
| return true | |
| elif ret is False: | |
| return false | |
| return None | |
| class BooleanAtom(Boolean): | |
| """ | |
| Base class of :py:class:`~.BooleanTrue` and :py:class:`~.BooleanFalse`. | |
| """ | |
| is_Boolean = True | |
| is_Atom = True | |
| _op_priority = 11 # higher than Expr | |
| def simplify(self, *a, **kw): | |
| return self | |
| def expand(self, *a, **kw): | |
| return self | |
| def canonical(self): | |
| return self | |
| def _noop(self, other=None): | |
| raise TypeError('BooleanAtom not allowed in this context.') | |
| __add__ = _noop | |
| __radd__ = _noop | |
| __sub__ = _noop | |
| __rsub__ = _noop | |
| __mul__ = _noop | |
| __rmul__ = _noop | |
| __pow__ = _noop | |
| __rpow__ = _noop | |
| __truediv__ = _noop | |
| __rtruediv__ = _noop | |
| __mod__ = _noop | |
| __rmod__ = _noop | |
| _eval_power = _noop | |
| # /// drop when Py2 is no longer supported | |
| def __lt__(self, other): | |
| raise TypeError(filldedent(''' | |
| A Boolean argument can only be used in | |
| Eq and Ne; all other relationals expect | |
| real expressions. | |
| ''')) | |
| __le__ = __lt__ | |
| __gt__ = __lt__ | |
| __ge__ = __lt__ | |
| # \\\ | |
| def _eval_simplify(self, **kwargs): | |
| return self | |
| class BooleanTrue(BooleanAtom, metaclass=Singleton): | |
| """ | |
| SymPy version of ``True``, a singleton that can be accessed via ``S.true``. | |
| This is the SymPy version of ``True``, for use in the logic module. The | |
| primary advantage of using ``true`` instead of ``True`` is that shorthand Boolean | |
| operations like ``~`` and ``>>`` will work as expected on this class, whereas with | |
| True they act bitwise on 1. Functions in the logic module will return this | |
| class when they evaluate to true. | |
| Notes | |
| ===== | |
| There is liable to be some confusion as to when ``True`` should | |
| be used and when ``S.true`` should be used in various contexts | |
| throughout SymPy. An important thing to remember is that | |
| ``sympify(True)`` returns ``S.true``. This means that for the most | |
| part, you can just use ``True`` and it will automatically be converted | |
| to ``S.true`` when necessary, similar to how you can generally use 1 | |
| instead of ``S.One``. | |
| The rule of thumb is: | |
| "If the boolean in question can be replaced by an arbitrary symbolic | |
| ``Boolean``, like ``Or(x, y)`` or ``x > 1``, use ``S.true``. | |
| Otherwise, use ``True``" | |
| In other words, use ``S.true`` only on those contexts where the | |
| boolean is being used as a symbolic representation of truth. | |
| For example, if the object ends up in the ``.args`` of any expression, | |
| then it must necessarily be ``S.true`` instead of ``True``, as | |
| elements of ``.args`` must be ``Basic``. On the other hand, | |
| ``==`` is not a symbolic operation in SymPy, since it always returns | |
| ``True`` or ``False``, and does so in terms of structural equality | |
| rather than mathematical, so it should return ``True``. The assumptions | |
| system should use ``True`` and ``False``. Aside from not satisfying | |
| the above rule of thumb, the assumptions system uses a three-valued logic | |
| (``True``, ``False``, ``None``), whereas ``S.true`` and ``S.false`` | |
| represent a two-valued logic. When in doubt, use ``True``. | |
| "``S.true == True is True``." | |
| While "``S.true is True``" is ``False``, "``S.true == True``" | |
| is ``True``, so if there is any doubt over whether a function or | |
| expression will return ``S.true`` or ``True``, just use ``==`` | |
| instead of ``is`` to do the comparison, and it will work in either | |
| case. Finally, for boolean flags, it's better to just use ``if x`` | |
| instead of ``if x is True``. To quote PEP 8: | |
| Do not compare boolean values to ``True`` or ``False`` | |
| using ``==``. | |
| * Yes: ``if greeting:`` | |
| * No: ``if greeting == True:`` | |
| * Worse: ``if greeting is True:`` | |
| Examples | |
| ======== | |
| >>> from sympy import sympify, true, false, Or | |
| >>> sympify(True) | |
| True | |
| >>> _ is True, _ is true | |
| (False, True) | |
| >>> Or(true, false) | |
| True | |
| >>> _ is true | |
| True | |
| Python operators give a boolean result for true but a | |
| bitwise result for True | |
| >>> ~true, ~True # doctest: +SKIP | |
| (False, -2) | |
| >>> true >> true, True >> True | |
| (True, 0) | |
| See Also | |
| ======== | |
| sympy.logic.boolalg.BooleanFalse | |
| """ | |
| def __bool__(self): | |
| return True | |
| def __hash__(self): | |
| return hash(True) | |
| def __eq__(self, other): | |
| if other is True: | |
| return True | |
| if other is False: | |
| return False | |
| return super().__eq__(other) | |
| def negated(self): | |
| return false | |
| def as_set(self): | |
| """ | |
| Rewrite logic operators and relationals in terms of real sets. | |
| Examples | |
| ======== | |
| >>> from sympy import true | |
| >>> true.as_set() | |
| UniversalSet | |
| """ | |
| return S.UniversalSet | |
| class BooleanFalse(BooleanAtom, metaclass=Singleton): | |
| """ | |
| SymPy version of ``False``, a singleton that can be accessed via ``S.false``. | |
| This is the SymPy version of ``False``, for use in the logic module. The | |
| primary advantage of using ``false`` instead of ``False`` is that shorthand | |
| Boolean operations like ``~`` and ``>>`` will work as expected on this class, | |
| whereas with ``False`` they act bitwise on 0. Functions in the logic module | |
| will return this class when they evaluate to false. | |
| Notes | |
| ====== | |
| See the notes section in :py:class:`sympy.logic.boolalg.BooleanTrue` | |
| Examples | |
| ======== | |
| >>> from sympy import sympify, true, false, Or | |
| >>> sympify(False) | |
| False | |
| >>> _ is False, _ is false | |
| (False, True) | |
| >>> Or(true, false) | |
| True | |
| >>> _ is true | |
| True | |
| Python operators give a boolean result for false but a | |
| bitwise result for False | |
| >>> ~false, ~False # doctest: +SKIP | |
| (True, -1) | |
| >>> false >> false, False >> False | |
| (True, 0) | |
| See Also | |
| ======== | |
| sympy.logic.boolalg.BooleanTrue | |
| """ | |
| def __bool__(self): | |
| return False | |
| def __hash__(self): | |
| return hash(False) | |
| def __eq__(self, other): | |
| if other is True: | |
| return False | |
| if other is False: | |
| return True | |
| return super().__eq__(other) | |
| def negated(self): | |
| return true | |
| def as_set(self): | |
| """ | |
| Rewrite logic operators and relationals in terms of real sets. | |
| Examples | |
| ======== | |
| >>> from sympy import false | |
| >>> false.as_set() | |
| EmptySet | |
| """ | |
| return S.EmptySet | |
| true = BooleanTrue() | |
| false = BooleanFalse() | |
| # We want S.true and S.false to work, rather than S.BooleanTrue and | |
| # S.BooleanFalse, but making the class and instance names the same causes some | |
| # major issues (like the inability to import the class directly from this | |
| # file). | |
| S.true = true | |
| S.false = false | |
| _sympy_converter[bool] = lambda x: true if x else false | |
| class BooleanFunction(Application, Boolean): | |
| """Boolean function is a function that lives in a boolean space | |
| It is used as base class for :py:class:`~.And`, :py:class:`~.Or`, | |
| :py:class:`~.Not`, etc. | |
| """ | |
| is_Boolean = True | |
| def _eval_simplify(self, **kwargs): | |
| rv = simplify_univariate(self) | |
| if not isinstance(rv, BooleanFunction): | |
| return rv.simplify(**kwargs) | |
| rv = rv.func(*[a.simplify(**kwargs) for a in rv.args]) | |
| return simplify_logic(rv) | |
| def simplify(self, **kwargs): | |
| from sympy.simplify.simplify import simplify | |
| return simplify(self, **kwargs) | |
| def __lt__(self, other): | |
| raise TypeError(filldedent(''' | |
| A Boolean argument can only be used in | |
| Eq and Ne; all other relationals expect | |
| real expressions. | |
| ''')) | |
| __le__ = __lt__ | |
| __ge__ = __lt__ | |
| __gt__ = __lt__ | |
| def binary_check_and_simplify(self, *args): | |
| return [as_Boolean(i) for i in args] | |
| def to_nnf(self, simplify=True): | |
| return self._to_nnf(*self.args, simplify=simplify) | |
| def to_anf(self, deep=True): | |
| return self._to_anf(*self.args, deep=deep) | |
| def _to_nnf(cls, *args, **kwargs): | |
| simplify = kwargs.get('simplify', True) | |
| argset = set() | |
| for arg in args: | |
| if not is_literal(arg): | |
| arg = arg.to_nnf(simplify) | |
| if simplify: | |
| if isinstance(arg, cls): | |
| arg = arg.args | |
| else: | |
| arg = (arg,) | |
| for a in arg: | |
| if Not(a) in argset: | |
| return cls.zero | |
| argset.add(a) | |
| else: | |
| argset.add(arg) | |
| return cls(*argset) | |
| def _to_anf(cls, *args, **kwargs): | |
| deep = kwargs.get('deep', True) | |
| new_args = [] | |
| for arg in args: | |
| if deep: | |
| if not is_literal(arg) or isinstance(arg, Not): | |
| arg = arg.to_anf(deep=deep) | |
| new_args.append(arg) | |
| return cls(*new_args, remove_true=False) | |
| # the diff method below is copied from Expr class | |
| def diff(self, *symbols, **assumptions): | |
| assumptions.setdefault("evaluate", True) | |
| return Derivative(self, *symbols, **assumptions) | |
| def _eval_derivative(self, x): | |
| if x in self.binary_symbols: | |
| from sympy.core.relational import Eq | |
| from sympy.functions.elementary.piecewise import Piecewise | |
| return Piecewise( | |
| (0, Eq(self.subs(x, 0), self.subs(x, 1))), | |
| (1, True)) | |
| elif x in self.free_symbols: | |
| # not implemented, see https://www.encyclopediaofmath.org/ | |
| # index.php/Boolean_differential_calculus | |
| pass | |
| else: | |
| return S.Zero | |
| class And(LatticeOp, BooleanFunction): | |
| """ | |
| Logical AND function. | |
| It evaluates its arguments in order, returning false immediately | |
| when an argument is false and true if they are all true. | |
| Examples | |
| ======== | |
| >>> from sympy.abc import x, y | |
| >>> from sympy import And | |
| >>> x & y | |
| x & y | |
| Notes | |
| ===== | |
| The ``&`` operator is provided as a convenience, but note that its use | |
| here is different from its normal use in Python, which is bitwise | |
| and. Hence, ``And(a, b)`` and ``a & b`` will produce different results if | |
| ``a`` and ``b`` are integers. | |
| >>> And(x, y).subs(x, 1) | |
| y | |
| """ | |
| zero = false | |
| identity = true | |
| nargs = None | |
| def _new_args_filter(cls, args): | |
| args = BooleanFunction.binary_check_and_simplify(*args) | |
| args = LatticeOp._new_args_filter(args, And) | |
| newargs = [] | |
| rel = set() | |
| for x in ordered(args): | |
| if x.is_Relational: | |
| c = x.canonical | |
| if c in rel: | |
| continue | |
| elif c.negated.canonical in rel: | |
| return [false] | |
| else: | |
| rel.add(c) | |
| newargs.append(x) | |
| return newargs | |
| def _eval_subs(self, old, new): | |
| args = [] | |
| bad = None | |
| for i in self.args: | |
| try: | |
| i = i.subs(old, new) | |
| except TypeError: | |
| # store TypeError | |
| if bad is None: | |
| bad = i | |
| continue | |
| if i == False: | |
| return false | |
| elif i != True: | |
| args.append(i) | |
| if bad is not None: | |
| # let it raise | |
| bad.subs(old, new) | |
| # If old is And, replace the parts of the arguments with new if all | |
| # are there | |
| if isinstance(old, And): | |
| old_set = set(old.args) | |
| if old_set.issubset(args): | |
| args = set(args) - old_set | |
| args.add(new) | |
| return self.func(*args) | |
| def _eval_simplify(self, **kwargs): | |
| from sympy.core.relational import Equality, Relational | |
| from sympy.solvers.solveset import linear_coeffs | |
| # standard simplify | |
| rv = super()._eval_simplify(**kwargs) | |
| if not isinstance(rv, And): | |
| return rv | |
| # simplify args that are equalities involving | |
| # symbols so x == 0 & x == y -> x==0 & y == 0 | |
| Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational), | |
| binary=True) | |
| if not Rel: | |
| return rv | |
| eqs, other = sift(Rel, lambda i: isinstance(i, Equality), binary=True) | |
| measure = kwargs['measure'] | |
| if eqs: | |
| ratio = kwargs['ratio'] | |
| reps = {} | |
| sifted = {} | |
| # group by length of free symbols | |
| sifted = sift(ordered([ | |
| (i.free_symbols, i) for i in eqs]), | |
| lambda x: len(x[0])) | |
| eqs = [] | |
| nonlineqs = [] | |
| while 1 in sifted: | |
| for free, e in sifted.pop(1): | |
| x = free.pop() | |
| if (e.lhs != x or x in e.rhs.free_symbols) and x not in reps: | |
| try: | |
| m, b = linear_coeffs( | |
| Add(e.lhs, -e.rhs, evaluate=False), x) | |
| enew = e.func(x, -b/m) | |
| if measure(enew) <= ratio*measure(e): | |
| e = enew | |
| else: | |
| eqs.append(e) | |
| continue | |
| except ValueError: | |
| pass | |
| if x in reps: | |
| eqs.append(e.subs(x, reps[x])) | |
| elif e.lhs == x and x not in e.rhs.free_symbols: | |
| reps[x] = e.rhs | |
| eqs.append(e) | |
| else: | |
| # x is not yet identified, but may be later | |
| nonlineqs.append(e) | |
| resifted = defaultdict(list) | |
| for k in sifted: | |
| for f, e in sifted[k]: | |
| e = e.xreplace(reps) | |
| f = e.free_symbols | |
| resifted[len(f)].append((f, e)) | |
| sifted = resifted | |
| for k in sifted: | |
| eqs.extend([e for f, e in sifted[k]]) | |
| nonlineqs = [ei.subs(reps) for ei in nonlineqs] | |
| other = [ei.subs(reps) for ei in other] | |
| rv = rv.func(*([i.canonical for i in (eqs + nonlineqs + other)] + nonRel)) | |
| patterns = _simplify_patterns_and() | |
| threeterm_patterns = _simplify_patterns_and3() | |
| return _apply_patternbased_simplification(rv, patterns, | |
| measure, false, | |
| threeterm_patterns=threeterm_patterns) | |
| def _eval_as_set(self): | |
| from sympy.sets.sets import Intersection | |
| return Intersection(*[arg.as_set() for arg in self.args]) | |
| def _eval_rewrite_as_Nor(self, *args, **kwargs): | |
| return Nor(*[Not(arg) for arg in self.args]) | |
| def to_anf(self, deep=True): | |
| if deep: | |
| result = And._to_anf(*self.args, deep=deep) | |
| return distribute_xor_over_and(result) | |
| return self | |
| class Or(LatticeOp, BooleanFunction): | |
| """ | |
| Logical OR function | |
| It evaluates its arguments in order, returning true immediately | |
| when an argument is true, and false if they are all false. | |
| Examples | |
| ======== | |
| >>> from sympy.abc import x, y | |
| >>> from sympy import Or | |
| >>> x | y | |
| x | y | |
| Notes | |
| ===== | |
| The ``|`` operator is provided as a convenience, but note that its use | |
| here is different from its normal use in Python, which is bitwise | |
| or. Hence, ``Or(a, b)`` and ``a | b`` will return different things if | |
| ``a`` and ``b`` are integers. | |
| >>> Or(x, y).subs(x, 0) | |
| y | |
| """ | |
| zero = true | |
| identity = false | |
| def _new_args_filter(cls, args): | |
| newargs = [] | |
| rel = [] | |
| args = BooleanFunction.binary_check_and_simplify(*args) | |
| for x in args: | |
| if x.is_Relational: | |
| c = x.canonical | |
| if c in rel: | |
| continue | |
| nc = c.negated.canonical | |
| if any(r == nc for r in rel): | |
| return [true] | |
| rel.append(c) | |
| newargs.append(x) | |
| return LatticeOp._new_args_filter(newargs, Or) | |
| def _eval_subs(self, old, new): | |
| args = [] | |
| bad = None | |
| for i in self.args: | |
| try: | |
| i = i.subs(old, new) | |
| except TypeError: | |
| # store TypeError | |
| if bad is None: | |
| bad = i | |
| continue | |
| if i == True: | |
| return true | |
| elif i != False: | |
| args.append(i) | |
| if bad is not None: | |
| # let it raise | |
| bad.subs(old, new) | |
| # If old is Or, replace the parts of the arguments with new if all | |
| # are there | |
| if isinstance(old, Or): | |
| old_set = set(old.args) | |
| if old_set.issubset(args): | |
| args = set(args) - old_set | |
| args.add(new) | |
| return self.func(*args) | |
| def _eval_as_set(self): | |
| from sympy.sets.sets import Union | |
| return Union(*[arg.as_set() for arg in self.args]) | |
| def _eval_rewrite_as_Nand(self, *args, **kwargs): | |
| return Nand(*[Not(arg) for arg in self.args]) | |
| def _eval_simplify(self, **kwargs): | |
| from sympy.core.relational import Le, Ge, Eq | |
| lege = self.atoms(Le, Ge) | |
| if lege: | |
| reps = {i: self.func( | |
| Eq(i.lhs, i.rhs), i.strict) for i in lege} | |
| return self.xreplace(reps)._eval_simplify(**kwargs) | |
| # standard simplify | |
| rv = super()._eval_simplify(**kwargs) | |
| if not isinstance(rv, Or): | |
| return rv | |
| patterns = _simplify_patterns_or() | |
| return _apply_patternbased_simplification(rv, patterns, | |
| kwargs['measure'], true) | |
| def to_anf(self, deep=True): | |
| args = range(1, len(self.args) + 1) | |
| args = (combinations(self.args, j) for j in args) | |
| args = chain.from_iterable(args) # powerset | |
| args = (And(*arg) for arg in args) | |
| args = (to_anf(x, deep=deep) if deep else x for x in args) | |
| return Xor(*list(args), remove_true=False) | |
| class Not(BooleanFunction): | |
| """ | |
| Logical Not function (negation) | |
| Returns ``true`` if the statement is ``false`` or ``False``. | |
| Returns ``false`` if the statement is ``true`` or ``True``. | |
| Examples | |
| ======== | |
| >>> from sympy import Not, And, Or | |
| >>> from sympy.abc import x, A, B | |
| >>> Not(True) | |
| False | |
| >>> Not(False) | |
| True | |
| >>> Not(And(True, False)) | |
| True | |
| >>> Not(Or(True, False)) | |
| False | |
| >>> Not(And(And(True, x), Or(x, False))) | |
| ~x | |
| >>> ~x | |
| ~x | |
| >>> Not(And(Or(A, B), Or(~A, ~B))) | |
| ~((A | B) & (~A | ~B)) | |
| Notes | |
| ===== | |
| - The ``~`` operator is provided as a convenience, but note that its use | |
| here is different from its normal use in Python, which is bitwise | |
| not. In particular, ``~a`` and ``Not(a)`` will be different if ``a`` is | |
| an integer. Furthermore, since bools in Python subclass from ``int``, | |
| ``~True`` is the same as ``~1`` which is ``-2``, which has a boolean | |
| value of True. To avoid this issue, use the SymPy boolean types | |
| ``true`` and ``false``. | |
| - As of Python 3.12, the bitwise not operator ``~`` used on a | |
| Python ``bool`` is deprecated and will emit a warning. | |
| >>> from sympy import true | |
| >>> ~True # doctest: +SKIP | |
| -2 | |
| >>> ~true | |
| False | |
| """ | |
| is_Not = True | |
| def eval(cls, arg): | |
| if isinstance(arg, Number) or arg in (True, False): | |
| return false if arg else true | |
| if arg.is_Not: | |
| return arg.args[0] | |
| # Simplify Relational objects. | |
| if arg.is_Relational: | |
| return arg.negated | |
| def _eval_as_set(self): | |
| """ | |
| Rewrite logic operators and relationals in terms of real sets. | |
| Examples | |
| ======== | |
| >>> from sympy import Not, Symbol | |
| >>> x = Symbol('x') | |
| >>> Not(x > 0).as_set() | |
| Interval(-oo, 0) | |
| """ | |
| return self.args[0].as_set().complement(S.Reals) | |
| def to_nnf(self, simplify=True): | |
| if is_literal(self): | |
| return self | |
| expr = self.args[0] | |
| func, args = expr.func, expr.args | |
| if func == And: | |
| return Or._to_nnf(*[Not(arg) for arg in args], simplify=simplify) | |
| if func == Or: | |
| return And._to_nnf(*[Not(arg) for arg in args], simplify=simplify) | |
| if func == Implies: | |
| a, b = args | |
| return And._to_nnf(a, Not(b), simplify=simplify) | |
| if func == Equivalent: | |
| return And._to_nnf(Or(*args), Or(*[Not(arg) for arg in args]), | |
| simplify=simplify) | |
| if func == Xor: | |
| result = [] | |
| for i in range(1, len(args)+1, 2): | |
| for neg in combinations(args, i): | |
| clause = [Not(s) if s in neg else s for s in args] | |
| result.append(Or(*clause)) | |
| return And._to_nnf(*result, simplify=simplify) | |
| if func == ITE: | |
| a, b, c = args | |
| return And._to_nnf(Or(a, Not(c)), Or(Not(a), Not(b)), simplify=simplify) | |
| raise ValueError("Illegal operator %s in expression" % func) | |
| def to_anf(self, deep=True): | |
| return Xor._to_anf(true, self.args[0], deep=deep) | |
| class Xor(BooleanFunction): | |
| """ | |
| Logical XOR (exclusive OR) function. | |
| Returns True if an odd number of the arguments are True and the rest are | |
| False. | |
| Returns False if an even number of the arguments are True and the rest are | |
| False. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Xor | |
| >>> from sympy import symbols | |
| >>> x, y = symbols('x y') | |
| >>> Xor(True, False) | |
| True | |
| >>> Xor(True, True) | |
| False | |
| >>> Xor(True, False, True, True, False) | |
| True | |
| >>> Xor(True, False, True, False) | |
| False | |
| >>> x ^ y | |
| x ^ y | |
| Notes | |
| ===== | |
| The ``^`` operator is provided as a convenience, but note that its use | |
| here is different from its normal use in Python, which is bitwise xor. In | |
| particular, ``a ^ b`` and ``Xor(a, b)`` will be different if ``a`` and | |
| ``b`` are integers. | |
| >>> Xor(x, y).subs(y, 0) | |
| x | |
| """ | |
| def __new__(cls, *args, remove_true=True, **kwargs): | |
| argset = set() | |
| obj = super().__new__(cls, *args, **kwargs) | |
| for arg in obj._args: | |
| if isinstance(arg, Number) or arg in (True, False): | |
| if arg: | |
| arg = true | |
| else: | |
| continue | |
| if isinstance(arg, Xor): | |
| for a in arg.args: | |
| argset.remove(a) if a in argset else argset.add(a) | |
| elif arg in argset: | |
| argset.remove(arg) | |
| else: | |
| argset.add(arg) | |
| rel = [(r, r.canonical, r.negated.canonical) | |
| for r in argset if r.is_Relational] | |
| odd = False # is number of complimentary pairs odd? start 0 -> False | |
| remove = [] | |
| for i, (r, c, nc) in enumerate(rel): | |
| for j in range(i + 1, len(rel)): | |
| rj, cj = rel[j][:2] | |
| if cj == nc: | |
| odd = not odd | |
| break | |
| elif cj == c: | |
| break | |
| else: | |
| continue | |
| remove.append((r, rj)) | |
| if odd: | |
| argset.remove(true) if true in argset else argset.add(true) | |
| for a, b in remove: | |
| argset.remove(a) | |
| argset.remove(b) | |
| if len(argset) == 0: | |
| return false | |
| elif len(argset) == 1: | |
| return argset.pop() | |
| elif True in argset and remove_true: | |
| argset.remove(True) | |
| return Not(Xor(*argset)) | |
| else: | |
| obj._args = tuple(ordered(argset)) | |
| obj._argset = frozenset(argset) | |
| return obj | |
| # XXX: This should be cached on the object rather than using cacheit | |
| # Maybe it can be computed in __new__? | |
| # type: ignore | |
| def args(self): | |
| return tuple(ordered(self._argset)) | |
| def to_nnf(self, simplify=True): | |
| args = [] | |
| for i in range(0, len(self.args)+1, 2): | |
| for neg in combinations(self.args, i): | |
| clause = [Not(s) if s in neg else s for s in self.args] | |
| args.append(Or(*clause)) | |
| return And._to_nnf(*args, simplify=simplify) | |
| def _eval_rewrite_as_Or(self, *args, **kwargs): | |
| a = self.args | |
| return Or(*[_convert_to_varsSOP(x, self.args) | |
| for x in _get_odd_parity_terms(len(a))]) | |
| def _eval_rewrite_as_And(self, *args, **kwargs): | |
| a = self.args | |
| return And(*[_convert_to_varsPOS(x, self.args) | |
| for x in _get_even_parity_terms(len(a))]) | |
| def _eval_simplify(self, **kwargs): | |
| # as standard simplify uses simplify_logic which writes things as | |
| # And and Or, we only simplify the partial expressions before using | |
| # patterns | |
| rv = self.func(*[a.simplify(**kwargs) for a in self.args]) | |
| rv = rv.to_anf() | |
| if not isinstance(rv, Xor): # This shouldn't really happen here | |
| return rv | |
| patterns = _simplify_patterns_xor() | |
| return _apply_patternbased_simplification(rv, patterns, | |
| kwargs['measure'], None) | |
| def _eval_subs(self, old, new): | |
| # If old is Xor, replace the parts of the arguments with new if all | |
| # are there | |
| if isinstance(old, Xor): | |
| old_set = set(old.args) | |
| if old_set.issubset(self.args): | |
| args = set(self.args) - old_set | |
| args.add(new) | |
| return self.func(*args) | |
| class Nand(BooleanFunction): | |
| """ | |
| Logical NAND function. | |
| It evaluates its arguments in order, giving True immediately if any | |
| of them are False, and False if they are all True. | |
| Returns True if any of the arguments are False | |
| Returns False if all arguments are True | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Nand | |
| >>> from sympy import symbols | |
| >>> x, y = symbols('x y') | |
| >>> Nand(False, True) | |
| True | |
| >>> Nand(True, True) | |
| False | |
| >>> Nand(x, y) | |
| ~(x & y) | |
| """ | |
| def eval(cls, *args): | |
| return Not(And(*args)) | |
| class Nor(BooleanFunction): | |
| """ | |
| Logical NOR function. | |
| It evaluates its arguments in order, giving False immediately if any | |
| of them are True, and True if they are all False. | |
| Returns False if any argument is True | |
| Returns True if all arguments are False | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Nor | |
| >>> from sympy import symbols | |
| >>> x, y = symbols('x y') | |
| >>> Nor(True, False) | |
| False | |
| >>> Nor(True, True) | |
| False | |
| >>> Nor(False, True) | |
| False | |
| >>> Nor(False, False) | |
| True | |
| >>> Nor(x, y) | |
| ~(x | y) | |
| """ | |
| def eval(cls, *args): | |
| return Not(Or(*args)) | |
| class Xnor(BooleanFunction): | |
| """ | |
| Logical XNOR function. | |
| Returns False if an odd number of the arguments are True and the rest are | |
| False. | |
| Returns True if an even number of the arguments are True and the rest are | |
| False. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Xnor | |
| >>> from sympy import symbols | |
| >>> x, y = symbols('x y') | |
| >>> Xnor(True, False) | |
| False | |
| >>> Xnor(True, True) | |
| True | |
| >>> Xnor(True, False, True, True, False) | |
| False | |
| >>> Xnor(True, False, True, False) | |
| True | |
| """ | |
| def eval(cls, *args): | |
| return Not(Xor(*args)) | |
| class Implies(BooleanFunction): | |
| r""" | |
| Logical implication. | |
| A implies B is equivalent to if A then B. Mathematically, it is written | |
| as `A \Rightarrow B` and is equivalent to `\neg A \vee B` or ``~A | B``. | |
| Accepts two Boolean arguments; A and B. | |
| Returns False if A is True and B is False | |
| Returns True otherwise. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Implies | |
| >>> from sympy import symbols | |
| >>> x, y = symbols('x y') | |
| >>> Implies(True, False) | |
| False | |
| >>> Implies(False, False) | |
| True | |
| >>> Implies(True, True) | |
| True | |
| >>> Implies(False, True) | |
| True | |
| >>> x >> y | |
| Implies(x, y) | |
| >>> y << x | |
| Implies(x, y) | |
| Notes | |
| ===== | |
| The ``>>`` and ``<<`` operators are provided as a convenience, but note | |
| that their use here is different from their normal use in Python, which is | |
| bit shifts. Hence, ``Implies(a, b)`` and ``a >> b`` will return different | |
| things if ``a`` and ``b`` are integers. In particular, since Python | |
| considers ``True`` and ``False`` to be integers, ``True >> True`` will be | |
| the same as ``1 >> 1``, i.e., 0, which has a truth value of False. To | |
| avoid this issue, use the SymPy objects ``true`` and ``false``. | |
| >>> from sympy import true, false | |
| >>> True >> False | |
| 1 | |
| >>> true >> false | |
| False | |
| """ | |
| def eval(cls, *args): | |
| try: | |
| newargs = [] | |
| for x in args: | |
| if isinstance(x, Number) or x in (0, 1): | |
| newargs.append(bool(x)) | |
| else: | |
| newargs.append(x) | |
| A, B = newargs | |
| except ValueError: | |
| raise ValueError( | |
| "%d operand(s) used for an Implies " | |
| "(pairs are required): %s" % (len(args), str(args))) | |
| if A in (True, False) or B in (True, False): | |
| return Or(Not(A), B) | |
| elif A == B: | |
| return true | |
| elif A.is_Relational and B.is_Relational: | |
| if A.canonical == B.canonical: | |
| return true | |
| if A.negated.canonical == B.canonical: | |
| return B | |
| else: | |
| return Basic.__new__(cls, *args) | |
| def to_nnf(self, simplify=True): | |
| a, b = self.args | |
| return Or._to_nnf(Not(a), b, simplify=simplify) | |
| def to_anf(self, deep=True): | |
| a, b = self.args | |
| return Xor._to_anf(true, a, And(a, b), deep=deep) | |
| class Equivalent(BooleanFunction): | |
| """ | |
| Equivalence relation. | |
| ``Equivalent(A, B)`` is True iff A and B are both True or both False. | |
| Returns True if all of the arguments are logically equivalent. | |
| Returns False otherwise. | |
| For two arguments, this is equivalent to :py:class:`~.Xnor`. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Equivalent, And | |
| >>> from sympy.abc import x | |
| >>> Equivalent(False, False, False) | |
| True | |
| >>> Equivalent(True, False, False) | |
| False | |
| >>> Equivalent(x, And(x, True)) | |
| True | |
| """ | |
| def __new__(cls, *args, **options): | |
| from sympy.core.relational import Relational | |
| args = [_sympify(arg) for arg in args] | |
| argset = set(args) | |
| for x in args: | |
| if isinstance(x, Number) or x in [True, False]: # Includes 0, 1 | |
| argset.discard(x) | |
| argset.add(bool(x)) | |
| rel = [] | |
| for r in argset: | |
| if isinstance(r, Relational): | |
| rel.append((r, r.canonical, r.negated.canonical)) | |
| remove = [] | |
| for i, (r, c, nc) in enumerate(rel): | |
| for j in range(i + 1, len(rel)): | |
| rj, cj = rel[j][:2] | |
| if cj == nc: | |
| return false | |
| elif cj == c: | |
| remove.append((r, rj)) | |
| break | |
| for a, b in remove: | |
| argset.remove(a) | |
| argset.remove(b) | |
| argset.add(True) | |
| if len(argset) <= 1: | |
| return true | |
| if True in argset: | |
| argset.discard(True) | |
| return And(*argset) | |
| if False in argset: | |
| argset.discard(False) | |
| return And(*[Not(arg) for arg in argset]) | |
| _args = frozenset(argset) | |
| obj = super().__new__(cls, _args) | |
| obj._argset = _args | |
| return obj | |
| # XXX: This should be cached on the object rather than using cacheit | |
| # Maybe it can be computed in __new__? | |
| # type: ignore | |
| def args(self): | |
| return tuple(ordered(self._argset)) | |
| def to_nnf(self, simplify=True): | |
| args = [] | |
| for a, b in zip(self.args, self.args[1:]): | |
| args.append(Or(Not(a), b)) | |
| args.append(Or(Not(self.args[-1]), self.args[0])) | |
| return And._to_nnf(*args, simplify=simplify) | |
| def to_anf(self, deep=True): | |
| a = And(*self.args) | |
| b = And(*[to_anf(Not(arg), deep=False) for arg in self.args]) | |
| b = distribute_xor_over_and(b) | |
| return Xor._to_anf(a, b, deep=deep) | |
| class ITE(BooleanFunction): | |
| """ | |
| If-then-else clause. | |
| ``ITE(A, B, C)`` evaluates and returns the result of B if A is true | |
| else it returns the result of C. All args must be Booleans. | |
| From a logic gate perspective, ITE corresponds to a 2-to-1 multiplexer, | |
| where A is the select signal. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import ITE, And, Xor, Or | |
| >>> from sympy.abc import x, y, z | |
| >>> ITE(True, False, True) | |
| False | |
| >>> ITE(Or(True, False), And(True, True), Xor(True, True)) | |
| True | |
| >>> ITE(x, y, z) | |
| ITE(x, y, z) | |
| >>> ITE(True, x, y) | |
| x | |
| >>> ITE(False, x, y) | |
| y | |
| >>> ITE(x, y, y) | |
| y | |
| Trying to use non-Boolean args will generate a TypeError: | |
| >>> ITE(True, [], ()) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: expecting bool, Boolean or ITE, not `[]` | |
| """ | |
| def __new__(cls, *args, **kwargs): | |
| from sympy.core.relational import Eq, Ne | |
| if len(args) != 3: | |
| raise ValueError('expecting exactly 3 args') | |
| a, b, c = args | |
| # check use of binary symbols | |
| if isinstance(a, (Eq, Ne)): | |
| # in this context, we can evaluate the Eq/Ne | |
| # if one arg is a binary symbol and the other | |
| # is true/false | |
| b, c = map(as_Boolean, (b, c)) | |
| bin_syms = set().union(*[i.binary_symbols for i in (b, c)]) | |
| if len(set(a.args) - bin_syms) == 1: | |
| # one arg is a binary_symbols | |
| _a = a | |
| if a.lhs is true: | |
| a = a.rhs | |
| elif a.rhs is true: | |
| a = a.lhs | |
| elif a.lhs is false: | |
| a = Not(a.rhs) | |
| elif a.rhs is false: | |
| a = Not(a.lhs) | |
| else: | |
| # binary can only equal True or False | |
| a = false | |
| if isinstance(_a, Ne): | |
| a = Not(a) | |
| else: | |
| a, b, c = BooleanFunction.binary_check_and_simplify( | |
| a, b, c) | |
| rv = None | |
| if kwargs.get('evaluate', True): | |
| rv = cls.eval(a, b, c) | |
| if rv is None: | |
| rv = BooleanFunction.__new__(cls, a, b, c, evaluate=False) | |
| return rv | |
| def eval(cls, *args): | |
| from sympy.core.relational import Eq, Ne | |
| # do the args give a singular result? | |
| a, b, c = args | |
| if isinstance(a, (Ne, Eq)): | |
| _a = a | |
| if true in a.args: | |
| a = a.lhs if a.rhs is true else a.rhs | |
| elif false in a.args: | |
| a = Not(a.lhs) if a.rhs is false else Not(a.rhs) | |
| else: | |
| _a = None | |
| if _a is not None and isinstance(_a, Ne): | |
| a = Not(a) | |
| if a is true: | |
| return b | |
| if a is false: | |
| return c | |
| if b == c: | |
| return b | |
| else: | |
| # or maybe the results allow the answer to be expressed | |
| # in terms of the condition | |
| if b is true and c is false: | |
| return a | |
| if b is false and c is true: | |
| return Not(a) | |
| if [a, b, c] != args: | |
| return cls(a, b, c, evaluate=False) | |
| def to_nnf(self, simplify=True): | |
| a, b, c = self.args | |
| return And._to_nnf(Or(Not(a), b), Or(a, c), simplify=simplify) | |
| def _eval_as_set(self): | |
| return self.to_nnf().as_set() | |
| def _eval_rewrite_as_Piecewise(self, *args, **kwargs): | |
| from sympy.functions.elementary.piecewise import Piecewise | |
| return Piecewise((args[1], args[0]), (args[2], True)) | |
| class Exclusive(BooleanFunction): | |
| """ | |
| True if only one or no argument is true. | |
| ``Exclusive(A, B, C)`` is equivalent to ``~(A & B) & ~(A & C) & ~(B & C)``. | |
| For two arguments, this is equivalent to :py:class:`~.Xor`. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Exclusive | |
| >>> Exclusive(False, False, False) | |
| True | |
| >>> Exclusive(False, True, False) | |
| True | |
| >>> Exclusive(False, True, True) | |
| False | |
| """ | |
| def eval(cls, *args): | |
| and_args = [] | |
| for a, b in combinations(args, 2): | |
| and_args.append(Not(And(a, b))) | |
| return And(*and_args) | |
| # end class definitions. Some useful methods | |
| def conjuncts(expr): | |
| """Return a list of the conjuncts in ``expr``. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import conjuncts | |
| >>> from sympy.abc import A, B | |
| >>> conjuncts(A & B) | |
| frozenset({A, B}) | |
| >>> conjuncts(A | B) | |
| frozenset({A | B}) | |
| """ | |
| return And.make_args(expr) | |
| def disjuncts(expr): | |
| """Return a list of the disjuncts in ``expr``. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import disjuncts | |
| >>> from sympy.abc import A, B | |
| >>> disjuncts(A | B) | |
| frozenset({A, B}) | |
| >>> disjuncts(A & B) | |
| frozenset({A & B}) | |
| """ | |
| return Or.make_args(expr) | |
| def distribute_and_over_or(expr): | |
| """ | |
| Given a sentence ``expr`` consisting of conjunctions and disjunctions | |
| of literals, return an equivalent sentence in CNF. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import distribute_and_over_or, And, Or, Not | |
| >>> from sympy.abc import A, B, C | |
| >>> distribute_and_over_or(Or(A, And(Not(B), Not(C)))) | |
| (A | ~B) & (A | ~C) | |
| """ | |
| return _distribute((expr, And, Or)) | |
| def distribute_or_over_and(expr): | |
| """ | |
| Given a sentence ``expr`` consisting of conjunctions and disjunctions | |
| of literals, return an equivalent sentence in DNF. | |
| Note that the output is NOT simplified. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import distribute_or_over_and, And, Or, Not | |
| >>> from sympy.abc import A, B, C | |
| >>> distribute_or_over_and(And(Or(Not(A), B), C)) | |
| (B & C) | (C & ~A) | |
| """ | |
| return _distribute((expr, Or, And)) | |
| def distribute_xor_over_and(expr): | |
| """ | |
| Given a sentence ``expr`` consisting of conjunction and | |
| exclusive disjunctions of literals, return an | |
| equivalent exclusive disjunction. | |
| Note that the output is NOT simplified. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import distribute_xor_over_and, And, Xor, Not | |
| >>> from sympy.abc import A, B, C | |
| >>> distribute_xor_over_and(And(Xor(Not(A), B), C)) | |
| (B & C) ^ (C & ~A) | |
| """ | |
| return _distribute((expr, Xor, And)) | |
| def _distribute(info): | |
| """ | |
| Distributes ``info[1]`` over ``info[2]`` with respect to ``info[0]``. | |
| """ | |
| if isinstance(info[0], info[2]): | |
| for arg in info[0].args: | |
| if isinstance(arg, info[1]): | |
| conj = arg | |
| break | |
| else: | |
| return info[0] | |
| rest = info[2](*[a for a in info[0].args if a is not conj]) | |
| return info[1](*list(map(_distribute, | |
| [(info[2](c, rest), info[1], info[2]) | |
| for c in conj.args])), remove_true=False) | |
| elif isinstance(info[0], info[1]): | |
| return info[1](*list(map(_distribute, | |
| [(x, info[1], info[2]) | |
| for x in info[0].args])), | |
| remove_true=False) | |
| else: | |
| return info[0] | |
| def to_anf(expr, deep=True): | |
| r""" | |
| Converts expr to Algebraic Normal Form (ANF). | |
| ANF is a canonical normal form, which means that two | |
| equivalent formulas will convert to the same ANF. | |
| A logical expression is in ANF if it has the form | |
| .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc | |
| i.e. it can be: | |
| - purely true, | |
| - purely false, | |
| - conjunction of variables, | |
| - exclusive disjunction. | |
| The exclusive disjunction can only contain true, variables | |
| or conjunction of variables. No negations are permitted. | |
| If ``deep`` is ``False``, arguments of the boolean | |
| expression are considered variables, i.e. only the | |
| top-level expression is converted to ANF. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import And, Or, Not, Implies, Equivalent | |
| >>> from sympy.logic.boolalg import to_anf | |
| >>> from sympy.abc import A, B, C | |
| >>> to_anf(Not(A)) | |
| A ^ True | |
| >>> to_anf(And(Or(A, B), Not(C))) | |
| A ^ B ^ (A & B) ^ (A & C) ^ (B & C) ^ (A & B & C) | |
| >>> to_anf(Implies(Not(A), Equivalent(B, C)), deep=False) | |
| True ^ ~A ^ (~A & (Equivalent(B, C))) | |
| """ | |
| expr = sympify(expr) | |
| if is_anf(expr): | |
| return expr | |
| return expr.to_anf(deep=deep) | |
| def to_nnf(expr, simplify=True): | |
| """ | |
| Converts ``expr`` to Negation Normal Form (NNF). | |
| A logical expression is in NNF if it | |
| contains only :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`, | |
| and :py:class:`~.Not` is applied only to literals. | |
| If ``simplify`` is ``True``, the result contains no redundant clauses. | |
| Examples | |
| ======== | |
| >>> from sympy.abc import A, B, C, D | |
| >>> from sympy.logic.boolalg import Not, Equivalent, to_nnf | |
| >>> to_nnf(Not((~A & ~B) | (C & D))) | |
| (A | B) & (~C | ~D) | |
| >>> to_nnf(Equivalent(A >> B, B >> A)) | |
| (A | ~B | (A & ~B)) & (B | ~A | (B & ~A)) | |
| """ | |
| if is_nnf(expr, simplify): | |
| return expr | |
| return expr.to_nnf(simplify) | |
| def to_cnf(expr, simplify=False, force=False): | |
| """ | |
| Convert a propositional logical sentence ``expr`` to conjunctive normal | |
| form: ``((A | ~B | ...) & (B | C | ...) & ...)``. | |
| If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest CNF | |
| form using the Quine-McCluskey algorithm; this may take a long | |
| time. If there are more than 8 variables the ``force`` flag must be set | |
| to ``True`` to simplify (default is ``False``). | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import to_cnf | |
| >>> from sympy.abc import A, B, D | |
| >>> to_cnf(~(A | B) | D) | |
| (D | ~A) & (D | ~B) | |
| >>> to_cnf((A | B) & (A | ~A), True) | |
| A | B | |
| """ | |
| expr = sympify(expr) | |
| if not isinstance(expr, BooleanFunction): | |
| return expr | |
| if simplify: | |
| if not force and len(_find_predicates(expr)) > 8: | |
| raise ValueError(filldedent(''' | |
| To simplify a logical expression with more | |
| than 8 variables may take a long time and requires | |
| the use of `force=True`.''')) | |
| return simplify_logic(expr, 'cnf', True, force=force) | |
| # Don't convert unless we have to | |
| if is_cnf(expr): | |
| return expr | |
| expr = eliminate_implications(expr) | |
| res = distribute_and_over_or(expr) | |
| return res | |
| def to_dnf(expr, simplify=False, force=False): | |
| """ | |
| Convert a propositional logical sentence ``expr`` to disjunctive normal | |
| form: ``((A & ~B & ...) | (B & C & ...) | ...)``. | |
| If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest DNF form using | |
| the Quine-McCluskey algorithm; this may take a long | |
| time. If there are more than 8 variables, the ``force`` flag must be set to | |
| ``True`` to simplify (default is ``False``). | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import to_dnf | |
| >>> from sympy.abc import A, B, C | |
| >>> to_dnf(B & (A | C)) | |
| (A & B) | (B & C) | |
| >>> to_dnf((A & B) | (A & ~B) | (B & C) | (~B & C), True) | |
| A | C | |
| """ | |
| expr = sympify(expr) | |
| if not isinstance(expr, BooleanFunction): | |
| return expr | |
| if simplify: | |
| if not force and len(_find_predicates(expr)) > 8: | |
| raise ValueError(filldedent(''' | |
| To simplify a logical expression with more | |
| than 8 variables may take a long time and requires | |
| the use of `force=True`.''')) | |
| return simplify_logic(expr, 'dnf', True, force=force) | |
| # Don't convert unless we have to | |
| if is_dnf(expr): | |
| return expr | |
| expr = eliminate_implications(expr) | |
| return distribute_or_over_and(expr) | |
| def is_anf(expr): | |
| r""" | |
| Checks if ``expr`` is in Algebraic Normal Form (ANF). | |
| A logical expression is in ANF if it has the form | |
| .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc | |
| i.e. it is purely true, purely false, conjunction of | |
| variables or exclusive disjunction. The exclusive | |
| disjunction can only contain true, variables or | |
| conjunction of variables. No negations are permitted. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import And, Not, Xor, true, is_anf | |
| >>> from sympy.abc import A, B, C | |
| >>> is_anf(true) | |
| True | |
| >>> is_anf(A) | |
| True | |
| >>> is_anf(And(A, B, C)) | |
| True | |
| >>> is_anf(Xor(A, Not(B))) | |
| False | |
| """ | |
| expr = sympify(expr) | |
| if is_literal(expr) and not isinstance(expr, Not): | |
| return True | |
| if isinstance(expr, And): | |
| for arg in expr.args: | |
| if not arg.is_Symbol: | |
| return False | |
| return True | |
| elif isinstance(expr, Xor): | |
| for arg in expr.args: | |
| if isinstance(arg, And): | |
| for a in arg.args: | |
| if not a.is_Symbol: | |
| return False | |
| elif is_literal(arg): | |
| if isinstance(arg, Not): | |
| return False | |
| else: | |
| return False | |
| return True | |
| else: | |
| return False | |
| def is_nnf(expr, simplified=True): | |
| """ | |
| Checks if ``expr`` is in Negation Normal Form (NNF). | |
| A logical expression is in NNF if it | |
| contains only :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`, | |
| and :py:class:`~.Not` is applied only to literals. | |
| If ``simplified`` is ``True``, checks if result contains no redundant clauses. | |
| Examples | |
| ======== | |
| >>> from sympy.abc import A, B, C | |
| >>> from sympy.logic.boolalg import Not, is_nnf | |
| >>> is_nnf(A & B | ~C) | |
| True | |
| >>> is_nnf((A | ~A) & (B | C)) | |
| False | |
| >>> is_nnf((A | ~A) & (B | C), False) | |
| True | |
| >>> is_nnf(Not(A & B) | C) | |
| False | |
| >>> is_nnf((A >> B) & (B >> A)) | |
| False | |
| """ | |
| expr = sympify(expr) | |
| if is_literal(expr): | |
| return True | |
| stack = [expr] | |
| while stack: | |
| expr = stack.pop() | |
| if expr.func in (And, Or): | |
| if simplified: | |
| args = expr.args | |
| for arg in args: | |
| if Not(arg) in args: | |
| return False | |
| stack.extend(expr.args) | |
| elif not is_literal(expr): | |
| return False | |
| return True | |
| def is_cnf(expr): | |
| """ | |
| Test whether or not an expression is in conjunctive normal form. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import is_cnf | |
| >>> from sympy.abc import A, B, C | |
| >>> is_cnf(A | B | C) | |
| True | |
| >>> is_cnf(A & B & C) | |
| True | |
| >>> is_cnf((A & B) | C) | |
| False | |
| """ | |
| return _is_form(expr, And, Or) | |
| def is_dnf(expr): | |
| """ | |
| Test whether or not an expression is in disjunctive normal form. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import is_dnf | |
| >>> from sympy.abc import A, B, C | |
| >>> is_dnf(A | B | C) | |
| True | |
| >>> is_dnf(A & B & C) | |
| True | |
| >>> is_dnf((A & B) | C) | |
| True | |
| >>> is_dnf(A & (B | C)) | |
| False | |
| """ | |
| return _is_form(expr, Or, And) | |
| def _is_form(expr, function1, function2): | |
| """ | |
| Test whether or not an expression is of the required form. | |
| """ | |
| expr = sympify(expr) | |
| vals = function1.make_args(expr) if isinstance(expr, function1) else [expr] | |
| for lit in vals: | |
| if isinstance(lit, function2): | |
| vals2 = function2.make_args(lit) if isinstance(lit, function2) else [lit] | |
| for l in vals2: | |
| if is_literal(l) is False: | |
| return False | |
| elif is_literal(lit) is False: | |
| return False | |
| return True | |
| def eliminate_implications(expr): | |
| """ | |
| Change :py:class:`~.Implies` and :py:class:`~.Equivalent` into | |
| :py:class:`~.And`, :py:class:`~.Or`, and :py:class:`~.Not`. | |
| That is, return an expression that is equivalent to ``expr``, but has only | |
| ``&``, ``|``, and ``~`` as logical | |
| operators. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import Implies, Equivalent, \ | |
| eliminate_implications | |
| >>> from sympy.abc import A, B, C | |
| >>> eliminate_implications(Implies(A, B)) | |
| B | ~A | |
| >>> eliminate_implications(Equivalent(A, B)) | |
| (A | ~B) & (B | ~A) | |
| >>> eliminate_implications(Equivalent(A, B, C)) | |
| (A | ~C) & (B | ~A) & (C | ~B) | |
| """ | |
| return to_nnf(expr, simplify=False) | |
| def is_literal(expr): | |
| """ | |
| Returns True if expr is a literal, else False. | |
| Examples | |
| ======== | |
| >>> from sympy import Or, Q | |
| >>> from sympy.abc import A, B | |
| >>> from sympy.logic.boolalg import is_literal | |
| >>> is_literal(A) | |
| True | |
| >>> is_literal(~A) | |
| True | |
| >>> is_literal(Q.zero(A)) | |
| True | |
| >>> is_literal(A + B) | |
| True | |
| >>> is_literal(Or(A, B)) | |
| False | |
| """ | |
| from sympy.assumptions import AppliedPredicate | |
| if isinstance(expr, Not): | |
| return is_literal(expr.args[0]) | |
| elif expr in (True, False) or isinstance(expr, AppliedPredicate) or expr.is_Atom: | |
| return True | |
| elif not isinstance(expr, BooleanFunction) and all( | |
| (isinstance(expr, AppliedPredicate) or a.is_Atom) for a in expr.args): | |
| return True | |
| return False | |
| def to_int_repr(clauses, symbols): | |
| """ | |
| Takes clauses in CNF format and puts them into an integer representation. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import to_int_repr | |
| >>> from sympy.abc import x, y | |
| >>> to_int_repr([x | y, y], [x, y]) == [{1, 2}, {2}] | |
| True | |
| """ | |
| # Convert the symbol list into a dict | |
| symbols = dict(zip(symbols, range(1, len(symbols) + 1))) | |
| def append_symbol(arg, symbols): | |
| if isinstance(arg, Not): | |
| return -symbols[arg.args[0]] | |
| else: | |
| return symbols[arg] | |
| return [{append_symbol(arg, symbols) for arg in Or.make_args(c)} | |
| for c in clauses] | |
| def term_to_integer(term): | |
| """ | |
| Return an integer corresponding to the base-2 digits given by *term*. | |
| Parameters | |
| ========== | |
| term : a string or list of ones and zeros | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import term_to_integer | |
| >>> term_to_integer([1, 0, 0]) | |
| 4 | |
| >>> term_to_integer('100') | |
| 4 | |
| """ | |
| return int(''.join(list(map(str, list(term)))), 2) | |
| integer_to_term = ibin # XXX could delete? | |
| def truth_table(expr, variables, input=True): | |
| """ | |
| Return a generator of all possible configurations of the input variables, | |
| and the result of the boolean expression for those values. | |
| Parameters | |
| ========== | |
| expr : Boolean expression | |
| variables : list of variables | |
| input : bool (default ``True``) | |
| Indicates whether to return the input combinations. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import truth_table | |
| >>> from sympy.abc import x,y | |
| >>> table = truth_table(x >> y, [x, y]) | |
| >>> for t in table: | |
| ... print('{0} -> {1}'.format(*t)) | |
| [0, 0] -> True | |
| [0, 1] -> True | |
| [1, 0] -> False | |
| [1, 1] -> True | |
| >>> table = truth_table(x | y, [x, y]) | |
| >>> list(table) | |
| [([0, 0], False), ([0, 1], True), ([1, 0], True), ([1, 1], True)] | |
| If ``input`` is ``False``, ``truth_table`` returns only a list of truth values. | |
| In this case, the corresponding input values of variables can be | |
| deduced from the index of a given output. | |
| >>> from sympy.utilities.iterables import ibin | |
| >>> vars = [y, x] | |
| >>> values = truth_table(x >> y, vars, input=False) | |
| >>> values = list(values) | |
| >>> values | |
| [True, False, True, True] | |
| >>> for i, value in enumerate(values): | |
| ... print('{0} -> {1}'.format(list(zip( | |
| ... vars, ibin(i, len(vars)))), value)) | |
| [(y, 0), (x, 0)] -> True | |
| [(y, 0), (x, 1)] -> False | |
| [(y, 1), (x, 0)] -> True | |
| [(y, 1), (x, 1)] -> True | |
| """ | |
| variables = [sympify(v) for v in variables] | |
| expr = sympify(expr) | |
| if not isinstance(expr, BooleanFunction) and not is_literal(expr): | |
| return | |
| table = product((0, 1), repeat=len(variables)) | |
| for term in table: | |
| value = expr.xreplace(dict(zip(variables, term))) | |
| if input: | |
| yield list(term), value | |
| else: | |
| yield value | |
| def _check_pair(minterm1, minterm2): | |
| """ | |
| Checks if a pair of minterms differs by only one bit. If yes, returns | |
| index, else returns `-1`. | |
| """ | |
| # Early termination seems to be faster than list comprehension, | |
| # at least for large examples. | |
| index = -1 | |
| for x, i in enumerate(minterm1): # zip(minterm1, minterm2) is slower | |
| if i != minterm2[x]: | |
| if index == -1: | |
| index = x | |
| else: | |
| return -1 | |
| return index | |
| def _convert_to_varsSOP(minterm, variables): | |
| """ | |
| Converts a term in the expansion of a function from binary to its | |
| variable form (for SOP). | |
| """ | |
| temp = [variables[n] if val == 1 else Not(variables[n]) | |
| for n, val in enumerate(minterm) if val != 3] | |
| return And(*temp) | |
| def _convert_to_varsPOS(maxterm, variables): | |
| """ | |
| Converts a term in the expansion of a function from binary to its | |
| variable form (for POS). | |
| """ | |
| temp = [variables[n] if val == 0 else Not(variables[n]) | |
| for n, val in enumerate(maxterm) if val != 3] | |
| return Or(*temp) | |
| def _convert_to_varsANF(term, variables): | |
| """ | |
| Converts a term in the expansion of a function from binary to its | |
| variable form (for ANF). | |
| Parameters | |
| ========== | |
| term : list of 1's and 0's (complementation pattern) | |
| variables : list of variables | |
| """ | |
| temp = [variables[n] for n, t in enumerate(term) if t == 1] | |
| if not temp: | |
| return true | |
| return And(*temp) | |
| def _get_odd_parity_terms(n): | |
| """ | |
| Returns a list of lists, with all possible combinations of n zeros and ones | |
| with an odd number of ones. | |
| """ | |
| return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 1] | |
| def _get_even_parity_terms(n): | |
| """ | |
| Returns a list of lists, with all possible combinations of n zeros and ones | |
| with an even number of ones. | |
| """ | |
| return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 0] | |
| def _simplified_pairs(terms): | |
| """ | |
| Reduces a set of minterms, if possible, to a simplified set of minterms | |
| with one less variable in the terms using QM method. | |
| """ | |
| if not terms: | |
| return [] | |
| simplified_terms = [] | |
| todo = list(range(len(terms))) | |
| # Count number of ones as _check_pair can only potentially match if there | |
| # is at most a difference of a single one | |
| termdict = defaultdict(list) | |
| for n, term in enumerate(terms): | |
| ones = sum(1 for t in term if t == 1) | |
| termdict[ones].append(n) | |
| variables = len(terms[0]) | |
| for k in range(variables): | |
| for i in termdict[k]: | |
| for j in termdict[k+1]: | |
| index = _check_pair(terms[i], terms[j]) | |
| if index != -1: | |
| # Mark terms handled | |
| todo[i] = todo[j] = None | |
| # Copy old term | |
| newterm = terms[i][:] | |
| # Set differing position to don't care | |
| newterm[index] = 3 | |
| # Add if not already there | |
| if newterm not in simplified_terms: | |
| simplified_terms.append(newterm) | |
| if simplified_terms: | |
| # Further simplifications only among the new terms | |
| simplified_terms = _simplified_pairs(simplified_terms) | |
| # Add remaining, non-simplified, terms | |
| simplified_terms.extend([terms[i] for i in todo if i is not None]) | |
| return simplified_terms | |
| def _rem_redundancy(l1, terms): | |
| """ | |
| After the truth table has been sufficiently simplified, use the prime | |
| implicant table method to recognize and eliminate redundant pairs, | |
| and return the essential arguments. | |
| """ | |
| if not terms: | |
| return [] | |
| nterms = len(terms) | |
| nl1 = len(l1) | |
| # Create dominating matrix | |
| dommatrix = [[0]*nl1 for n in range(nterms)] | |
| colcount = [0]*nl1 | |
| rowcount = [0]*nterms | |
| for primei, prime in enumerate(l1): | |
| for termi, term in enumerate(terms): | |
| # Check prime implicant covering term | |
| if all(t == 3 or t == mt for t, mt in zip(prime, term)): | |
| dommatrix[termi][primei] = 1 | |
| colcount[primei] += 1 | |
| rowcount[termi] += 1 | |
| # Keep track if anything changed | |
| anythingchanged = True | |
| # Then, go again | |
| while anythingchanged: | |
| anythingchanged = False | |
| for rowi in range(nterms): | |
| # Still non-dominated? | |
| if rowcount[rowi]: | |
| row = dommatrix[rowi] | |
| for row2i in range(nterms): | |
| # Still non-dominated? | |
| if rowi != row2i and rowcount[rowi] and (rowcount[rowi] <= rowcount[row2i]): | |
| row2 = dommatrix[row2i] | |
| if all(row2[n] >= row[n] for n in range(nl1)): | |
| # row2 dominating row, remove row2 | |
| rowcount[row2i] = 0 | |
| anythingchanged = True | |
| for primei, prime in enumerate(row2): | |
| if prime: | |
| # Make corresponding entry 0 | |
| dommatrix[row2i][primei] = 0 | |
| colcount[primei] -= 1 | |
| colcache = {} | |
| for coli in range(nl1): | |
| # Still non-dominated? | |
| if colcount[coli]: | |
| if coli in colcache: | |
| col = colcache[coli] | |
| else: | |
| col = [dommatrix[i][coli] for i in range(nterms)] | |
| colcache[coli] = col | |
| for col2i in range(nl1): | |
| # Still non-dominated? | |
| if coli != col2i and colcount[col2i] and (colcount[coli] >= colcount[col2i]): | |
| if col2i in colcache: | |
| col2 = colcache[col2i] | |
| else: | |
| col2 = [dommatrix[i][col2i] for i in range(nterms)] | |
| colcache[col2i] = col2 | |
| if all(col[n] >= col2[n] for n in range(nterms)): | |
| # col dominating col2, remove col2 | |
| colcount[col2i] = 0 | |
| anythingchanged = True | |
| for termi, term in enumerate(col2): | |
| if term and dommatrix[termi][col2i]: | |
| # Make corresponding entry 0 | |
| dommatrix[termi][col2i] = 0 | |
| rowcount[termi] -= 1 | |
| if not anythingchanged: | |
| # Heuristically select the prime implicant covering most terms | |
| maxterms = 0 | |
| bestcolidx = -1 | |
| for coli in range(nl1): | |
| s = colcount[coli] | |
| if s > maxterms: | |
| bestcolidx = coli | |
| maxterms = s | |
| # In case we found a prime implicant covering at least two terms | |
| if bestcolidx != -1 and maxterms > 1: | |
| for primei, prime in enumerate(l1): | |
| if primei != bestcolidx: | |
| for termi, term in enumerate(colcache[bestcolidx]): | |
| if term and dommatrix[termi][primei]: | |
| # Make corresponding entry 0 | |
| dommatrix[termi][primei] = 0 | |
| anythingchanged = True | |
| rowcount[termi] -= 1 | |
| colcount[primei] -= 1 | |
| return [l1[i] for i in range(nl1) if colcount[i]] | |
| def _input_to_binlist(inputlist, variables): | |
| binlist = [] | |
| bits = len(variables) | |
| for val in inputlist: | |
| if isinstance(val, int): | |
| binlist.append(ibin(val, bits)) | |
| elif isinstance(val, dict): | |
| nonspecvars = list(variables) | |
| for key in val.keys(): | |
| nonspecvars.remove(key) | |
| for t in product((0, 1), repeat=len(nonspecvars)): | |
| d = dict(zip(nonspecvars, t)) | |
| d.update(val) | |
| binlist.append([d[v] for v in variables]) | |
| elif isinstance(val, (list, tuple)): | |
| if len(val) != bits: | |
| raise ValueError("Each term must contain {bits} bits as there are" | |
| "\n{bits} variables (or be an integer)." | |
| "".format(bits=bits)) | |
| binlist.append(list(val)) | |
| else: | |
| raise TypeError("A term list can only contain lists," | |
| " ints or dicts.") | |
| return binlist | |
| def SOPform(variables, minterms, dontcares=None): | |
| """ | |
| The SOPform function uses simplified_pairs and a redundant group- | |
| eliminating algorithm to convert the list of all input combos that | |
| generate '1' (the minterms) into the smallest sum-of-products form. | |
| The variables must be given as the first argument. | |
| Return a logical :py:class:`~.Or` function (i.e., the "sum of products" or | |
| "SOP" form) that gives the desired outcome. If there are inputs that can | |
| be ignored, pass them as a list, too. | |
| The result will be one of the (perhaps many) functions that satisfy | |
| the conditions. | |
| Examples | |
| ======== | |
| >>> from sympy.logic import SOPform | |
| >>> from sympy import symbols | |
| >>> w, x, y, z = symbols('w x y z') | |
| >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1], | |
| ... [0, 1, 1, 1], [1, 0, 1, 1], [1, 1, 1, 1]] | |
| >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]] | |
| >>> SOPform([w, x, y, z], minterms, dontcares) | |
| (y & z) | (~w & ~x) | |
| The terms can also be represented as integers: | |
| >>> minterms = [1, 3, 7, 11, 15] | |
| >>> dontcares = [0, 2, 5] | |
| >>> SOPform([w, x, y, z], minterms, dontcares) | |
| (y & z) | (~w & ~x) | |
| They can also be specified using dicts, which does not have to be fully | |
| specified: | |
| >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}] | |
| >>> SOPform([w, x, y, z], minterms) | |
| (x & ~w) | (y & z & ~x) | |
| Or a combination: | |
| >>> minterms = [4, 7, 11, [1, 1, 1, 1]] | |
| >>> dontcares = [{w : 0, x : 0, y: 0}, 5] | |
| >>> SOPform([w, x, y, z], minterms, dontcares) | |
| (w & y & z) | (~w & ~y) | (x & z & ~w) | |
| See also | |
| ======== | |
| POSform | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm | |
| .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term | |
| """ | |
| if not minterms: | |
| return false | |
| variables = tuple(map(sympify, variables)) | |
| minterms = _input_to_binlist(minterms, variables) | |
| dontcares = _input_to_binlist((dontcares or []), variables) | |
| for d in dontcares: | |
| if d in minterms: | |
| raise ValueError('%s in minterms is also in dontcares' % d) | |
| return _sop_form(variables, minterms, dontcares) | |
| def _sop_form(variables, minterms, dontcares): | |
| new = _simplified_pairs(minterms + dontcares) | |
| essential = _rem_redundancy(new, minterms) | |
| return Or(*[_convert_to_varsSOP(x, variables) for x in essential]) | |
| def POSform(variables, minterms, dontcares=None): | |
| """ | |
| The POSform function uses simplified_pairs and a redundant-group | |
| eliminating algorithm to convert the list of all input combinations | |
| that generate '1' (the minterms) into the smallest product-of-sums form. | |
| The variables must be given as the first argument. | |
| Return a logical :py:class:`~.And` function (i.e., the "product of sums" | |
| or "POS" form) that gives the desired outcome. If there are inputs that can | |
| be ignored, pass them as a list, too. | |
| The result will be one of the (perhaps many) functions that satisfy | |
| the conditions. | |
| Examples | |
| ======== | |
| >>> from sympy.logic import POSform | |
| >>> from sympy import symbols | |
| >>> w, x, y, z = symbols('w x y z') | |
| >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1], [0, 1, 1, 1], | |
| ... [1, 0, 1, 1], [1, 1, 1, 1]] | |
| >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]] | |
| >>> POSform([w, x, y, z], minterms, dontcares) | |
| z & (y | ~w) | |
| The terms can also be represented as integers: | |
| >>> minterms = [1, 3, 7, 11, 15] | |
| >>> dontcares = [0, 2, 5] | |
| >>> POSform([w, x, y, z], minterms, dontcares) | |
| z & (y | ~w) | |
| They can also be specified using dicts, which does not have to be fully | |
| specified: | |
| >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}] | |
| >>> POSform([w, x, y, z], minterms) | |
| (x | y) & (x | z) & (~w | ~x) | |
| Or a combination: | |
| >>> minterms = [4, 7, 11, [1, 1, 1, 1]] | |
| >>> dontcares = [{w : 0, x : 0, y: 0}, 5] | |
| >>> POSform([w, x, y, z], minterms, dontcares) | |
| (w | x) & (y | ~w) & (z | ~y) | |
| See also | |
| ======== | |
| SOPform | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm | |
| .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term | |
| """ | |
| if not minterms: | |
| return false | |
| variables = tuple(map(sympify, variables)) | |
| minterms = _input_to_binlist(minterms, variables) | |
| dontcares = _input_to_binlist((dontcares or []), variables) | |
| for d in dontcares: | |
| if d in minterms: | |
| raise ValueError('%s in minterms is also in dontcares' % d) | |
| maxterms = [] | |
| for t in product((0, 1), repeat=len(variables)): | |
| t = list(t) | |
| if (t not in minterms) and (t not in dontcares): | |
| maxterms.append(t) | |
| new = _simplified_pairs(maxterms + dontcares) | |
| essential = _rem_redundancy(new, maxterms) | |
| return And(*[_convert_to_varsPOS(x, variables) for x in essential]) | |
| def ANFform(variables, truthvalues): | |
| """ | |
| The ANFform function converts the list of truth values to | |
| Algebraic Normal Form (ANF). | |
| The variables must be given as the first argument. | |
| Return True, False, logical :py:class:`~.And` function (i.e., the | |
| "Zhegalkin monomial") or logical :py:class:`~.Xor` function (i.e., | |
| the "Zhegalkin polynomial"). When True and False | |
| are represented by 1 and 0, respectively, then | |
| :py:class:`~.And` is multiplication and :py:class:`~.Xor` is addition. | |
| Formally a "Zhegalkin monomial" is the product (logical | |
| And) of a finite set of distinct variables, including | |
| the empty set whose product is denoted 1 (True). | |
| A "Zhegalkin polynomial" is the sum (logical Xor) of a | |
| set of Zhegalkin monomials, with the empty set denoted | |
| by 0 (False). | |
| Parameters | |
| ========== | |
| variables : list of variables | |
| truthvalues : list of 1's and 0's (result column of truth table) | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import ANFform | |
| >>> from sympy.abc import x, y | |
| >>> ANFform([x], [1, 0]) | |
| x ^ True | |
| >>> ANFform([x, y], [0, 1, 1, 1]) | |
| x ^ y ^ (x & y) | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Zhegalkin_polynomial | |
| """ | |
| n_vars = len(variables) | |
| n_values = len(truthvalues) | |
| if n_values != 2 ** n_vars: | |
| raise ValueError("The number of truth values must be equal to 2^%d, " | |
| "got %d" % (n_vars, n_values)) | |
| variables = tuple(map(sympify, variables)) | |
| coeffs = anf_coeffs(truthvalues) | |
| terms = [] | |
| for i, t in enumerate(product((0, 1), repeat=n_vars)): | |
| if coeffs[i] == 1: | |
| terms.append(t) | |
| return Xor(*[_convert_to_varsANF(x, variables) for x in terms], | |
| remove_true=False) | |
| def anf_coeffs(truthvalues): | |
| """ | |
| Convert a list of truth values of some boolean expression | |
| to the list of coefficients of the polynomial mod 2 (exclusive | |
| disjunction) representing the boolean expression in ANF | |
| (i.e., the "Zhegalkin polynomial"). | |
| There are `2^n` possible Zhegalkin monomials in `n` variables, since | |
| each monomial is fully specified by the presence or absence of | |
| each variable. | |
| We can enumerate all the monomials. For example, boolean | |
| function with four variables ``(a, b, c, d)`` can contain | |
| up to `2^4 = 16` monomials. The 13-th monomial is the | |
| product ``a & b & d``, because 13 in binary is 1, 1, 0, 1. | |
| A given monomial's presence or absence in a polynomial corresponds | |
| to that monomial's coefficient being 1 or 0 respectively. | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import anf_coeffs, bool_monomial, Xor | |
| >>> from sympy.abc import a, b, c | |
| >>> truthvalues = [0, 1, 1, 0, 0, 1, 0, 1] | |
| >>> coeffs = anf_coeffs(truthvalues) | |
| >>> coeffs | |
| [0, 1, 1, 0, 0, 0, 1, 0] | |
| >>> polynomial = Xor(*[ | |
| ... bool_monomial(k, [a, b, c]) | |
| ... for k, coeff in enumerate(coeffs) if coeff == 1 | |
| ... ]) | |
| >>> polynomial | |
| b ^ c ^ (a & b) | |
| """ | |
| s = '{:b}'.format(len(truthvalues)) | |
| n = len(s) - 1 | |
| if len(truthvalues) != 2**n: | |
| raise ValueError("The number of truth values must be a power of two, " | |
| "got %d" % len(truthvalues)) | |
| coeffs = [[v] for v in truthvalues] | |
| for i in range(n): | |
| tmp = [] | |
| for j in range(2 ** (n-i-1)): | |
| tmp.append(coeffs[2*j] + | |
| list(map(lambda x, y: x^y, coeffs[2*j], coeffs[2*j+1]))) | |
| coeffs = tmp | |
| return coeffs[0] | |
| def bool_minterm(k, variables): | |
| """ | |
| Return the k-th minterm. | |
| Minterms are numbered by a binary encoding of the complementation | |
| pattern of the variables. This convention assigns the value 1 to | |
| the direct form and 0 to the complemented form. | |
| Parameters | |
| ========== | |
| k : int or list of 1's and 0's (complementation pattern) | |
| variables : list of variables | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import bool_minterm | |
| >>> from sympy.abc import x, y, z | |
| >>> bool_minterm([1, 0, 1], [x, y, z]) | |
| x & z & ~y | |
| >>> bool_minterm(6, [x, y, z]) | |
| x & y & ~z | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_minterms | |
| """ | |
| if isinstance(k, int): | |
| k = ibin(k, len(variables)) | |
| variables = tuple(map(sympify, variables)) | |
| return _convert_to_varsSOP(k, variables) | |
| def bool_maxterm(k, variables): | |
| """ | |
| Return the k-th maxterm. | |
| Each maxterm is assigned an index based on the opposite | |
| conventional binary encoding used for minterms. The maxterm | |
| convention assigns the value 0 to the direct form and 1 to | |
| the complemented form. | |
| Parameters | |
| ========== | |
| k : int or list of 1's and 0's (complementation pattern) | |
| variables : list of variables | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import bool_maxterm | |
| >>> from sympy.abc import x, y, z | |
| >>> bool_maxterm([1, 0, 1], [x, y, z]) | |
| y | ~x | ~z | |
| >>> bool_maxterm(6, [x, y, z]) | |
| z | ~x | ~y | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_maxterms | |
| """ | |
| if isinstance(k, int): | |
| k = ibin(k, len(variables)) | |
| variables = tuple(map(sympify, variables)) | |
| return _convert_to_varsPOS(k, variables) | |
| def bool_monomial(k, variables): | |
| """ | |
| Return the k-th monomial. | |
| Monomials are numbered by a binary encoding of the presence and | |
| absences of the variables. This convention assigns the value | |
| 1 to the presence of variable and 0 to the absence of variable. | |
| Each boolean function can be uniquely represented by a | |
| Zhegalkin Polynomial (Algebraic Normal Form). The Zhegalkin | |
| Polynomial of the boolean function with `n` variables can contain | |
| up to `2^n` monomials. We can enumerate all the monomials. | |
| Each monomial is fully specified by the presence or absence | |
| of each variable. | |
| For example, boolean function with four variables ``(a, b, c, d)`` | |
| can contain up to `2^4 = 16` monomials. The 13-th monomial is the | |
| product ``a & b & d``, because 13 in binary is 1, 1, 0, 1. | |
| Parameters | |
| ========== | |
| k : int or list of 1's and 0's | |
| variables : list of variables | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import bool_monomial | |
| >>> from sympy.abc import x, y, z | |
| >>> bool_monomial([1, 0, 1], [x, y, z]) | |
| x & z | |
| >>> bool_monomial(6, [x, y, z]) | |
| x & y | |
| """ | |
| if isinstance(k, int): | |
| k = ibin(k, len(variables)) | |
| variables = tuple(map(sympify, variables)) | |
| return _convert_to_varsANF(k, variables) | |
| def _find_predicates(expr): | |
| """Helper to find logical predicates in BooleanFunctions. | |
| A logical predicate is defined here as anything within a BooleanFunction | |
| that is not a BooleanFunction itself. | |
| """ | |
| if not isinstance(expr, BooleanFunction): | |
| return {expr} | |
| return set().union(*(map(_find_predicates, expr.args))) | |
| def simplify_logic(expr, form=None, deep=True, force=False, dontcare=None): | |
| """ | |
| This function simplifies a boolean function to its simplified version | |
| in SOP or POS form. The return type is an :py:class:`~.Or` or | |
| :py:class:`~.And` object in SymPy. | |
| Parameters | |
| ========== | |
| expr : Boolean | |
| form : string (``'cnf'`` or ``'dnf'``) or ``None`` (default). | |
| If ``'cnf'`` or ``'dnf'``, the simplest expression in the corresponding | |
| normal form is returned; if ``None``, the answer is returned | |
| according to the form with fewest args (in CNF by default). | |
| deep : bool (default ``True``) | |
| Indicates whether to recursively simplify any | |
| non-boolean functions contained within the input. | |
| force : bool (default ``False``) | |
| As the simplifications require exponential time in the number | |
| of variables, there is by default a limit on expressions with | |
| 8 variables. When the expression has more than 8 variables | |
| only symbolical simplification (controlled by ``deep``) is | |
| made. By setting ``force`` to ``True``, this limit is removed. Be | |
| aware that this can lead to very long simplification times. | |
| dontcare : Boolean | |
| Optimize expression under the assumption that inputs where this | |
| expression is true are don't care. This is useful in e.g. Piecewise | |
| conditions, where later conditions do not need to consider inputs that | |
| are converted by previous conditions. For example, if a previous | |
| condition is ``And(A, B)``, the simplification of expr can be made | |
| with don't cares for ``And(A, B)``. | |
| Examples | |
| ======== | |
| >>> from sympy.logic import simplify_logic | |
| >>> from sympy.abc import x, y, z | |
| >>> b = (~x & ~y & ~z) | ( ~x & ~y & z) | |
| >>> simplify_logic(b) | |
| ~x & ~y | |
| >>> simplify_logic(x | y, dontcare=y) | |
| x | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Don%27t-care_term | |
| """ | |
| if form not in (None, 'cnf', 'dnf'): | |
| raise ValueError("form can be cnf or dnf only") | |
| expr = sympify(expr) | |
| # check for quick exit if form is given: right form and all args are | |
| # literal and do not involve Not | |
| if form: | |
| form_ok = False | |
| if form == 'cnf': | |
| form_ok = is_cnf(expr) | |
| elif form == 'dnf': | |
| form_ok = is_dnf(expr) | |
| if form_ok and all(is_literal(a) | |
| for a in expr.args): | |
| return expr | |
| from sympy.core.relational import Relational | |
| if deep: | |
| variables = expr.atoms(Relational) | |
| from sympy.simplify.simplify import simplify | |
| s = tuple(map(simplify, variables)) | |
| expr = expr.xreplace(dict(zip(variables, s))) | |
| if not isinstance(expr, BooleanFunction): | |
| return expr | |
| # Replace Relationals with Dummys to possibly | |
| # reduce the number of variables | |
| repl = {} | |
| undo = {} | |
| from sympy.core.symbol import Dummy | |
| variables = expr.atoms(Relational) | |
| if dontcare is not None: | |
| dontcare = sympify(dontcare) | |
| variables.update(dontcare.atoms(Relational)) | |
| while variables: | |
| var = variables.pop() | |
| if var.is_Relational: | |
| d = Dummy() | |
| undo[d] = var | |
| repl[var] = d | |
| nvar = var.negated | |
| if nvar in variables: | |
| repl[nvar] = Not(d) | |
| variables.remove(nvar) | |
| expr = expr.xreplace(repl) | |
| if dontcare is not None: | |
| dontcare = dontcare.xreplace(repl) | |
| # Get new variables after replacing | |
| variables = _find_predicates(expr) | |
| if not force and len(variables) > 8: | |
| return expr.xreplace(undo) | |
| if dontcare is not None: | |
| # Add variables from dontcare | |
| dcvariables = _find_predicates(dontcare) | |
| variables.update(dcvariables) | |
| # if too many restore to variables only | |
| if not force and len(variables) > 8: | |
| variables = _find_predicates(expr) | |
| dontcare = None | |
| # group into constants and variable values | |
| c, v = sift(ordered(variables), lambda x: x in (True, False), binary=True) | |
| variables = c + v | |
| # standardize constants to be 1 or 0 in keeping with truthtable | |
| c = [1 if i == True else 0 for i in c] | |
| truthtable = _get_truthtable(v, expr, c) | |
| if dontcare is not None: | |
| dctruthtable = _get_truthtable(v, dontcare, c) | |
| truthtable = [t for t in truthtable if t not in dctruthtable] | |
| else: | |
| dctruthtable = [] | |
| big = len(truthtable) >= (2 ** (len(variables) - 1)) | |
| if form == 'dnf' or form is None and big: | |
| return _sop_form(variables, truthtable, dctruthtable).xreplace(undo) | |
| return POSform(variables, truthtable, dctruthtable).xreplace(undo) | |
| def _get_truthtable(variables, expr, const): | |
| """ Return a list of all combinations leading to a True result for ``expr``. | |
| """ | |
| _variables = variables.copy() | |
| def _get_tt(inputs): | |
| if _variables: | |
| v = _variables.pop() | |
| tab = [[i[0].xreplace({v: false}), [0] + i[1]] for i in inputs if i[0] is not false] | |
| tab.extend([[i[0].xreplace({v: true}), [1] + i[1]] for i in inputs if i[0] is not false]) | |
| return _get_tt(tab) | |
| return inputs | |
| res = [const + k[1] for k in _get_tt([[expr, []]]) if k[0]] | |
| if res == [[]]: | |
| return [] | |
| else: | |
| return res | |
| def _finger(eq): | |
| """ | |
| Assign a 5-item fingerprint to each symbol in the equation: | |
| [ | |
| # of times it appeared as a Symbol; | |
| # of times it appeared as a Not(symbol); | |
| # of times it appeared as a Symbol in an And or Or; | |
| # of times it appeared as a Not(Symbol) in an And or Or; | |
| a sorted tuple of tuples, (i, j, k), where i is the number of arguments | |
| in an And or Or with which it appeared as a Symbol, and j is | |
| the number of arguments that were Not(Symbol); k is the number | |
| of times that (i, j) was seen. | |
| ] | |
| Examples | |
| ======== | |
| >>> from sympy.logic.boolalg import _finger as finger | |
| >>> from sympy import And, Or, Not, Xor, to_cnf, symbols | |
| >>> from sympy.abc import a, b, x, y | |
| >>> eq = Or(And(Not(y), a), And(Not(y), b), And(x, y)) | |
| >>> dict(finger(eq)) | |
| {(0, 0, 1, 0, ((2, 0, 1),)): [x], | |
| (0, 0, 1, 0, ((2, 1, 1),)): [a, b], | |
| (0, 0, 1, 2, ((2, 0, 1),)): [y]} | |
| >>> dict(finger(x & ~y)) | |
| {(0, 1, 0, 0, ()): [y], (1, 0, 0, 0, ()): [x]} | |
| In the following, the (5, 2, 6) means that there were 6 Or | |
| functions in which a symbol appeared as itself amongst 5 arguments in | |
| which there were also 2 negated symbols, e.g. ``(a0 | a1 | a2 | ~a3 | ~a4)`` | |
| is counted once for a0, a1 and a2. | |
| >>> dict(finger(to_cnf(Xor(*symbols('a:5'))))) | |
| {(0, 0, 8, 8, ((5, 0, 1), (5, 2, 6), (5, 4, 1))): [a0, a1, a2, a3, a4]} | |
| The equation must not have more than one level of nesting: | |
| >>> dict(finger(And(Or(x, y), y))) | |
| {(0, 0, 1, 0, ((2, 0, 1),)): [x], (1, 0, 1, 0, ((2, 0, 1),)): [y]} | |
| >>> dict(finger(And(Or(x, And(a, x)), y))) | |
| Traceback (most recent call last): | |
| ... | |
| NotImplementedError: unexpected level of nesting | |
| So y and x have unique fingerprints, but a and b do not. | |
| """ | |
| f = eq.free_symbols | |
| d = dict(list(zip(f, [[0]*4 + [defaultdict(int)] for fi in f]))) | |
| for a in eq.args: | |
| if a.is_Symbol: | |
| d[a][0] += 1 | |
| elif a.is_Not: | |
| d[a.args[0]][1] += 1 | |
| else: | |
| o = len(a.args), sum(isinstance(ai, Not) for ai in a.args) | |
| for ai in a.args: | |
| if ai.is_Symbol: | |
| d[ai][2] += 1 | |
| d[ai][-1][o] += 1 | |
| elif ai.is_Not: | |
| d[ai.args[0]][3] += 1 | |
| else: | |
| raise NotImplementedError('unexpected level of nesting') | |
| inv = defaultdict(list) | |
| for k, v in ordered(iter(d.items())): | |
| v[-1] = tuple(sorted([i + (j,) for i, j in v[-1].items()])) | |
| inv[tuple(v)].append(k) | |
| return inv | |
| def bool_map(bool1, bool2): | |
| """ | |
| Return the simplified version of *bool1*, and the mapping of variables | |
| that makes the two expressions *bool1* and *bool2* represent the same | |
| logical behaviour for some correspondence between the variables | |
| of each. | |
| If more than one mappings of this sort exist, one of them | |
| is returned. | |
| For example, ``And(x, y)`` is logically equivalent to ``And(a, b)`` for | |
| the mapping ``{x: a, y: b}`` or ``{x: b, y: a}``. | |
| If no such mapping exists, return ``False``. | |
| Examples | |
| ======== | |
| >>> from sympy import SOPform, bool_map, Or, And, Not, Xor | |
| >>> from sympy.abc import w, x, y, z, a, b, c, d | |
| >>> function1 = SOPform([x, z, y],[[1, 0, 1], [0, 0, 1]]) | |
| >>> function2 = SOPform([a, b, c],[[1, 0, 1], [1, 0, 0]]) | |
| >>> bool_map(function1, function2) | |
| (y & ~z, {y: a, z: b}) | |
| The results are not necessarily unique, but they are canonical. Here, | |
| ``(w, z)`` could be ``(a, d)`` or ``(d, a)``: | |
| >>> eq = Or(And(Not(y), w), And(Not(y), z), And(x, y)) | |
| >>> eq2 = Or(And(Not(c), a), And(Not(c), d), And(b, c)) | |
| >>> bool_map(eq, eq2) | |
| ((x & y) | (w & ~y) | (z & ~y), {w: a, x: b, y: c, z: d}) | |
| >>> eq = And(Xor(a, b), c, And(c,d)) | |
| >>> bool_map(eq, eq.subs(c, x)) | |
| (c & d & (a | b) & (~a | ~b), {a: a, b: b, c: d, d: x}) | |
| """ | |
| def match(function1, function2): | |
| """Return the mapping that equates variables between two | |
| simplified boolean expressions if possible. | |
| By "simplified" we mean that a function has been denested | |
| and is either an And (or an Or) whose arguments are either | |
| symbols (x), negated symbols (Not(x)), or Or (or an And) whose | |
| arguments are only symbols or negated symbols. For example, | |
| ``And(x, Not(y), Or(w, Not(z)))``. | |
| Basic.match is not robust enough (see issue 4835) so this is | |
| a workaround that is valid for simplified boolean expressions | |
| """ | |
| # do some quick checks | |
| if function1.__class__ != function2.__class__: | |
| return None # maybe simplification makes them the same? | |
| if len(function1.args) != len(function2.args): | |
| return None # maybe simplification makes them the same? | |
| if function1.is_Symbol: | |
| return {function1: function2} | |
| # get the fingerprint dictionaries | |
| f1 = _finger(function1) | |
| f2 = _finger(function2) | |
| # more quick checks | |
| if len(f1) != len(f2): | |
| return False | |
| # assemble the match dictionary if possible | |
| matchdict = {} | |
| for k in f1.keys(): | |
| if k not in f2: | |
| return False | |
| if len(f1[k]) != len(f2[k]): | |
| return False | |
| for i, x in enumerate(f1[k]): | |
| matchdict[x] = f2[k][i] | |
| return matchdict | |
| a = simplify_logic(bool1) | |
| b = simplify_logic(bool2) | |
| m = match(a, b) | |
| if m: | |
| return a, m | |
| return m | |
| def _apply_patternbased_simplification(rv, patterns, measure, | |
| dominatingvalue, | |
| replacementvalue=None, | |
| threeterm_patterns=None): | |
| """ | |
| Replace patterns of Relational | |
| Parameters | |
| ========== | |
| rv : Expr | |
| Boolean expression | |
| patterns : tuple | |
| Tuple of tuples, with (pattern to simplify, simplified pattern) with | |
| two terms. | |
| measure : function | |
| Simplification measure. | |
| dominatingvalue : Boolean or ``None`` | |
| The dominating value for the function of consideration. | |
| For example, for :py:class:`~.And` ``S.false`` is dominating. | |
| As soon as one expression is ``S.false`` in :py:class:`~.And`, | |
| the whole expression is ``S.false``. | |
| replacementvalue : Boolean or ``None``, optional | |
| The resulting value for the whole expression if one argument | |
| evaluates to ``dominatingvalue``. | |
| For example, for :py:class:`~.Nand` ``S.false`` is dominating, but | |
| in this case the resulting value is ``S.true``. Default is ``None``. | |
| If ``replacementvalue`` is ``None`` and ``dominatingvalue`` is not | |
| ``None``, ``replacementvalue = dominatingvalue``. | |
| threeterm_patterns : tuple, optional | |
| Tuple of tuples, with (pattern to simplify, simplified pattern) with | |
| three terms. | |
| """ | |
| from sympy.core.relational import Relational, _canonical | |
| if replacementvalue is None and dominatingvalue is not None: | |
| replacementvalue = dominatingvalue | |
| # Use replacement patterns for Relationals | |
| Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational), | |
| binary=True) | |
| if len(Rel) <= 1: | |
| return rv | |
| Rel, nonRealRel = sift(Rel, lambda i: not any(s.is_real is False | |
| for s in i.free_symbols), | |
| binary=True) | |
| Rel = [i.canonical for i in Rel] | |
| if threeterm_patterns and len(Rel) >= 3: | |
| Rel = _apply_patternbased_threeterm_simplification(Rel, | |
| threeterm_patterns, rv.func, dominatingvalue, | |
| replacementvalue, measure) | |
| Rel = _apply_patternbased_twoterm_simplification(Rel, patterns, | |
| rv.func, dominatingvalue, replacementvalue, measure) | |
| rv = rv.func(*([_canonical(i) for i in ordered(Rel)] | |
| + nonRel + nonRealRel)) | |
| return rv | |
| def _apply_patternbased_twoterm_simplification(Rel, patterns, func, | |
| dominatingvalue, | |
| replacementvalue, | |
| measure): | |
| """ Apply pattern-based two-term simplification.""" | |
| from sympy.functions.elementary.miscellaneous import Min, Max | |
| from sympy.core.relational import Ge, Gt, _Inequality | |
| changed = True | |
| while changed and len(Rel) >= 2: | |
| changed = False | |
| # Use only < or <= | |
| Rel = [r.reversed if isinstance(r, (Ge, Gt)) else r for r in Rel] | |
| # Sort based on ordered | |
| Rel = list(ordered(Rel)) | |
| # Eq and Ne must be tested reversed as well | |
| rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] | |
| # Create a list of possible replacements | |
| results = [] | |
| # Try all combinations of possibly reversed relational | |
| for ((i, pi), (j, pj)) in combinations(enumerate(rtmp), 2): | |
| for pattern, simp in patterns: | |
| res = [] | |
| for p1, p2 in product(pi, pj): | |
| # use SymPy matching | |
| oldexpr = Tuple(p1, p2) | |
| tmpres = oldexpr.match(pattern) | |
| if tmpres: | |
| res.append((tmpres, oldexpr)) | |
| if res: | |
| for tmpres, oldexpr in res: | |
| # we have a matching, compute replacement | |
| np = simp.xreplace(tmpres) | |
| if np == dominatingvalue: | |
| # if dominatingvalue, the whole expression | |
| # will be replacementvalue | |
| return [replacementvalue] | |
| # add replacement | |
| if not isinstance(np, ITE) and not np.has(Min, Max): | |
| # We only want to use ITE and Min/Max replacements if | |
| # they simplify to a relational | |
| costsaving = measure(func(*oldexpr.args)) - measure(np) | |
| if costsaving > 0: | |
| results.append((costsaving, ([i, j], np))) | |
| if results: | |
| # Sort results based on complexity | |
| results = sorted(results, | |
| key=lambda pair: pair[0], reverse=True) | |
| # Replace the one providing most simplification | |
| replacement = results[0][1] | |
| idx, newrel = replacement | |
| idx.sort() | |
| # Remove the old relationals | |
| for index in reversed(idx): | |
| del Rel[index] | |
| if dominatingvalue is None or newrel != Not(dominatingvalue): | |
| # Insert the new one (no need to insert a value that will | |
| # not affect the result) | |
| if newrel.func == func: | |
| for a in newrel.args: | |
| Rel.append(a) | |
| else: | |
| Rel.append(newrel) | |
| # We did change something so try again | |
| changed = True | |
| return Rel | |
| def _apply_patternbased_threeterm_simplification(Rel, patterns, func, | |
| dominatingvalue, | |
| replacementvalue, | |
| measure): | |
| """ Apply pattern-based three-term simplification.""" | |
| from sympy.functions.elementary.miscellaneous import Min, Max | |
| from sympy.core.relational import Le, Lt, _Inequality | |
| changed = True | |
| while changed and len(Rel) >= 3: | |
| changed = False | |
| # Use only > or >= | |
| Rel = [r.reversed if isinstance(r, (Le, Lt)) else r for r in Rel] | |
| # Sort based on ordered | |
| Rel = list(ordered(Rel)) | |
| # Create a list of possible replacements | |
| results = [] | |
| # Eq and Ne must be tested reversed as well | |
| rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] | |
| # Try all combinations of possibly reversed relational | |
| for ((i, pi), (j, pj), (k, pk)) in permutations(enumerate(rtmp), 3): | |
| for pattern, simp in patterns: | |
| res = [] | |
| for p1, p2, p3 in product(pi, pj, pk): | |
| # use SymPy matching | |
| oldexpr = Tuple(p1, p2, p3) | |
| tmpres = oldexpr.match(pattern) | |
| if tmpres: | |
| res.append((tmpres, oldexpr)) | |
| if res: | |
| for tmpres, oldexpr in res: | |
| # we have a matching, compute replacement | |
| np = simp.xreplace(tmpres) | |
| if np == dominatingvalue: | |
| # if dominatingvalue, the whole expression | |
| # will be replacementvalue | |
| return [replacementvalue] | |
| # add replacement | |
| if not isinstance(np, ITE) and not np.has(Min, Max): | |
| # We only want to use ITE and Min/Max replacements if | |
| # they simplify to a relational | |
| costsaving = measure(func(*oldexpr.args)) - measure(np) | |
| if costsaving > 0: | |
| results.append((costsaving, ([i, j, k], np))) | |
| if results: | |
| # Sort results based on complexity | |
| results = sorted(results, | |
| key=lambda pair: pair[0], reverse=True) | |
| # Replace the one providing most simplification | |
| replacement = results[0][1] | |
| idx, newrel = replacement | |
| idx.sort() | |
| # Remove the old relationals | |
| for index in reversed(idx): | |
| del Rel[index] | |
| if dominatingvalue is None or newrel != Not(dominatingvalue): | |
| # Insert the new one (no need to insert a value that will | |
| # not affect the result) | |
| if newrel.func == func: | |
| for a in newrel.args: | |
| Rel.append(a) | |
| else: | |
| Rel.append(newrel) | |
| # We did change something so try again | |
| changed = True | |
| return Rel | |
| def _simplify_patterns_and(): | |
| """ Two-term patterns for And.""" | |
| from sympy.core import Wild | |
| from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt | |
| from sympy.functions.elementary.complexes import Abs | |
| from sympy.functions.elementary.miscellaneous import Min, Max | |
| a = Wild('a') | |
| b = Wild('b') | |
| c = Wild('c') | |
| # Relationals patterns should be in alphabetical order | |
| # (pattern1, pattern2, simplified) | |
| # Do not use Ge, Gt | |
| _matchers_and = ((Tuple(Eq(a, b), Lt(a, b)), false), | |
| #(Tuple(Eq(a, b), Lt(b, a)), S.false), | |
| #(Tuple(Le(b, a), Lt(a, b)), S.false), | |
| #(Tuple(Lt(b, a), Le(a, b)), S.false), | |
| (Tuple(Lt(b, a), Lt(a, b)), false), | |
| (Tuple(Eq(a, b), Le(b, a)), Eq(a, b)), | |
| #(Tuple(Eq(a, b), Le(a, b)), Eq(a, b)), | |
| #(Tuple(Le(b, a), Lt(b, a)), Gt(a, b)), | |
| (Tuple(Le(b, a), Le(a, b)), Eq(a, b)), | |
| #(Tuple(Le(b, a), Ne(a, b)), Gt(a, b)), | |
| #(Tuple(Lt(b, a), Ne(a, b)), Gt(a, b)), | |
| (Tuple(Le(a, b), Lt(a, b)), Lt(a, b)), | |
| (Tuple(Le(a, b), Ne(a, b)), Lt(a, b)), | |
| (Tuple(Lt(a, b), Ne(a, b)), Lt(a, b)), | |
| # Sign | |
| (Tuple(Eq(a, b), Eq(a, -b)), And(Eq(a, S.Zero), Eq(b, S.Zero))), | |
| # Min/Max/ITE | |
| (Tuple(Le(b, a), Le(c, a)), Ge(a, Max(b, c))), | |
| (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Ge(a, b), Gt(a, c))), | |
| (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Max(b, c))), | |
| (Tuple(Le(a, b), Le(a, c)), Le(a, Min(b, c))), | |
| (Tuple(Le(a, b), Lt(a, c)), ITE(b < c, Le(a, b), Lt(a, c))), | |
| (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Min(b, c))), | |
| (Tuple(Le(a, b), Le(c, a)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, false, And(Le(a, b), Ge(a, c))))), | |
| (Tuple(Le(c, a), Le(a, b)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, false, And(Le(a, b), Ge(a, c))))), | |
| (Tuple(Lt(a, b), Lt(c, a)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))), | |
| (Tuple(Lt(c, a), Lt(a, b)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))), | |
| (Tuple(Le(a, b), Lt(c, a)), ITE(b <= c, false, And(Le(a, b), Gt(a, c)))), | |
| (Tuple(Le(c, a), Lt(a, b)), ITE(b <= c, false, And(Lt(a, b), Ge(a, c)))), | |
| (Tuple(Eq(a, b), Eq(a, c)), ITE(Eq(b, c), Eq(a, b), false)), | |
| (Tuple(Lt(a, b), Lt(-b, a)), ITE(b > 0, Lt(Abs(a), b), false)), | |
| (Tuple(Le(a, b), Le(-b, a)), ITE(b >= 0, Le(Abs(a), b), false)), | |
| ) | |
| return _matchers_and | |
| def _simplify_patterns_and3(): | |
| """ Three-term patterns for And.""" | |
| from sympy.core import Wild | |
| from sympy.core.relational import Eq, Ge, Gt | |
| a = Wild('a') | |
| b = Wild('b') | |
| c = Wild('c') | |
| # Relationals patterns should be in alphabetical order | |
| # (pattern1, pattern2, pattern3, simplified) | |
| # Do not use Le, Lt | |
| _matchers_and = ((Tuple(Ge(a, b), Ge(b, c), Gt(c, a)), false), | |
| (Tuple(Ge(a, b), Gt(b, c), Gt(c, a)), false), | |
| (Tuple(Gt(a, b), Gt(b, c), Gt(c, a)), false), | |
| # (Tuple(Ge(c, a), Gt(a, b), Gt(b, c)), S.false), | |
| # Lower bound relations | |
| # Commented out combinations that does not simplify | |
| (Tuple(Ge(a, b), Ge(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))), | |
| (Tuple(Ge(a, b), Ge(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))), | |
| # (Tuple(Ge(a, b), Gt(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))), | |
| (Tuple(Ge(a, b), Gt(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))), | |
| # (Tuple(Gt(a, b), Ge(a, c), Ge(b, c)), And(Gt(a, b), Ge(b, c))), | |
| (Tuple(Ge(a, c), Gt(a, b), Gt(b, c)), And(Gt(a, b), Gt(b, c))), | |
| (Tuple(Ge(b, c), Gt(a, b), Gt(a, c)), And(Gt(a, b), Ge(b, c))), | |
| (Tuple(Gt(a, b), Gt(a, c), Gt(b, c)), And(Gt(a, b), Gt(b, c))), | |
| # Upper bound relations | |
| # Commented out combinations that does not simplify | |
| (Tuple(Ge(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))), | |
| (Tuple(Ge(b, a), Ge(c, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))), | |
| # (Tuple(Ge(b, a), Gt(c, a), Ge(b, c)), And(Gt(c, a), Ge(b, c))), | |
| (Tuple(Ge(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))), | |
| # (Tuple(Gt(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))), | |
| (Tuple(Ge(c, a), Gt(b, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))), | |
| (Tuple(Ge(b, c), Gt(b, a), Gt(c, a)), And(Gt(c, a), Ge(b, c))), | |
| (Tuple(Gt(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))), | |
| # Circular relation | |
| (Tuple(Ge(a, b), Ge(b, c), Ge(c, a)), And(Eq(a, b), Eq(b, c))), | |
| ) | |
| return _matchers_and | |
| def _simplify_patterns_or(): | |
| """ Two-term patterns for Or.""" | |
| from sympy.core import Wild | |
| from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt | |
| from sympy.functions.elementary.complexes import Abs | |
| from sympy.functions.elementary.miscellaneous import Min, Max | |
| a = Wild('a') | |
| b = Wild('b') | |
| c = Wild('c') | |
| # Relationals patterns should be in alphabetical order | |
| # (pattern1, pattern2, simplified) | |
| # Do not use Ge, Gt | |
| _matchers_or = ((Tuple(Le(b, a), Le(a, b)), true), | |
| #(Tuple(Le(b, a), Lt(a, b)), true), | |
| (Tuple(Le(b, a), Ne(a, b)), true), | |
| #(Tuple(Le(a, b), Lt(b, a)), true), | |
| #(Tuple(Le(a, b), Ne(a, b)), true), | |
| #(Tuple(Eq(a, b), Le(b, a)), Ge(a, b)), | |
| #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)), | |
| (Tuple(Eq(a, b), Le(a, b)), Le(a, b)), | |
| (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)), | |
| #(Tuple(Le(b, a), Lt(b, a)), Ge(a, b)), | |
| (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)), | |
| (Tuple(Lt(b, a), Ne(a, b)), Ne(a, b)), | |
| (Tuple(Le(a, b), Lt(a, b)), Le(a, b)), | |
| #(Tuple(Lt(a, b), Ne(a, b)), Ne(a, b)), | |
| (Tuple(Eq(a, b), Ne(a, c)), ITE(Eq(b, c), true, Ne(a, c))), | |
| (Tuple(Ne(a, b), Ne(a, c)), ITE(Eq(b, c), Ne(a, b), true)), | |
| # Min/Max/ITE | |
| (Tuple(Le(b, a), Le(c, a)), Ge(a, Min(b, c))), | |
| #(Tuple(Ge(b, a), Ge(c, a)), Ge(Min(b, c), a)), | |
| (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Lt(c, a), Le(b, a))), | |
| (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Min(b, c))), | |
| #(Tuple(Gt(b, a), Gt(c, a)), Gt(Min(b, c), a)), | |
| (Tuple(Le(a, b), Le(a, c)), Le(a, Max(b, c))), | |
| #(Tuple(Le(b, a), Le(c, a)), Le(Max(b, c), a)), | |
| (Tuple(Le(a, b), Lt(a, c)), ITE(b >= c, Le(a, b), Lt(a, c))), | |
| (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Max(b, c))), | |
| #(Tuple(Lt(b, a), Lt(c, a)), Lt(Max(b, c), a)), | |
| (Tuple(Le(a, b), Le(c, a)), ITE(b >= c, true, Or(Le(a, b), Ge(a, c)))), | |
| (Tuple(Le(c, a), Le(a, b)), ITE(b >= c, true, Or(Le(a, b), Ge(a, c)))), | |
| (Tuple(Lt(a, b), Lt(c, a)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))), | |
| (Tuple(Lt(c, a), Lt(a, b)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))), | |
| (Tuple(Le(a, b), Lt(c, a)), ITE(b >= c, true, Or(Le(a, b), Gt(a, c)))), | |
| (Tuple(Le(c, a), Lt(a, b)), ITE(b >= c, true, Or(Lt(a, b), Ge(a, c)))), | |
| (Tuple(Lt(b, a), Lt(a, -b)), ITE(b >= 0, Gt(Abs(a), b), true)), | |
| (Tuple(Le(b, a), Le(a, -b)), ITE(b > 0, Ge(Abs(a), b), true)), | |
| ) | |
| return _matchers_or | |
| def _simplify_patterns_xor(): | |
| """ Two-term patterns for Xor.""" | |
| from sympy.functions.elementary.miscellaneous import Min, Max | |
| from sympy.core import Wild | |
| from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt | |
| a = Wild('a') | |
| b = Wild('b') | |
| c = Wild('c') | |
| # Relationals patterns should be in alphabetical order | |
| # (pattern1, pattern2, simplified) | |
| # Do not use Ge, Gt | |
| _matchers_xor = (#(Tuple(Le(b, a), Lt(a, b)), true), | |
| #(Tuple(Lt(b, a), Le(a, b)), true), | |
| #(Tuple(Eq(a, b), Le(b, a)), Gt(a, b)), | |
| #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)), | |
| (Tuple(Eq(a, b), Le(a, b)), Lt(a, b)), | |
| (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)), | |
| (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)), | |
| (Tuple(Le(a, b), Le(b, a)), Ne(a, b)), | |
| (Tuple(Le(b, a), Ne(a, b)), Le(a, b)), | |
| # (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)), | |
| (Tuple(Lt(b, a), Ne(a, b)), Lt(a, b)), | |
| # (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)), | |
| # (Tuple(Le(a, b), Ne(a, b)), Ge(a, b)), | |
| # (Tuple(Lt(a, b), Ne(a, b)), Gt(a, b)), | |
| # Min/Max/ITE | |
| (Tuple(Le(b, a), Le(c, a)), | |
| And(Ge(a, Min(b, c)), Lt(a, Max(b, c)))), | |
| (Tuple(Le(b, a), Lt(c, a)), | |
| ITE(b > c, And(Gt(a, c), Lt(a, b)), | |
| And(Ge(a, b), Le(a, c)))), | |
| (Tuple(Lt(b, a), Lt(c, a)), | |
| And(Gt(a, Min(b, c)), Le(a, Max(b, c)))), | |
| (Tuple(Le(a, b), Le(a, c)), | |
| And(Le(a, Max(b, c)), Gt(a, Min(b, c)))), | |
| (Tuple(Le(a, b), Lt(a, c)), | |
| ITE(b < c, And(Lt(a, c), Gt(a, b)), | |
| And(Le(a, b), Ge(a, c)))), | |
| (Tuple(Lt(a, b), Lt(a, c)), | |
| And(Lt(a, Max(b, c)), Ge(a, Min(b, c)))), | |
| ) | |
| return _matchers_xor | |
| def simplify_univariate(expr): | |
| """return a simplified version of univariate boolean expression, else ``expr``""" | |
| from sympy.functions.elementary.piecewise import Piecewise | |
| from sympy.core.relational import Eq, Ne | |
| if not isinstance(expr, BooleanFunction): | |
| return expr | |
| if expr.atoms(Eq, Ne): | |
| return expr | |
| c = expr | |
| free = c.free_symbols | |
| if len(free) != 1: | |
| return c | |
| x = free.pop() | |
| ok, i = Piecewise((0, c), evaluate=False | |
| )._intervals(x, err_on_Eq=True) | |
| if not ok: | |
| return c | |
| if not i: | |
| return false | |
| args = [] | |
| for a, b, _, _ in i: | |
| if a is S.NegativeInfinity: | |
| if b is S.Infinity: | |
| c = true | |
| else: | |
| if c.subs(x, b) == True: | |
| c = (x <= b) | |
| else: | |
| c = (x < b) | |
| else: | |
| incl_a = (c.subs(x, a) == True) | |
| incl_b = (c.subs(x, b) == True) | |
| if incl_a and incl_b: | |
| if b.is_infinite: | |
| c = (x >= a) | |
| else: | |
| c = And(a <= x, x <= b) | |
| elif incl_a: | |
| c = And(a <= x, x < b) | |
| elif incl_b: | |
| if b.is_infinite: | |
| c = (x > a) | |
| else: | |
| c = And(a < x, x <= b) | |
| else: | |
| c = And(a < x, x < b) | |
| args.append(c) | |
| return Or(*args) | |
| # Classes corresponding to logic gates | |
| # Used in gateinputcount method | |
| BooleanGates = (And, Or, Xor, Nand, Nor, Not, Xnor, ITE) | |
| def gateinputcount(expr): | |
| """ | |
| Return the total number of inputs for the logic gates realizing the | |
| Boolean expression. | |
| Returns | |
| ======= | |
| int | |
| Number of gate inputs | |
| Note | |
| ==== | |
| Not all Boolean functions count as gate here, only those that are | |
| considered to be standard gates. These are: :py:class:`~.And`, | |
| :py:class:`~.Or`, :py:class:`~.Xor`, :py:class:`~.Not`, and | |
| :py:class:`~.ITE` (multiplexer). :py:class:`~.Nand`, :py:class:`~.Nor`, | |
| and :py:class:`~.Xnor` will be evaluated to ``Not(And())`` etc. | |
| Examples | |
| ======== | |
| >>> from sympy.logic import And, Or, Nand, Not, gateinputcount | |
| >>> from sympy.abc import x, y, z | |
| >>> expr = And(x, y) | |
| >>> gateinputcount(expr) | |
| 2 | |
| >>> gateinputcount(Or(expr, z)) | |
| 4 | |
| Note that ``Nand`` is automatically evaluated to ``Not(And())`` so | |
| >>> gateinputcount(Nand(x, y, z)) | |
| 4 | |
| >>> gateinputcount(Not(And(x, y, z))) | |
| 4 | |
| Although this can be avoided by using ``evaluate=False`` | |
| >>> gateinputcount(Nand(x, y, z, evaluate=False)) | |
| 3 | |
| Also note that a comparison will count as a Boolean variable: | |
| >>> gateinputcount(And(x > z, y >= 2)) | |
| 2 | |
| As will a symbol: | |
| >>> gateinputcount(x) | |
| 0 | |
| """ | |
| if not isinstance(expr, Boolean): | |
| raise TypeError("Expression must be Boolean") | |
| if isinstance(expr, BooleanGates): | |
| return len(expr.args) + sum(gateinputcount(x) for x in expr.args) | |
| return 0 | |