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 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.
@@ -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.
@@ -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()