| | """ |
| | Boolean algebra module for SymPy |
| | """ |
| |
|
| | from __future__ import annotations |
| | from typing import TYPE_CHECKING, overload, Any |
| | from collections.abc import Iterable, Mapping |
| |
|
| | 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) |
| |
|
| |
|
| | @sympify_method_args |
| | class Boolean(Basic): |
| | """A Boolean object is an object for which logic operations make sense.""" |
| |
|
| | __slots__ = () |
| |
|
| | kind = BooleanKind |
| |
|
| | if TYPE_CHECKING: |
| |
|
| | def __new__(cls, *args: Basic | complex) -> Boolean: |
| | ... |
| |
|
| | @overload |
| | def subs(self, arg1: Mapping[Basic | complex, Boolean | complex], arg2: None=None) -> Boolean: ... |
| | @overload |
| | def subs(self, arg1: Iterable[tuple[Basic | complex, Boolean | complex]], arg2: None=None, **kwargs: Any) -> Boolean: ... |
| | @overload |
| | def subs(self, arg1: Boolean | complex, arg2: Boolean | complex) -> Boolean: ... |
| | @overload |
| | def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ... |
| | @overload |
| | def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ... |
| | @overload |
| | def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ... |
| |
|
| | def subs(self, arg1: Mapping[Basic | complex, Basic | complex] | Basic | complex, |
| | arg2: Basic | complex | None = None, **kwargs: Any) -> Basic: |
| | ... |
| |
|
| | def simplify(self, **kwargs) -> Boolean: |
| | ... |
| |
|
| | @sympify_return([('other', 'Boolean')], NotImplemented) |
| | def __and__(self, other): |
| | return And(self, other) |
| |
|
| | __rand__ = __and__ |
| |
|
| | @sympify_return([('other', 'Boolean')], NotImplemented) |
| | def __or__(self, other): |
| | return Or(self, other) |
| |
|
| | __ror__ = __or__ |
| |
|
| | def __invert__(self): |
| | """Overloading for ~""" |
| | return Not(self) |
| |
|
| | @sympify_return([('other', 'Boolean')], NotImplemented) |
| | def __rshift__(self, other): |
| | return Implies(self, other) |
| |
|
| | @sympify_return([('other', 'Boolean')], NotImplemented) |
| | def __lshift__(self, other): |
| | return Implies(other, self) |
| |
|
| | __rrshift__ = __lshift__ |
| | __rlshift__ = __rshift__ |
| |
|
| | @sympify_return([('other', 'Boolean')], NotImplemented) |
| | 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): |
| | |
| | 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() |
| | 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") |
| |
|
| | @property |
| | 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 |
| |
|
| | def simplify(self, *a, **kw): |
| | return self |
| |
|
| | def expand(self, *a, **kw): |
| | return self |
| |
|
| | @property |
| | 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 |
| |
|
| | 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) |
| |
|
| | @property |
| | 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) |
| |
|
| | @property |
| | 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() |
| | |
| | |
| | |
| | |
| | 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__ |
| |
|
| | @classmethod |
| | 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) |
| |
|
| | @classmethod |
| | 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) |
| |
|
| | @classmethod |
| | 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) |
| |
|
| | |
| | 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: |
| | |
| | |
| | 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 |
| |
|
| | if TYPE_CHECKING: |
| |
|
| | def __new__(cls, *args: Boolean | bool) -> Boolean: |
| | ... |
| |
|
| | @property |
| | def args(self) -> tuple[Boolean, ...]: |
| | ... |
| |
|
| | @classmethod |
| | 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: |
| | |
| | if bad is None: |
| | bad = i |
| | continue |
| | if i == False: |
| | return false |
| | elif i != True: |
| | args.append(i) |
| | if bad is not None: |
| | |
| | bad.subs(old, new) |
| | |
| | |
| | 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 |
| | |
| | rv = super()._eval_simplify(**kwargs) |
| | if not isinstance(rv, And): |
| | return rv |
| |
|
| | |
| | |
| | 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 = {} |
| | |
| | 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: |
| | |
| | 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 |
| |
|
| | if TYPE_CHECKING: |
| |
|
| | def __new__(cls, *args: Boolean | bool) -> Boolean: |
| | ... |
| |
|
| | @property |
| | def args(self) -> tuple[Boolean, ...]: |
| | ... |
| |
|
| | @classmethod |
| | 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: |
| | |
| | if bad is None: |
| | bad = i |
| | continue |
| | if i == True: |
| | return true |
| | elif i != False: |
| | args.append(i) |
| | if bad is not None: |
| | |
| | bad.subs(old, new) |
| | |
| | |
| | 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) |
| | |
| | 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) |
| | 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 |
| |
|
| | @classmethod |
| | 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] |
| | |
| | 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 |
| | 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 |
| |
|
| | |
| | |
| | @property |
| | @cacheit |
| | 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): |
| | |
| | |
| | |
| | rv = self.func(*[a.simplify(**kwargs) for a in self.args]) |
| | rv = rv.to_anf() |
| | if not isinstance(rv, Xor): |
| | return rv |
| | patterns = _simplify_patterns_xor() |
| | return _apply_patternbased_simplification(rv, patterns, |
| | kwargs['measure'], None) |
| |
|
| | def _eval_subs(self, old, new): |
| | |
| | |
| | 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) |
| | |
| | """ |
| | @classmethod |
| | 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) |
| | |
| | """ |
| | @classmethod |
| | 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 |
| | |
| | """ |
| | @classmethod |
| | 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 |
| | |
| | """ |
| | @classmethod |
| | 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]: |
| | 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 |
| |
|
| | |
| | |
| | @property |
| | @cacheit |
| | 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 |
| | |
| | if isinstance(a, (Eq, Ne)): |
| | |
| | |
| | |
| | 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: |
| | |
| | _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: |
| | |
| | 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 |
| |
|
| | @classmethod |
| | def eval(cls, *args): |
| | from sympy.core.relational import Eq, Ne |
| | |
| | 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: |
| | |
| | |
| | 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 |
| | |
| | """ |
| | @classmethod |
| | def eval(cls, *args): |
| | and_args = [] |
| | for a, b in combinations(args, 2): |
| | and_args.append(Not(And(a, b))) |
| | return And(*and_args) |
| |
|
| |
|
| | |
| |
|
| |
|
| | 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) |
| |
|
| | |
| | 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) |
| |
|
| | |
| | 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 |
| | |
| | """ |
| |
|
| | |
| | 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 |
| |
|
| |
|
| | 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`. |
| | """ |
| | |
| | |
| | index = -1 |
| | for x, i in enumerate(minterm1): |
| | 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))) |
| |
|
| | |
| | |
| | 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: |
| | |
| | todo[i] = todo[j] = None |
| | |
| | newterm = terms[i][:] |
| | |
| | newterm[index] = 3 |
| | |
| | if newterm not in simplified_terms: |
| | simplified_terms.append(newterm) |
| |
|
| | if simplified_terms: |
| | |
| | simplified_terms = _simplified_pairs(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) |
| |
|
| | |
| | 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): |
| | |
| | if all(t == 3 or t == mt for t, mt in zip(prime, term)): |
| | dommatrix[termi][primei] = 1 |
| | colcount[primei] += 1 |
| | rowcount[termi] += 1 |
| |
|
| | |
| | anythingchanged = True |
| | |
| | while anythingchanged: |
| | anythingchanged = False |
| |
|
| | for rowi in range(nterms): |
| | |
| | if rowcount[rowi]: |
| | row = dommatrix[rowi] |
| | for row2i in range(nterms): |
| | |
| | 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)): |
| | |
| | rowcount[row2i] = 0 |
| | anythingchanged = True |
| | for primei, prime in enumerate(row2): |
| | if prime: |
| | |
| | dommatrix[row2i][primei] = 0 |
| | colcount[primei] -= 1 |
| |
|
| | colcache = {} |
| |
|
| | for coli in range(nl1): |
| | |
| | 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): |
| | |
| | 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)): |
| | |
| | colcount[col2i] = 0 |
| | anythingchanged = True |
| | for termi, term in enumerate(col2): |
| | if term and dommatrix[termi][col2i]: |
| | |
| | dommatrix[termi][col2i] = 0 |
| | rowcount[termi] -= 1 |
| |
|
| | if not anythingchanged: |
| | |
| | maxterms = 0 |
| | bestcolidx = -1 |
| | for coli in range(nl1): |
| | s = colcount[coli] |
| | if s > maxterms: |
| | bestcolidx = coli |
| | maxterms = s |
| |
|
| | |
| | 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]: |
| | |
| | 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) |
| | |
| | |
| | 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 |
| | |
| | |
| | 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) |
| |
|
| | |
| | variables = _find_predicates(expr) |
| | if not force and len(variables) > 8: |
| | return expr.xreplace(undo) |
| | if dontcare is not None: |
| | |
| | dcvariables = _find_predicates(dontcare) |
| | variables.update(dcvariables) |
| | |
| | if not force and len(variables) > 8: |
| | variables = _find_predicates(expr) |
| | dontcare = None |
| | |
| | c, v = sift(ordered(variables), lambda x: x in (True, False), binary=True) |
| | variables = c + v |
| | |
| | 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 |
| | """ |
| |
|
| | |
| | if function1.__class__ != function2.__class__: |
| | return None |
| | if len(function1.args) != len(function2.args): |
| | return None |
| | if function1.is_Symbol: |
| | return {function1: function2} |
| |
|
| | |
| | f1 = _finger(function1) |
| | f2 = _finger(function2) |
| |
|
| | |
| | if len(f1) != len(f2): |
| | return False |
| |
|
| | |
| | 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 |
| | |
| | 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 |
| | |
| | Rel = [r.reversed if isinstance(r, (Ge, Gt)) else r for r in Rel] |
| | |
| | Rel = list(ordered(Rel)) |
| | |
| | rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] |
| | |
| | results = [] |
| | |
| | for ((i, pi), (j, pj)) in combinations(enumerate(rtmp), 2): |
| | for pattern, simp in patterns: |
| | res = [] |
| | for p1, p2 in product(pi, pj): |
| | |
| | oldexpr = Tuple(p1, p2) |
| | tmpres = oldexpr.match(pattern) |
| | if tmpres: |
| | res.append((tmpres, oldexpr)) |
| |
|
| | if res: |
| | for tmpres, oldexpr in res: |
| | |
| | np = simp.xreplace(tmpres) |
| | if np == dominatingvalue: |
| | |
| | |
| | return [replacementvalue] |
| | |
| | if not isinstance(np, ITE) and not np.has(Min, Max): |
| | |
| | |
| | costsaving = measure(func(*oldexpr.args)) - measure(np) |
| | if costsaving > 0: |
| | results.append((costsaving, ([i, j], np))) |
| | if results: |
| | |
| | results = sorted(results, |
| | key=lambda pair: pair[0], reverse=True) |
| | |
| | replacement = results[0][1] |
| | idx, newrel = replacement |
| | idx.sort() |
| | |
| | for index in reversed(idx): |
| | del Rel[index] |
| | if dominatingvalue is None or newrel != Not(dominatingvalue): |
| | |
| | |
| | if newrel.func == func: |
| | for a in newrel.args: |
| | Rel.append(a) |
| | else: |
| | Rel.append(newrel) |
| | |
| | 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 |
| | |
| | Rel = [r.reversed if isinstance(r, (Le, Lt)) else r for r in Rel] |
| | |
| | Rel = list(ordered(Rel)) |
| | |
| | results = [] |
| | |
| | rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] |
| | |
| | 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): |
| | |
| | oldexpr = Tuple(p1, p2, p3) |
| | tmpres = oldexpr.match(pattern) |
| | if tmpres: |
| | res.append((tmpres, oldexpr)) |
| |
|
| | if res: |
| | for tmpres, oldexpr in res: |
| | |
| | np = simp.xreplace(tmpres) |
| | if np == dominatingvalue: |
| | |
| | |
| | return [replacementvalue] |
| | |
| | if not isinstance(np, ITE) and not np.has(Min, Max): |
| | |
| | |
| | costsaving = measure(func(*oldexpr.args)) - measure(np) |
| | if costsaving > 0: |
| | results.append((costsaving, ([i, j, k], np))) |
| | if results: |
| | |
| | results = sorted(results, |
| | key=lambda pair: pair[0], reverse=True) |
| | |
| | replacement = results[0][1] |
| | idx, newrel = replacement |
| | idx.sort() |
| | |
| | for index in reversed(idx): |
| | del Rel[index] |
| | if dominatingvalue is None or newrel != Not(dominatingvalue): |
| | |
| | |
| | if newrel.func == func: |
| | for a in newrel.args: |
| | Rel.append(a) |
| | else: |
| | Rel.append(newrel) |
| | |
| | changed = True |
| | return Rel |
| |
|
| |
|
| | @cacheit |
| | 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') |
| | |
| | |
| | |
| | _matchers_and = ((Tuple(Eq(a, b), Lt(a, b)), false), |
| | |
| | |
| | |
| | (Tuple(Lt(b, a), Lt(a, b)), false), |
| | (Tuple(Eq(a, b), Le(b, a)), Eq(a, b)), |
| | |
| | |
| | (Tuple(Le(b, a), Le(a, b)), Eq(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)), |
| | |
| | (Tuple(Eq(a, b), Eq(a, -b)), And(Eq(a, S.Zero), Eq(b, S.Zero))), |
| | |
| | (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 |
| |
|
| |
|
| | @cacheit |
| | 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') |
| | |
| | |
| | |
| | _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(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), Gt(b, c)), And(Ge(a, b), Gt(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))), |
| | |
| | |
| | (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), Gt(b, c)), And(Gt(c, a), Gt(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))), |
| | |
| | (Tuple(Ge(a, b), Ge(b, c), Ge(c, a)), And(Eq(a, b), Eq(b, c))), |
| | ) |
| | return _matchers_and |
| |
|
| |
|
| | @cacheit |
| | 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') |
| | |
| | |
| | |
| | _matchers_or = ((Tuple(Le(b, a), Le(a, b)), true), |
| | |
| | (Tuple(Le(b, a), Ne(a, b)), true), |
| | |
| | |
| | |
| | |
| | (Tuple(Eq(a, b), Le(a, b)), Le(a, b)), |
| | (Tuple(Eq(a, b), Lt(a, b)), Le(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(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)), |
| | |
| | (Tuple(Le(b, a), Le(c, a)), Ge(a, Min(b, c))), |
| | |
| | (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(Le(a, b), Le(a, c)), Le(a, Max(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, Max(b, c))), |
| | |
| | (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 |
| |
|
| |
|
| | @cacheit |
| | 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') |
| | |
| | |
| | |
| | _matchers_xor = ( |
| | |
| | |
| | |
| | (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), Ne(a, b)), Lt(a, b)), |
| | |
| | |
| | |
| | |
| | (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) |
| |
|
| |
|
| | |
| | |
| | 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 |
| |
|