unicode-logic-kit 0.31.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.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Lambda elimination, single-step reduction, and well-formedness for the export pipeline.
|
|
2
|
+
|
|
3
|
+
Lambda terms (``Lambda`` / ``Application`` / ``LambdaVar``) cannot be exported to
|
|
4
|
+
Z3, Prover9, or TPTP, nor fed to the classical normal-form passes, because those
|
|
5
|
+
back-ends understand only first-order constructs. This module bridges the gap:
|
|
6
|
+
|
|
7
|
+
- ``has_lambdas`` — predicate: does any lambda construct occur anywhere?
|
|
8
|
+
- ``eliminate_lambdas`` — beta-eta normalize, fold residual applied predicates /
|
|
9
|
+
functions back into ``Atom`` / ``Function`` calls, and verify the result is
|
|
10
|
+
lambda-free (raising ``ValueError`` on a stuck / partially-applied term).
|
|
11
|
+
- ``beta_reduce_step`` — contract exactly ONE leftmost-outermost beta-redex.
|
|
12
|
+
- ``reduce_trace`` — the list of intermediate terms from the original to the
|
|
13
|
+
beta-normal form, one ``beta_reduce_step`` apart (teaching / debugging aid).
|
|
14
|
+
|
|
15
|
+
All functions are purely functional: inputs are never mutated and every returned
|
|
16
|
+
node is freshly built. ``eliminate_lambdas`` produces a node ready for
|
|
17
|
+
``to_fol`` / ``to_z3`` / ``to_prover9`` / ``to_tptp`` and the normal-form API.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .nodes import (
|
|
21
|
+
Node,
|
|
22
|
+
Variable, Constant, Number, Function,
|
|
23
|
+
Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
24
|
+
SortedQuantifier, SortedConstant,
|
|
25
|
+
WeakConjunction, WeakDisjunction,
|
|
26
|
+
StrongConjunction, StrongDisjunction,
|
|
27
|
+
LukNegation, LukImplication, LukEquivalence,
|
|
28
|
+
LambdaVar, Lambda, Application,
|
|
29
|
+
substitute, beta_eta_normalize, ReductionLimitError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Lambda detection
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
def has_lambdas(node: Node) -> bool:
|
|
38
|
+
"""Return True iff any Lambda, Application, or LambdaVar occurs anywhere in node.
|
|
39
|
+
|
|
40
|
+
Pure structural scan; the input is never mutated.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(node, (Lambda, Application, LambdaVar)):
|
|
43
|
+
return True
|
|
44
|
+
if isinstance(node, (Variable, Constant, Number, SortedConstant)):
|
|
45
|
+
return False
|
|
46
|
+
if isinstance(node, (Atom, Function)):
|
|
47
|
+
return any(has_lambdas(a) for a in node.args)
|
|
48
|
+
if isinstance(node, (Not, LukNegation)):
|
|
49
|
+
return has_lambdas(node.formula)
|
|
50
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff,
|
|
51
|
+
WeakConjunction, WeakDisjunction,
|
|
52
|
+
StrongConjunction, StrongDisjunction,
|
|
53
|
+
LukImplication, LukEquivalence)):
|
|
54
|
+
return has_lambdas(node.left) or has_lambdas(node.right)
|
|
55
|
+
if isinstance(node, (Quantifier, SortedQuantifier)):
|
|
56
|
+
return has_lambdas(node.formula)
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Applicative collapse (fold residual Applications back into Atom/Function calls)
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
#
|
|
64
|
+
# Beta-reduction can leave an Application whose head is an applied predicate or
|
|
65
|
+
# symbol rather than a Lambda, e.g. substituting the nullary predicate
|
|
66
|
+
# Atom('Q', []) for P in (λP. P(x))(Q) yields Application(Atom('Q', []), x).
|
|
67
|
+
# That is not a redex, but it is the curried spelling of the first-order call
|
|
68
|
+
# Q(x). This pass un-curries such spines back into Atom / Function nodes so the
|
|
69
|
+
# result is lambda-free and exportable. A spine whose head is a (free) LambdaVar
|
|
70
|
+
# is genuinely stuck and is left untouched for eliminate_lambdas to reject.
|
|
71
|
+
|
|
72
|
+
def _spine(node: Application):
|
|
73
|
+
"""Uncurry a left-nested Application into (head, [arg0, arg1, ...])."""
|
|
74
|
+
args = []
|
|
75
|
+
n: Node = node
|
|
76
|
+
while isinstance(n, Application):
|
|
77
|
+
args.append(n.arg)
|
|
78
|
+
n = n.func
|
|
79
|
+
args.reverse()
|
|
80
|
+
return n, args
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _collapse(node: Node) -> Node:
|
|
84
|
+
"""Recursively fold residual applied-predicate / applied-symbol spines.
|
|
85
|
+
|
|
86
|
+
Returns a new node. Application spines headed by an Atom (a predicate name)
|
|
87
|
+
become Atom calls; spines headed by a first-order symbol (Constant / Variable
|
|
88
|
+
/ Function) become Function calls. Spines headed by a Lambda or LambdaVar are
|
|
89
|
+
rebuilt unchanged (the Lambda case cannot survive normalization; the LambdaVar
|
|
90
|
+
case is the stuck term reported later).
|
|
91
|
+
"""
|
|
92
|
+
if isinstance(node, (Variable, LambdaVar, Constant, Number, SortedConstant)):
|
|
93
|
+
return node
|
|
94
|
+
if isinstance(node, Atom):
|
|
95
|
+
return Atom(node.predicate, [_collapse(a) for a in node.args])
|
|
96
|
+
if isinstance(node, Function):
|
|
97
|
+
return Function(node.name, [_collapse(a) for a in node.args])
|
|
98
|
+
if isinstance(node, Application):
|
|
99
|
+
head, args = _spine(node)
|
|
100
|
+
head = _collapse(head)
|
|
101
|
+
args = [_collapse(a) for a in args]
|
|
102
|
+
if isinstance(head, Atom) and not head.args:
|
|
103
|
+
# Applied predicate name: Q · args → Q(args)
|
|
104
|
+
return Atom(head.predicate, args)
|
|
105
|
+
if isinstance(head, Constant):
|
|
106
|
+
# Applied first-order constant: f · args → f(args)
|
|
107
|
+
return Function(head.name, args)
|
|
108
|
+
if isinstance(head, (Variable, Function)):
|
|
109
|
+
name = head.name
|
|
110
|
+
existing = head.args if isinstance(head, Function) else []
|
|
111
|
+
return Function(name, list(existing) + args)
|
|
112
|
+
# head is a Lambda or LambdaVar: rebuild the spine verbatim.
|
|
113
|
+
result: Node = head
|
|
114
|
+
for a in args:
|
|
115
|
+
result = Application(result, a)
|
|
116
|
+
return result
|
|
117
|
+
if isinstance(node, Lambda):
|
|
118
|
+
return Lambda(node.param, _collapse(node.body))
|
|
119
|
+
if isinstance(node, (Not, LukNegation)):
|
|
120
|
+
return type(node)(_collapse(node.formula))
|
|
121
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff,
|
|
122
|
+
WeakConjunction, WeakDisjunction,
|
|
123
|
+
StrongConjunction, StrongDisjunction,
|
|
124
|
+
LukImplication, LukEquivalence)):
|
|
125
|
+
return type(node)(_collapse(node.left), _collapse(node.right))
|
|
126
|
+
if isinstance(node, Quantifier):
|
|
127
|
+
return Quantifier(node.type, node.variable, _collapse(node.formula))
|
|
128
|
+
if isinstance(node, SortedQuantifier):
|
|
129
|
+
return SortedQuantifier(node.type, node.variable, node.sort,
|
|
130
|
+
_collapse(node.formula))
|
|
131
|
+
return node
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _find_leftover(node: Node):
|
|
135
|
+
"""Return the first Lambda / Application / LambdaVar found (pre-order), or None."""
|
|
136
|
+
if isinstance(node, (Lambda, Application, LambdaVar)):
|
|
137
|
+
return node
|
|
138
|
+
if isinstance(node, (Variable, Constant, Number, SortedConstant)):
|
|
139
|
+
return None
|
|
140
|
+
if isinstance(node, (Atom, Function)):
|
|
141
|
+
for a in node.args:
|
|
142
|
+
found = _find_leftover(a)
|
|
143
|
+
if found is not None:
|
|
144
|
+
return found
|
|
145
|
+
return None
|
|
146
|
+
if isinstance(node, (Not, LukNegation)):
|
|
147
|
+
return _find_leftover(node.formula)
|
|
148
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff,
|
|
149
|
+
WeakConjunction, WeakDisjunction,
|
|
150
|
+
StrongConjunction, StrongDisjunction,
|
|
151
|
+
LukImplication, LukEquivalence)):
|
|
152
|
+
return _find_leftover(node.left) or _find_leftover(node.right)
|
|
153
|
+
if isinstance(node, (Quantifier, SortedQuantifier)):
|
|
154
|
+
return _find_leftover(node.formula)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# Lambda elimination
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def eliminate_lambdas(node: Node) -> Node:
|
|
163
|
+
"""Return a lambda-free node ready for the FOL export / normal-form pipeline.
|
|
164
|
+
|
|
165
|
+
The node is first beta-eta normalized, then residual applied-predicate and
|
|
166
|
+
applied-symbol spines are folded back into Atom / Function calls. If any
|
|
167
|
+
Lambda, Application, or LambdaVar still remains (a partially applied or stuck
|
|
168
|
+
term — e.g. a free higher-order variable applied to arguments), a ValueError
|
|
169
|
+
naming the leftover construct is raised.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ReductionLimitError: if normalization does not terminate within limits.
|
|
173
|
+
ValueError: if the normalized node is not lambda-free.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
A lambda-free Node; the input is never mutated.
|
|
177
|
+
"""
|
|
178
|
+
normalized = beta_eta_normalize(node) # may raise ReductionLimitError
|
|
179
|
+
collapsed = _collapse(normalized)
|
|
180
|
+
leftover = _find_leftover(collapsed)
|
|
181
|
+
if leftover is not None:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"eliminate_lambdas: term is not lambda-free after normalization; "
|
|
184
|
+
f"leftover {type(leftover).__name__} "
|
|
185
|
+
f"(a partially applied / stuck lambda term): {leftover}"
|
|
186
|
+
)
|
|
187
|
+
return collapsed
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# Single-step (leftmost-outermost) beta-reduction
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
def beta_reduce_step(node: Node) -> tuple:
|
|
195
|
+
"""Contract the single leftmost-outermost beta-redex in node.
|
|
196
|
+
|
|
197
|
+
Performs at most ONE contraction: it walks the term in normal order (the
|
|
198
|
+
whole application before its sub-terms), and contracts the first
|
|
199
|
+
Application(Lambda(p, body), arg) it meets via capture-avoiding substitute().
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
(new_node, reduced) where reduced is True iff a redex was contracted.
|
|
203
|
+
When reduced is False the node is already in beta-normal form and is
|
|
204
|
+
returned unchanged (the same object). The input is never mutated.
|
|
205
|
+
"""
|
|
206
|
+
if isinstance(node, Application):
|
|
207
|
+
func, reduced = beta_reduce_step(node.func)
|
|
208
|
+
if reduced:
|
|
209
|
+
# A redex strictly to the left (inside func) is contracted first.
|
|
210
|
+
return Application(func, node.arg), True
|
|
211
|
+
if isinstance(node.func, Lambda):
|
|
212
|
+
# This application itself is the leftmost-outermost redex.
|
|
213
|
+
contracted = substitute(node.func.body, node.func.param, node.arg)
|
|
214
|
+
return contracted, True
|
|
215
|
+
arg, reduced = beta_reduce_step(node.arg)
|
|
216
|
+
if reduced:
|
|
217
|
+
return Application(node.func, arg), True
|
|
218
|
+
return node, False
|
|
219
|
+
|
|
220
|
+
if isinstance(node, Lambda):
|
|
221
|
+
body, reduced = beta_reduce_step(node.body)
|
|
222
|
+
return (Lambda(node.param, body), True) if reduced else (node, False)
|
|
223
|
+
|
|
224
|
+
if isinstance(node, Quantifier):
|
|
225
|
+
formula, reduced = beta_reduce_step(node.formula)
|
|
226
|
+
return (Quantifier(node.type, node.variable, formula), True) if reduced else (node, False)
|
|
227
|
+
|
|
228
|
+
if isinstance(node, SortedQuantifier):
|
|
229
|
+
formula, reduced = beta_reduce_step(node.formula)
|
|
230
|
+
if reduced:
|
|
231
|
+
return SortedQuantifier(node.type, node.variable, node.sort, formula), True
|
|
232
|
+
return node, False
|
|
233
|
+
|
|
234
|
+
if isinstance(node, (Not, LukNegation)):
|
|
235
|
+
formula, reduced = beta_reduce_step(node.formula)
|
|
236
|
+
return (type(node)(formula), True) if reduced else (node, False)
|
|
237
|
+
|
|
238
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff,
|
|
239
|
+
WeakConjunction, WeakDisjunction,
|
|
240
|
+
StrongConjunction, StrongDisjunction,
|
|
241
|
+
LukImplication, LukEquivalence)):
|
|
242
|
+
left, reduced = beta_reduce_step(node.left)
|
|
243
|
+
if reduced:
|
|
244
|
+
return type(node)(left, node.right), True
|
|
245
|
+
right, reduced = beta_reduce_step(node.right)
|
|
246
|
+
if reduced:
|
|
247
|
+
return type(node)(node.left, right), True
|
|
248
|
+
return node, False
|
|
249
|
+
|
|
250
|
+
if isinstance(node, (Atom, Function)):
|
|
251
|
+
new_args = list(node.args)
|
|
252
|
+
for i, a in enumerate(node.args):
|
|
253
|
+
new_arg, reduced = beta_reduce_step(a)
|
|
254
|
+
if reduced:
|
|
255
|
+
new_args[i] = new_arg
|
|
256
|
+
if isinstance(node, Atom):
|
|
257
|
+
return Atom(node.predicate, new_args), True
|
|
258
|
+
return Function(node.name, new_args), True
|
|
259
|
+
return node, False
|
|
260
|
+
|
|
261
|
+
# Variable / LambdaVar / Constant / Number / SortedConstant and unknowns.
|
|
262
|
+
return node, False
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
# Reduction trace
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
def reduce_trace(node: Node, limit: int = 1000) -> list:
|
|
270
|
+
"""Return the leftmost-outermost reduction trace from node to beta-normal form.
|
|
271
|
+
|
|
272
|
+
The result is [original, after step 1, after step 2, ...], each element one
|
|
273
|
+
``beta_reduce_step`` apart, ending once no redex remains. A term already in
|
|
274
|
+
beta-normal form yields a single-element list ``[node]``.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
node: the term to reduce.
|
|
278
|
+
limit: maximum number of reduction steps before giving up.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
ReductionLimitError: if more than ``limit`` steps are taken (the term is
|
|
282
|
+
likely not normalizing under this strategy).
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
A list of Node snapshots; the input is never mutated.
|
|
286
|
+
"""
|
|
287
|
+
trace = [node]
|
|
288
|
+
current = node
|
|
289
|
+
for _ in range(limit):
|
|
290
|
+
nxt, reduced = beta_reduce_step(current)
|
|
291
|
+
if not reduced:
|
|
292
|
+
return trace
|
|
293
|
+
trace.append(nxt)
|
|
294
|
+
current = nxt
|
|
295
|
+
raise ReductionLimitError(
|
|
296
|
+
f"reduce_trace exceeded {limit} steps; term may not be normalizing."
|
|
297
|
+
)
|