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,753 @@
1
+ r"""Free logic → HOL export: TPTP **THF** problems and Isabelle/HOL theories.
2
+
3
+ Free logic (:mod:`unicode_logic_kit.semantics.free_logic`) drops classical FOL's
4
+ assumption that every term denotes an *existing* individual: quantifiers range
5
+ over an *inner* domain of existing objects, while a constant or function term may
6
+ denote an *outer* (merely possible) object, or fail to denote at all. This module
7
+ embeds that into HOL the same way :mod:`unicode_logic_kit.hol.classical` embeds
8
+ classical MSFOL — by GUARD-RELATIVIZATION — using **two** uninterpreted unary
9
+ guard predicates over a single flat individual type ``e``:
10
+
11
+ * ``D(t)`` — "``t`` denotes" (some object of the outer domain);
12
+ * ``E!(t)`` — "``t`` exists" (an object of the INNER domain; ``E!`` is strictly
13
+ narrower than ``D``, tied together by the axiom ``E!(x) → D(x)``).
14
+
15
+ The translation, matching :func:`~unicode_logic_kit.semantics.free_logic.free_satisfies`
16
+ clause by clause:
17
+
18
+ * an ordinary atom ``P(t1, …, tn)`` becomes ``D*(t1) ∧ … ∧ D*(tn) ∧ P(t1, …, tn)``
19
+ (mirrors ``_atom``'s "non-denoting ⇒ false" reading — a real free-logic
20
+ ``NOT``, so this is NOT the same as classically negating the un-guarded atom).
21
+ ``D*`` applies ``D`` to the term AND to every compound subterm of it,
22
+ ``D*(f(s)) = D*(s) ∧ D(f(s))``: ``free_logic._term_value`` makes ``f(s)``
23
+ non-denoting whenever ``s`` is, while a HOL function is total and could map a
24
+ non-denoting argument's carrier value into ``D``;
25
+ * the object-language existence predicate ``E!(t)`` — the SAME predicate
26
+ :data:`~unicode_logic_kit.semantics.free_logic._EXISTS_PRED` names — is emitted
27
+ as the guard predicate itself, guarded only by ``D*`` of the proper subterms
28
+ of ``t`` (bare for a variable or constant: with the tie ``E!(x) → D(x)`` it
29
+ already reads "``t`` denotes AND is existing");
30
+ * ``=`` is native HOL identity under the same guard,
31
+ ``s = t ↦ D*(s) ∧ D*(t) ∧ s = t``; under ``policy="positive"`` a *self*-identity
32
+ atom ``t = t`` (the SAME term written twice) is exempted from the guard and
33
+ emitted bare (mirrors ``_atom``'s ``policy == "positive"`` carve-out for the
34
+ non-denoting branch). ``≠`` is translated as the negation of the (possibly
35
+ exempted) ``=`` translation, matching ``_atom``'s own ``eq`` / ``not eq``
36
+ symmetry between the two;
37
+ * a quantifier is relativized to ``E!``: ``∀x φ ↦ ∀x. E!(x) → φ``,
38
+ ``∃x φ ↦ ∃x. E!(x) ∧ φ`` — the same shape
39
+ :func:`~unicode_logic_kit.hol.classical`'s MSFOL sort-relativization uses, with
40
+ ``E!`` playing the role of the sort guard.
41
+
42
+ The tie ``E!(x) → D(x)`` is the ONLY background fact. There is deliberately no
43
+ ``∃x. E!(x)``: an empty inner domain is a legitimate free-logic model
44
+ (:func:`~unicode_logic_kit.semantics.free_logic.free_is_valid` searches it by
45
+ default), so ``(∀x P(x)) → ∃x P(x)`` must come out invalid here as well. The tie
46
+ is a THF ``axiom`` and, in Isabelle, a **premise of the goal**, never a
47
+ persistent ``axiomatization`` fact: exactly like
48
+ :mod:`~unicode_logic_kit.hol.isabelle_conditional`'s ``nested Sel`` premise, this
49
+ lets nitpick construct ``D``/``E!`` itself when searching for a countermodel and
50
+ certify it as *genuine* — an ``axiomatization`` would downgrade that to
51
+ ``quasi_genuine`` (nitpick cannot verify an axiomatised fact's consistency) and
52
+ lose the refutation half of the decision procedure.
53
+
54
+ **Why the embedding is faithful in both directions.** A :class:`FreeModel`
55
+ becomes a HOL model over ``outer ∪ {⊥}``: ``D`` is ``outer``, ``E!`` is
56
+ ``existing``, and a non-denoting constant as well as every undefined or
57
+ ``⊥``-involving function value is ``⊥``; then ``D*(t)`` holds exactly when ``t``
58
+ denotes, and the guarded formula has ``free_holds``'s truth value. Conversely a
59
+ HOL model gives a :class:`FreeModel` with ``outer = D`` (plus one fresh
60
+ non-existing object if ``D`` is empty — no formula can observe it),
61
+ ``existing = E!`` (inside ``D`` by the tie), a constant denoting its HOL value iff
62
+ that value lies in ``D``, and a function table defined exactly where the
63
+ arguments and the value lie in ``D``; by induction ``D*(t)`` holds iff ``t``
64
+ denotes there, with the same value. So a HOL proof certifies a free-logic
65
+ validity, and a genuine HOL countermodel is a free-logic countermodel.
66
+
67
+ **Equality.** ``=`` is the target format's own identity (THF's infix
68
+ ``( S = T )``, Isabelle's polymorphic ``(s = t)``), never an uninterpreted
69
+ ``feq`` predicate: ``free_logic._atom`` compares the referents of two denoting
70
+ terms by identity, and the ``D*`` guard already covers every non-denoting case.
71
+ An uninterpreted ``feq`` would let nitpick certify a "countermodel" to
72
+ ``∀x ∀y ((x = y ∧ P(x)) → P(y))``, which is free-logically valid. This departs
73
+ on purpose from :mod:`~unicode_logic_kit.hol.classical`'s default export, whose
74
+ documented reading of ``=`` is an uninterpreted predicate unless
75
+ ``native_equality=True``; the order glyphs ``< > ≤ ≥`` stay uninterpreted
76
+ predicates here, as they are ordinary predicates to ``free_holds``.
77
+
78
+ **Scope.** Classical connectives (``¬ ∧ ∨ ⊕ → ↔``), quantifiers, and atoms over
79
+ variable / constant / number / function terms only — exactly the fragment
80
+ :func:`~unicode_logic_kit.semantics.free_logic.free_satisfies` interprets. Modal,
81
+ second-order, Łukasiewicz, substructural and lambda nodes are outside free
82
+ logic's own scope and raise ``NotImplementedError`` by name, never silently
83
+ approximated.
84
+
85
+ **``policy="supervaluation"``.** Refused with ``NotImplementedError`` from every
86
+ entry point in this module. Supervaluationist truth is a property of a whole
87
+ MODEL's set of truth-value GAPS (true under every classical completion of that
88
+ specific model — see the ``semantics.free_logic`` module docstring), not a fact
89
+ derivable from a single formula's guarded translation the way negative/positive
90
+ are: encoding it soundly would need second-order quantification over per-model
91
+ gap assignments, a different and much larger undertaking this module does not
92
+ fold in. Use :func:`~unicode_logic_kit.semantics.free_logic.free_holds` with
93
+ ``policy="supervaluation"`` directly instead.
94
+
95
+ **Honesty / scope.** This module only *emits* problems and theories; it does
96
+ **not** run Leo-III, Satallax, Vampire, Isabelle, or Sledgehammer. The embedded
97
+ logic is first-order, so it is semi-decidable only, same as
98
+ :mod:`~unicode_logic_kit.hol.classical`.
99
+
100
+ Public API: :func:`to_thf_free`, :func:`to_isabelle_free`, :func:`free_theory`.
101
+ """
102
+
103
+ from typing import Dict, List, Optional, Tuple
104
+
105
+ from ..fol._fol_nodes import constant_name_to_ascii
106
+ from ..fol._numeral_symbols import numerals_as_constants, prefixed_numeral_name
107
+ from ..fol._symbol_names import dedupe
108
+ from ..fol._truth_constants import truth_value
109
+ from ..fol.nodes import (
110
+ Node, Variable, Constant, Number, Function,
111
+ Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
112
+ )
113
+ from .classical import _refuse_open_assertion
114
+
115
+ #: The object-language existence predicate — the SAME name
116
+ #: :mod:`~unicode_logic_kit.semantics.free_logic` reserves for it
117
+ #: (``_EXISTS_PRED``) and this module's own quantifier/E!-guard.
118
+ _EXISTS_PRED = "E!"
119
+
120
+ #: This module's own denotation guard. ``semantics.free_logic`` has no surface
121
+ #: syntax for it, and — UNLIKE ``_EXISTS_PRED``/``"E!"`` — no special meaning
122
+ #: for it AT ALL: it is purely an artefact of this HOL translation, invisible
123
+ #: to :func:`~unicode_logic_kit.semantics.free_logic.free_holds`. Named ``"D!"``
124
+ #: (bang-suffixed, matching ``_EXISTS_PRED``'s own convention), NOT bare
125
+ #: ``"D"``: a user formula plausibly has its OWN ordinary predicate named a
126
+ #: single letter like ``D`` (e.g. "Dog"/"Drinks" abbreviated), and that must
127
+ #: stay a distinct, un-guarded symbol rather than being silently reinterpreted
128
+ #: as this guard. A formula that itself uses the reserved ``"D!"`` name
129
+ #: (unreachable from the parser, but constructible directly as an AST) is
130
+ #: REFUSED with a ``ValueError`` by :func:`_guard_atom`: to ``free_holds`` such
131
+ #: an atom is an ordinary predicate, so merging it with the guard would
132
+ #: silently change its meaning.
133
+ _DENOTES_PRED = "D!"
134
+
135
+ # Comparison glyphs and the two guard predicates get valid, collision-priority
136
+ # THF/Isabelle identifiers (the SAME flt/fgt/fle/fge stems as classical.py's
137
+ # _PRED_ALIAS, extended with the guard predicates' own stems). "=" is absent on
138
+ # purpose: it is always printed as native identity, and "≠" is rewritten to
139
+ # the negation of "=" before printing (see the module docstring's Equality).
140
+ _PRED_ALIAS = {
141
+ "<": "flt", ">": "fgt", "≤": "fle", "≥": "fge",
142
+ _DENOTES_PRED: "denotes", _EXISTS_PRED: "existsBang",
143
+ }
144
+
145
+ _FORALL = "∀"
146
+ _EXISTS = "∃"
147
+
148
+ _POLICIES = ("negative", "positive", "supervaluation")
149
+
150
+
151
+ def _check_policy(policy: str, who: str) -> None:
152
+ """Validate ``policy``; refuse ``"supervaluation"`` loudly (see module docstring)."""
153
+ if policy == "supervaluation":
154
+ raise NotImplementedError(
155
+ f"{who}: policy='supervaluation' has no guarded-translation reading. "
156
+ "Supervaluationist truth is a property of a MODEL's set of gap atoms "
157
+ "(true under every classical completion of THAT model), not a fact "
158
+ "derivable from a single formula's guarded translation the way "
159
+ "negative/positive are -- encoding it soundly would need second-order "
160
+ "quantification over per-model gap assignments. Use "
161
+ "semantics.free_logic.free_holds(..., policy='supervaluation') instead.")
162
+ if policy not in _POLICIES:
163
+ raise ValueError(
164
+ f"{who}: unknown policy {policy!r} (negative / positive; "
165
+ "supervaluation is refused with NotImplementedError, see above).")
166
+
167
+
168
+ # ===========================================================================
169
+ # The guard rewrite: free-logic AST -> classical FOL AST over D / E!
170
+ # ===========================================================================
171
+
172
+ def _denotes(term: Node) -> Node:
173
+ """``D*(t)`` — ``t`` denotes: ``D`` of ``t`` and of every compound subterm.
174
+
175
+ ``D*(f(s1, …, sn)) = D*(s1) ∧ … ∧ D*(sn) ∧ D(f(s1, …, sn))``, mirroring
176
+ ``free_logic._term_value``: a function term is non-denoting as soon as one
177
+ argument is, whatever the (total) HOL function does with that argument's
178
+ carrier value. A variable, constant or number is guarded by ``D`` alone.
179
+ """
180
+ guard: Node = Atom(_DENOTES_PRED, [term])
181
+ if isinstance(term, Function):
182
+ for arg in reversed(term.args):
183
+ guard = And(_denotes(arg), guard)
184
+ return guard
185
+
186
+
187
+ def _is_identity(node: Node) -> bool:
188
+ """True for an ``s = t`` atom, which both printers render as native identity.
189
+
190
+ See the module docstring's **Equality** section: identity of referents is
191
+ what ``free_logic._atom`` computes once both sides denote, and the ``D*``
192
+ guard handles every non-denoting case.
193
+ """
194
+ return isinstance(node, Atom) and node.predicate == "=" and len(node.args) == 2
195
+
196
+
197
+ def _guard_atom(atom: Atom, policy: str) -> Node:
198
+ """Translate one atom per the module docstring's clause-by-clause table."""
199
+ if atom.predicate == _DENOTES_PRED:
200
+ raise ValueError(
201
+ f"hol.free: {_DENOTES_PRED!r} is reserved for the denotation guard of "
202
+ "this embedding; to free_holds it would be an ordinary predicate, so "
203
+ "rename that predicate.")
204
+ if atom.predicate == _EXISTS_PRED and len(atom.args) == 1:
205
+ # E! is genuinely the free-logic existence predicate (free_holds's own
206
+ # _EXISTS_PRED special case): E!(t) is true iff t denotes an existing
207
+ # object. With the tie E!(x) -> D(x), only the PROPER subterms of t
208
+ # still need their D* guard.
209
+ term = atom.args[0]
210
+ guarded: Node = atom
211
+ if isinstance(term, Function):
212
+ for arg in reversed(term.args):
213
+ guarded = And(_denotes(arg), guarded)
214
+ return guarded
215
+ if atom.predicate == "≠" and len(atom.args) == 2:
216
+ # _atom's own eq/not-eq symmetry: "≠" is always the negation of what
217
+ # "=" would return for the SAME arguments (both policies, both the
218
+ # denoting and non-denoting branches) — see the module docstring.
219
+ return Not(_guard_atom(Atom("=", atom.args), policy))
220
+ if (atom.predicate == "=" and len(atom.args) == 2 and policy == "positive"
221
+ and atom.args[0] == atom.args[1]):
222
+ # Positive free logic: self-identity (the literal SAME term twice)
223
+ # holds unconditionally -- _atom's policy == "positive" carve-out. No
224
+ # D-guard is added; the printers render it as native identity, which
225
+ # is reflexively true.
226
+ return atom
227
+ if not atom.args:
228
+ return atom # nothing to guard
229
+ guarded: Node = atom
230
+ for term in reversed(atom.args):
231
+ guarded = And(_denotes(term), guarded)
232
+ return guarded
233
+
234
+
235
+ def _guard(node: Node, policy: str) -> Node:
236
+ """Recursively translate ``node`` into the guarded classical FOL AST.
237
+
238
+ Reused, unchanged, by both :func:`to_thf_free` and :func:`to_isabelle_free`
239
+ (and, through them, :func:`free_theory`) — a single guard rewrite that both
240
+ printers render, so the two formats can never drift on what "guarded" means.
241
+ """
242
+ if isinstance(node, Atom):
243
+ return _guard_atom(node, policy)
244
+ if isinstance(node, Not):
245
+ return Not(_guard(node.formula, policy))
246
+ if isinstance(node, And):
247
+ return And(_guard(node.left, policy), _guard(node.right, policy))
248
+ if isinstance(node, Or):
249
+ return Or(_guard(node.left, policy), _guard(node.right, policy))
250
+ if isinstance(node, Xor):
251
+ return Xor(_guard(node.left, policy), _guard(node.right, policy))
252
+ if isinstance(node, Implies):
253
+ return Implies(_guard(node.left, policy), _guard(node.right, policy))
254
+ if isinstance(node, Iff):
255
+ return Iff(_guard(node.left, policy), _guard(node.right, policy))
256
+ if isinstance(node, Quantifier):
257
+ body = _guard(node.formula, policy)
258
+ exists_here = Atom(_EXISTS_PRED, [node.variable])
259
+ if node.type in (_FORALL, "forall"):
260
+ return Quantifier(_FORALL, node.variable, Implies(exists_here, body))
261
+ if node.type in (_EXISTS, "exists"):
262
+ return Quantifier(_EXISTS, node.variable, And(exists_here, body))
263
+ raise ValueError(f"hol.free: unknown quantifier type {node.type!r}.")
264
+ raise NotImplementedError(
265
+ f"hol.free: {type(node).__name__} is outside the free-logic fragment this "
266
+ "export covers -- classical connectives (not/and/or/xor/implies/iff), "
267
+ "quantifiers, and atoms over variable/constant/number/function terms "
268
+ "only, matching semantics.free_logic.free_satisfies. Modal / second-order "
269
+ "/ Łukasiewicz / substructural / lambda nodes have no free-logic reading "
270
+ "here."
271
+ )
272
+
273
+
274
+ def _free_variables(formula: Node) -> List[str]:
275
+ """Names of variables occurring free in ``formula`` (first-occurrence order).
276
+
277
+ ``Quantifier`` binds its variable; everything else is structural. Mirrors
278
+ :func:`unicode_logic_kit.hol.classical._free_variables`, restricted to the
279
+ node types this module's guard rewrite supports.
280
+ """
281
+ out: List[str] = []
282
+ seen = set()
283
+
284
+ def rec(node: Node, bound: frozenset) -> None:
285
+ if isinstance(node, Variable):
286
+ if node.name not in bound and node.name not in seen:
287
+ seen.add(node.name)
288
+ out.append(node.name)
289
+ return
290
+ if isinstance(node, Quantifier):
291
+ rec(node.formula, bound | {node.variable.name})
292
+ return
293
+ for child in node._child_nodes():
294
+ rec(child, bound)
295
+
296
+ rec(formula, frozenset())
297
+ return out
298
+
299
+
300
+ def _close(formula: Node) -> Node:
301
+ """Universally close every free variable of ``formula`` (deterministic order).
302
+
303
+ Mirrors :func:`unicode_logic_kit.hol.classical.to_thf_fol`'s own closure step:
304
+ TPTP/Isabelle formula roles do not admit free variables, and closing BEFORE
305
+ :func:`_guard` runs means the closure-introduced quantifiers get the SAME
306
+ ``E!``-relativization as every other quantifier in the formula.
307
+ """
308
+ closed = formula
309
+ for name in reversed(_free_variables(formula)):
310
+ closed = Quantifier(_FORALL, Variable(name), closed)
311
+ return closed
312
+
313
+
314
+ # ===========================================================================
315
+ # Shared: name sanitisation and signature extraction
316
+ # ===========================================================================
317
+
318
+ def _sanitize(name: str) -> str:
319
+ """Lower-case-initial ASCII alnum/underscore functor stem for THF/Isabelle.
320
+
321
+ Structurally identical to
322
+ :func:`unicode_logic_kit.hol.classical._sanitize` (see there for the full
323
+ rationale): equality/comparison glyphs and the two guard predicates map
324
+ through ``_PRED_ALIAS`` first; every other name is transliterated to ASCII
325
+ via :func:`~unicode_logic_kit.fol._fol_nodes.constant_name_to_ascii`, filtered
326
+ to alnum-or-underscore, and lower-cased at the initial character (THF/
327
+ Isabelle both read an upper-case initial as a VARIABLE, not a constant).
328
+ De-collision across distinct source symbols is :class:`_SymbolResolver`'s
329
+ job, not this function's — never emit its output directly for a
330
+ declaration or a usage.
331
+ """
332
+ if name in _PRED_ALIAS:
333
+ return _PRED_ALIAS[name]
334
+ ascii_name = constant_name_to_ascii(name)
335
+ safe = "".join(c if (c.isalnum() or c == "_") else "_" for c in ascii_name)
336
+ if not safe:
337
+ return "p"
338
+ if safe[0].isdigit():
339
+ safe = "p" + safe
340
+ return safe[:1].lower() + safe[1:]
341
+
342
+
343
+ def _signature(formula: Node):
344
+ """Collect ``(preds, funcs, consts)`` with arities from the GUARDED ``formula``.
345
+
346
+ Same shape as :func:`unicode_logic_kit.hol.classical._signature`. ``D`` and
347
+ ``E!`` are always FORCED into ``preds`` (arity 1) regardless of whether they
348
+ happen to occur in this particular formula, because the tie axiom
349
+ (``E!(x) -> D(x)``) references both of them unconditionally — see
350
+ :func:`to_thf_free` / :func:`to_isabelle_free`. Identity atoms are native
351
+ and need no declaration.
352
+ """
353
+ preds, funcs, consts = set(), set(), set()
354
+ for n in formula.walk():
355
+ if isinstance(n, Atom):
356
+ if not _is_identity(n) and truth_value(n) is None:
357
+ preds.add((n.predicate, len(n.args)))
358
+ elif isinstance(n, Function):
359
+ funcs.add((n.name, len(n.args)))
360
+ elif isinstance(n, Constant):
361
+ consts.add(n.name)
362
+ elif isinstance(n, Number):
363
+ consts.add("n" + str(n.value))
364
+ preds |= {(_DENOTES_PRED, 1), (_EXISTS_PRED, 1)}
365
+ return preds, funcs, consts
366
+
367
+
368
+ # ===========================================================================
369
+ # Global symbol resolver — DISTINCT source symbols map to DISTINCT identifiers
370
+ # ===========================================================================
371
+
372
+ _CAT_PRED = "pred"
373
+ _CAT_FUNC = "func"
374
+ _CAT_CONST = "const"
375
+
376
+
377
+ class _SymbolResolver:
378
+ """Assign a UNIQUE, valid emitted identifier to every source symbol.
379
+
380
+ Structurally identical to
381
+ :class:`unicode_logic_kit.hol.classical._SymbolResolver` (see there for the
382
+ full rationale): keyed by ``(category, raw_name, arity)``, so a predicate
383
+ used at two arities — or a predicate/function/constant sharing a raw name —
384
+ gets distinct identifiers. The ``_PRED_ALIAS`` targets (``flt``/``fgt``/…
385
+ and ``D``/``E!``'s own ``denotes``/``existsBang`` stems) are assigned FIRST,
386
+ so they claim their natural stem and a colliding user symbol (e.g. a
387
+ predicate literally named ``D``) is de-collided away from them instead of
388
+ the other way round.
389
+ """
390
+
391
+ def __init__(self, formula: Node):
392
+ self._used = set()
393
+ self._map: Dict[Tuple[str, str, int], str] = {}
394
+ preds, funcs, consts = _signature(formula)
395
+ for name, arity in sorted(preds):
396
+ if name in _PRED_ALIAS:
397
+ self._assign(_CAT_PRED, name, arity)
398
+ for name, arity in sorted(preds):
399
+ if name not in _PRED_ALIAS:
400
+ self._assign(_CAT_PRED, name, arity)
401
+ for name, arity in sorted(funcs):
402
+ self._assign(_CAT_FUNC, name, arity)
403
+ for name in sorted(consts):
404
+ self._assign(_CAT_CONST, name, 0)
405
+
406
+ def _assign(self, category: str, raw: str, arity: int) -> str:
407
+ key = (category, raw, arity)
408
+ if key in self._map:
409
+ return self._map[key]
410
+ cand = dedupe(_sanitize(raw), self._used)
411
+ self._map[key] = cand
412
+ return cand
413
+
414
+ def name(self, category: str, raw: str, arity: int) -> str:
415
+ """Return the unique emitted identifier for ``(category, raw, arity)``."""
416
+ key = (category, raw, arity)
417
+ if key not in self._map:
418
+ return self._assign(category, raw, arity)
419
+ return self._map[key]
420
+
421
+
422
+ class _VarResolver:
423
+ """Map DISTINCT bound/free variable names to DISTINCT emitted variable tokens.
424
+
425
+ Identical to :class:`unicode_logic_kit.hol.classical._VarResolver`: THF
426
+ upper-cases variable tokens, so two source names differing only by case
427
+ would otherwise collide on one token. ``used`` is the set of tokens a variable
428
+ must not take (shared, not copied): the Isabelle export passes the tokens of the
429
+ theory's constants, functions and predicates, because a binder shadows a
430
+ constant of its own name inside its scope.
431
+ """
432
+
433
+ def __init__(self, render, used=None):
434
+ self._render = render
435
+ self._used = set() if used is None else used
436
+ self._map: Dict[str, str] = {}
437
+
438
+ def token(self, raw: str) -> str:
439
+ if raw in self._map:
440
+ return self._map[raw]
441
+ cand = dedupe(self._render(raw), self._used)
442
+ self._map[raw] = cand
443
+ return cand
444
+
445
+
446
+ # ===========================================================================
447
+ # (A) TPTP THF export
448
+ # ===========================================================================
449
+
450
+ def _thf_term(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
451
+ """Render an individual (``$i``) term in THF applicative (``@``) form."""
452
+ if isinstance(node, Variable):
453
+ return vars_.token(node.name)
454
+ if isinstance(node, Constant):
455
+ return syms.name(_CAT_CONST, node.name, 0)
456
+ if isinstance(node, Number):
457
+ return syms.name(_CAT_CONST, "n" + str(node.value), 0)
458
+ if isinstance(node, Function):
459
+ head = syms.name(_CAT_FUNC, node.name, len(node.args))
460
+ return "( " + " @ ".join([head] + [_thf_term(a, syms, vars_) for a in node.args]) + " )"
461
+ raise NotImplementedError(
462
+ f"to_thf_free: unsupported term {type(node).__name__} (free-logic terms: "
463
+ "variable / constant / number / function only)."
464
+ )
465
+
466
+
467
+ def _thf_formula(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
468
+ """Render the GUARDED formula as a THF ``$o`` term (classical connectives only)."""
469
+ def f(n):
470
+ return _thf_formula(n, syms, vars_)
471
+ if isinstance(node, Atom):
472
+ if truth_value(node) is not None:
473
+ return "$true" if truth_value(node) else "$false"
474
+ if _is_identity(node):
475
+ # Native THF identity (see the module docstring's Equality section).
476
+ left = _thf_term(node.args[0], syms, vars_)
477
+ right = _thf_term(node.args[1], syms, vars_)
478
+ return f"( {left} = {right} )"
479
+ head = syms.name(_CAT_PRED, node.predicate, len(node.args))
480
+ if not node.args:
481
+ return head
482
+ return "( " + " @ ".join([head] + [_thf_term(a, syms, vars_) for a in node.args]) + " )"
483
+ if isinstance(node, Not):
484
+ return f"( ~ {f(node.formula)} )"
485
+ if isinstance(node, And):
486
+ return f"( {f(node.left)} & {f(node.right)} )"
487
+ if isinstance(node, Or):
488
+ return f"( {f(node.left)} | {f(node.right)} )"
489
+ if isinstance(node, Implies):
490
+ return f"( {f(node.left)} => {f(node.right)} )"
491
+ if isinstance(node, Iff):
492
+ return f"( {f(node.left)} <=> {f(node.right)} )"
493
+ if isinstance(node, Xor):
494
+ return f"( {f(node.left)} <~> {f(node.right)} )"
495
+ if isinstance(node, Quantifier):
496
+ x = vars_.token(node.variable.name)
497
+ binder = "!" if node.type in (_FORALL, "forall") else "?"
498
+ return f"( {binder} [{x}: $i] : {f(node.formula)} )"
499
+ raise NotImplementedError(
500
+ f"to_thf_free: {type(node).__name__} should not occur in the guarded AST "
501
+ "this printer renders."
502
+ )
503
+
504
+
505
+ def _thf_signature_decls(formula: Node, syms: "_SymbolResolver") -> List[str]:
506
+ """Type declarations for every predicate / function / constant, ``D``/``E!`` included."""
507
+ preds, funcs, consts = _signature(formula)
508
+ decls: List[str] = []
509
+ for name, arity in sorted(preds):
510
+ ident = syms.name(_CAT_PRED, name, arity)
511
+ typ = " > ".join(["$i"] * arity + ["$o"]) if arity else "$o"
512
+ decls.append(f"thf({ident}_decl, type, ( {ident} : ( {typ} ) )).")
513
+ for name, arity in sorted(funcs):
514
+ ident = syms.name(_CAT_FUNC, name, arity)
515
+ typ = " > ".join(["$i"] * (arity + 1))
516
+ decls.append(f"thf({ident}_decl, type, ( {ident} : ( {typ} ) )).")
517
+ for name in sorted(consts):
518
+ ident = syms.name(_CAT_CONST, name, 0)
519
+ decls.append(f"thf({ident}_decl, type, ( {ident} : $i )).")
520
+ return decls
521
+
522
+
523
+ def to_thf_free(formula: Node, *, policy: str = "negative", conjecture: bool = True) -> str:
524
+ """Emit a complete TPTP **THF** problem for a free-logic ``formula``.
525
+
526
+ Guards every atom with ``D`` and every quantifier with ``E!`` (see the
527
+ module docstring), then declares a typed THF constant for every predicate /
528
+ function / constant in the GUARDED signature (``D``/``E!`` always included),
529
+ plus the ``E!(x) -> D(x)`` tie as an ``axiom`` (no nonempty-existence fact —
530
+ an empty inner domain is a free-logic model), and the guarded translation
531
+ itself as the ``conjecture`` (default) or an ``axiom``.
532
+
533
+ Args:
534
+ formula: the free-logic AST node (classical connectives / quantifiers /
535
+ atoms — see the module docstring for the exact fragment).
536
+ policy: ``"negative"`` (default) or ``"positive"`` — see
537
+ :mod:`~unicode_logic_kit.semantics.free_logic`.
538
+ ``"supervaluation"`` raises ``NotImplementedError``.
539
+ conjecture: emit the goal as ``conjecture`` (default) or ``axiom``. A free
540
+ variable is a parameter: one unknown individual that exists. For a
541
+ conjecture that is the formula closed universally under the ``E!``
542
+ guard, which is what is written. For an axiom it is not (``∀x P(x)``
543
+ says more than ``P(x)``), so an asserted formula with a free variable
544
+ is refused by name.
545
+
546
+ Raises:
547
+ NotImplementedError: for ``policy="supervaluation"``, for a node
548
+ outside the free-logic fragment (modal / second-order /
549
+ Łukasiewicz / substructural / lambda), or for ``conjecture=False``
550
+ with a formula that has a free variable.
551
+ ValueError: for any other unknown ``policy``.
552
+ """
553
+ _check_policy(policy, "to_thf_free")
554
+ # A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
555
+ # a user constant spelled like it is refused, not merged with it.
556
+ [formula], _ = numerals_as_constants([formula], where="to_thf_free",
557
+ spell=prefixed_numeral_name)
558
+ _refuse_open_assertion(formula, conjecture, "to_thf_free")
559
+ guarded = _guard(_close(formula), policy)
560
+ role = "conjecture" if conjecture else "axiom"
561
+ syms = _SymbolResolver(guarded)
562
+ vars_ = _VarResolver(lambda raw: _sanitize(raw).upper())
563
+ d_name = syms.name(_CAT_PRED, _DENOTES_PRED, 1)
564
+ e_name = syms.name(_CAT_PRED, _EXISTS_PRED, 1)
565
+ lines = [
566
+ "% Free logic embedded into THF via denotation/existence guards.",
567
+ f"% D(t): 't denotes'; {e_name}(t): 't exists' (E! implies D). policy={policy!r}.",
568
+ ]
569
+ lines += _thf_signature_decls(guarded, syms)
570
+ lines.append(f"thf(free_tie, axiom, "
571
+ f"( ! [X: $i] : ( ( {e_name} @ X ) => ( {d_name} @ X ) ) )).")
572
+ lines.append(f"thf(goal, {role}, {_thf_formula(guarded, syms, vars_)}).")
573
+ return "\n".join(lines) + "\n"
574
+
575
+
576
+ # ===========================================================================
577
+ # (B) Isabelle/HOL export
578
+ # ===========================================================================
579
+
580
+ _ISA_BINOP = {And: "\\<and>", Or: "\\<or>", Implies: "\\<longrightarrow>",
581
+ Iff: "\\<longleftrightarrow>"}
582
+
583
+
584
+ def _isa_term(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
585
+ """Render an individual term in Isabelle/HOL term syntax (curried application)."""
586
+ if isinstance(node, Variable):
587
+ return vars_.token(node.name)
588
+ if isinstance(node, Constant):
589
+ return syms.name(_CAT_CONST, node.name, 0)
590
+ if isinstance(node, Number):
591
+ return syms.name(_CAT_CONST, "n" + str(node.value), 0)
592
+ if isinstance(node, Function):
593
+ head = syms.name(_CAT_FUNC, node.name, len(node.args))
594
+ return "(" + " ".join([head] + [_isa_term(a, syms, vars_) for a in node.args]) + ")"
595
+ raise NotImplementedError(
596
+ f"to_isabelle_free: unsupported term {type(node).__name__} (free-logic "
597
+ "terms: variable / constant / number / function only)."
598
+ )
599
+
600
+
601
+ def _isa_formula(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
602
+ """Render the GUARDED formula in Isabelle/HOL syntax (classical connectives only)."""
603
+ def g(n):
604
+ return _isa_formula(n, syms, vars_)
605
+ if isinstance(node, Atom):
606
+ if truth_value(node) is not None:
607
+ return "True" if truth_value(node) else "False"
608
+ if _is_identity(node):
609
+ # Native Isabelle identity (see the module docstring's Equality section).
610
+ left = _isa_term(node.args[0], syms, vars_)
611
+ right = _isa_term(node.args[1], syms, vars_)
612
+ return f"({left} = {right})"
613
+ head = syms.name(_CAT_PRED, node.predicate, len(node.args))
614
+ if not node.args:
615
+ return head
616
+ return "(" + " ".join([head] + [_isa_term(a, syms, vars_) for a in node.args]) + ")"
617
+ if isinstance(node, Not):
618
+ return f"(\\<not> {g(node.formula)})"
619
+ if type(node) in _ISA_BINOP:
620
+ op = _ISA_BINOP[type(node)]
621
+ return f"({g(node.left)} {op} {g(node.right)})"
622
+ if isinstance(node, Xor):
623
+ inner = f"({g(node.left)} \\<longleftrightarrow> {g(node.right)})"
624
+ return f"(\\<not> {inner})"
625
+ if isinstance(node, Quantifier):
626
+ binder = "\\<forall>" if node.type in (_FORALL, "forall") else "\\<exists>"
627
+ return f"({binder} {vars_.token(node.variable.name)}. {g(node.formula)})"
628
+ raise NotImplementedError(
629
+ f"to_isabelle_free: {type(node).__name__} should not occur in the guarded "
630
+ "AST this printer renders."
631
+ )
632
+
633
+
634
+ def _isa_consts_block(formula: Node, syms: "_SymbolResolver") -> List[str]:
635
+ """``consts`` declarations for every predicate / function / constant, ``D``/``E!`` included.
636
+
637
+ Individuals live in a single uninterpreted HOL type ``e`` (the "outer
638
+ domain" of the module docstring — existing or merely possible).
639
+ """
640
+ preds, funcs, consts = _signature(formula)
641
+ lines: List[str] = []
642
+ arrow = " \\<Rightarrow> "
643
+ for name, arity in sorted(preds):
644
+ ident = syms.name(_CAT_PRED, name, arity)
645
+ typ = arrow.join(["e"] * arity + ["bool"]) if arity else "bool"
646
+ lines.append(f'consts {ident} :: "{typ}"')
647
+ for name, arity in sorted(funcs):
648
+ ident = syms.name(_CAT_FUNC, name, arity)
649
+ typ = arrow.join(["e"] * (arity + 1))
650
+ lines.append(f'consts {ident} :: "{typ}"')
651
+ for name in sorted(consts):
652
+ ident = syms.name(_CAT_CONST, name, 0)
653
+ lines.append(f'consts {ident} :: "e"')
654
+ return lines
655
+
656
+
657
+ def to_isabelle_free(formula: Node, *, policy: str = "negative",
658
+ theory_name: str = "Free_Export", lemma_name: str = "goal",
659
+ proof: str = "oops") -> str:
660
+ r"""Emit a loadable **Isabelle/HOL** theory with the guarded ``formula`` as a lemma.
661
+
662
+ The theory declares a single uninterpreted individual type ``e`` (the outer
663
+ domain — existing or merely possible) and a ``consts`` entry for every
664
+ predicate / function / constant in the GUARDED signature (``D``/``E!``
665
+ always included, see the module docstring), then states
666
+ ``lemma <lemma_name>: "<E!->D tie> \<Longrightarrow> <guarded formula>"`` —
667
+ the tie is a PREMISE of the goal, not ``axiomatization``, so nitpick can
668
+ construct ``D``/``E!`` itself and certify a countermodel as genuine (mirrors
669
+ :mod:`~unicode_logic_kit.hol.isabelle_conditional`'s ``nested Sel`` premise).
670
+
671
+ Args:
672
+ formula: the free-logic AST node.
673
+ policy: ``"negative"`` (default) or ``"positive"``.
674
+ ``"supervaluation"`` raises ``NotImplementedError``.
675
+ theory_name: the Isabelle theory identifier.
676
+ lemma_name: the lemma identifier.
677
+ proof: the proof text under the lemma; defaults to ``"oops"`` (state,
678
+ don't discharge — this function does not run Isabelle). Since the
679
+ premises are stated with the bare ``P1 \<Longrightarrow> P2 \<Longrightarrow> Q``
680
+ lemma syntax (no ``assumes``/``shows``), a proof method sees them as
681
+ an ordinary implication goal — no ``using`` clause is required to
682
+ reach them.
683
+
684
+ Raises:
685
+ NotImplementedError: for ``policy="supervaluation"``, for a node
686
+ outside the free-logic fragment, or for a constant or function that is spelled
687
+ like a numeral of the formula (``Number(1)`` next to ``Constant('n1')``): a
688
+ numeral is a constant of its own, named ``n1`` here, and the two would be one
689
+ symbol of the theory, so the writer refuses to merge them.
690
+ ValueError: for any other unknown ``policy``, or for a numeral that is ``inf``,
691
+ ``-inf`` or ``nan``.
692
+ """
693
+ _check_policy(policy, "to_isabelle_free")
694
+ # A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
695
+ # a user constant spelled like it is refused, not merged with it.
696
+ [formula], _ = numerals_as_constants([formula], where="to_isabelle_free",
697
+ spell=prefixed_numeral_name)
698
+ guarded = _guard(_close(formula), policy)
699
+ syms = _SymbolResolver(guarded)
700
+ vars_ = _VarResolver(_sanitize, used=syms._used)
701
+ d_name = syms.name(_CAT_PRED, _DENOTES_PRED, 1)
702
+ e_name = syms.name(_CAT_PRED, _EXISTS_PRED, 1)
703
+ tie = f"(\\<forall>x. {e_name} x \\<longrightarrow> {d_name} x)"
704
+ lines = [
705
+ f"theory {theory_name}",
706
+ " imports Main",
707
+ "begin",
708
+ "",
709
+ "(* Free logic embedded into Isabelle/HOL over an uninterpreted individual",
710
+ " type e (existing or merely possible), guarded by D (denotes) and",
711
+ f" {e_name} (exists; implies D). policy={policy!r}.",
712
+ " The E!->D tie is a PREMISE of the goal, not `axiomatization`, so nitpick",
713
+ " constructs D/E! itself and can certify a countermodel as genuine (mirrors",
714
+ " hol.isabelle_conditional's `nested`). No nonempty-existence fact: an empty",
715
+ " inner domain is a free-logic model. '=' is HOL identity under the D guard. *)",
716
+ "",
717
+ "typedecl e \\<comment> \\<open>outer domain: existing or merely possible\\<close>",
718
+ ]
719
+ lines += _isa_consts_block(guarded, syms)
720
+ lines.append("")
721
+ lines.append(f'lemma {lemma_name}: "{tie} \\<Longrightarrow> '
722
+ f'{_isa_formula(guarded, syms, vars_)}"')
723
+ lines.append(f" {proof}")
724
+ lines.append("")
725
+ lines.append("end")
726
+ return "\n".join(lines) + "\n"
727
+
728
+
729
+ def free_theory(formula: Node, *, policy: str = "negative",
730
+ theory_name: str = "FreeDecide", lemma_name: str = "goal",
731
+ proof: Optional[str] = None) -> str:
732
+ """Emit a self-contained theory asserting ``formula``'s free-logic validity.
733
+
734
+ A thin wrapper around :func:`to_isabelle_free`, named and keyword-shaped
735
+ like
736
+ :func:`~unicode_logic_kit.hol.isabelle_conditional.isabelle_conditional_theory`
737
+ for :mod:`~unicode_logic_kit.hol.isabelle_runner`'s prove/refute two-step
738
+ protocol (:func:`~unicode_logic_kit.hol.isabelle_runner.isabelle_decide_free`):
739
+ ``proof`` defaults to ``"oops"`` when omitted (state, don't discharge), and
740
+ ``theory_name`` / ``lemma_name`` default to values distinct from
741
+ :func:`to_isabelle_free`'s own so both can be called back to back in one
742
+ script without a duplicate-theory-name collision.
743
+
744
+ Raises:
745
+ NotImplementedError: for ``policy="supervaluation"``, for a node
746
+ outside the free-logic fragment, or for a numeral next to a constant or
747
+ function spelled like it (see :func:`to_isabelle_free`).
748
+ ValueError: for any other unknown ``policy``, or for a numeral that is
749
+ ``inf``, ``-inf`` or ``nan``.
750
+ """
751
+ return to_isabelle_free(formula, policy=policy, theory_name=theory_name,
752
+ lemma_name=lemma_name,
753
+ proof=proof if proof is not None else "oops")