123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- """
- The classes used here are for the internal use of assumptions system
- only and should not be used anywhere else as these do not possess the
- signatures common to SymPy objects. For general use of logic constructs
- please refer to sympy.logic classes And, Or, Not, etc.
- """
- from itertools import combinations, product
- from sympy.core.singleton import S
- from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor)
- from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
- from sympy.logic.boolalg import Or, And, Not, Xnor
- from itertools import zip_longest
- class Literal:
- """
- The smallest element of a CNF object.
- Parameters
- ==========
- lit : Boolean expression
- is_Not : bool
- Examples
- ========
- >>> from sympy import Q
- >>> from sympy.assumptions.cnf import Literal
- >>> from sympy.abc import x
- >>> Literal(Q.even(x))
- Literal(Q.even(x), False)
- >>> Literal(~Q.even(x))
- Literal(Q.even(x), True)
- """
- def __new__(cls, lit, is_Not=False):
- if isinstance(lit, Not):
- lit = lit.args[0]
- is_Not = True
- elif isinstance(lit, (AND, OR, Literal)):
- return ~lit if is_Not else lit
- obj = super().__new__(cls)
- obj.lit = lit
- obj.is_Not = is_Not
- return obj
- @property
- def arg(self):
- return self.lit
- def rcall(self, expr):
- if callable(self.lit):
- lit = self.lit(expr)
- else:
- try:
- lit = self.lit.apply(expr)
- except AttributeError:
- lit = self.lit.rcall(expr)
- return type(self)(lit, self.is_Not)
- def __invert__(self):
- is_Not = not self.is_Not
- return Literal(self.lit, is_Not)
- def __str__(self):
- return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not)
- __repr__ = __str__
- def __eq__(self, other):
- return self.arg == other.arg and self.is_Not == other.is_Not
- def __hash__(self):
- h = hash((type(self).__name__, self.arg, self.is_Not))
- return h
- class OR:
- """
- A low-level implementation for Or
- """
- def __init__(self, *args):
- self._args = args
- @property
- def args(self):
- return sorted(self._args, key=str)
- def rcall(self, expr):
- return type(self)(*[arg.rcall(expr)
- for arg in self._args
- ])
- def __invert__(self):
- return AND(*[~arg for arg in self._args])
- def __hash__(self):
- return hash((type(self).__name__,) + tuple(self.args))
- def __eq__(self, other):
- return self.args == other.args
- def __str__(self):
- s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')'
- return s
- __repr__ = __str__
- class AND:
- """
- A low-level implementation for And
- """
- def __init__(self, *args):
- self._args = args
- def __invert__(self):
- return OR(*[~arg for arg in self._args])
- @property
- def args(self):
- return sorted(self._args, key=str)
- def rcall(self, expr):
- return type(self)(*[arg.rcall(expr)
- for arg in self._args
- ])
- def __hash__(self):
- return hash((type(self).__name__,) + tuple(self.args))
- def __eq__(self, other):
- return self.args == other.args
- def __str__(self):
- s = '('+' & '.join([str(arg) for arg in self.args])+')'
- return s
- __repr__ = __str__
- def to_NNF(expr, composite_map=None):
- """
- Generates the Negation Normal Form of any boolean expression in terms
- of AND, OR, and Literal objects.
- Examples
- ========
- >>> from sympy import Q, Eq
- >>> from sympy.assumptions.cnf import to_NNF
- >>> from sympy.abc import x, y
- >>> expr = Q.even(x) & ~Q.positive(x)
- >>> to_NNF(expr)
- (Literal(Q.even(x), False) & Literal(Q.positive(x), True))
- Supported boolean objects are converted to corresponding predicates.
- >>> to_NNF(Eq(x, y))
- Literal(Q.eq(x, y), False)
- If ``composite_map`` argument is given, ``to_NNF`` decomposes the
- specified predicate into a combination of primitive predicates.
- >>> cmap = {Q.nonpositive: Q.negative | Q.zero}
- >>> to_NNF(Q.nonpositive, cmap)
- (Literal(Q.negative, False) | Literal(Q.zero, False))
- >>> to_NNF(Q.nonpositive(x), cmap)
- (Literal(Q.negative(x), False) | Literal(Q.zero(x), False))
- """
- from sympy.assumptions.ask import Q
- from sympy.assumptions.assume import AppliedPredicate, Predicate
- if composite_map is None:
- composite_map = dict()
- binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
- if type(expr) in binrelpreds:
- pred = binrelpreds[type(expr)]
- expr = pred(*expr.args)
- if isinstance(expr, Not):
- arg = expr.args[0]
- tmp = to_NNF(arg, composite_map) # Strategy: negate the NNF of expr
- return ~tmp
- if isinstance(expr, Or):
- return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)])
- if isinstance(expr, And):
- return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)])
- if isinstance(expr, Nand):
- tmp = AND(*[to_NNF(x, composite_map) for x in expr.args])
- return ~tmp
- if isinstance(expr, Nor):
- tmp = OR(*[to_NNF(x, composite_map) for x in expr.args])
- return ~tmp
- if isinstance(expr, Xor):
- cnfs = []
- for i in range(0, len(expr.args) + 1, 2):
- for neg in combinations(expr.args, i):
- clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
- for s in expr.args]
- cnfs.append(OR(*clause))
- return AND(*cnfs)
- if isinstance(expr, Xnor):
- cnfs = []
- for i in range(0, len(expr.args) + 1, 2):
- for neg in combinations(expr.args, i):
- clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
- for s in expr.args]
- cnfs.append(OR(*clause))
- return ~AND(*cnfs)
- if isinstance(expr, Implies):
- L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map)
- return OR(~L, R)
- if isinstance(expr, Equivalent):
- cnfs = []
- for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]):
- a = to_NNF(a, composite_map)
- b = to_NNF(b, composite_map)
- cnfs.append(OR(~a, b))
- return AND(*cnfs)
- if isinstance(expr, ITE):
- L = to_NNF(expr.args[0], composite_map)
- M = to_NNF(expr.args[1], composite_map)
- R = to_NNF(expr.args[2], composite_map)
- return AND(OR(~L, M), OR(L, R))
- if isinstance(expr, AppliedPredicate):
- pred, args = expr.function, expr.arguments
- newpred = composite_map.get(pred, None)
- if newpred is not None:
- return to_NNF(newpred.rcall(*args), composite_map)
- if isinstance(expr, Predicate):
- newpred = composite_map.get(expr, None)
- if newpred is not None:
- return to_NNF(newpred, composite_map)
- return Literal(expr)
- def distribute_AND_over_OR(expr):
- """
- Distributes AND over OR in the NNF expression.
- Returns the result( Conjunctive Normal Form of expression)
- as a CNF object.
- """
- if not isinstance(expr, (AND, OR)):
- tmp = set()
- tmp.add(frozenset((expr,)))
- return CNF(tmp)
- if isinstance(expr, OR):
- return CNF.all_or(*[distribute_AND_over_OR(arg)
- for arg in expr._args])
- if isinstance(expr, AND):
- return CNF.all_and(*[distribute_AND_over_OR(arg)
- for arg in expr._args])
- class CNF:
- """
- Class to represent CNF of a Boolean expression.
- Consists of set of clauses, which themselves are stored as
- frozenset of Literal objects.
- Examples
- ========
- >>> from sympy import Q
- >>> from sympy.assumptions.cnf import CNF
- >>> from sympy.abc import x
- >>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x))
- >>> cnf.clauses
- {frozenset({Literal(Q.zero(x), True)}),
- frozenset({Literal(Q.negative(x), False),
- Literal(Q.positive(x), False), Literal(Q.zero(x), False)})}
- """
- def __init__(self, clauses=None):
- if not clauses:
- clauses = set()
- self.clauses = clauses
- def add(self, prop):
- clauses = CNF.to_CNF(prop).clauses
- self.add_clauses(clauses)
- def __str__(self):
- s = ' & '.join(
- ['(' + ' | '.join([str(lit) for lit in clause]) +')'
- for clause in self.clauses]
- )
- return s
- def extend(self, props):
- for p in props:
- self.add(p)
- return self
- def copy(self):
- return CNF(set(self.clauses))
- def add_clauses(self, clauses):
- self.clauses |= clauses
- @classmethod
- def from_prop(cls, prop):
- res = cls()
- res.add(prop)
- return res
- def __iand__(self, other):
- self.add_clauses(other.clauses)
- return self
- def all_predicates(self):
- predicates = set()
- for c in self.clauses:
- predicates |= {arg.lit for arg in c}
- return predicates
- def _or(self, cnf):
- clauses = set()
- for a, b in product(self.clauses, cnf.clauses):
- tmp = set(a)
- for t in b:
- tmp.add(t)
- clauses.add(frozenset(tmp))
- return CNF(clauses)
- def _and(self, cnf):
- clauses = self.clauses.union(cnf.clauses)
- return CNF(clauses)
- def _not(self):
- clss = list(self.clauses)
- ll = set()
- for x in clss[-1]:
- ll.add(frozenset((~x,)))
- ll = CNF(ll)
- for rest in clss[:-1]:
- p = set()
- for x in rest:
- p.add(frozenset((~x,)))
- ll = ll._or(CNF(p))
- return ll
- def rcall(self, expr):
- clause_list = list()
- for clause in self.clauses:
- lits = [arg.rcall(expr) for arg in clause]
- clause_list.append(OR(*lits))
- expr = AND(*clause_list)
- return distribute_AND_over_OR(expr)
- @classmethod
- def all_or(cls, *cnfs):
- b = cnfs[0].copy()
- for rest in cnfs[1:]:
- b = b._or(rest)
- return b
- @classmethod
- def all_and(cls, *cnfs):
- b = cnfs[0].copy()
- for rest in cnfs[1:]:
- b = b._and(rest)
- return b
- @classmethod
- def to_CNF(cls, expr):
- from sympy.assumptions.facts import get_composite_predicates
- expr = to_NNF(expr, get_composite_predicates())
- expr = distribute_AND_over_OR(expr)
- return expr
- @classmethod
- def CNF_to_cnf(cls, cnf):
- """
- Converts CNF object to SymPy's boolean expression
- retaining the form of expression.
- """
- def remove_literal(arg):
- return Not(arg.lit) if arg.is_Not else arg.lit
- return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses))
- class EncodedCNF:
- """
- Class for encoding the CNF expression.
- """
- def __init__(self, data=None, encoding=None):
- if not data and not encoding:
- data = list()
- encoding = dict()
- self.data = data
- self.encoding = encoding
- self._symbols = list(encoding.keys())
- def from_cnf(self, cnf):
- self._symbols = list(cnf.all_predicates())
- n = len(self._symbols)
- self.encoding = dict(list(zip(self._symbols, list(range(1, n + 1)))))
- self.data = [self.encode(clause) for clause in cnf.clauses]
- @property
- def symbols(self):
- return self._symbols
- @property
- def variables(self):
- return range(1, len(self._symbols) + 1)
- def copy(self):
- new_data = [set(clause) for clause in self.data]
- return EncodedCNF(new_data, dict(self.encoding))
- def add_prop(self, prop):
- cnf = CNF.from_prop(prop)
- self.add_from_cnf(cnf)
- def add_from_cnf(self, cnf):
- clauses = [self.encode(clause) for clause in cnf.clauses]
- self.data += clauses
- def encode_arg(self, arg):
- literal = arg.lit
- value = self.encoding.get(literal, None)
- if value is None:
- n = len(self._symbols)
- self._symbols.append(literal)
- value = self.encoding[literal] = n + 1
- if arg.is_Not:
- return -value
- else:
- return value
- def encode(self, clause):
- return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause}
|