eqtheory 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eqtheory-0.1.0/LICENSE +21 -0
- eqtheory-0.1.0/PKG-INFO +123 -0
- eqtheory-0.1.0/README.md +102 -0
- eqtheory-0.1.0/eqtheory/__init__.py +31 -0
- eqtheory-0.1.0/eqtheory/cli.py +192 -0
- eqtheory-0.1.0/eqtheory/completion.py +393 -0
- eqtheory-0.1.0/eqtheory/egraph.py +406 -0
- eqtheory-0.1.0/eqtheory/lean/__init__.py +32 -0
- eqtheory-0.1.0/eqtheory/lean/certs.py +333 -0
- eqtheory-0.1.0/eqtheory/lean/check.py +107 -0
- eqtheory-0.1.0/eqtheory/llm.py +362 -0
- eqtheory-0.1.0/eqtheory/models/__init__.py +25 -0
- eqtheory-0.1.0/eqtheory/models/finite.py +780 -0
- eqtheory-0.1.0/eqtheory/models/infinite.py +96 -0
- eqtheory-0.1.0/eqtheory/proofs.py +129 -0
- eqtheory-0.1.0/eqtheory/solve.py +226 -0
- eqtheory-0.1.0/eqtheory/terms.py +309 -0
- eqtheory-0.1.0/eqtheory/viz.py +115 -0
- eqtheory-0.1.0/eqtheory.egg-info/PKG-INFO +123 -0
- eqtheory-0.1.0/eqtheory.egg-info/SOURCES.txt +30 -0
- eqtheory-0.1.0/eqtheory.egg-info/dependency_links.txt +1 -0
- eqtheory-0.1.0/eqtheory.egg-info/entry_points.txt +2 -0
- eqtheory-0.1.0/eqtheory.egg-info/requires.txt +7 -0
- eqtheory-0.1.0/eqtheory.egg-info/top_level.txt +1 -0
- eqtheory-0.1.0/pyproject.toml +33 -0
- eqtheory-0.1.0/setup.cfg +4 -0
- eqtheory-0.1.0/tests/test_completion_and_certs.py +96 -0
- eqtheory-0.1.0/tests/test_egraph.py +75 -0
- eqtheory-0.1.0/tests/test_models.py +70 -0
- eqtheory-0.1.0/tests/test_more.py +163 -0
- eqtheory-0.1.0/tests/test_solve_llm_cli.py +95 -0
- eqtheory-0.1.0/tests/test_terms.py +108 -0
eqtheory-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oliver Sick
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
eqtheory-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eqtheory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Equational theories over magmas: term rewriting with a Knuth-Bendix order, proof-producing e-graphs, goal-directed superposition, finite and infinite countermodels, Lean 4 certificates.
|
|
5
|
+
Author: Oliver Sick
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: equational theories,magma,term rewriting,e-graph,superposition,Lean 4,SAT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: viz
|
|
16
|
+
Requires-Dist: graphviz>=0.20; extra == "viz"
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# eqtheory
|
|
23
|
+
|
|
24
|
+
A Python library for **equational theories over magmas**: given two
|
|
25
|
+
equations in one binary operation `◇`, decide whether the first implies
|
|
26
|
+
the second — and hand back a **Lean 4 certificate** either way.
|
|
27
|
+
|
|
28
|
+
* term rewriting with a ground **Knuth–Bendix order**, one-way matching,
|
|
29
|
+
unification,
|
|
30
|
+
* a proof-producing **e-graph** (congruence closure with a proof forest,
|
|
31
|
+
shortest shared explanations) and **equality saturation**,
|
|
32
|
+
* **ordered completion** and **goal-directed superposition** with
|
|
33
|
+
replayed proof chains, interreduction and node budgets,
|
|
34
|
+
* **finite countermodels**: symbolic linear families, structured tables,
|
|
35
|
+
a complete propagating cell search and a **CDCL** SAT search with
|
|
36
|
+
symmetry breaking (exhaustion verdicts are proofs),
|
|
37
|
+
* **infinite countermodels on ℕ** for *Austin pairs* (true in every
|
|
38
|
+
finite magma, false in general),
|
|
39
|
+
* **Lean 4 certificates** for every answer, in the shapes accepted by the
|
|
40
|
+
SAIR Stage-2 judge, with an optional compile check,
|
|
41
|
+
* an optional, hygienic **LLM stage** with a configurable prompt,
|
|
42
|
+
* a **CLI** with e-graph and proof **visualisation** (SVG/PNG/DOT).
|
|
43
|
+
|
|
44
|
+
The algorithms are those of the SAIR Stage-2 solver (800/800 practice,
|
|
45
|
+
see the paper in [osick/SAIR-callenges](https://github.com/osick/SAIR-callenges/tree/main/challenge_02/docs/paper)),
|
|
46
|
+
re-packaged with a clean API. Pure Python 3.11+,
|
|
47
|
+
no required dependencies.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -e . # from this directory
|
|
51
|
+
pip install -e ".[viz,dev]" # graphviz bindings, pytest
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from eqtheory import Problem, solve
|
|
58
|
+
|
|
59
|
+
p = Problem.parse("x = y ◇ (x ◇ y)", "x = (x ◇ y) ◇ x")
|
|
60
|
+
ans = solve(p)
|
|
61
|
+
print(ans.verdict, ans.stage) # "true" / "false", which engine decided
|
|
62
|
+
print(ans.code) # the Lean 4 certificate
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each engine is a plain function:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from eqtheory import prove_by_superposition, find_countermodel, residue_affine_countermodel, prove_goal
|
|
69
|
+
from eqtheory.lean import certs
|
|
70
|
+
|
|
71
|
+
pr = prove_by_superposition(p.hypothesis, p.goal) # lemma + instantiation + derived set
|
|
72
|
+
cm = find_countermodel(p.hypothesis, p.goal, time_budget=60) # Countermodel(n, table) or None
|
|
73
|
+
nm = residue_affine_countermodel(p.hypothesis, p.goal) # ℕ model for Austin pairs
|
|
74
|
+
eg, l, r = prove_goal(p.hypothesis, p.goal) # e-graph with the goal sides merged
|
|
75
|
+
lemmas, chain = eg.explain(l, r) # shared proof, replayable
|
|
76
|
+
eg.render("egraph.svg", highlight=(l, r)) # picture
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Command line
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
eqtheory solve "x = y ◇ (x ◇ y)" "x = (x ◇ y) ◇ x" # full ladder, prints certificate
|
|
83
|
+
eqtheory solve --lean --json ... # certificate compiled with Lean
|
|
84
|
+
eqtheory prove ... # superposition only
|
|
85
|
+
eqtheory model --infinite --cert ... # countermodels, finite then ℕ
|
|
86
|
+
eqtheory egraph --render egraph.svg --lemmas ... # saturate and draw the e-graph
|
|
87
|
+
eqtheory viz-proof --out proof.dot ... # the extracted proof as a graph
|
|
88
|
+
eqtheory cert --table "[[0,1],[1,0]]" ... # wrap a table as a certificate
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`--lean` needs `EQTHEORY_LEAN_BIN` (a Lean 4.33 binary) and
|
|
92
|
+
`EQTHEORY_JUDGE_ROOT` (a built checkout of the Stage-2 judge library) or
|
|
93
|
+
`EQTHEORY_LEAN_PATH`. `--llm` needs `OPENROUTER_API_KEY`.
|
|
94
|
+
|
|
95
|
+
## Documentation
|
|
96
|
+
|
|
97
|
+
* [User guide](docs/guide.md) — the pipeline, budgets, composing your own ladder
|
|
98
|
+
* [Mathematical background](docs/background.md) — the theory behind each engine, with references
|
|
99
|
+
* [Certificates](docs/certificates.md) — the Lean shapes and the proof-policy lessons
|
|
100
|
+
* [API overview](docs/api.md)
|
|
101
|
+
* [Limitations](docs/limitations.md) — theoretical and pragmatic
|
|
102
|
+
* [Releasing](docs/releasing.md) — moving to its own repository, CI, tags, PyPI
|
|
103
|
+
* `examples/single_file_solver.py` — a complete solver in 60 lines
|
|
104
|
+
* `artifacts/` — sample renderings and benchmark reports
|
|
105
|
+
|
|
106
|
+
## Status
|
|
107
|
+
|
|
108
|
+
Version 0.1.0 (2026-09-01). Tests: `python -m pytest` (70 tests; the Lean
|
|
109
|
+
end-to-end test is skipped unless Lean is configured). Benchmark (2026-09-01, 200-problem Stage-2 stress set, every certificate
|
|
110
|
+
compiled with Lean, LLM never needed): **200/200 correct** — see
|
|
111
|
+
`artifacts/bench-stress-2026-09-01/REPORT.md`.
|
|
112
|
+
|
|
113
|
+
## Limitations (short form)
|
|
114
|
+
|
|
115
|
+
Implication between magma laws is undecidable; every engine is budgeted,
|
|
116
|
+
so "no answer" is not a verdict. Finite search is complete per size but
|
|
117
|
+
cannot see infinite-only countermodels beyond the ℕ residue-class family;
|
|
118
|
+
table certificates stop at Fin 10 (affine models go further); `grind`
|
|
119
|
+
certificates are Lean-version-sensitive; the compile check is not the
|
|
120
|
+
competition judge; the LLM stage adds hygiene, not reach. Details and
|
|
121
|
+
measurements: [docs/limitations.md](docs/limitations.md).
|
|
122
|
+
|
|
123
|
+
License: MIT.
|
eqtheory-0.1.0/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# eqtheory
|
|
2
|
+
|
|
3
|
+
A Python library for **equational theories over magmas**: given two
|
|
4
|
+
equations in one binary operation `◇`, decide whether the first implies
|
|
5
|
+
the second — and hand back a **Lean 4 certificate** either way.
|
|
6
|
+
|
|
7
|
+
* term rewriting with a ground **Knuth–Bendix order**, one-way matching,
|
|
8
|
+
unification,
|
|
9
|
+
* a proof-producing **e-graph** (congruence closure with a proof forest,
|
|
10
|
+
shortest shared explanations) and **equality saturation**,
|
|
11
|
+
* **ordered completion** and **goal-directed superposition** with
|
|
12
|
+
replayed proof chains, interreduction and node budgets,
|
|
13
|
+
* **finite countermodels**: symbolic linear families, structured tables,
|
|
14
|
+
a complete propagating cell search and a **CDCL** SAT search with
|
|
15
|
+
symmetry breaking (exhaustion verdicts are proofs),
|
|
16
|
+
* **infinite countermodels on ℕ** for *Austin pairs* (true in every
|
|
17
|
+
finite magma, false in general),
|
|
18
|
+
* **Lean 4 certificates** for every answer, in the shapes accepted by the
|
|
19
|
+
SAIR Stage-2 judge, with an optional compile check,
|
|
20
|
+
* an optional, hygienic **LLM stage** with a configurable prompt,
|
|
21
|
+
* a **CLI** with e-graph and proof **visualisation** (SVG/PNG/DOT).
|
|
22
|
+
|
|
23
|
+
The algorithms are those of the SAIR Stage-2 solver (800/800 practice,
|
|
24
|
+
see the paper in [osick/SAIR-callenges](https://github.com/osick/SAIR-callenges/tree/main/challenge_02/docs/paper)),
|
|
25
|
+
re-packaged with a clean API. Pure Python 3.11+,
|
|
26
|
+
no required dependencies.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -e . # from this directory
|
|
30
|
+
pip install -e ".[viz,dev]" # graphviz bindings, pytest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from eqtheory import Problem, solve
|
|
37
|
+
|
|
38
|
+
p = Problem.parse("x = y ◇ (x ◇ y)", "x = (x ◇ y) ◇ x")
|
|
39
|
+
ans = solve(p)
|
|
40
|
+
print(ans.verdict, ans.stage) # "true" / "false", which engine decided
|
|
41
|
+
print(ans.code) # the Lean 4 certificate
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each engine is a plain function:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from eqtheory import prove_by_superposition, find_countermodel, residue_affine_countermodel, prove_goal
|
|
48
|
+
from eqtheory.lean import certs
|
|
49
|
+
|
|
50
|
+
pr = prove_by_superposition(p.hypothesis, p.goal) # lemma + instantiation + derived set
|
|
51
|
+
cm = find_countermodel(p.hypothesis, p.goal, time_budget=60) # Countermodel(n, table) or None
|
|
52
|
+
nm = residue_affine_countermodel(p.hypothesis, p.goal) # ℕ model for Austin pairs
|
|
53
|
+
eg, l, r = prove_goal(p.hypothesis, p.goal) # e-graph with the goal sides merged
|
|
54
|
+
lemmas, chain = eg.explain(l, r) # shared proof, replayable
|
|
55
|
+
eg.render("egraph.svg", highlight=(l, r)) # picture
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Command line
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
eqtheory solve "x = y ◇ (x ◇ y)" "x = (x ◇ y) ◇ x" # full ladder, prints certificate
|
|
62
|
+
eqtheory solve --lean --json ... # certificate compiled with Lean
|
|
63
|
+
eqtheory prove ... # superposition only
|
|
64
|
+
eqtheory model --infinite --cert ... # countermodels, finite then ℕ
|
|
65
|
+
eqtheory egraph --render egraph.svg --lemmas ... # saturate and draw the e-graph
|
|
66
|
+
eqtheory viz-proof --out proof.dot ... # the extracted proof as a graph
|
|
67
|
+
eqtheory cert --table "[[0,1],[1,0]]" ... # wrap a table as a certificate
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`--lean` needs `EQTHEORY_LEAN_BIN` (a Lean 4.33 binary) and
|
|
71
|
+
`EQTHEORY_JUDGE_ROOT` (a built checkout of the Stage-2 judge library) or
|
|
72
|
+
`EQTHEORY_LEAN_PATH`. `--llm` needs `OPENROUTER_API_KEY`.
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
* [User guide](docs/guide.md) — the pipeline, budgets, composing your own ladder
|
|
77
|
+
* [Mathematical background](docs/background.md) — the theory behind each engine, with references
|
|
78
|
+
* [Certificates](docs/certificates.md) — the Lean shapes and the proof-policy lessons
|
|
79
|
+
* [API overview](docs/api.md)
|
|
80
|
+
* [Limitations](docs/limitations.md) — theoretical and pragmatic
|
|
81
|
+
* [Releasing](docs/releasing.md) — moving to its own repository, CI, tags, PyPI
|
|
82
|
+
* `examples/single_file_solver.py` — a complete solver in 60 lines
|
|
83
|
+
* `artifacts/` — sample renderings and benchmark reports
|
|
84
|
+
|
|
85
|
+
## Status
|
|
86
|
+
|
|
87
|
+
Version 0.1.0 (2026-09-01). Tests: `python -m pytest` (70 tests; the Lean
|
|
88
|
+
end-to-end test is skipped unless Lean is configured). Benchmark (2026-09-01, 200-problem Stage-2 stress set, every certificate
|
|
89
|
+
compiled with Lean, LLM never needed): **200/200 correct** — see
|
|
90
|
+
`artifacts/bench-stress-2026-09-01/REPORT.md`.
|
|
91
|
+
|
|
92
|
+
## Limitations (short form)
|
|
93
|
+
|
|
94
|
+
Implication between magma laws is undecidable; every engine is budgeted,
|
|
95
|
+
so "no answer" is not a verdict. Finite search is complete per size but
|
|
96
|
+
cannot see infinite-only countermodels beyond the ℕ residue-class family;
|
|
97
|
+
table certificates stop at Fin 10 (affine models go further); `grind`
|
|
98
|
+
certificates are Lean-version-sensitive; the compile check is not the
|
|
99
|
+
competition judge; the LLM stage adds hygiene, not reach. Details and
|
|
100
|
+
measurements: [docs/limitations.md](docs/limitations.md).
|
|
101
|
+
|
|
102
|
+
License: MIT.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""eqtheory — equational theories over magmas.
|
|
2
|
+
|
|
3
|
+
Term rewriting with a Knuth–Bendix order, proof-producing e-graph
|
|
4
|
+
saturation, goal-directed superposition, finite and infinite countermodel
|
|
5
|
+
search, and Lean 4 certificate generation. The algorithms of the SAIR
|
|
6
|
+
Stage-2 solver (2026) as a library; no lookup tables, no stored proofs —
|
|
7
|
+
everything is derived from the two equations of a problem at runtime.
|
|
8
|
+
"""
|
|
9
|
+
from .terms import ( # noqa: F401
|
|
10
|
+
Equation, Problem, Term, OP,
|
|
11
|
+
parse_term, render_term, parse_equation, normalize,
|
|
12
|
+
term_vars, term_leaves, positions, subterm, replace_at,
|
|
13
|
+
match, unify, substitute, rename,
|
|
14
|
+
kbo_greater, holds, evaluate,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
from .solve import solve, Answer, Config, Trace # noqa: E402,F401
|
|
20
|
+
from .completion import derive_lemmas, prove_by_superposition, Budget # noqa: E402,F401
|
|
21
|
+
from .egraph import EGraph, saturate, prove_goal, prove_singleton # noqa: E402,F401
|
|
22
|
+
from .models import find_countermodel, residue_affine_countermodel, Countermodel, NatResidueModel # noqa: E402,F401
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Equation", "Problem", "Term", "OP", "parse_term", "render_term", "parse_equation", "normalize",
|
|
26
|
+
"term_vars", "term_leaves", "positions", "subterm", "replace_at", "match", "unify", "substitute", "rename",
|
|
27
|
+
"kbo_greater", "holds", "evaluate",
|
|
28
|
+
"solve", "Answer", "Config", "Trace", "derive_lemmas", "prove_by_superposition", "Budget",
|
|
29
|
+
"EGraph", "saturate", "prove_goal", "prove_singleton",
|
|
30
|
+
"find_countermodel", "residue_affine_countermodel", "Countermodel", "NatResidueModel", "__version__",
|
|
31
|
+
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Command line: ``eqtheory solve|egraph|model|cert|prove|viz-proof``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from . import __version__, completion, egraph, llm as llm_mod, viz
|
|
10
|
+
from .solve import solve as run_solve, Config, Trace
|
|
11
|
+
from .lean import certs, check as lean_check
|
|
12
|
+
from .models import finite, infinite
|
|
13
|
+
from .terms import Problem
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _problem(a) -> Problem:
|
|
17
|
+
return Problem.parse(a.hypothesis, a.goal)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _judge(a):
|
|
21
|
+
if not getattr(a, "lean", False):
|
|
22
|
+
return None
|
|
23
|
+
cfg = lean_check.configure(timeout=a.lean_timeout)
|
|
24
|
+
if cfg is None:
|
|
25
|
+
sys.exit("Lean not configured: set EQTHEORY_LEAN_BIN and EQTHEORY_JUDGE_ROOT (or EQTHEORY_LEAN_PATH)")
|
|
26
|
+
pr = _problem(a)
|
|
27
|
+
return lean_check.make_judge(pr.hypothesis, pr.goal, cfg)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cmd_solve(a):
|
|
31
|
+
pr = _problem(a)
|
|
32
|
+
cfg = Config(model_budget=a.model_budget, superposition_budget=a.superposition_budget,
|
|
33
|
+
eqsat_budget=a.eqsat_budget, infinite_budget=a.infinite_budget, llm_rounds=a.llm_rounds)
|
|
34
|
+
complete = None
|
|
35
|
+
if a.llm:
|
|
36
|
+
complete = llm_mod.OpenRouterClient(model=a.llm_model, seed=a.llm_seed, reasoning_effort=a.llm_effort)
|
|
37
|
+
tr = Trace()
|
|
38
|
+
t0 = time.monotonic()
|
|
39
|
+
ans = run_solve(pr, cfg, judge=_judge(a), complete=complete, trace=tr)
|
|
40
|
+
out = {"hypothesis": pr.hypothesis.text, "goal": pr.goal.text, "seconds": round(time.monotonic() - t0, 2),
|
|
41
|
+
"verdict": ans.verdict if ans else None, "stage": ans.stage if ans else None,
|
|
42
|
+
"verified": ans.verified if ans else None,
|
|
43
|
+
"facts": {"exhausted": tr.facts.exhausted, "searched": tr.facts.searched},
|
|
44
|
+
"trace": tr.steps}
|
|
45
|
+
if a.json:
|
|
46
|
+
out["code"] = ans.code if ans else None
|
|
47
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
48
|
+
return
|
|
49
|
+
print(f"{pr.hypothesis.text} ⇒ {pr.goal.text}")
|
|
50
|
+
for stage, sec, note in tr.steps:
|
|
51
|
+
print(f" {stage:24s} {sec:8.2f}s {note}")
|
|
52
|
+
if ans is None:
|
|
53
|
+
print("no answer")
|
|
54
|
+
return
|
|
55
|
+
print(f"verdict: {ans.verdict} (stage {ans.stage}{'' if ans.verified else ', UNVERIFIED'})")
|
|
56
|
+
if a.out:
|
|
57
|
+
open(a.out, "w", encoding="utf-8").write(ans.code)
|
|
58
|
+
print(f"certificate written to {a.out}")
|
|
59
|
+
else:
|
|
60
|
+
print(ans.code)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cmd_egraph(a):
|
|
64
|
+
pr = _problem(a)
|
|
65
|
+
lemmas = completion.derive_lemmas(pr.hypothesis) if a.lemmas else []
|
|
66
|
+
eg, l, r, ok = egraph.saturate(pr.hypothesis, pr.goal.variables, (pr.goal.lhs, pr.goal.rhs), lemmas=lemmas,
|
|
67
|
+
time_budget=a.budget, inst_cap=a.inst_cap, max_rounds=a.rounds)
|
|
68
|
+
n_classes = len(eg.classes())
|
|
69
|
+
print(f"e-graph: {len(eg.nodes)} nodes, {n_classes} classes, goal sides {'merged' if ok else 'NOT merged'}")
|
|
70
|
+
if a.render:
|
|
71
|
+
kind = viz.render_egraph(eg, a.render, highlight=(l, r), proof_pairs=((l, r),) if ok else (),
|
|
72
|
+
max_classes=a.max_classes)
|
|
73
|
+
print(f"rendered ({kind}) → {a.render}")
|
|
74
|
+
if a.dot:
|
|
75
|
+
open(a.dot, "w", encoding="utf-8").write(viz.egraph_to_dot(eg, highlight=(l, r), max_classes=a.max_classes))
|
|
76
|
+
print(f"dot → {a.dot}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_model(a):
|
|
80
|
+
pr = _problem(a)
|
|
81
|
+
facts = finite.SearchFacts()
|
|
82
|
+
cm = finite.find_countermodel(pr.hypothesis, pr.goal, time_budget=a.budget, max_n=a.max_n, facts=facts)
|
|
83
|
+
if cm is not None:
|
|
84
|
+
print(f"countermodel on Fin {cm.n}:")
|
|
85
|
+
for row in cm.table:
|
|
86
|
+
print(" " + " ".join(str(v) for v in row))
|
|
87
|
+
aff = cm.affine_form()
|
|
88
|
+
if aff:
|
|
89
|
+
print(f" affine: (a·i + b·j + c) mod n with (a, b, c) = {aff}")
|
|
90
|
+
if a.cert:
|
|
91
|
+
print(certs.false_code(cm.n, cm.table))
|
|
92
|
+
return
|
|
93
|
+
print(f"no finite countermodel found; sizes proven empty: {facts.exhausted}, swept: {facts.searched}")
|
|
94
|
+
if a.infinite:
|
|
95
|
+
m = infinite.residue_affine_countermodel(pr.hypothesis, pr.goal, time_budget=a.budget)
|
|
96
|
+
if m is None:
|
|
97
|
+
print("no residue-class affine model on ℕ")
|
|
98
|
+
return
|
|
99
|
+
print(f"ℕ model: m={m.m} A={m.A} B={m.B} C={m.C} witness={m.witness}")
|
|
100
|
+
if a.cert:
|
|
101
|
+
print(certs.false_nat_residue_code(pr.hypothesis, m.m, m.A, m.B, m.C, m.witness))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cmd_prove(a):
|
|
105
|
+
pr = _problem(a)
|
|
106
|
+
t0 = time.monotonic()
|
|
107
|
+
proof = completion.prove_by_superposition(pr.hypothesis, pr.goal, deadline=time.monotonic() + a.budget)
|
|
108
|
+
if proof is None:
|
|
109
|
+
print(f"no superposition proof within {a.budget}s")
|
|
110
|
+
return
|
|
111
|
+
print(f"proved by {proof.lemma['name']} in {time.monotonic() - t0:.1f}s "
|
|
112
|
+
f"({len(proof.derived)} lemmas derived)")
|
|
113
|
+
code = certs.superposition_code(pr.hypothesis, pr.goal, proof)
|
|
114
|
+
print(code)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cmd_cert(a):
|
|
118
|
+
pr = _problem(a)
|
|
119
|
+
if a.table:
|
|
120
|
+
table = json.loads(a.table)
|
|
121
|
+
if not finite.is_countermodel(pr.hypothesis, pr.goal, len(table), table):
|
|
122
|
+
sys.exit("the table is not a countermodel of this problem")
|
|
123
|
+
print(certs.false_code(len(table), table))
|
|
124
|
+
elif a.proof:
|
|
125
|
+
print(certs.true_code(open(a.proof, encoding="utf-8").read()))
|
|
126
|
+
else:
|
|
127
|
+
sys.exit("give --table or --proof")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cmd_viz_proof(a):
|
|
131
|
+
pr = _problem(a)
|
|
132
|
+
res = egraph.prove_goal(pr.hypothesis, pr.goal, time_budget=a.budget)
|
|
133
|
+
if res is None:
|
|
134
|
+
sys.exit("saturation did not connect the goal sides")
|
|
135
|
+
eg, l, r = res
|
|
136
|
+
lemmas, chain = eg.explain(l, r)
|
|
137
|
+
dot = viz.chain_to_dot(lemmas, chain, title=f"{pr.hypothesis.text} ⇒ {pr.goal.text}")
|
|
138
|
+
open(a.out, "w", encoding="utf-8").write(dot)
|
|
139
|
+
print(f"proof graph (dot) → {a.out}; {len(lemmas)} shared lemmas, chain length {len(chain)}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(argv=None):
|
|
143
|
+
p = argparse.ArgumentParser(prog="eqtheory", description="equational theories over magmas")
|
|
144
|
+
p.add_argument("--version", action="version", version=f"eqtheory {__version__}")
|
|
145
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
146
|
+
|
|
147
|
+
def common(sp, lean=False):
|
|
148
|
+
sp.add_argument("hypothesis", help='e.g. "x = y ◇ (x ◇ y)"')
|
|
149
|
+
sp.add_argument("goal")
|
|
150
|
+
if lean:
|
|
151
|
+
sp.add_argument("--lean", action="store_true", help="check certificates with Lean (EQTHEORY_* env)")
|
|
152
|
+
sp.add_argument("--lean-timeout", type=float, default=300.0)
|
|
153
|
+
|
|
154
|
+
s = sub.add_parser("solve", help="run the full ladder"); common(s, lean=True)
|
|
155
|
+
s.add_argument("--json", action="store_true"); s.add_argument("--out", help="write the certificate here")
|
|
156
|
+
s.add_argument("--model-budget", type=float, default=60.0); s.add_argument("--superposition-budget", type=float, default=300.0)
|
|
157
|
+
s.add_argument("--eqsat-budget", type=float, default=25.0); s.add_argument("--infinite-budget", type=float, default=20.0)
|
|
158
|
+
s.add_argument("--llm", action="store_true", help="add an LLM round (OPENROUTER_API_KEY)")
|
|
159
|
+
s.add_argument("--llm-model", default="openai/gpt-oss-120b"); s.add_argument("--llm-seed", type=int, default=0)
|
|
160
|
+
s.add_argument("--llm-effort", default="low"); s.add_argument("--llm-rounds", type=int, default=4)
|
|
161
|
+
s.set_defaults(fn=cmd_solve)
|
|
162
|
+
|
|
163
|
+
s = sub.add_parser("egraph", help="saturate and render the e-graph"); common(s)
|
|
164
|
+
s.add_argument("--render", metavar="FILE", help="image output (.svg/.png/.pdf; graphviz optional)")
|
|
165
|
+
s.add_argument("--dot", metavar="FILE"); s.add_argument("--lemmas", action="store_true")
|
|
166
|
+
s.add_argument("--budget", type=float, default=10.0); s.add_argument("--inst-cap", type=int, default=20_000)
|
|
167
|
+
s.add_argument("--rounds", type=int, default=8); s.add_argument("--max-classes", type=int, default=200)
|
|
168
|
+
s.set_defaults(fn=cmd_egraph)
|
|
169
|
+
|
|
170
|
+
s = sub.add_parser("model", help="search countermodels"); common(s)
|
|
171
|
+
s.add_argument("--budget", type=float, default=60.0); s.add_argument("--max-n", type=int, default=finite.MAX_TABLE_N)
|
|
172
|
+
s.add_argument("--infinite", action="store_true", help="also try the ℕ residue-class family")
|
|
173
|
+
s.add_argument("--cert", action="store_true", help="print the Lean certificate")
|
|
174
|
+
s.set_defaults(fn=cmd_model)
|
|
175
|
+
|
|
176
|
+
s = sub.add_parser("prove", help="goal-directed superposition"); common(s)
|
|
177
|
+
s.add_argument("--budget", type=float, default=300.0); s.set_defaults(fn=cmd_prove)
|
|
178
|
+
|
|
179
|
+
s = sub.add_parser("cert", help="wrap a table or a tactic body as a certificate"); common(s)
|
|
180
|
+
s.add_argument("--table", help="JSON table"); s.add_argument("--proof", help="file with a tactic body")
|
|
181
|
+
s.set_defaults(fn=cmd_cert)
|
|
182
|
+
|
|
183
|
+
s = sub.add_parser("viz-proof", help="render an e-graph proof as a dot graph"); common(s)
|
|
184
|
+
s.add_argument("--out", required=True); s.add_argument("--budget", type=float, default=10.0)
|
|
185
|
+
s.set_defaults(fn=cmd_viz_proof)
|
|
186
|
+
|
|
187
|
+
a = p.parse_args(argv)
|
|
188
|
+
a.fn(a)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|