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 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)