goedel 0.2.0__py3-none-any.whl
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.
- goedel/__init__.py +232 -0
- goedel/arithmetic.py +423 -0
- goedel/arithmetic_dag.py +429 -0
- goedel/catalogue.py +111 -0
- goedel/dag_proofs.py +177 -0
- goedel/fixedpoint.py +235 -0
- goedel/inference.py +434 -0
- goedel/metatheorems.py +164 -0
- goedel/modal.py +284 -0
- goedel/natural_deduction.py +355 -0
- goedel/pa_provability.py +237 -0
- goedel/proof.py +218 -0
- goedel/proof_coding.py +145 -0
- goedel/py.typed +0 -0
- goedel/register_machine.py +286 -0
- goedel/resources/catalogue.json +1781 -0
- goedel/resources/catalogue_review.json +593 -0
- goedel/resources/expr.schema.json +77 -0
- goedel/resources/inference_rules.json +138 -0
- goedel/resources/pa_hbl_review.json +1 -0
- goedel/resources/pa_proof_representation.json +1 -0
- goedel/resources/pa_proof_review.json +1 -0
- goedel/resources/provability_representation.json +1 -0
- goedel/resources/reference_cases.json +4631 -0
- goedel/resources/resource_manifest.json +15 -0
- goedel/resources/substitution_representation.json +1 -0
- goedel/semantics.py +320 -0
- goedel/sequence.py +124 -0
- goedel/syntax.py +390 -0
- goedel-0.2.0.dist-info/METADATA +93 -0
- goedel-0.2.0.dist-info/RECORD +35 -0
- goedel-0.2.0.dist-info/WHEEL +5 -0
- goedel-0.2.0.dist-info/licenses/LICENSE +202 -0
- goedel-0.2.0.dist-info/licenses/NOTICE +4 -0
- goedel-0.2.0.dist-info/top_level.txt +1 -0
goedel/__init__.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Finite syntax, exact coding, explicit proof checking, and qualified metamathematics."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from .arithmetic import (
|
|
6
|
+
add,
|
|
7
|
+
arithmetic_language,
|
|
8
|
+
arithmetic_theory,
|
|
9
|
+
expand_numeral,
|
|
10
|
+
format_formula,
|
|
11
|
+
induction_axiom,
|
|
12
|
+
is_axiom,
|
|
13
|
+
multiply,
|
|
14
|
+
numeral,
|
|
15
|
+
numeral_value,
|
|
16
|
+
parse_formula,
|
|
17
|
+
q_axioms,
|
|
18
|
+
recognize_induction,
|
|
19
|
+
successor,
|
|
20
|
+
zero,
|
|
21
|
+
)
|
|
22
|
+
from .arithmetic_dag import (
|
|
23
|
+
arithmetic_dag,
|
|
24
|
+
arithmetic_dag_decode,
|
|
25
|
+
arithmetic_dag_encode,
|
|
26
|
+
dag_fingerprint,
|
|
27
|
+
dag_substitute,
|
|
28
|
+
dag_substitute_numeral,
|
|
29
|
+
serialize_arithmetic_dag,
|
|
30
|
+
substitute_code,
|
|
31
|
+
validate_arithmetic_dag,
|
|
32
|
+
)
|
|
33
|
+
from .catalogue import (
|
|
34
|
+
catalogue_manifest,
|
|
35
|
+
check_applicability,
|
|
36
|
+
definition,
|
|
37
|
+
list_definitions,
|
|
38
|
+
list_statements,
|
|
39
|
+
lookup_statement,
|
|
40
|
+
relation_graph,
|
|
41
|
+
theory_context,
|
|
42
|
+
)
|
|
43
|
+
from .dag_proofs import check_scoped_dag_proof, dag_alpha_equal
|
|
44
|
+
from .fixedpoint import fixed_point
|
|
45
|
+
from .inference import (
|
|
46
|
+
catalogue_graph,
|
|
47
|
+
catalogue_topics,
|
|
48
|
+
check_derivation,
|
|
49
|
+
explain_derivation,
|
|
50
|
+
infer,
|
|
51
|
+
inference_context,
|
|
52
|
+
list_inference_rules,
|
|
53
|
+
search_catalogue,
|
|
54
|
+
)
|
|
55
|
+
from .metatheorems import check_metatheorem, godel_sentence
|
|
56
|
+
from .modal import (
|
|
57
|
+
ModalExpr,
|
|
58
|
+
evaluate_modal,
|
|
59
|
+
frame_check,
|
|
60
|
+
kripke_model,
|
|
61
|
+
modal_atom,
|
|
62
|
+
modal_bottom,
|
|
63
|
+
modal_box,
|
|
64
|
+
modal_conjunction,
|
|
65
|
+
modal_diamond,
|
|
66
|
+
modal_disjunction,
|
|
67
|
+
modal_implication,
|
|
68
|
+
modal_negate,
|
|
69
|
+
validate_modal_frame,
|
|
70
|
+
)
|
|
71
|
+
from .natural_deduction import check_scoped_proof
|
|
72
|
+
from .pa_provability import (
|
|
73
|
+
check_pa_proof,
|
|
74
|
+
consistency_formula,
|
|
75
|
+
hilbert_q_axiom,
|
|
76
|
+
pa_proof_decode,
|
|
77
|
+
pa_proof_encode,
|
|
78
|
+
proof_PA_formula,
|
|
79
|
+
provability_formula,
|
|
80
|
+
provability_identity,
|
|
81
|
+
)
|
|
82
|
+
from .proof import bounded_proof_search, check_proof
|
|
83
|
+
from .proof_coding import check_proof_code, decode_proof, encode_proof, proof_object
|
|
84
|
+
from .register_machine import check_register_trace, run_register_program
|
|
85
|
+
from .semantics import evaluate_formula, evaluate_term, finite_model_check, finite_structure
|
|
86
|
+
from .sequence import sequence_decode, sequence_encode
|
|
87
|
+
from .syntax import (
|
|
88
|
+
Expr,
|
|
89
|
+
GoedelError,
|
|
90
|
+
alpha_equal,
|
|
91
|
+
bottom,
|
|
92
|
+
bound_variables,
|
|
93
|
+
conjunction,
|
|
94
|
+
constant,
|
|
95
|
+
decode,
|
|
96
|
+
deserialize,
|
|
97
|
+
disjunction,
|
|
98
|
+
encode,
|
|
99
|
+
equal,
|
|
100
|
+
exists,
|
|
101
|
+
fingerprint,
|
|
102
|
+
forall,
|
|
103
|
+
free_variables,
|
|
104
|
+
function_term,
|
|
105
|
+
implication,
|
|
106
|
+
negate,
|
|
107
|
+
normalize,
|
|
108
|
+
numbering,
|
|
109
|
+
predicate,
|
|
110
|
+
serialize,
|
|
111
|
+
substitute,
|
|
112
|
+
validate_expr,
|
|
113
|
+
variable,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
__version__ = version("goedel")
|
|
118
|
+
except PackageNotFoundError:
|
|
119
|
+
__version__ = "0.2.0+uninstalled"
|
|
120
|
+
|
|
121
|
+
__all__ = [
|
|
122
|
+
"Expr",
|
|
123
|
+
"GoedelError",
|
|
124
|
+
"alpha_equal",
|
|
125
|
+
"bottom",
|
|
126
|
+
"bound_variables",
|
|
127
|
+
"bounded_proof_search",
|
|
128
|
+
"catalogue_manifest",
|
|
129
|
+
"check_applicability",
|
|
130
|
+
"check_proof",
|
|
131
|
+
"conjunction",
|
|
132
|
+
"constant",
|
|
133
|
+
"decode",
|
|
134
|
+
"definition",
|
|
135
|
+
"deserialize",
|
|
136
|
+
"disjunction",
|
|
137
|
+
"encode",
|
|
138
|
+
"equal",
|
|
139
|
+
"exists",
|
|
140
|
+
"fingerprint",
|
|
141
|
+
"forall",
|
|
142
|
+
"free_variables",
|
|
143
|
+
"function_term",
|
|
144
|
+
"implication",
|
|
145
|
+
"list_definitions",
|
|
146
|
+
"list_statements",
|
|
147
|
+
"lookup_statement",
|
|
148
|
+
"negate",
|
|
149
|
+
"normalize",
|
|
150
|
+
"numbering",
|
|
151
|
+
"predicate",
|
|
152
|
+
"relation_graph",
|
|
153
|
+
"serialize",
|
|
154
|
+
"substitute",
|
|
155
|
+
"theory_context",
|
|
156
|
+
"validate_expr",
|
|
157
|
+
"variable",
|
|
158
|
+
"ModalExpr",
|
|
159
|
+
"add",
|
|
160
|
+
"arithmetic_language",
|
|
161
|
+
"arithmetic_theory",
|
|
162
|
+
"check_scoped_proof",
|
|
163
|
+
"evaluate_formula",
|
|
164
|
+
"evaluate_modal",
|
|
165
|
+
"evaluate_term",
|
|
166
|
+
"expand_numeral",
|
|
167
|
+
"finite_model_check",
|
|
168
|
+
"finite_structure",
|
|
169
|
+
"format_formula",
|
|
170
|
+
"frame_check",
|
|
171
|
+
"induction_axiom",
|
|
172
|
+
"is_axiom",
|
|
173
|
+
"kripke_model",
|
|
174
|
+
"modal_atom",
|
|
175
|
+
"modal_bottom",
|
|
176
|
+
"modal_box",
|
|
177
|
+
"modal_conjunction",
|
|
178
|
+
"modal_diamond",
|
|
179
|
+
"modal_disjunction",
|
|
180
|
+
"modal_implication",
|
|
181
|
+
"modal_negate",
|
|
182
|
+
"multiply",
|
|
183
|
+
"numeral",
|
|
184
|
+
"numeral_value",
|
|
185
|
+
"parse_formula",
|
|
186
|
+
"q_axioms",
|
|
187
|
+
"recognize_induction",
|
|
188
|
+
"successor",
|
|
189
|
+
"validate_modal_frame",
|
|
190
|
+
"zero",
|
|
191
|
+
"arithmetic_dag",
|
|
192
|
+
"arithmetic_dag_decode",
|
|
193
|
+
"arithmetic_dag_encode",
|
|
194
|
+
"dag_fingerprint",
|
|
195
|
+
"dag_substitute",
|
|
196
|
+
"dag_substitute_numeral",
|
|
197
|
+
"serialize_arithmetic_dag",
|
|
198
|
+
"substitute_code",
|
|
199
|
+
"validate_arithmetic_dag",
|
|
200
|
+
"check_scoped_dag_proof",
|
|
201
|
+
"dag_alpha_equal",
|
|
202
|
+
"check_proof_code",
|
|
203
|
+
"decode_proof",
|
|
204
|
+
"encode_proof",
|
|
205
|
+
"proof_object",
|
|
206
|
+
"sequence_decode",
|
|
207
|
+
"sequence_encode",
|
|
208
|
+
"fixed_point",
|
|
209
|
+
"catalogue_graph",
|
|
210
|
+
"catalogue_topics",
|
|
211
|
+
"check_derivation",
|
|
212
|
+
"explain_derivation",
|
|
213
|
+
"infer",
|
|
214
|
+
"inference_context",
|
|
215
|
+
"list_inference_rules",
|
|
216
|
+
"search_catalogue",
|
|
217
|
+
"check_register_trace",
|
|
218
|
+
"run_register_program",
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
__all__ += [
|
|
222
|
+
"proof_PA_formula",
|
|
223
|
+
"provability_formula",
|
|
224
|
+
"consistency_formula",
|
|
225
|
+
"provability_identity",
|
|
226
|
+
"check_pa_proof",
|
|
227
|
+
"hilbert_q_axiom",
|
|
228
|
+
"pa_proof_encode",
|
|
229
|
+
"pa_proof_decode",
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
__all__ += ["check_metatheorem", "godel_sentence"]
|
goedel/arithmetic.py
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""Concrete arithmetic syntax, compact numerals, Q and the PA induction schema.
|
|
2
|
+
|
|
3
|
+
Numeral nodes abbreviate the documented binary Horner term. They are never
|
|
4
|
+
uninterpreted numeric constants or assertions about a theory's consistency.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .syntax import (
|
|
13
|
+
BINDERS,
|
|
14
|
+
FORMULAS,
|
|
15
|
+
TERMS,
|
|
16
|
+
Expr,
|
|
17
|
+
_error,
|
|
18
|
+
_expr,
|
|
19
|
+
_free,
|
|
20
|
+
_name,
|
|
21
|
+
_wire,
|
|
22
|
+
alpha_equal,
|
|
23
|
+
conjunction,
|
|
24
|
+
constant,
|
|
25
|
+
equal,
|
|
26
|
+
exists,
|
|
27
|
+
forall,
|
|
28
|
+
function_term,
|
|
29
|
+
implication,
|
|
30
|
+
negate,
|
|
31
|
+
substitute,
|
|
32
|
+
variable,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def arithmetic_language() -> dict[str, Any]:
|
|
37
|
+
"""Return the versioned first-order arithmetic signature and numeral convention."""
|
|
38
|
+
return {
|
|
39
|
+
"id": "goedel-arithmetic-v1",
|
|
40
|
+
"constants": ["zero"],
|
|
41
|
+
"functions": [{"name": "S", "arity": 1}, {"name": "add", "arity": 2}, {"name": "mul", "arity": 2}],
|
|
42
|
+
"relations": [],
|
|
43
|
+
"equality": True,
|
|
44
|
+
"numerals": "binary-horner-v1",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def zero() -> Expr:
|
|
49
|
+
"""Construct the arithmetic zero symbol."""
|
|
50
|
+
return constant("zero")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def successor(term: Expr) -> Expr:
|
|
54
|
+
"""Construct the arithmetic successor term S(term)."""
|
|
55
|
+
return function_term("S", [term])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add(left: Expr, right: Expr) -> Expr:
|
|
59
|
+
"""Construct arithmetic addition, without evaluating it."""
|
|
60
|
+
return function_term("add", [left, right])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def multiply(left: Expr, right: Expr) -> Expr:
|
|
64
|
+
"""Construct arithmetic multiplication, without evaluating it."""
|
|
65
|
+
return function_term("mul", [left, right])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def numeral(value: str | int) -> Expr:
|
|
69
|
+
"""Construct an exact compact numeral from decimal text or a Python integer."""
|
|
70
|
+
if type(value) is int:
|
|
71
|
+
if value < 0:
|
|
72
|
+
_error("Numerals are nonnegative")
|
|
73
|
+
if value.bit_length() > 13607:
|
|
74
|
+
_error("Numeral digit budget exceeded", "RESOURCE_LIMIT")
|
|
75
|
+
value = str(value)
|
|
76
|
+
if not isinstance(value, str):
|
|
77
|
+
_error("Numeral value must be canonical decimal text or an integer")
|
|
78
|
+
return Expr("num", value)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _arithmetic(expr: Expr) -> Expr:
|
|
82
|
+
node = _expr(expr)
|
|
83
|
+
|
|
84
|
+
def walk(e: Expr) -> None:
|
|
85
|
+
if e.kind == "const" and e.name != "zero":
|
|
86
|
+
_error("Foreign constant in arithmetic signature")
|
|
87
|
+
if e.kind == "func" and {"S": 1, "add": 2, "mul": 2}.get(e.name) != len(e.args):
|
|
88
|
+
_error("Foreign function or arity in arithmetic signature")
|
|
89
|
+
if e.kind == "pred":
|
|
90
|
+
_error("Arithmetic signature has equality and no predicate symbols")
|
|
91
|
+
for arg in e.args:
|
|
92
|
+
walk(arg)
|
|
93
|
+
|
|
94
|
+
walk(node)
|
|
95
|
+
return node
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def numeral_value(term: Expr) -> str:
|
|
99
|
+
"""Evaluate a closed arithmetic term in the natural numbers, exactly.
|
|
100
|
+
|
|
101
|
+
This finite term evaluation does not decide quantified arithmetic truth.
|
|
102
|
+
The returned value has at most 4096 decimal digits.
|
|
103
|
+
"""
|
|
104
|
+
node = _arithmetic(term)
|
|
105
|
+
if node.kind not in TERMS or _free(node):
|
|
106
|
+
_error("A closed arithmetic term is required")
|
|
107
|
+
|
|
108
|
+
def walk(e: Expr) -> int:
|
|
109
|
+
if e.kind == "num":
|
|
110
|
+
value = int(e.name)
|
|
111
|
+
elif e.kind == "const":
|
|
112
|
+
value = 0
|
|
113
|
+
else:
|
|
114
|
+
args = [walk(arg) for arg in e.args]
|
|
115
|
+
value = args[0] + 1 if e.name == "S" else (sum(args) if e.name == "add" else args[0] * args[1])
|
|
116
|
+
if value.bit_length() > 13607:
|
|
117
|
+
_error("Arithmetic value budget exceeded", "RESOURCE_LIMIT")
|
|
118
|
+
return value
|
|
119
|
+
|
|
120
|
+
result = str(walk(node))
|
|
121
|
+
if len(result) > 4096:
|
|
122
|
+
_error("Arithmetic value budget exceeded", "RESOURCE_LIMIT")
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def expand_numeral(term: Expr, max_nodes: int = 1000) -> Expr:
|
|
127
|
+
"""Replace numeral abbreviations by actual binary Horner arithmetic terms.
|
|
128
|
+
|
|
129
|
+
nu(0)=0, nu(1)=S0, nu(2k)=2*nu(k), nu(2k+1)=S(2*nu(k)).
|
|
130
|
+
Existing expression depth and node limits still apply.
|
|
131
|
+
"""
|
|
132
|
+
if type(max_nodes) is not int or not 1 <= max_nodes <= 1000:
|
|
133
|
+
_error("max_nodes must be an integer from 1 to 1000")
|
|
134
|
+
node = _expr(term)
|
|
135
|
+
used = 0
|
|
136
|
+
|
|
137
|
+
def reserve(amount: int) -> None:
|
|
138
|
+
nonlocal used
|
|
139
|
+
used += amount
|
|
140
|
+
if used > max_nodes:
|
|
141
|
+
_error("Numeral expansion budget exceeded", "RESOURCE_LIMIT")
|
|
142
|
+
|
|
143
|
+
def build(n: int) -> Expr:
|
|
144
|
+
if n == 0:
|
|
145
|
+
reserve(1)
|
|
146
|
+
return zero()
|
|
147
|
+
reserve(2)
|
|
148
|
+
result = successor(zero())
|
|
149
|
+
for bit in bin(n)[3:]:
|
|
150
|
+
reserve(4 + (bit == "1"))
|
|
151
|
+
result = multiply(successor(successor(zero())), result)
|
|
152
|
+
if bit == "1":
|
|
153
|
+
result = successor(result)
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
def walk(e: Expr) -> Expr:
|
|
157
|
+
if e.kind == "num":
|
|
158
|
+
return build(int(e.name))
|
|
159
|
+
reserve(1)
|
|
160
|
+
return Expr(e.kind, e.name, tuple(walk(a) for a in e.args))
|
|
161
|
+
|
|
162
|
+
return walk(node)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def q_axioms() -> list[Expr]:
|
|
166
|
+
"""Return the seven closed Robinson-Q axioms in the documented Q1--Q7 order."""
|
|
167
|
+
x, y = variable("x"), variable("y")
|
|
168
|
+
return [
|
|
169
|
+
forall("x", negate(equal(successor(x), zero()))),
|
|
170
|
+
forall("x", forall("y", implication(equal(successor(x), successor(y)), equal(x, y)))),
|
|
171
|
+
forall("x", implication(negate(equal(x, zero())), exists("y", equal(x, successor(y))))),
|
|
172
|
+
forall("x", equal(add(x, zero()), x)),
|
|
173
|
+
forall("x", forall("y", equal(add(x, successor(y)), successor(add(x, y))))),
|
|
174
|
+
forall("x", equal(multiply(x, zero()), zero())),
|
|
175
|
+
forall("x", forall("y", equal(multiply(x, successor(y)), add(multiply(x, y), x)))),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def induction_axiom(body: Expr, variable: str, close: bool = True) -> Expr:
|
|
180
|
+
"""Construct a PA induction instance, optionally closing all free parameters."""
|
|
181
|
+
node = _arithmetic(body)
|
|
182
|
+
_name(variable)
|
|
183
|
+
if node.kind not in FORMULAS or type(close) is not bool:
|
|
184
|
+
_error("Induction requires an arithmetic formula and a boolean close flag")
|
|
185
|
+
base = substitute(node, variable, zero())
|
|
186
|
+
step = forall(variable, implication(node, substitute(node, variable, successor(Expr("var", variable)))))
|
|
187
|
+
result = implication(conjunction(base, step), forall(variable, node))
|
|
188
|
+
if close:
|
|
189
|
+
for parameter in reversed(sorted(_free(node) - {variable})):
|
|
190
|
+
result = forall(parameter, result)
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def recognize_induction(formula: Expr) -> dict[str, Any]:
|
|
195
|
+
"""Recognize exact PA induction syntax up to bound-variable renaming.
|
|
196
|
+
|
|
197
|
+
Open instances and full universal closures are recognized. Axiom membership
|
|
198
|
+
additionally requires closure; malformed or foreign syntax is rejected.
|
|
199
|
+
"""
|
|
200
|
+
node = _arithmetic(formula)
|
|
201
|
+
if node.kind not in FORMULAS:
|
|
202
|
+
_error("An arithmetic formula is required")
|
|
203
|
+
missing = {"is_instance": False, "variable": None, "body": None, "parameters": []}
|
|
204
|
+
outer: list[str] = []
|
|
205
|
+
core = node
|
|
206
|
+
while core.kind == "forall":
|
|
207
|
+
outer.append(core.name)
|
|
208
|
+
core = core.args[0]
|
|
209
|
+
if core.kind != "implies" or core.args[1].kind != "forall":
|
|
210
|
+
return missing
|
|
211
|
+
binder, body = core.args[1].name, core.args[1].args[0]
|
|
212
|
+
parameters = sorted(_free(body) - {binder})
|
|
213
|
+
if outer and (len(set(outer)) != len(outer) or set(outer) != set(parameters)):
|
|
214
|
+
return missing
|
|
215
|
+
if not alpha_equal(core, induction_axiom(body, binder, close=False)):
|
|
216
|
+
return missing
|
|
217
|
+
return {"is_instance": True, "variable": binder, "body": _wire(body), "parameters": parameters}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def arithmetic_theory(name: str = "PA", extensions: list[Expr] | tuple[Expr, ...] = ()) -> dict[str, Any]:
|
|
221
|
+
"""Describe executable Q or PA axiom membership plus explicit closed extensions.
|
|
222
|
+
|
|
223
|
+
PA uses all seven Q axioms plus induction; the predecessor axiom is redundant
|
|
224
|
+
in this presentation. Extensions are declared axioms, not proved conclusions.
|
|
225
|
+
"""
|
|
226
|
+
if not isinstance(name, str) or name not in {"Q", "PA"} or not isinstance(extensions, (list, tuple)):
|
|
227
|
+
_error("Theory must be Q or PA, with a list of closed arithmetic extensions")
|
|
228
|
+
extra = [_arithmetic(e) for e in extensions]
|
|
229
|
+
if any(e.kind not in FORMULAS or _free(e) for e in extra):
|
|
230
|
+
_error("Extensions must be closed arithmetic formulas")
|
|
231
|
+
if len(extra) > 256:
|
|
232
|
+
_error("Theory extension budget exceeded", "RESOURCE_LIMIT")
|
|
233
|
+
if any(alpha_equal(e, prior) for i, e in enumerate(extra) for prior in extra[:i]):
|
|
234
|
+
_error("Duplicate theory extension")
|
|
235
|
+
return {
|
|
236
|
+
"id": name,
|
|
237
|
+
"version": "1.0",
|
|
238
|
+
"signature": arithmetic_language(),
|
|
239
|
+
"logic": "CLASSICAL_FOL_EQUALITY",
|
|
240
|
+
"axioms": [_wire(a) for a in q_axioms()],
|
|
241
|
+
"schemas": ["PA_INDUCTION"] if name == "PA" else [],
|
|
242
|
+
"presentation": {"kind": "DECIDABLE", "id": f"goedel-{name}-v1"},
|
|
243
|
+
"extensions": [_wire(e) for e in extra],
|
|
244
|
+
"consistency_status": "NOT_ESTABLISHED",
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def is_axiom(theory: dict[str, Any], formula: Expr) -> bool:
|
|
249
|
+
"""Decide membership in the declared built-in arithmetic axiom presentation."""
|
|
250
|
+
if not isinstance(theory, dict) or not isinstance(theory.get("id"), str) or theory["id"] not in {"Q", "PA"}:
|
|
251
|
+
_error("A built-in arithmetic theory is required")
|
|
252
|
+
expected = arithmetic_theory(theory["id"], theory.get("extensions", []))
|
|
253
|
+
if theory != expected:
|
|
254
|
+
_error("Theory presentation was modified or has an unknown version")
|
|
255
|
+
node = _arithmetic(formula)
|
|
256
|
+
if node.kind not in FORMULAS:
|
|
257
|
+
_error("A formula is required")
|
|
258
|
+
if _free(node):
|
|
259
|
+
return False
|
|
260
|
+
if any(alpha_equal(node, a) for a in expected["axioms"] + expected["extensions"]):
|
|
261
|
+
return True
|
|
262
|
+
return theory["id"] == "PA" and recognize_induction(node)["is_instance"]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
_TOKEN = re.compile(r"\s*(->|[A-Za-z][A-Za-z0-9_]*|0|[1-9][0-9]*|[@(),.=!&|+*])")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def parse_formula(text: str) -> Expr:
|
|
269
|
+
"""Parse documented ASCII first-order notation without executing input text."""
|
|
270
|
+
if not isinstance(text, str):
|
|
271
|
+
_error("Formula text must be a string")
|
|
272
|
+
if len(text) > 65536:
|
|
273
|
+
_error("Parser input budget exceeded", "RESOURCE_LIMIT")
|
|
274
|
+
tokens: list[str] = []
|
|
275
|
+
position = 0
|
|
276
|
+
while position < len(text):
|
|
277
|
+
if text[position:].isspace():
|
|
278
|
+
break
|
|
279
|
+
match = _TOKEN.match(text, position)
|
|
280
|
+
if match is None:
|
|
281
|
+
_error("Unrecognized formula token")
|
|
282
|
+
tokens.append(match[1])
|
|
283
|
+
position = match.end()
|
|
284
|
+
if len(tokens) > 5000:
|
|
285
|
+
_error("Parser token budget exceeded", "RESOURCE_LIMIT")
|
|
286
|
+
index = 0
|
|
287
|
+
|
|
288
|
+
def peek() -> str:
|
|
289
|
+
return tokens[index] if index < len(tokens) else ""
|
|
290
|
+
|
|
291
|
+
def take(token: str | None = None) -> str:
|
|
292
|
+
nonlocal index
|
|
293
|
+
value = peek()
|
|
294
|
+
if not value or (token is not None and value != token):
|
|
295
|
+
_error("Unexpected formula token")
|
|
296
|
+
index += 1
|
|
297
|
+
return value
|
|
298
|
+
|
|
299
|
+
def term(level: int = 0) -> Expr:
|
|
300
|
+
if level == 0:
|
|
301
|
+
result = term(1)
|
|
302
|
+
while peek() == "+":
|
|
303
|
+
take()
|
|
304
|
+
result = add(result, term(1))
|
|
305
|
+
return result
|
|
306
|
+
if level == 1:
|
|
307
|
+
result = term(2)
|
|
308
|
+
while peek() == "*":
|
|
309
|
+
take()
|
|
310
|
+
result = multiply(result, term(2))
|
|
311
|
+
return result
|
|
312
|
+
value = take()
|
|
313
|
+
if value == "(":
|
|
314
|
+
result = term()
|
|
315
|
+
take(")")
|
|
316
|
+
return result
|
|
317
|
+
if value == "@":
|
|
318
|
+
return constant(take())
|
|
319
|
+
if value.isdecimal():
|
|
320
|
+
return numeral(value)
|
|
321
|
+
_name(value)
|
|
322
|
+
if peek() != "(":
|
|
323
|
+
return variable(value)
|
|
324
|
+
take("(")
|
|
325
|
+
args: list[Expr] = []
|
|
326
|
+
if peek() != ")":
|
|
327
|
+
args.append(term())
|
|
328
|
+
while peek() == ",":
|
|
329
|
+
take()
|
|
330
|
+
args.append(term())
|
|
331
|
+
take(")")
|
|
332
|
+
return function_term(value, args)
|
|
333
|
+
|
|
334
|
+
def formula(level: int = 0) -> Expr:
|
|
335
|
+
nonlocal index
|
|
336
|
+
if level < 3:
|
|
337
|
+
op = ("->", "|", "&")[level]
|
|
338
|
+
kind = ("implies", "or", "and")[level]
|
|
339
|
+
result = formula(level + 1)
|
|
340
|
+
while peek() == op:
|
|
341
|
+
take()
|
|
342
|
+
result = Expr(kind, "", (result, formula(level if level == 0 else level + 1)))
|
|
343
|
+
return result
|
|
344
|
+
if peek() == "!":
|
|
345
|
+
take()
|
|
346
|
+
return negate(formula(3))
|
|
347
|
+
if peek() in {"forall", "exists"} and index + 2 < len(tokens) and tokens[index + 2] == ".":
|
|
348
|
+
binder, name = take(), take()
|
|
349
|
+
take(".")
|
|
350
|
+
return Expr(binder, _name(name), (formula(),))
|
|
351
|
+
if peek() == "false" and (index + 1 == len(tokens) or tokens[index + 1] not in {"(", "="}):
|
|
352
|
+
take()
|
|
353
|
+
return Expr("bottom")
|
|
354
|
+
start = index
|
|
355
|
+
if peek() == "(":
|
|
356
|
+
try:
|
|
357
|
+
left = term()
|
|
358
|
+
if peek() == "=":
|
|
359
|
+
take()
|
|
360
|
+
return equal(left, term())
|
|
361
|
+
except ValueError as exc:
|
|
362
|
+
if getattr(exc, "code", None) == "RESOURCE_LIMIT":
|
|
363
|
+
raise
|
|
364
|
+
index = start
|
|
365
|
+
take("(")
|
|
366
|
+
result = formula()
|
|
367
|
+
take(")")
|
|
368
|
+
return result
|
|
369
|
+
start = index
|
|
370
|
+
left = term()
|
|
371
|
+
if peek() == "=":
|
|
372
|
+
take()
|
|
373
|
+
return equal(left, term())
|
|
374
|
+
if left.kind == "var":
|
|
375
|
+
return Expr("pred", left.name)
|
|
376
|
+
if left.kind == "func" and tokens[start] == left.name and start + 1 < len(tokens) and tokens[start + 1] == "(":
|
|
377
|
+
depth = 0
|
|
378
|
+
closing = None
|
|
379
|
+
for cursor in range(start + 1, index):
|
|
380
|
+
depth += tokens[cursor] == "("
|
|
381
|
+
depth -= tokens[cursor] == ")"
|
|
382
|
+
if depth == 0:
|
|
383
|
+
closing = cursor
|
|
384
|
+
break
|
|
385
|
+
if closing == index - 1:
|
|
386
|
+
return Expr("pred", left.name, left.args)
|
|
387
|
+
_error("A formula requires equality or a predicate")
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
result = formula()
|
|
391
|
+
except RecursionError:
|
|
392
|
+
_error("Parser nesting budget exceeded", "RESOURCE_LIMIT")
|
|
393
|
+
if index != len(tokens):
|
|
394
|
+
_error("Trailing formula tokens")
|
|
395
|
+
return result
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def format_formula(expr: Expr) -> str:
|
|
399
|
+
"""Format a formula as deterministic ASCII notation accepted by parse_formula."""
|
|
400
|
+
node = _expr(expr)
|
|
401
|
+
if node.kind not in FORMULAS:
|
|
402
|
+
_error("A formula is required")
|
|
403
|
+
|
|
404
|
+
def walk(e: Expr) -> str:
|
|
405
|
+
if e.kind in {"var", "num"}:
|
|
406
|
+
return e.name
|
|
407
|
+
if e.kind == "const":
|
|
408
|
+
return "@" + e.name
|
|
409
|
+
args = [walk(a) for a in e.args]
|
|
410
|
+
if e.kind == "func" and e.name in {"add", "mul"} and len(args) == 2:
|
|
411
|
+
return f"({args[0]} {'+' if e.name == 'add' else '*'} {args[1]})"
|
|
412
|
+
if e.kind in {"func", "pred"}:
|
|
413
|
+
return e.name + "(" + ",".join(args) + ")"
|
|
414
|
+
if e.kind == "bottom":
|
|
415
|
+
return "false"
|
|
416
|
+
if e.kind in BINDERS:
|
|
417
|
+
return f"({e.kind} {e.name}. {args[0]})"
|
|
418
|
+
if e.kind == "not":
|
|
419
|
+
return "!(" + args[0] + ")"
|
|
420
|
+
operator = {"eq": "=", "and": "&", "or": "|", "implies": "->"}[e.kind]
|
|
421
|
+
return f"({args[0]} {operator} {args[1]})"
|
|
422
|
+
|
|
423
|
+
return walk(node)
|