Spaces:
Sleeping
Sleeping
""" | |
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, zip_longest | |
from sympy.assumptions.assume import AppliedPredicate, Predicate | |
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le | |
from sympy.core.singleton import S | |
from sympy.logic.boolalg import Or, And, Not, Xnor | |
from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor) | |
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 | |
def arg(self): | |
return self.lit | |
def rcall(self, expr): | |
if callable(self.lit): | |
lit = self.lit(expr) | |
else: | |
lit = self.lit.apply(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 | |
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]) | |
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 | |
if composite_map is None: | |
composite_map = {} | |
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 | |
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) | |
tmp.update(b) | |
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 = {frozenset((~x,)) for x in clss[-1]} | |
ll = CNF(ll) | |
for rest in clss[:-1]: | |
p = {frozenset((~x,)) for x in rest} | |
ll = ll._or(CNF(p)) | |
return ll | |
def rcall(self, expr): | |
clause_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) | |
def all_or(cls, *cnfs): | |
b = cnfs[0].copy() | |
for rest in cnfs[1:]: | |
b = b._or(rest) | |
return b | |
def all_and(cls, *cnfs): | |
b = cnfs[0].copy() | |
for rest in cnfs[1:]: | |
b = b._and(rest) | |
return b | |
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 | |
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 = [] | |
encoding = {} | |
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(zip(self._symbols, range(1, n + 1))) | |
self.data = [self.encode(clause) for clause in cnf.clauses] | |
def symbols(self): | |
return self._symbols | |
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} | |