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.
Files changed (237) hide show
  1. unicode_logic_kit/__init__.py +385 -0
  2. unicode_logic_kit/__main__.py +520 -0
  3. unicode_logic_kit/_deadline.py +219 -0
  4. unicode_logic_kit/ace/__init__.py +126 -0
  5. unicode_logic_kit/ace/_align.py +135 -0
  6. unicode_logic_kit/ace/chem_lexicon.py +128 -0
  7. unicode_logic_kit/ace/drs_reader.py +570 -0
  8. unicode_logic_kit/ace/mapping.py +666 -0
  9. unicode_logic_kit/ace/reverse_modal.py +138 -0
  10. unicode_logic_kit/ace/runner.py +551 -0
  11. unicode_logic_kit/ace/translate.py +452 -0
  12. unicode_logic_kit/ace/verbalize.py +1070 -0
  13. unicode_logic_kit/api.py +1284 -0
  14. unicode_logic_kit/atp/__init__.py +177 -0
  15. unicode_logic_kit/atp/_ascii_names.py +113 -0
  16. unicode_logic_kit/atp/_html.py +72 -0
  17. unicode_logic_kit/atp/_substructural_input.py +228 -0
  18. unicode_logic_kit/atp/_tff_problem.py +715 -0
  19. unicode_logic_kit/atp/_tptp_problem.py +1111 -0
  20. unicode_logic_kit/atp/_writer_support.py +289 -0
  21. unicode_logic_kit/atp/clingo_backend.py +1180 -0
  22. unicode_logic_kit/atp/cvc5_backend.py +1385 -0
  23. unicode_logic_kit/atp/eprover_backend.py +732 -0
  24. unicode_logic_kit/atp/finite_domain.py +1055 -0
  25. unicode_logic_kit/atp/fitch.py +1547 -0
  26. unicode_logic_kit/atp/fitch_search.py +551 -0
  27. unicode_logic_kit/atp/hets_backend.py +339 -0
  28. unicode_logic_kit/atp/hybrid_down.py +120 -0
  29. unicode_logic_kit/atp/incremental.py +250 -0
  30. unicode_logic_kit/atp/kripke_enum.py +741 -0
  31. unicode_logic_kit/atp/lambek.py +436 -0
  32. unicode_logic_kit/atp/leo3_backend.py +332 -0
  33. unicode_logic_kit/atp/linear.py +738 -0
  34. unicode_logic_kit/atp/lj.py +705 -0
  35. unicode_logic_kit/atp/logic_backends.py +566 -0
  36. unicode_logic_kit/atp/ltl_tableau.py +1084 -0
  37. unicode_logic_kit/atp/minizinc_backend.py +1402 -0
  38. unicode_logic_kit/atp/modal_tableau.py +1382 -0
  39. unicode_logic_kit/atp/nanocop_backend.py +410 -0
  40. unicode_logic_kit/atp/portfolio.py +489 -0
  41. unicode_logic_kit/atp/protocol.py +1803 -0
  42. unicode_logic_kit/atp/prover9_entailment.py +1153 -0
  43. unicode_logic_kit/atp/resolution.py +1376 -0
  44. unicode_logic_kit/atp/resolution_check.py +1114 -0
  45. unicode_logic_kit/atp/sequent.py +1050 -0
  46. unicode_logic_kit/atp/tableau.py +921 -0
  47. unicode_logic_kit/atp/tableau_check.py +543 -0
  48. unicode_logic_kit/atp/tptp_ncl.py +811 -0
  49. unicode_logic_kit/atp/tptp_tff.py +1546 -0
  50. unicode_logic_kit/atp/tstp.py +1333 -0
  51. unicode_logic_kit/atp/tstp_check.py +1096 -0
  52. unicode_logic_kit/atp/twee_backend.py +236 -0
  53. unicode_logic_kit/atp/twee_check.py +711 -0
  54. unicode_logic_kit/atp/twee_entailment.py +953 -0
  55. unicode_logic_kit/atp/vampire_entailment.py +540 -0
  56. unicode_logic_kit/atp/z3_arith.py +470 -0
  57. unicode_logic_kit/atp/z3_equivalence.py +36 -0
  58. unicode_logic_kit/atp/z3_fuzzy.py +362 -0
  59. unicode_logic_kit/atp/z3_input.py +500 -0
  60. unicode_logic_kit/atp/z3_models.py +208 -0
  61. unicode_logic_kit/chem/__init__.py +88 -0
  62. unicode_logic_kit/chem/_naming.py +284 -0
  63. unicode_logic_kit/chem/cache.py +185 -0
  64. unicode_logic_kit/chem/interop.py +244 -0
  65. unicode_logic_kit/chem/mol.py +525 -0
  66. unicode_logic_kit/chem/signature.py +112 -0
  67. unicode_logic_kit/comorphism.py +497 -0
  68. unicode_logic_kit/dl/__init__.py +384 -0
  69. unicode_logic_kit/dl/classification.py +227 -0
  70. unicode_logic_kit/dl/concepts.py +632 -0
  71. unicode_logic_kit/dl/datatypes.py +818 -0
  72. unicode_logic_kit/dl/owl_functional.py +2433 -0
  73. unicode_logic_kit/dl/owl_manchester.py +1637 -0
  74. unicode_logic_kit/dl/owl_reasoner.py +790 -0
  75. unicode_logic_kit/dl/parser.py +391 -0
  76. unicode_logic_kit/dl/tableau.py +4048 -0
  77. unicode_logic_kit/dl/translate.py +2704 -0
  78. unicode_logic_kit/drt/__init__.py +94 -0
  79. unicode_logic_kit/drt/export.py +179 -0
  80. unicode_logic_kit/drt/nodes.py +506 -0
  81. unicode_logic_kit/drt/parser.py +965 -0
  82. unicode_logic_kit/drt/resolve.py +195 -0
  83. unicode_logic_kit/drt/reverse.py +175 -0
  84. unicode_logic_kit/eval/__init__.py +106 -0
  85. unicode_logic_kit/eval/batch.py +382 -0
  86. unicode_logic_kit/eval/canonical.py +663 -0
  87. unicode_logic_kit/eval/chem_batch.py +606 -0
  88. unicode_logic_kit/eval/converses.py +200 -0
  89. unicode_logic_kit/eval/datasets/__init__.py +136 -0
  90. unicode_logic_kit/eval/datasets/_base.py +263 -0
  91. unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
  92. unicode_logic_kit/eval/datasets/c3po.py +678 -0
  93. unicode_logic_kit/eval/datasets/folio.py +158 -0
  94. unicode_logic_kit/eval/datasets/fracas.py +418 -0
  95. unicode_logic_kit/eval/datasets/groves.py +191 -0
  96. unicode_logic_kit/eval/datasets/logicbench.py +467 -0
  97. unicode_logic_kit/eval/datasets/logicnli.py +303 -0
  98. unicode_logic_kit/eval/datasets/malls.py +133 -0
  99. unicode_logic_kit/eval/datasets/pfolio.py +594 -0
  100. unicode_logic_kit/eval/datasets/pmb.py +242 -0
  101. unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
  102. unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
  103. unicode_logic_kit/eval/datasets/proverqa.py +674 -0
  104. unicode_logic_kit/eval/datasets/willow.py +478 -0
  105. unicode_logic_kit/eval/equivalence.py +466 -0
  106. unicode_logic_kit/eval/exercise_gen.py +533 -0
  107. unicode_logic_kit/eval/explain.py +791 -0
  108. unicode_logic_kit/eval/generality.py +750 -0
  109. unicode_logic_kit/eval/metric_hf.py +458 -0
  110. unicode_logic_kit/eval/predicate_match.py +343 -0
  111. unicode_logic_kit/eval/theory_check.py +1170 -0
  112. unicode_logic_kit/eval/validate.py +306 -0
  113. unicode_logic_kit/fol/__init__.py +177 -0
  114. unicode_logic_kit/fol/_atom_keys.py +510 -0
  115. unicode_logic_kit/fol/_fol_nodes.py +3586 -0
  116. unicode_logic_kit/fol/_free_parameters.py +105 -0
  117. unicode_logic_kit/fol/_ho_nodes.py +448 -0
  118. unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
  119. unicode_logic_kit/fol/_identifiers.py +1091 -0
  120. unicode_logic_kit/fol/_lambek_nodes.py +112 -0
  121. unicode_logic_kit/fol/_linear_nodes.py +352 -0
  122. unicode_logic_kit/fol/_modal_nodes.py +1467 -0
  123. unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
  124. unicode_logic_kit/fol/_numeral_symbols.py +231 -0
  125. unicode_logic_kit/fol/_so_nodes.py +200 -0
  126. unicode_logic_kit/fol/_symbol_names.py +81 -0
  127. unicode_logic_kit/fol/_team_nodes.py +181 -0
  128. unicode_logic_kit/fol/_tptp_symbols.py +551 -0
  129. unicode_logic_kit/fol/_truth_constants.py +117 -0
  130. unicode_logic_kit/fol/casl_export.py +1135 -0
  131. unicode_logic_kit/fol/casl_import.py +929 -0
  132. unicode_logic_kit/fol/derivation.py +367 -0
  133. unicode_logic_kit/fol/dialect_detect.py +70 -0
  134. unicode_logic_kit/fol/dialect_repair.py +537 -0
  135. unicode_logic_kit/fol/frames.py +637 -0
  136. unicode_logic_kit/fol/grammars/terminals.lark +31 -0
  137. unicode_logic_kit/fol/lambda_tools.py +297 -0
  138. unicode_logic_kit/fol/latex_input.py +429 -0
  139. unicode_logic_kit/fol/modal_translation.py +944 -0
  140. unicode_logic_kit/fol/msflparser.py +1033 -0
  141. unicode_logic_kit/fol/naming.py +422 -0
  142. unicode_logic_kit/fol/nodes.py +241 -0
  143. unicode_logic_kit/fol/normalforms.py +492 -0
  144. unicode_logic_kit/fol/pal.py +287 -0
  145. unicode_logic_kit/fol/prolog_export.py +566 -0
  146. unicode_logic_kit/fol/prolog_input.py +505 -0
  147. unicode_logic_kit/fol/prover9_input.py +1325 -0
  148. unicode_logic_kit/fol/qml.py +1760 -0
  149. unicode_logic_kit/fol/qmltp_input.py +525 -0
  150. unicode_logic_kit/fol/sanitize.py +221 -0
  151. unicode_logic_kit/fol/serialize.py +79 -0
  152. unicode_logic_kit/fol/signature.py +1290 -0
  153. unicode_logic_kit/fol/simplify_check.py +544 -0
  154. unicode_logic_kit/fol/spans.py +594 -0
  155. unicode_logic_kit/fol/tptp_input.py +1503 -0
  156. unicode_logic_kit/fol/tptp_repair.py +941 -0
  157. unicode_logic_kit/fol/unification.py +157 -0
  158. unicode_logic_kit/fol/verbalize.py +263 -0
  159. unicode_logic_kit/hets/__init__.py +163 -0
  160. unicode_logic_kit/hets/bridge.py +142 -0
  161. unicode_logic_kit/hets/client.py +748 -0
  162. unicode_logic_kit/hets/docker.py +420 -0
  163. unicode_logic_kit/hets/dol.py +712 -0
  164. unicode_logic_kit/hets/haskell_json.py +355 -0
  165. unicode_logic_kit/hets/owl_backend.py +794 -0
  166. unicode_logic_kit/hets/owl_cli.py +598 -0
  167. unicode_logic_kit/hets/symbols.py +512 -0
  168. unicode_logic_kit/hol/__init__.py +140 -0
  169. unicode_logic_kit/hol/_ho_common.py +323 -0
  170. unicode_logic_kit/hol/_isabelle_binders.py +125 -0
  171. unicode_logic_kit/hol/classical.py +812 -0
  172. unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
  173. unicode_logic_kit/hol/deepshallow/_common.py +177 -0
  174. unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
  175. unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
  176. unicode_logic_kit/hol/deepshallow/modal.py +217 -0
  177. unicode_logic_kit/hol/deepshallow/qml.py +406 -0
  178. unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
  179. unicode_logic_kit/hol/free.py +753 -0
  180. unicode_logic_kit/hol/goedel.py +336 -0
  181. unicode_logic_kit/hol/ho_modal.py +1743 -0
  182. unicode_logic_kit/hol/intuitionistic.py +403 -0
  183. unicode_logic_kit/hol/isabelle_conditional.py +593 -0
  184. unicode_logic_kit/hol/isabelle_modal.py +1908 -0
  185. unicode_logic_kit/hol/isabelle_relevant.py +412 -0
  186. unicode_logic_kit/hol/isabelle_runner.py +1147 -0
  187. unicode_logic_kit/hol/isabelle_substructural.py +884 -0
  188. unicode_logic_kit/hol/lean.py +1018 -0
  189. unicode_logic_kit/hol/manyvalued.py +921 -0
  190. unicode_logic_kit/hol/secondorder.py +687 -0
  191. unicode_logic_kit/hol/thf_modal.py +941 -0
  192. unicode_logic_kit/hol/thirdorder.py +397 -0
  193. unicode_logic_kit/ilp/__init__.py +89 -0
  194. unicode_logic_kit/ilp/readback.py +389 -0
  195. unicode_logic_kit/ilp/separation.py +153 -0
  196. unicode_logic_kit/ilp/task.py +730 -0
  197. unicode_logic_kit/logic.py +163 -0
  198. unicode_logic_kit/mcp/__init__.py +28 -0
  199. unicode_logic_kit/mcp/__main__.py +5 -0
  200. unicode_logic_kit/mcp/chem_tools.py +1031 -0
  201. unicode_logic_kit/mcp/server.py +2453 -0
  202. unicode_logic_kit/mcp/syntax_spec.py +681 -0
  203. unicode_logic_kit/prob/__init__.py +53 -0
  204. unicode_logic_kit/prob/_bdd.py +225 -0
  205. unicode_logic_kit/prob/_column_gen.py +668 -0
  206. unicode_logic_kit/prob/distribution.py +686 -0
  207. unicode_logic_kit/prob/nilsson.py +470 -0
  208. unicode_logic_kit/py.typed +0 -0
  209. unicode_logic_kit/semantics/__init__.py +137 -0
  210. unicode_logic_kit/semantics/_modal_reject.py +156 -0
  211. unicode_logic_kit/semantics/action_models.py +466 -0
  212. unicode_logic_kit/semantics/asp_models.py +1200 -0
  213. unicode_logic_kit/semantics/conditional.py +580 -0
  214. unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
  215. unicode_logic_kit/semantics/free_logic.py +913 -0
  216. unicode_logic_kit/semantics/fuzzy.py +384 -0
  217. unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
  218. unicode_logic_kit/semantics/intuitionistic.py +581 -0
  219. unicode_logic_kit/semantics/kripke.py +1139 -0
  220. unicode_logic_kit/semantics/manyvalued.py +580 -0
  221. unicode_logic_kit/semantics/matrix.py +342 -0
  222. unicode_logic_kit/semantics/model_eval.py +1135 -0
  223. unicode_logic_kit/semantics/modelfinder.py +1036 -0
  224. unicode_logic_kit/semantics/nonmonotonic.py +372 -0
  225. unicode_logic_kit/semantics/relevant.py +331 -0
  226. unicode_logic_kit/semantics/secondorder.py +657 -0
  227. unicode_logic_kit/semantics/structures.py +352 -0
  228. unicode_logic_kit/semantics/tarski.py +975 -0
  229. unicode_logic_kit/semantics/team.py +315 -0
  230. unicode_logic_kit/semantics/team_translation.py +416 -0
  231. unicode_logic_kit/semantics/thirdorder.py +358 -0
  232. unicode_logic_kit/semantics/tnorm.py +85 -0
  233. unicode_logic_kit/semantics/truthtable.py +201 -0
  234. unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
  235. unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
  236. unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
  237. 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
+ )