cnf.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. """
  2. The classes used here are for the internal use of assumptions system
  3. only and should not be used anywhere else as these do not possess the
  4. signatures common to SymPy objects. For general use of logic constructs
  5. please refer to sympy.logic classes And, Or, Not, etc.
  6. """
  7. from itertools import combinations, product, zip_longest
  8. from sympy.assumptions.assume import AppliedPredicate, Predicate
  9. from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
  10. from sympy.core.singleton import S
  11. from sympy.logic.boolalg import Or, And, Not, Xnor
  12. from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor)
  13. class Literal:
  14. """
  15. The smallest element of a CNF object.
  16. Parameters
  17. ==========
  18. lit : Boolean expression
  19. is_Not : bool
  20. Examples
  21. ========
  22. >>> from sympy import Q
  23. >>> from sympy.assumptions.cnf import Literal
  24. >>> from sympy.abc import x
  25. >>> Literal(Q.even(x))
  26. Literal(Q.even(x), False)
  27. >>> Literal(~Q.even(x))
  28. Literal(Q.even(x), True)
  29. """
  30. def __new__(cls, lit, is_Not=False):
  31. if isinstance(lit, Not):
  32. lit = lit.args[0]
  33. is_Not = True
  34. elif isinstance(lit, (AND, OR, Literal)):
  35. return ~lit if is_Not else lit
  36. obj = super().__new__(cls)
  37. obj.lit = lit
  38. obj.is_Not = is_Not
  39. return obj
  40. @property
  41. def arg(self):
  42. return self.lit
  43. def rcall(self, expr):
  44. if callable(self.lit):
  45. lit = self.lit(expr)
  46. else:
  47. lit = self.lit.apply(expr)
  48. return type(self)(lit, self.is_Not)
  49. def __invert__(self):
  50. is_Not = not self.is_Not
  51. return Literal(self.lit, is_Not)
  52. def __str__(self):
  53. return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not)
  54. __repr__ = __str__
  55. def __eq__(self, other):
  56. return self.arg == other.arg and self.is_Not == other.is_Not
  57. def __hash__(self):
  58. h = hash((type(self).__name__, self.arg, self.is_Not))
  59. return h
  60. class OR:
  61. """
  62. A low-level implementation for Or
  63. """
  64. def __init__(self, *args):
  65. self._args = args
  66. @property
  67. def args(self):
  68. return sorted(self._args, key=str)
  69. def rcall(self, expr):
  70. return type(self)(*[arg.rcall(expr)
  71. for arg in self._args
  72. ])
  73. def __invert__(self):
  74. return AND(*[~arg for arg in self._args])
  75. def __hash__(self):
  76. return hash((type(self).__name__,) + tuple(self.args))
  77. def __eq__(self, other):
  78. return self.args == other.args
  79. def __str__(self):
  80. s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')'
  81. return s
  82. __repr__ = __str__
  83. class AND:
  84. """
  85. A low-level implementation for And
  86. """
  87. def __init__(self, *args):
  88. self._args = args
  89. def __invert__(self):
  90. return OR(*[~arg for arg in self._args])
  91. @property
  92. def args(self):
  93. return sorted(self._args, key=str)
  94. def rcall(self, expr):
  95. return type(self)(*[arg.rcall(expr)
  96. for arg in self._args
  97. ])
  98. def __hash__(self):
  99. return hash((type(self).__name__,) + tuple(self.args))
  100. def __eq__(self, other):
  101. return self.args == other.args
  102. def __str__(self):
  103. s = '('+' & '.join([str(arg) for arg in self.args])+')'
  104. return s
  105. __repr__ = __str__
  106. def to_NNF(expr, composite_map=None):
  107. """
  108. Generates the Negation Normal Form of any boolean expression in terms
  109. of AND, OR, and Literal objects.
  110. Examples
  111. ========
  112. >>> from sympy import Q, Eq
  113. >>> from sympy.assumptions.cnf import to_NNF
  114. >>> from sympy.abc import x, y
  115. >>> expr = Q.even(x) & ~Q.positive(x)
  116. >>> to_NNF(expr)
  117. (Literal(Q.even(x), False) & Literal(Q.positive(x), True))
  118. Supported boolean objects are converted to corresponding predicates.
  119. >>> to_NNF(Eq(x, y))
  120. Literal(Q.eq(x, y), False)
  121. If ``composite_map`` argument is given, ``to_NNF`` decomposes the
  122. specified predicate into a combination of primitive predicates.
  123. >>> cmap = {Q.nonpositive: Q.negative | Q.zero}
  124. >>> to_NNF(Q.nonpositive, cmap)
  125. (Literal(Q.negative, False) | Literal(Q.zero, False))
  126. >>> to_NNF(Q.nonpositive(x), cmap)
  127. (Literal(Q.negative(x), False) | Literal(Q.zero(x), False))
  128. """
  129. from sympy.assumptions.ask import Q
  130. if composite_map is None:
  131. composite_map = {}
  132. binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
  133. if type(expr) in binrelpreds:
  134. pred = binrelpreds[type(expr)]
  135. expr = pred(*expr.args)
  136. if isinstance(expr, Not):
  137. arg = expr.args[0]
  138. tmp = to_NNF(arg, composite_map) # Strategy: negate the NNF of expr
  139. return ~tmp
  140. if isinstance(expr, Or):
  141. return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)])
  142. if isinstance(expr, And):
  143. return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)])
  144. if isinstance(expr, Nand):
  145. tmp = AND(*[to_NNF(x, composite_map) for x in expr.args])
  146. return ~tmp
  147. if isinstance(expr, Nor):
  148. tmp = OR(*[to_NNF(x, composite_map) for x in expr.args])
  149. return ~tmp
  150. if isinstance(expr, Xor):
  151. cnfs = []
  152. for i in range(0, len(expr.args) + 1, 2):
  153. for neg in combinations(expr.args, i):
  154. clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
  155. for s in expr.args]
  156. cnfs.append(OR(*clause))
  157. return AND(*cnfs)
  158. if isinstance(expr, Xnor):
  159. cnfs = []
  160. for i in range(0, len(expr.args) + 1, 2):
  161. for neg in combinations(expr.args, i):
  162. clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
  163. for s in expr.args]
  164. cnfs.append(OR(*clause))
  165. return ~AND(*cnfs)
  166. if isinstance(expr, Implies):
  167. L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map)
  168. return OR(~L, R)
  169. if isinstance(expr, Equivalent):
  170. cnfs = []
  171. for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]):
  172. a = to_NNF(a, composite_map)
  173. b = to_NNF(b, composite_map)
  174. cnfs.append(OR(~a, b))
  175. return AND(*cnfs)
  176. if isinstance(expr, ITE):
  177. L = to_NNF(expr.args[0], composite_map)
  178. M = to_NNF(expr.args[1], composite_map)
  179. R = to_NNF(expr.args[2], composite_map)
  180. return AND(OR(~L, M), OR(L, R))
  181. if isinstance(expr, AppliedPredicate):
  182. pred, args = expr.function, expr.arguments
  183. newpred = composite_map.get(pred, None)
  184. if newpred is not None:
  185. return to_NNF(newpred.rcall(*args), composite_map)
  186. if isinstance(expr, Predicate):
  187. newpred = composite_map.get(expr, None)
  188. if newpred is not None:
  189. return to_NNF(newpred, composite_map)
  190. return Literal(expr)
  191. def distribute_AND_over_OR(expr):
  192. """
  193. Distributes AND over OR in the NNF expression.
  194. Returns the result( Conjunctive Normal Form of expression)
  195. as a CNF object.
  196. """
  197. if not isinstance(expr, (AND, OR)):
  198. tmp = set()
  199. tmp.add(frozenset((expr,)))
  200. return CNF(tmp)
  201. if isinstance(expr, OR):
  202. return CNF.all_or(*[distribute_AND_over_OR(arg)
  203. for arg in expr._args])
  204. if isinstance(expr, AND):
  205. return CNF.all_and(*[distribute_AND_over_OR(arg)
  206. for arg in expr._args])
  207. class CNF:
  208. """
  209. Class to represent CNF of a Boolean expression.
  210. Consists of set of clauses, which themselves are stored as
  211. frozenset of Literal objects.
  212. Examples
  213. ========
  214. >>> from sympy import Q
  215. >>> from sympy.assumptions.cnf import CNF
  216. >>> from sympy.abc import x
  217. >>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x))
  218. >>> cnf.clauses
  219. {frozenset({Literal(Q.zero(x), True)}),
  220. frozenset({Literal(Q.negative(x), False),
  221. Literal(Q.positive(x), False), Literal(Q.zero(x), False)})}
  222. """
  223. def __init__(self, clauses=None):
  224. if not clauses:
  225. clauses = set()
  226. self.clauses = clauses
  227. def add(self, prop):
  228. clauses = CNF.to_CNF(prop).clauses
  229. self.add_clauses(clauses)
  230. def __str__(self):
  231. s = ' & '.join(
  232. ['(' + ' | '.join([str(lit) for lit in clause]) +')'
  233. for clause in self.clauses]
  234. )
  235. return s
  236. def extend(self, props):
  237. for p in props:
  238. self.add(p)
  239. return self
  240. def copy(self):
  241. return CNF(set(self.clauses))
  242. def add_clauses(self, clauses):
  243. self.clauses |= clauses
  244. @classmethod
  245. def from_prop(cls, prop):
  246. res = cls()
  247. res.add(prop)
  248. return res
  249. def __iand__(self, other):
  250. self.add_clauses(other.clauses)
  251. return self
  252. def all_predicates(self):
  253. predicates = set()
  254. for c in self.clauses:
  255. predicates |= {arg.lit for arg in c}
  256. return predicates
  257. def _or(self, cnf):
  258. clauses = set()
  259. for a, b in product(self.clauses, cnf.clauses):
  260. tmp = set(a)
  261. tmp.update(b)
  262. clauses.add(frozenset(tmp))
  263. return CNF(clauses)
  264. def _and(self, cnf):
  265. clauses = self.clauses.union(cnf.clauses)
  266. return CNF(clauses)
  267. def _not(self):
  268. clss = list(self.clauses)
  269. ll = {frozenset((~x,)) for x in clss[-1]}
  270. ll = CNF(ll)
  271. for rest in clss[:-1]:
  272. p = {frozenset((~x,)) for x in rest}
  273. ll = ll._or(CNF(p))
  274. return ll
  275. def rcall(self, expr):
  276. clause_list = []
  277. for clause in self.clauses:
  278. lits = [arg.rcall(expr) for arg in clause]
  279. clause_list.append(OR(*lits))
  280. expr = AND(*clause_list)
  281. return distribute_AND_over_OR(expr)
  282. @classmethod
  283. def all_or(cls, *cnfs):
  284. b = cnfs[0].copy()
  285. for rest in cnfs[1:]:
  286. b = b._or(rest)
  287. return b
  288. @classmethod
  289. def all_and(cls, *cnfs):
  290. b = cnfs[0].copy()
  291. for rest in cnfs[1:]:
  292. b = b._and(rest)
  293. return b
  294. @classmethod
  295. def to_CNF(cls, expr):
  296. from sympy.assumptions.facts import get_composite_predicates
  297. expr = to_NNF(expr, get_composite_predicates())
  298. expr = distribute_AND_over_OR(expr)
  299. return expr
  300. @classmethod
  301. def CNF_to_cnf(cls, cnf):
  302. """
  303. Converts CNF object to SymPy's boolean expression
  304. retaining the form of expression.
  305. """
  306. def remove_literal(arg):
  307. return Not(arg.lit) if arg.is_Not else arg.lit
  308. return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses))
  309. class EncodedCNF:
  310. """
  311. Class for encoding the CNF expression.
  312. """
  313. def __init__(self, data=None, encoding=None):
  314. if not data and not encoding:
  315. data = []
  316. encoding = {}
  317. self.data = data
  318. self.encoding = encoding
  319. self._symbols = list(encoding.keys())
  320. def from_cnf(self, cnf):
  321. self._symbols = list(cnf.all_predicates())
  322. n = len(self._symbols)
  323. self.encoding = dict(zip(self._symbols, range(1, n + 1)))
  324. self.data = [self.encode(clause) for clause in cnf.clauses]
  325. @property
  326. def symbols(self):
  327. return self._symbols
  328. @property
  329. def variables(self):
  330. return range(1, len(self._symbols) + 1)
  331. def copy(self):
  332. new_data = [set(clause) for clause in self.data]
  333. return EncodedCNF(new_data, dict(self.encoding))
  334. def add_prop(self, prop):
  335. cnf = CNF.from_prop(prop)
  336. self.add_from_cnf(cnf)
  337. def add_from_cnf(self, cnf):
  338. clauses = [self.encode(clause) for clause in cnf.clauses]
  339. self.data += clauses
  340. def encode_arg(self, arg):
  341. literal = arg.lit
  342. value = self.encoding.get(literal, None)
  343. if value is None:
  344. n = len(self._symbols)
  345. self._symbols.append(literal)
  346. value = self.encoding[literal] = n + 1
  347. if arg.is_Not:
  348. return -value
  349. else:
  350. return value
  351. def encode(self, clause):
  352. return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause}