1818import functools
1919import itertools
2020import operator
21+ import os
2122import typing as t
23+ import uuid
2224
2325from collections import Counter
2426
2830Name = t .Hashable
2931Model = t .Dict [Name , bool ]
3032
31- __all__ = ('NNF' , 'Internal' , 'And' , 'Or' , 'Var' , 'Builder' , 'all_models' ,
32- 'decision' , 'true' , 'false' , 'dsharp' , 'dimacs' , 'amc' , 'operators' )
33+ __all__ = ('NNF' , 'Internal' , 'And' , 'Or' , 'Var' , 'Aux' , 'Builder' ,
34+ 'all_models' , 'complete_models' , 'decision' , 'true' , 'false' ,
35+ 'dsharp' , 'dimacs' , 'amc' , 'tseitin' , 'operators' )
3336
3437
3538def all_models (names : 't.Iterable[Name]' ) -> t .Iterator [Model ]:
@@ -587,6 +590,11 @@ def neg(node: NNF) -> NNF:
587590
588591 return neg (self )
589592
593+ def to_CNF (self ) -> 'And[Or[Var]]' :
594+ """Compile theory to a semantically equivalent CNF formula."""
595+ from nnf import tseitin
596+ return tseitin .to_CNF (self )
597+
590598 def _cnf_satisfiable (self ) -> bool :
591599 """A naive DPLL SAT solver."""
592600 def DPLL (clauses : t .FrozenSet [t .FrozenSet [Var ]]) -> bool :
@@ -1067,7 +1075,13 @@ def name(node: NNF) -> int:
10671075 if node not in names :
10681076 number = next (counter )
10691077 if isinstance (node , Var ):
1070- label = str (node .name ).replace ('"' , r'\"' )
1078+ if isinstance (node .name , Aux ):
1079+ # This matches the repr, but in this context it could
1080+ # be reasonable to number them instead
1081+ label = "<{}>" .format (node .name .hex [:4 ])
1082+ else :
1083+ label = str (node .name )
1084+ label = label .replace ('"' , r'\"' )
10711085 color = colors ['var' ]
10721086 if not node .true :
10731087 label = '¬' + label
@@ -1265,6 +1279,15 @@ def __deepcopy__(self: T_NNF, memodict: t.Dict[t.Any, t.Any]) -> T_NNF:
12651279 return self
12661280
12671281
1282+ class Aux (uuid .UUID ):
1283+ """Unique UUID labels for auxiliary variables.
1284+
1285+ Don't instantiate directly, call :meth:`Var.aux` instead.
1286+ """
1287+
1288+ __slots__ = ()
1289+
1290+
12681291class Var (NNF ):
12691292 """A variable, or its negation.
12701293
@@ -1320,10 +1343,12 @@ def __delattr__(self, name: str) -> None:
13201343
13211344 def __repr__ (self ) -> str :
13221345 if isinstance (self .name , str ):
1323- return str (self .name ) if self .true else "~{}" .format (self .name )
1346+ base = str (self .name )
1347+ elif isinstance (self .name , Aux ):
1348+ base = "<{}>" .format (self .name .hex [:4 ])
13241349 else :
13251350 base = "{}({!r})" .format (self .__class__ .__name__ , self .name )
1326- return base if self .true else '~' + base
1351+ return base if self .true else '~' + base
13271352
13281353 def __invert__ (self ) -> 'Var' :
13291354 return Var (self .name , not self .true )
@@ -1350,6 +1375,12 @@ def __setstate__(self, state: t.Tuple[Name, bool]) -> None:
13501375 object .__setattr__ (self , 'name' , state [0 ])
13511376 object .__setattr__ (self , 'true' , state [1 ])
13521377
1378+ @staticmethod
1379+ def aux () -> 'Var' :
1380+ """Create an auxiliary variable with a unique label."""
1381+ # See implementation of uuid.uuid4()
1382+ return Var (Aux (bytes = os .urandom (16 ), version = 4 ))
1383+
13531384
13541385class Internal (NNF , t .Generic [T_NNF_co ]):
13551386 """Base class for internal nodes, i.e. And and Or nodes."""
@@ -1539,6 +1570,21 @@ def map(self, func: t.Callable[[T_NNF_co], U_NNF]) -> 'Or[U_NNF]':
15391570 ...
15401571
15411572
1573+ def complete_models (
1574+ models : t .Iterable [Model ],
1575+ names : t .Iterable [Name ]
1576+ ) -> t .Iterator [Model ]:
1577+ names = frozenset (names )
1578+ diff = None
1579+ for model in models :
1580+ if diff is None :
1581+ diff = names - model .keys ()
1582+ for supplement in all_models (diff ):
1583+ new = model .copy ()
1584+ new .update (supplement )
1585+ yield new
1586+
1587+
15421588def decision (
15431589 var : Var ,
15441590 if_true : T_NNF ,
0 commit comments