Skip to content

Commit 994d6d4

Browse files
committed
Make weakref memoization more test-friendly
Do pre-marking with a `set` method that we can patch in the tests. Run assertions both before and after `.mark_deterministic()`. Move _WeakrefMemoized to the end of the file as it's just noise for Mypy, not the actually interesting code to read.
1 parent 3f97229 commit 994d6d4

File tree

6 files changed

+51
-43
lines changed

6 files changed

+51
-43
lines changed

nnf/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -726,9 +726,9 @@ def to_MODS(self) -> 'Or[And[Var]]':
726726
And(Var(name, val) for name, val in model.items())
727727
for model in self.models()
728728
)
729-
NNF.is_MODS.memo[new] = True
730-
NNF._is_DNF_loose.memo[new] = True
731-
NNF._is_DNF_strict.memo[new] = True
729+
NNF.is_MODS.set(new, True)
730+
NNF._is_DNF_loose.set(new, True)
731+
NNF._is_DNF_strict.set(new, True)
732732
return new
733733

734734
def to_model(self) -> Model:
@@ -810,7 +810,7 @@ def smooth(node: NNF) -> NNF:
810810
return new
811811

812812
ret = smooth(self)
813-
NNF.smooth.memo[ret] = True
813+
NNF.smooth.set(ret, True)
814814
return ret
815815

816816
def simplify(self, merge_nodes: bool = True) -> 'NNF':

nnf/dimacs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,5 +313,5 @@ def _parse_cnf(tokens: t.Iterable[str]) -> And[Or[Var]]:
313313
# Adding an empty clause is not desirable
314314
clauses.add(Or(clause))
315315
sentence = And(clauses)
316-
NNF._is_CNF_loose.memo[sentence] = True
316+
NNF._is_CNF_loose.set(sentence, True)
317317
return sentence

nnf/dsharp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,5 @@ def compile(
144144

145145
result = loads(out, var_labels=var_labels)
146146
result.mark_deterministic()
147-
NNF.decomposable.memo[result] = True
147+
NNF.decomposable.set(result, True)
148148
return result

nnf/tseitin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ def process_node(node: NNF) -> Var:
5353
root = process_node(theory)
5454
clauses.append(Or({root}))
5555
ret = And(clauses)
56-
NNF._is_CNF_loose.memo[ret] = True
57-
NNF._is_CNF_strict.memo[ret] = True
56+
NNF._is_CNF_loose.set(ret, True)
57+
NNF._is_CNF_strict.set(ret, True)
5858
return ret

nnf/util.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,9 @@
2929
memoize = t.cast(t.Callable[[T], T], functools.lru_cache(maxsize=None))
3030

3131

32-
class _WeakrefMemoized(t.Generic[T_NNF, T]):
33-
def __init__(self) -> None:
34-
assert t.TYPE_CHECKING, "Not a real class"
35-
self.memo = NotImplemented # type: weakref.WeakKeyDictionary[T_NNF, T]
36-
self.__wrapped__ = NotImplemented # type: t.Callable[[T_NNF], T]
37-
38-
@t.overload
39-
def __get__(self: U, instance: None, owner: t.Type[T_NNF]) -> "U":
40-
...
41-
42-
@t.overload # noqa: F811
43-
def __get__( # noqa: F811
44-
self, instance: T_NNF, owner: t.Optional[t.Type[T_NNF]] = None
45-
) -> t.Callable[[], T]:
46-
...
47-
48-
def __get__( # noqa: F811
49-
self, instance: object, owner: object = None
50-
) -> t.Any:
51-
...
52-
53-
def __call__(self, sentence: T_NNF) -> T:
54-
...
55-
56-
5732
def weakref_memoize(
5833
func: t.Callable[[T_NNF], T]
59-
) -> _WeakrefMemoized[T_NNF, T]:
34+
) -> "_WeakrefMemoized[T_NNF, T]":
6035
"""Make a function cache its return values using weakrefs.
6136
6237
This makes it possible to remember sentences' properties without keeping
@@ -102,4 +77,34 @@ def wrapped(self: T_NNF) -> T:
10277
return ret
10378

10479
wrapped.memo = memo # type: ignore
80+
wrapped.set = memo.__setitem__ # type: ignore
10581
return t.cast(_WeakrefMemoized[T_NNF, T], wrapped)
82+
83+
84+
class _WeakrefMemoized(t.Generic[T_NNF, T]):
85+
"""Fake class for typechecking. Should never be instantiated."""
86+
def __init__(self) -> None:
87+
assert t.TYPE_CHECKING, "Not a real class"
88+
self.memo = NotImplemented # type: weakref.WeakKeyDictionary[T_NNF, T]
89+
self.__wrapped__ = NotImplemented # type: t.Callable[[T_NNF], T]
90+
91+
@t.overload
92+
def __get__(self: U, instance: None, owner: t.Type[T_NNF]) -> "U":
93+
...
94+
95+
@t.overload # noqa: F811
96+
def __get__( # noqa: F811
97+
self, instance: T_NNF, owner: t.Optional[t.Type[T_NNF]] = None
98+
) -> t.Callable[[], T]:
99+
...
100+
101+
def __get__( # noqa: F811
102+
self, instance: object, owner: object = None
103+
) -> t.Any:
104+
...
105+
106+
def __call__(self, sentence: T_NNF) -> T:
107+
...
108+
109+
def set(self, sentence: T_NNF, value: T) -> None:
110+
...

test_nnf.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import platform
44
import shutil
55
import os
6+
import types
67

78
from pathlib import Path
89

@@ -16,6 +17,15 @@
1617
from nnf import (Var, And, Or, amc, dimacs, dsharp, operators,
1718
tseitin, complete_models, using_kissat)
1819

20+
memoized = [
21+
method
22+
for method in vars(nnf.NNF).values()
23+
if isinstance(method, types.FunctionType) and hasattr(method, "memo")
24+
]
25+
assert memoized, "No memoized methods found, did the implementation change?"
26+
for method in memoized:
27+
method.set = lambda *args: None # type: ignore
28+
1929
settings.register_profile('patient', deadline=2000,
2030
suppress_health_check=(HealthCheck.too_slow,))
2131
settings.load_profile('patient')
@@ -357,7 +367,6 @@ def test_dsharp_output(fname: str):
357367
sentence = dsharp.load(f)
358368
with open(basepath + '.cnf') as f:
359369
clauses = dimacs.load(f)
360-
nnf.NNF.decomposable.memo.clear()
361370
assert sentence.decomposable()
362371
# this is not a complete check, but clauses.models() is very expensive
363372
assert all(clauses.satisfied_by(model) for model in sentence.models())
@@ -464,7 +473,6 @@ def test_smoothing(sentence: nnf.NNF):
464473
event("Sentence not smooth yet")
465474
smoothed = sentence.make_smooth()
466475
assert type(sentence) is type(smoothed)
467-
nnf.NNF.smooth.memo.clear()
468476
assert smoothed.smooth()
469477
assert sentence.equivalent(smoothed)
470478
assert smoothed.make_smooth() == smoothed
@@ -528,6 +536,7 @@ def test_model_counting(sentence: nnf.NNF):
528536

529537
def test_uf20_model_counting():
530538
for sentence in uf20:
539+
assert sentence.model_count() == len(list(sentence.models()))
531540
sentence.mark_deterministic()
532541
assert sentence.model_count() == len(list(sentence.models()))
533542

@@ -546,6 +555,7 @@ def test_validity(sentence: nnf.NNF):
546555

547556
def test_uf20_validity():
548557
for sentence in uf20:
558+
assert not sentence.valid()
549559
sentence.mark_deterministic()
550560
assert not sentence.valid()
551561

@@ -590,8 +600,6 @@ def test_is_DNF_examples():
590600
def test_to_MODS(sentence: nnf.NNF):
591601
assume(len(sentence.vars()) <= 5)
592602
mods = sentence.to_MODS()
593-
nnf.NNF.is_MODS.memo.clear()
594-
nnf.NNF._is_DNF_strict.memo.clear()
595603
assert mods.is_MODS()
596604
assert isinstance(mods, Or)
597605
assert mods.model_count() == len(mods.children)
@@ -807,8 +815,6 @@ def test_dsharp_compile_uf20():
807815
for sentence in uf20_cnf:
808816
compiled = dsharp.compile(sentence)
809817
compiled_smooth = dsharp.compile(sentence, smooth=True)
810-
nnf.NNF.decomposable.memo.clear()
811-
nnf.NNF.smooth.memo.clear()
812818
assert sentence.equivalent(compiled)
813819
assert sentence.equivalent(compiled_smooth)
814820
assert compiled.decomposable()
@@ -820,8 +826,6 @@ def test_dsharp_compile(sentence: And[Or[Var]]):
820826
assume(all(len(clause) > 0 for clause in sentence))
821827
compiled = dsharp.compile(sentence)
822828
compiled_smooth = dsharp.compile(sentence, smooth=True)
823-
nnf.NNF.decomposable.memo.clear()
824-
nnf.NNF.smooth.memo.clear()
825829
assert compiled.decomposable()
826830
assert compiled_smooth.decomposable()
827831
assert compiled_smooth.smooth()
@@ -869,7 +873,6 @@ def test_tseitin(sentence: nnf.NNF):
869873
assume(sentence.size() <= 10)
870874

871875
T = tseitin.to_CNF(sentence)
872-
nnf.NNF.is_CNF.memo.clear()
873876
assert T.is_CNF()
874877
assert T.forget_aux().equivalent(sentence)
875878

0 commit comments

Comments
 (0)