Skip to content

Commit 36fc523

Browse files
committed
Replace a few methods by simple SAT-backed implementations
1 parent 193d632 commit 36fc523

File tree

1 file changed

+13
-105
lines changed

1 file changed

+13
-105
lines changed

nnf/__init__.py

Lines changed: 13 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,11 @@ def implies(self, other: 'NNF') -> bool:
388388
return not self.condition(other.negate().to_model()).satisfiable()
389389
if self.term():
390390
return not other.negate().condition(self.to_model()).satisfiable()
391-
return (self.negate() | other).valid() # todo: speed this up
391+
392+
if not self.vars() & other.vars():
393+
return not self.satisfiable() or other.valid()
394+
395+
return not (self & other.negate()).satisfiable()
392396

393397
entails = implies
394398

@@ -458,122 +462,26 @@ def count(node: NNF) -> int:
458462

459463
return sum(1 for _ in sentence.models())
460464

461-
def contradicts(
462-
self,
463-
other: 'NNF',
464-
*,
465-
decomposable: _Tristate = None
466-
) -> bool:
467-
"""There is no set of values that satisfies both sentences.
468-
469-
May be very expensive.
470-
"""
471-
if decomposable is None:
472-
decomposable = self.decomposable() and other.decomposable()
473-
474-
if len(self.vars()) > len(other.vars()):
475-
# The one with the fewest vars has the smallest models
476-
a, b = other, self
477-
else:
478-
a, b = self, other
479-
480-
if decomposable:
481-
for model in a.models(decomposable=True):
482-
if b._consistent_with_model(model):
483-
return False
484-
return True
465+
def contradicts(self, other: "NNF") -> bool:
466+
"""There is no set of values that satisfies both sentences."""
467+
if not self.vars() & other.vars():
468+
return not (self.satisfiable() and other.satisfiable())
485469

486-
for model in b.models():
487-
# Hopefully, a.vars() <= b.vars() and .satisfiable() is fast
488-
if a.condition(model).satisfiable():
489-
return False
490-
return True
470+
return not (self & other).satisfiable()
491471

492472
def equivalent(self, other: 'NNF') -> bool:
493473
"""Test whether two sentences have the same models.
494474
495475
If the sentences don't contain the same variables they are
496476
considered equivalent if the variables that aren't shared are
497477
independent, i.e. their value doesn't affect the value of the sentence.
498-
499-
Warning: may be very slow. Decomposability helps.
500478
"""
501-
# TODO: add arguments for decomposability, determinism (per sentence?)
502479
if self == other:
503480
return True
504481

505-
models_a = list(self.models())
506-
models_b = list(other.models())
507-
if models_a == models_b:
508-
return True
509-
510-
vars_a = self.vars()
511-
vars_b = other.vars()
512-
if vars_a == vars_b:
513-
if len(models_a) != len(models_b):
514-
return False
515-
516-
Model_T = t.FrozenSet[t.Tuple[Name, bool]]
517-
518-
def dict_hashable(model: t.Dict[Name, bool]) -> Model_T:
519-
return frozenset(model.items())
520-
521-
modset_a = frozenset(map(dict_hashable, models_a))
522-
modset_b = frozenset(map(dict_hashable, models_b))
523-
524-
if vars_a == vars_b:
525-
return modset_a == modset_b
526-
527-
# There are variables that appear in one sentence but not the other
528-
# The sentences can still be equivalent if those variables don't
529-
# affect the truth value of the sentence they appear in
530-
# That is, for every model that contains such a variable there's
531-
# another model with that variable flipped
532-
533-
# In such a scenario we can calculate the number of "shared" models in
534-
# advance
535-
# Each additional variable not shared with the other doubles the
536-
# number of models compared to the number of shared models
537-
# so we calculate those factors and check whether that works out,
538-
# which is much cheaper than actually checking everything, which we
539-
# do afterwards if necessary
540-
if len(modset_a) % 2**(len(vars_a - vars_b)) != 0:
541-
return False
542-
if len(modset_b) % 2**(len(vars_b - vars_a)) != 0:
543-
return False
544-
if (len(modset_a) // 2**(len(vars_a - vars_b)) !=
545-
len(modset_b) // 2**(len(vars_b - vars_a))):
546-
return False
547-
548-
def flip(model: Model_T, var: Name) -> Model_T:
549-
other = (var, False) if (var, True) in model else (var, True)
550-
return (model - {(var, False), (var, True)}) | {other}
551-
552-
modset_a_small = set() # type: t.Set[Model_T]
553-
for model in modset_a:
554-
for var in vars_a - vars_b:
555-
if flip(model, var) not in modset_a:
556-
return False
557-
true_vars = {(var, True) for var in vars_a - vars_b}
558-
if true_vars <= model: # Don't add it multiple times
559-
modset_a_small.add(model - true_vars)
560-
561-
modset_b_small = set() # type: t.Set[Model_T]
562-
for model in modset_b:
563-
for var in vars_b - vars_a:
564-
if flip(model, var) not in modset_b:
565-
return False
566-
true_vars = {(var, True) for var in vars_b - vars_a}
567-
if true_vars <= model:
568-
modset_b_small.add(model - true_vars)
569-
570-
if modset_a_small != modset_b_small:
571-
return False
572-
573-
assert (len(modset_a_small)
574-
== len(modset_a) // 2**(len(vars_a - vars_b)))
575-
576-
return True
482+
return not (
483+
(self & other.negate()) | (self.negate() & other)
484+
).satisfiable()
577485

578486
def negate(self) -> 'NNF':
579487
"""Return a new sentence that's true iff the original is false."""

0 commit comments

Comments
 (0)