Skip to content

Commit 0528a55

Browse files
committed
src: Rewritten and moved _Substitution to an extra module substitution.py. There is a working cython variant subtitution.pyx, which is not used.
1 parent 6595037 commit 0528a55

File tree

9 files changed

+469
-138
lines changed

9 files changed

+469
-138
lines changed

Makefile

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ ignores = --ignore=logic1/theories/RCF/test_simplify_motor_redlog.txt
44
.PHONY: pytest pytest-full test-doc mypy test test-all doc pygount\
55
coverage coverage_html clean veryclean conda-build
66

7+
cython:
8+
python setup.py build_ext --inplace
9+
710
pytest:
8-
pytest -n 8 --exitfirst --doctest-modules $(ignores)
11+
pytest -n 8 --doctest-cython --exitfirst --doctest-modules $(ignores)
912

1013
pytest-fast:
1114
PYTHONOPTIMIZE=TRUE pytest -n 8 --disable-warnings --exitfirst --doctest-modules $(ignores)
1215

1316
pytest-seq:
14-
pytest --exitfirst --doctest-modules $(ignores)
17+
pytest --doctest-cython --exitfirst --doctest-modules $(ignores)
1518

1619
pytest-full:
1720
pytest -n 8 --doctest-modules $(ignores)

cython.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# A conda environment for running and developing cython module within Logic1
2+
#
3+
# You might want to adapt the name to your preferences.
4+
#
5+
# Install and activate as follows:
6+
# $ conda env create -f cython.yaml
7+
# $ conda activate cython
8+
#
9+
# Further packages can be added interactively anytime later on.
10+
#
11+
# https://docs.conda.io/projects/conda/en/stable/user-guide/tasks/manage-environments.html
12+
#
13+
name: cython
14+
channels:
15+
- conda-forge
16+
dependencies:
17+
- python=3.11
18+
- cython
19+
- clang

logic1/abc/simplify.py

+21-14
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,12 @@ def is_valid(self, f: Formula[α, τ, χ, σ], assume: Iterable[α] = []) -> Opt
152152
simplifying `f` to :data:`.T` or :data:`.F`, respectively. Returns
153153
:data:`None` in the sense of "don't know" otherwise.
154154
"""
155-
match self.simplify(f, assume):
156-
case _T():
157-
return True
158-
case _F():
159-
return False
160-
case _:
161-
return None
155+
f = self.simplify(f, assume)
156+
if f is _T():
157+
return True
158+
if f is _F():
159+
return False
160+
return None
162161

163162
def _post_process(self, f: Formula[α, τ, χ, σ]) -> Formula[α, τ, χ, σ]:
164163
"""A hook for post-processing the final result in subclasses.
@@ -216,6 +215,16 @@ def _simpl_and_or(self, f: And[α, τ, χ, σ] | Or[α, τ, χ, σ], ir: ρ) ->
216215
"""Simplify the negation normal form `f`, which starts with either
217216
:class:`.And` or :class:`.Or`, modulo `ir`.
218217
"""
218+
219+
# def log(msg: str):
220+
# from IPython.lib import pretty
221+
# print('+' + (78 - len(msg)) * '-' + ' ' + msg)
222+
# pretty_f = pretty.pretty(f, newline='\n| ')
223+
# pretty_nodes = pretty.pretty(ir._subst.nodes, newline='\n| ' + len('ir._subst.nodes=') * ' ')
224+
# pretty_ref_nodes = pretty.pretty(ref._subst.nodes, newline='\n| ' + len('ref._subst.nodes=') * ' ')
225+
# print(f'| f={pretty_f}\n| {ir._knowl=!s}\n| ir._subst.nodes={pretty_nodes}\n| {ref._knowl=!s}\n| ref._subst.nodes={pretty_ref_nodes}')
226+
# print('+' + 79 * '-')
227+
219228
ref = ir
220229
ir = ir.next_()
221230
gand = f.op
@@ -295,13 +304,11 @@ def _simpl_atomic(self, atom: α, ir: ρ) -> Formula[α, τ, χ, σ]:
295304
except ir.Inconsistent:
296305
return _F()
297306
final_atoms = list(ir.extract(And, ref))
298-
match len(final_atoms):
299-
case 0:
300-
return _T()
301-
case 1:
302-
return final_atoms[0]
303-
case _:
304-
assert False, final_atoms
307+
if len(final_atoms) == 0:
308+
return _T()
309+
if len(final_atoms) == 1:
310+
return final_atoms[0]
311+
assert False, final_atoms
305312

306313
def _simpl_quantified(self, f: QuantifiedFormula[α, τ, χ, σ], ir: ρ) -> Formula[α, τ, χ, σ]:
307314
"""Simplify the negation normal form `f`, which starts with either

logic1/firstorder/formula.py

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from typing import Any, Callable, Final, Generic, Iterable, Iterator, Optional, Self, TypeVar
66
from typing_extensions import TypeIs
77

8+
from IPython.lib import pretty
9+
810
from ..support.tracing import trace # noqa
911

1012

@@ -791,6 +793,16 @@ def _repr_latex_(self) -> str:
791793
as_latex += '{}\\dots'
792794
return f'$\\displaystyle {as_latex}$'
793795

796+
def _repr_pretty_(self, p: pretty.RepresentationPrinter, cycle: bool) -> None:
797+
assert not cycle
798+
op = self.__class__.__name__
799+
with p.group(len(op) + 1, op + '(', ')'):
800+
for idx, arg in enumerate(self.args):
801+
if idx:
802+
p.text(',')
803+
p.breakable()
804+
p.pretty(arg)
805+
794806
def simplify(self) -> Formula[α, τ, χ, σ]:
795807
"""Fast basic simplification. The result is equivalent to `self`. The
796808
following first-order simplifications are applied:

logic1/theories/RCF/simplify.py

+27-122
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
from ... import abc
1717
from ...firstorder import And, _F, Not, Or, _T
18-
from .atomic import AtomicFormula, DEFINITE, Eq, Ge, Le, Gt, Lt, Ne, SortKey, Term, Variable
18+
from .atomic import AtomicFormula, DEFINITE, Eq, Ge, Le, Gt, Lt, Ne, Term, Variable
19+
from .substitution import _SubstValue, _Substitution # type: ignore
1920
from .typing import Formula
2021

2122
from ...support.tracing import trace # noqa
@@ -65,10 +66,17 @@ def __post_init__(self) -> None:
6566
assert self.ropen or self.end is not oo, self
6667
assert all(self.start < x < self.end for x in self.exc), self
6768

68-
def __str__(self):
69+
def __str__(self) -> str:
6970
left = '(' if self.lopen else '['
71+
start = '-oo' if self.start == -oo else str(self.start)
72+
end = 'oo' if self.end == oo else str(self.end)
7073
right = ')' if self.ropen else ']'
71-
return f'{left}{self.start}, {self.end}{right} \\ {self.exc}'
74+
exc_entries = {str(q) for q in self.exc}
75+
if exc_entries:
76+
exc = f' \\ {{{", ".join(exc_entries)}}}'
77+
else:
78+
exc = ''
79+
return f'{left}{start}, {end}{right}{exc}'
7280

7381
def intersection(self, other: Self) -> Self:
7482
if self.start < other.start:
@@ -128,113 +136,6 @@ def transform(self, scale: mpq, shift: mpq) -> Self:
128136
return self.__class__(lopen, start, end, ropen, exc)
129137

130138

131-
@dataclass(frozen=True)
132-
class _SubstValue:
133-
coefficient: mpq
134-
variable: Optional[Variable]
135-
136-
def __eq__(self, other: Self) -> bool: # type: ignore[override]
137-
if self.coefficient != other.coefficient:
138-
return False
139-
if self.variable is None and other.variable is None:
140-
return True
141-
if self.variable is None or other.variable is None:
142-
return False
143-
return self.variable.sort_key() == other.variable.sort_key()
144-
145-
def __post_init__(self) -> None:
146-
assert self.coefficient != 0 or self.variable is None
147-
148-
@lru_cache(maxsize=CACHE_SIZE)
149-
def as_term(self) -> Term:
150-
if self.variable is None:
151-
return Term(self.coefficient)
152-
else:
153-
return self.coefficient * self.variable
154-
155-
156-
@dataclass(unsafe_hash=True)
157-
class _Substitution:
158-
parents: dict[SortKey[Variable], _SubstValue] = field(default_factory=dict)
159-
160-
def __iter__(self) -> Iterator[tuple[Variable, _SubstValue]]:
161-
for key in self.parents:
162-
yield key.term, self.internal_find(key.term)
163-
164-
def as_gb(self, ignore: Optional[Variable] = None) -> list[Term]:
165-
"""Convert this :class:._Substitution` into a Gröbner basis that can be
166-
used as an argument to reduction methods.
167-
"""
168-
G = []
169-
for var, val in self:
170-
if ignore is None or var.sort_key() != ignore.sort_key():
171-
G.append(var - val.as_term())
172-
return G
173-
174-
def copy(self) -> Self:
175-
return self.__class__(self.parents.copy())
176-
177-
def find(self, v: Variable) -> _SubstValue:
178-
return self.internal_find(v)
179-
180-
def internal_find(self, v: Optional[Variable]) -> _SubstValue:
181-
if v is None:
182-
return _SubstValue(mpq(1), None)
183-
sort_key = Variable.sort_key(v)
184-
try:
185-
parent = self.parents[sort_key]
186-
except KeyError:
187-
return _SubstValue(mpq(1), v)
188-
root = self.internal_find(parent.variable)
189-
root = _SubstValue(parent.coefficient * root.coefficient, root.variable)
190-
self.parents[sort_key] = root
191-
return root
192-
193-
def union(self, val1: _SubstValue, val2: _SubstValue) -> None:
194-
root1 = self.internal_find(val1.variable)
195-
root2 = self.internal_find(val2.variable)
196-
c1 = val1.coefficient * root1.coefficient
197-
c2 = val2.coefficient * root2.coefficient
198-
if root1.variable is not None and root2.variable is not None:
199-
sort_key1 = Variable.sort_key(root1.variable)
200-
sort_key2 = Variable.sort_key(root2.variable)
201-
if sort_key1 == sort_key2:
202-
if c1 != c2:
203-
self.parents[sort_key1] = _SubstValue(mpq(0), None)
204-
elif sort_key1 < sort_key2: # type: ignore
205-
self.parents[sort_key2] = _SubstValue(c1 / c2, root1.variable)
206-
else:
207-
self.parents[sort_key1] = _SubstValue(c2 / c1, root2.variable)
208-
elif root1.variable is None and root2.variable is not None:
209-
sort_key2 = Variable.sort_key(root2.variable)
210-
self.parents[sort_key2] = _SubstValue(c1 / c2, None)
211-
elif root1.variable is not None and root2.variable is None:
212-
sort_key1 = Variable.sort_key(root1.variable)
213-
self.parents[sort_key1] = _SubstValue(c2 / c1, None)
214-
else:
215-
if c1 != c2:
216-
raise InternalRepresentation.Inconsistent()
217-
218-
def is_redundant(self, val1: _SubstValue, val2: _SubstValue) -> Optional[bool]:
219-
"""Check if the equation ``val1 == val2`` is redundant modulo self.
220-
"""
221-
root1 = self.internal_find(val1.variable)
222-
root2 = self.internal_find(val2.variable)
223-
c1 = val1.coefficient * root1.coefficient
224-
c2 = val2.coefficient * root2.coefficient
225-
if root1.variable is None and root2.variable is None:
226-
return c1 == c2
227-
if root1.variable is None or root2.variable is None:
228-
return None
229-
if root1.variable == root2.variable and c1 == c2:
230-
return True
231-
else:
232-
return None
233-
234-
def equations(self) -> Iterator[Eq]:
235-
raise NotImplementedError()
236-
237-
238139
@dataclass
239140
class _BasicKnowledge:
240141

@@ -429,6 +330,10 @@ def __iter__(self) -> Iterator[_BasicKnowledge]:
429330
for t, range_ in self.dict_.items():
430331
yield _BasicKnowledge(t, range_)
431332

333+
def __str__(self) -> str:
334+
entries = [str(key) + ' in ' + str(range) for key, range in self.dict_.items()]
335+
return f'{{{", ".join(entries)}}}'
336+
432337
def add(self, bknowl: _BasicKnowledge) -> None:
433338
bknowl = self.prune(bknowl)
434339
self.dict_[bknowl.term] = bknowl.range
@@ -638,18 +543,18 @@ def create_initial_representation(self, assume: Iterable[AtomicFormula]) \
638543
ir = InternalRepresentation(_options=self._options)
639544
for atom in assume:
640545
simplified_atom = self._simpl_at(atom, And, explode_always=False)
641-
match simplified_atom:
642-
case AtomicFormula():
643-
ir.add(And, [simplified_atom])
644-
case And(args=args):
645-
assert all(isinstance(arg, AtomicFormula) for arg in args)
646-
ir.add(And, args)
647-
case _T():
648-
continue
649-
case _F():
650-
raise InternalRepresentation.Inconsistent()
651-
case _:
652-
assert False, simplified_atom
546+
if Formula.is_atomic(simplified_atom):
547+
ir.add(And, [simplified_atom])
548+
elif Formula.is_and(simplified_atom):
549+
args = simplified_atom.args
550+
assert all(isinstance(arg, AtomicFormula) for arg in args)
551+
ir.add(And, args)
552+
elif Formula.is_true(simplified_atom):
553+
continue
554+
elif Formula.is_false(simplified_atom):
555+
raise InternalRepresentation.Inconsistent()
556+
else:
557+
assert False, simplified_atom
653558
return ir
654559

655560
def _post_process(self, f: Formula) -> Formula:

0 commit comments

Comments
 (0)