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,593 @@
1
+ r"""Isabelle/HOL export for the Lewis counterfactual conditionals ``□→`` / ``◇→``.
2
+
3
+ The modal exporter (:mod:`unicode_logic_kit.hol.isabelle_modal`) embeds ``□``/``◇``
4
+ over an **accessibility relation**; a counterfactual is not evaluated that way. Its
5
+ truth condition reads a **similarity ordering** — Lewis's system of spheres — so it
6
+ needs its own embedding, which is what this module emits.
7
+
8
+ The embedding is the *lightweight shallow* one: a formula becomes a predicate on
9
+ worlds (``tau = w ⇒ bool``), the sphere system is the uninterpreted constant
10
+ ``Sel``, and each propositional atom is an uninterpreted ``w ⇒ bool`` constant.
11
+ Validity is ``nested Sel ⟹ <centering premise> ⟹ ∀x. φ x``.
12
+
13
+ ``nested`` appears as a **premise of the goal**, never as an ``axiomatization``.
14
+ That is deliberate: nitpick cannot certify a model as *genuine* while axiomatised
15
+ constants are in play (it has no way to check the axioms are consistent) and
16
+ downgrades the verdict to ``quasi_genuine``, which would lose us the refutation
17
+ half of the decision procedure. As a premise, nitpick constructs ``Sel`` itself.
18
+ The centering premise is added the same way, for the same reason.
19
+
20
+ ``CondC`` is the same truth condition as
21
+ :func:`unicode_logic_kit.semantics.conditional.cf_satisfies` and as the ``CondM``
22
+ clause of :mod:`unicode_logic_kit.hol.deepshallow.conditional`, so what Isabelle
23
+ certifies here is what the toolkit's own evaluator computes.
24
+
25
+ CENTERING — the premise denotes the Python class exactly
26
+ --------------------------------------------------------
27
+ ``centering`` (``"none"`` = Lewis V, ``"weak"`` = VW and the default, ``"strong"``
28
+ = VC — the level names come from
29
+ :data:`~unicode_logic_kit.semantics.conditional.CENTERING_LEVELS`, imported rather
30
+ than duplicated) selects one extra premise. That premise denotes *exactly* the
31
+ class of models :func:`~unicode_logic_kit.semantics.conditional.cf_countermodel`
32
+ enumerates at the same level, which is what makes the two routes comparable
33
+ verdict by verdict; the argument, for a finite world set ``W`` and the family
34
+ ``F(x) = {S ⊆ W : Sel x S}``:
35
+
36
+ 1. SHAPE — ``nested Sel`` makes ``F(x)`` a ⊆-chain, and over a finite ``W`` a chain
37
+ is exactly a finite strictly increasing sequence: the ``List[FrozenSet]``,
38
+ innermost-first shape ``_sphere_chains`` emits.
39
+ 2. ``∅`` IS INERT — ``CondC``'s non-vacuous disjunct needs ``∃u. S u ∧ f u``, which
40
+ ``∅`` never supplies, and its vacuous disjunct holds of ``∅`` outright. So
41
+ ``F(x)`` and ``F(x) \ {∅}`` agree on every ``CondC f g x``, and the Python pool
42
+ ranging over non-empty subsets only loses nothing.
43
+ 3. TRUTH CONDITION — HOL takes the ∃-form ("SOME sphere permits the antecedent and
44
+ has the consequent throughout"); Python takes the FIRST permitting chain member.
45
+ On a chain these coincide: the ⊆-least permitting member is contained in any
46
+ permitting witness, so its antecedent-worlds are among that witness's. This part
47
+ is centering-independent and is why the two routes already agreed at what is now
48
+ called ``"none"``.
49
+ 4. THE LEVELS — ``"none"``: no premise; Python additionally emits the empty chain,
50
+ matching ``F(x) = ∅`` (equivalently ``{∅}``, by 2). ``"weak"``:
51
+ ``weakly_centered Sel`` says some sphere of ``x`` contains ``x`` and every
52
+ non-empty sphere of ``x`` contains ``x``; Python emits exactly the non-empty
53
+ chains all of whose members contain ``x``, and deleting ``∅`` from any HOL
54
+ witness by (2) lands back in that set. ``"strong"``: ``strongly_centered Sel``
55
+ says ``{x}`` is a sphere, which with nesting makes it the ⊆-least non-empty one;
56
+ Python pins ``chain[0] = {x}``.
57
+ 5. QUANTIFIER STRUCTURE — the HOL conditions are ``∀x. …`` and the Python search
58
+ assigns an independent chain per world, imposing the level per world.
59
+
60
+ The two routes also bound the search differently — ``cf_valid`` by ``max_worlds``,
61
+ this route by nitpick's ``card`` (default ``1-3``) — but at the centered levels the
62
+ defaults now coincide at three worlds
63
+ (:data:`~unicode_logic_kit.semantics.conditional.DEFAULT_MAX_WORLDS`), which is what
64
+ lets both routes refute the two bound-sensitive schemas (conditional excluded middle
65
+ at VC, importation at VW) rather than only this one. At ``centering="none"`` the
66
+ Python bound stays 2, its enumeration being an order of magnitude larger per world,
67
+ so a ``True`` from ``cf_valid`` there is the weaker claim of the two.
68
+ """
69
+
70
+ from typing import Dict, List, Optional, Sequence, Tuple, Type
71
+
72
+ from ..fol.nodes import (
73
+ Node, Atom, Not, And, Or, Xor, Implies, Iff, Would, Might,
74
+ Variable, Quantifier,
75
+ )
76
+ from ..fol._atom_keys import AtomKeys
77
+ from ..fol._msfl_nodes import key_text
78
+ from ..fol._truth_constants import truth_value as _truth_value
79
+ from .deepshallow._common import AtomConsts, theory_name_ok
80
+ from ._ho_common import ThfNames
81
+ from ..semantics.conditional import CENTERING_LEVELS, check_centering # noqa: F401
82
+ from ..semantics._modal_reject import reject_equality
83
+
84
+ #: The Isabelle type of an embedded formula: a predicate on worlds.
85
+ _TAU = r"w \<Rightarrow> bool"
86
+
87
+ #: Toolkit node type -> (shallow constant, arity). ``Xor`` and ``Might`` are
88
+ #: desugared rather than given constants of their own, matching the way the
89
+ #: evaluator derives them.
90
+ _CONNECTIVES: Dict[Type[Node], Tuple[str, int]] = {
91
+ Not: ("NegC", 1),
92
+ And: ("AndC", 2),
93
+ Or: ("OrC", 2),
94
+ Implies: ("ImpC", 2),
95
+ Iff: ("IffC", 2),
96
+ Would: ("CondC", 2),
97
+ }
98
+
99
+ #: Definitions the goal has to unfold before a proof method can see through it.
100
+ _BASE_DEFS = ("nested_def", "NegC_def", "AndC_def", "OrC_def", "ImpC_def",
101
+ "IffC_def", "CondC_def")
102
+
103
+ #: The centering definition each level's premise needs unfolded, if any. The
104
+ #: unfold list is level-dependent on purpose: ``unfolding`` is CHANGED-wrapped over
105
+ #: the whole batch, so an unconditional list naming both predicates would happen to
106
+ #: work today only because ``nested_def`` always fires — an accident, not a
107
+ #: guarantee. An unfolded-but-absent premise is a silent proof-failure generator.
108
+ _CENTERING_DEFS: Dict[str, Tuple[str, ...]] = {
109
+ "none": (),
110
+ "weak": ("weakly_centered_def",),
111
+ "strong": ("strongly_centered_def",),
112
+ }
113
+
114
+ #: The extra goal premise per level. ``"none"`` is Lewis's bare V: nesting only.
115
+ _CENTERING_PREMISE: Dict[str, Tuple[str, ...]] = {
116
+ "none": (),
117
+ "weak": ("weakly_centered Sel",),
118
+ "strong": ("strongly_centered Sel",),
119
+ }
120
+
121
+ #: Level -> Lewis's own name for the system, recorded in the emitted theory so a
122
+ #: reader (and a structure test) can see which logic was decided.
123
+ _CENTERING_SYSTEM: Dict[str, str] = {"none": "V", "weak": "VW", "strong": "VC"}
124
+
125
+
126
+ def _defs_for(centering: str) -> Tuple[str, ...]:
127
+ """The ``unfolding`` list for ``centering`` — nesting, the level's centering
128
+ definition (if it has one), then the connective definitions."""
129
+ check_centering(centering)
130
+ return (_BASE_DEFS[0],) + _CENTERING_DEFS[centering] + _BASE_DEFS[1:]
131
+
132
+ #: Proof methods tried in order, as a ``by (… | …)`` battery. ``smt (verit)``
133
+ #: comes FIRST, not as a fallback: the ``|`` combinator has no per-method timeout,
134
+ #: and ``blast`` does not terminate on agglomeration
135
+ #: ``(A □→ B) ∧ (A □→ C) → (A □→ (B ∧ C))`` — the validity whose proof actually
136
+ #: uses the nesting premise — so a blast-first battery hangs before ever reaching
137
+ #: verit (measured: blast-first 97s and fails; verit-first closes it in ~9s, and
138
+ #: is no slower on the easy goals).
139
+ DEFAULT_METHODS: Tuple[str, ...] = ("smt (verit)", "blast", "force", "metis")
140
+
141
+ _PREAMBLE = r'''typedecl w \<comment> \<open>worlds\<close>
142
+ type_synonym tau = "w \<Rightarrow> bool"
143
+ type_synonym sph = "w \<Rightarrow> (w \<Rightarrow> bool) \<Rightarrow> bool"
144
+
145
+ consts Sel :: sph \<comment> \<open>Sel x S: S is a sphere around x (innermost = closest)\<close>
146
+
147
+ definition nested :: "sph \<Rightarrow> bool" where
148
+ "nested Sp \<equiv> \<forall>x S T. Sp x S \<longrightarrow> Sp x T \<longrightarrow> (\<forall>u. S u \<longrightarrow> T u) \<or> (\<forall>u. T u \<longrightarrow> S u)"
149
+
150
+ text \<open>Centering. Nesting alone is Lewis's system V; VW adds weak centering, VC
151
+ strong centering. Both predicates are defined at every level and only the emitted
152
+ lemma's premise selects one -- an unused definition is inert, so the preamble stays a
153
+ single constant string.
154
+
155
+ weakly_centered is stated in two clauses: SOME sphere of x contains x, and EVERY
156
+ NON-EMPTY sphere of x contains x. The first clause must mention S x rather than merely
157
+ assert that x has some sphere, or a system whose only sphere is the empty one would
158
+ satisfy it -- and that system is exactly the vacuity that makes modus ponens for the
159
+ conditional fail. The second clause is guarded by an emptiness test because Lewis's own
160
+ sphere systems contain the empty set (it is the union of the empty family); the
161
+ unguarded form would denote a strictly smaller class of Sel with the same validities,
162
+ since the empty sphere is truth-value inert for CondC.
163
+
164
+ strongly_centered says "the singleton of x IS a sphere around x", not "the innermost
165
+ sphere is that singleton": together with the nested premise that always accompanies it,
166
+ every sphere S around x is comparable with it, hence S is empty or contains x, so the
167
+ singleton is the smallest non-empty sphere. That also makes strong centering entail weak
168
+ centering, which is why the strong goal does not carry both premises.\<close>
169
+
170
+ definition weakly_centered :: "sph \<Rightarrow> bool" where
171
+ "weakly_centered Sp \<equiv> \<forall>x. (\<exists>S. Sp x S \<and> S x)
172
+ \<and> (\<forall>S. Sp x S \<longrightarrow> (\<exists>u. S u) \<longrightarrow> S x)"
173
+
174
+ definition strongly_centered :: "sph \<Rightarrow> bool" where
175
+ "strongly_centered Sp \<equiv> \<forall>x. Sp x (\<lambda>u. u = x)"
176
+
177
+ definition NegC :: "tau \<Rightarrow> tau" where "NegC f \<equiv> \<lambda>x. \<not> f x"
178
+ definition AndC :: "tau \<Rightarrow> tau \<Rightarrow> tau" where "AndC f g \<equiv> \<lambda>x. f x \<and> g x"
179
+ definition OrC :: "tau \<Rightarrow> tau \<Rightarrow> tau" where "OrC f g \<equiv> \<lambda>x. f x \<or> g x"
180
+ definition ImpC :: "tau \<Rightarrow> tau \<Rightarrow> tau" where "ImpC f g \<equiv> \<lambda>x. f x \<longrightarrow> g x"
181
+ definition IffC :: "tau \<Rightarrow> tau \<Rightarrow> tau" where "IffC f g \<equiv> \<lambda>x. f x = g x"
182
+
183
+ text \<open>The Lewis sphere condition: either the smallest antecedent-permitting sphere
184
+ has the consequent throughout its antecedent-worlds, or no sphere holds an
185
+ antecedent-world at all (the vacuous case).\<close>
186
+
187
+ definition CondC :: "tau \<Rightarrow> tau \<Rightarrow> tau" where
188
+ "CondC f g \<equiv> \<lambda>x.
189
+ (\<exists>S. Sel x S \<and> (\<exists>u. S u \<and> f u) \<and> (\<forall>u. S u \<longrightarrow> f u \<longrightarrow> g u))
190
+ \<or> (\<forall>S. Sel x S \<longrightarrow> (\<forall>u. S u \<longrightarrow> \<not> f u))"
191
+ '''
192
+
193
+
194
+ #: The sphere (counterfactual) embedding reads an atom as a world-indexed
195
+ #: PROPOSITION keyed by its rendered form — `p_a___b` in Isabelle, a `w > $o`
196
+ #: functor in THF — so it interprets no term at all. An identity atom is
197
+ #: therefore refused by name, through the same helper the Kripke evaluator, the
198
+ #: modal tableau, the standard translation and the GMT embedding use: until
199
+ #: 0.30.0 `a = b` came out as the letter `a___b`, under which `a = a` is not
200
+ #: valid and `(a = b) □→ (b = a)` is decided by the sphere function alone.
201
+ _EQUALITY_ROUTE = "the propositional sphere (counterfactual) embedding"
202
+ _EQUALITY_ATOM_READING = ("an atom is a world-indexed proposition keyed by its "
203
+ "rendered form, and no term is interpreted")
204
+ _EQUALITY_INSTEAD = (
205
+ "Counterfactuals over identity are outside this embedding; decide identity "
206
+ "with unicode_logic_kit.fol.qml.qml_is_valid (rigid identity over the object "
207
+ "domain) on the modal fragment instead."
208
+ )
209
+
210
+
211
+ def _atom_is_propositional(node: Atom) -> bool:
212
+ """True unless an argument carries a :class:`Variable` (⇒ genuinely first-order)."""
213
+ return not any(isinstance(n, Variable) for a in node.args for n in a.walk())
214
+
215
+
216
+ def to_isabelle_conditional(formula: Node,
217
+ atoms: Optional[AtomConsts] = None) -> str:
218
+ r"""Encode a propositional/counterfactual formula as a shallow ``tau`` term.
219
+
220
+ ``□→`` becomes ``CondC``; ``◇→`` desugars to ``NegC (CondC a (NegC b))`` and
221
+ ``Xor`` to ``NegC (IffC …)``, exactly as the evaluator derives them. Each
222
+ distinct ground atom becomes an uninterpreted ``w ⇒ bool`` constant, collected
223
+ into ``atoms``.
224
+
225
+ Args:
226
+ formula: the AST node to encode.
227
+ atoms: the constant resolver to accumulate into; a fresh one is used when
228
+ omitted (pass your own to emit the matching ``consts`` declarations).
229
+
230
+ Raises:
231
+ NotImplementedError: on a quantifier, a first-order atom, or a modal
232
+ operator. ``□``/``◇`` range over an accessibility relation and have no
233
+ reading in the sphere semantics — the same boundary
234
+ :func:`~unicode_logic_kit.semantics.conditional.cf_satisfies` enforces —
235
+ so they are rejected rather than silently reinterpreted.
236
+ """
237
+ atoms = AtomConsts() if atoms is None else atoms
238
+ term = _encode(formula, atoms)
239
+ # An atom is named by the text it prints as: two different atoms that print alike would be
240
+ # ONE constant and the theory another problem (checked after the encoding, which refuses
241
+ # the nodes it cannot read first).
242
+ AtomKeys("to_isabelle_conditional", "refuse").letters([formula])
243
+ return term
244
+
245
+
246
+ def _encode(formula: Node, atoms: AtomConsts) -> str:
247
+ """Recursive worker for :func:`to_isabelle_conditional`."""
248
+ if isinstance(formula, Atom):
249
+ constant = _truth_value(formula)
250
+ if constant is not None:
251
+ # `$true` / `$false`: the proposition true (false) at every world.
252
+ return "(\\<lambda>x. True)" if constant else "(\\<lambda>x. False)"
253
+ reject_equality(formula, "to_isabelle_conditional", _EQUALITY_ROUTE,
254
+ atom_reading=_EQUALITY_ATOM_READING,
255
+ instead=_EQUALITY_INSTEAD)
256
+ if not _atom_is_propositional(formula):
257
+ raise NotImplementedError(
258
+ "to_isabelle_conditional: atom with a free variable is first-order; "
259
+ "the sphere embedding is propositional.")
260
+ return atoms.name(key_text(formula))
261
+ if isinstance(formula, (Quantifier, Variable)):
262
+ raise NotImplementedError(
263
+ "to_isabelle_conditional: quantifiers/variables are first-order; the "
264
+ "sphere embedding is propositional.")
265
+ if isinstance(formula, Xor):
266
+ return (f"(NegC (IffC {_encode(formula.left, atoms)} "
267
+ f"{_encode(formula.right, atoms)}))")
268
+ if isinstance(formula, Might):
269
+ # ◇→ is the dual ¬(A □→ ¬B) — no constant of its own, so the emitted theory
270
+ # cannot drift from the evaluator's derivation of it.
271
+ return (f"(NegC (CondC {_encode(formula.left, atoms)} "
272
+ f"(NegC {_encode(formula.right, atoms)})))")
273
+ spec = _CONNECTIVES.get(type(formula))
274
+ if spec is None:
275
+ raise NotImplementedError(
276
+ f"to_isabelle_conditional: unsupported node type "
277
+ f"{type(formula).__name__}. The sphere semantics reads a similarity "
278
+ "ordering, not an accessibility relation, so the modal operators belong "
279
+ "to hol.isabelle_modal instead.")
280
+ name, arity = spec
281
+ if arity == 1:
282
+ return f"({name} {_encode(formula.formula, atoms)})"
283
+ return (f"({name} {_encode(formula.left, atoms)} "
284
+ f"{_encode(formula.right, atoms)})")
285
+
286
+
287
+ def battery_proof(methods: Sequence[str] = DEFAULT_METHODS, *,
288
+ centering: str = "weak") -> str:
289
+ """A ``unfolding …`` + ``by (m1 | m2 | …)`` proof over the shallow definitions.
290
+
291
+ ``centering`` must be the SAME level the goal's premise was built with (see
292
+ :func:`isabelle_conditional_theory`): the unfold list is what lets the proof
293
+ battery see through that premise, so a mismatch does not produce a wrong
294
+ verdict but a failed proof, which the runner reports as UNKNOWN or INVALID.
295
+ Keyword-only, so ``battery_proof(methods)`` positional callers keep working.
296
+
297
+ Raises:
298
+ ValueError: on an empty method battery or an unknown ``centering`` level.
299
+ """
300
+ if not methods:
301
+ raise ValueError("battery_proof: need at least one proof method.")
302
+ alts = " | ".join(methods)
303
+ return f" unfolding {' '.join(_defs_for(centering))}\n by ({alts})"
304
+
305
+
306
+ def nitpick_proof(card: str = "1-3", timeout: int = 60) -> str:
307
+ """A ``nitpick[expect = genuine]`` refutation attempt over the world type ``w``."""
308
+ return (f" nitpick[card w = {card}, timeout = {int(timeout)}, "
309
+ f"expect = genuine]\n oops")
310
+
311
+
312
+ def isabelle_conditional_theory(formula: Node, *,
313
+ theory_name: str = "CondDecide",
314
+ proof: Optional[str] = None,
315
+ lemma_name: str = "goal",
316
+ centering: str = "weak") -> str:
317
+ r"""Emit a self-contained theory asserting ``formula``'s counterfactual validity.
318
+
319
+ The goal is ``nested Sel ⟹ <centering premise> ⟹ ∀x. φ x`` — validity over
320
+ every nested sphere system meeting the requested centering level. Both premises
321
+ are premises rather than axioms so that nitpick can build ``Sel`` itself and
322
+ certify a counter-model as genuine.
323
+
324
+ Args:
325
+ formula: the AST node to decide.
326
+ theory_name: the Isabelle theory identifier.
327
+ proof: the proof text under the lemma; defaults to
328
+ ``battery_proof(centering=centering)``. Pass :func:`nitpick_proof` to
329
+ emit the refutation theory instead. If you pass your own
330
+ :func:`battery_proof` text, build it at THIS level — the unfold list
331
+ has to match the premise.
332
+ lemma_name: the lemma identifier.
333
+ centering: ``"none"`` (V) / ``"weak"`` (VW, default) / ``"strong"`` (VC).
334
+ The default matches
335
+ :func:`~unicode_logic_kit.semantics.conditional.cf_valid`'s, so the
336
+ Isabelle route and the internal evaluator decide the same question
337
+ unless told otherwise. See the module docstring for why the emitted
338
+ premise denotes exactly the class the Python search enumerates.
339
+
340
+ Raises:
341
+ ValueError: on an illegal theory name or an unknown ``centering`` level.
342
+ NotImplementedError: propagated from :func:`to_isabelle_conditional`.
343
+ """
344
+ check_centering(centering)
345
+ if not theory_name_ok(theory_name):
346
+ raise ValueError(f"illegal theory name {theory_name!r}.")
347
+ atoms = AtomConsts()
348
+ term = to_isabelle_conditional(formula, atoms)
349
+ body = [f"theory {theory_name}", " imports Main", "begin", "", _PREAMBLE]
350
+ decls = atoms.decls(_TAU)
351
+ if decls:
352
+ body.append("\n".join(decls))
353
+ body.append("")
354
+ # Provenance: the emitted theory records which sphere class it decided. At
355
+ # centering="none" the lemma line is byte-identical to a pre-centering one, so
356
+ # without this the level would be unrecoverable from the artefact.
357
+ body.append(f"text \\<open>centering = {centering} "
358
+ f"(Lewis {_CENTERING_SYSTEM[centering]})\\<close>")
359
+ body.append("")
360
+ arrow = " \\<Longrightarrow> "
361
+ prems = ("nested Sel",) + _CENTERING_PREMISE[centering]
362
+ body.append(
363
+ f'lemma {lemma_name}: "{arrow.join(prems)}{arrow}\\<forall>x. ({term}) x"')
364
+ body.append(proof if proof is not None
365
+ else battery_proof(centering=centering))
366
+ body.append("")
367
+ body.append("end")
368
+ return "\n".join(body) + "\n"
369
+
370
+
371
+ # --------------------------------------------------------------------------
372
+ # THF
373
+ # --------------------------------------------------------------------------
374
+ #
375
+ # The THF sibling of the theory above: the same shallow embedding over the
376
+ # sphere system ``Sel``, for a higher-order ATP (Leo-III, Vampire-THF,
377
+ # Satallax) instead of Isabelle. ``Sel : w > (w>$o) > $o`` is a legal TH0
378
+ # uninterpreted constant -- :mod:`unicode_logic_kit.hol.thf_modal`'s
379
+ # ``muntil``/``msince`` already quantify over an identically-shaped ``mu>$o``
380
+ # variable, so this needs no new expressiveness from TH0, just a
381
+ # differently-typed free symbol. ``nested``/the chosen centering premise stay
382
+ # PREMISES of the conjecture rather than axioms, for the same reason they stay
383
+ # premises on the Isabelle side (see the module docstring).
384
+ #
385
+ # TWO differences from a line-for-line transcription of ``_PREAMBLE``, both
386
+ # for the same reason (measured by hand with a battery of THF micro-examples
387
+ # in the scratchpad -- see the matching comment in isabelle_relevant.py, whose
388
+ # THF side hit the identical issue first):
389
+ #
390
+ # 1. ``nested``/``weakly_centered``/``strongly_centered`` are declared NULLARY
391
+ # (``$o``, not ``(w>(w>$o)>$o) > $o``) and characterised by a ``<=>``
392
+ # biconditional that mentions ``sel`` DIRECTLY, rather than as a schema
393
+ # parameterised over an arbitrary sphere-function ``Sp`` (the Isabelle
394
+ # side's ``sph => bool``, applied to ``Sel`` only at use). This embedding
395
+ # only ever needs them applied to the ONE fixed ``sel`` constant, so
396
+ # nothing is lost by stating that instance directly; what is gained is that
397
+ # unfolding a nullary ``<=>`` is ordinary clausification, while unfolding a
398
+ # ``=``-defined, then-applied function value needs higher-order
399
+ # superposition Vampire's default portfolio did not close in 60s even for
400
+ # trivial goals.
401
+ # 2. ``NegC``/``AndC``/``OrC``/``ImpC``/``IffC``/``CondC`` are not
402
+ # freestanding combinators :func:`_thf_encode` would APPLY to
403
+ # already-built ``tau`` terms; :func:`_thf_encode` instead THREADS the
404
+ # current world through the recursion and renders ``φ`` at that world
405
+ # directly with native THF ``&``/``|``/``~``/``=>``/``<=>`` (only
406
+ # ``CondC``'s own ``?[S:w>$o]``/``![S:w>$o]`` sphere quantifiers are
407
+ # genuinely higher-order, and a BOUND variable applied to an argument needs
408
+ # no reduction, unlike a NAMED combinator that must first be unfolded).
409
+ # Both changes are meaning-preserving -- the same shallow truth conditions,
410
+ # just not indirected through a rewrite Vampire's default strategy struggled
411
+ # to perform -- and mirror isabelle_relevant.py's own THF side exactly.
412
+
413
+ #: The embedding's own functors, pre-claimed so no user atom's THF stem can
414
+ #: collide with them (see hol._ho_common.ThfNames).
415
+ _THF_RESERVED = ("w", "sel", "nested", "weakly_centered", "strongly_centered")
416
+
417
+ _THF_PRELUDE = [
418
+ "% Lewis counterfactual conditionals (box-arrow / diamond-arrow) -> THF.",
419
+ "% Shallow embedding: a formula is true/false AT A WORLD, over the",
420
+ "% similarity ordering `sel`; `nested` and the chosen centering level are",
421
+ "% PREMISES of the conjecture, never axioms -- see isabelle_conditional.py's",
422
+ "% module docstring, _PREAMBLE, and the comment above _THF_PRELUDE for why",
423
+ "% nested/weakly_centered/strongly_centered are stated directly of `sel`",
424
+ "% and the connectives are inlined at each world rather than routed",
425
+ "% through separately-declared NegC/AndC/OrC/ImpC/IffC/CondC combinators.",
426
+ "thf(w_type, type, ( w : $tType )).",
427
+ "thf(sel_type, type, ( sel : w > ( w > $o ) > $o )).",
428
+ "thf(nested_type, type, ( nested : $o )).",
429
+ "thf(nested_def, definition, ( nested <=> "
430
+ "( ! [X: w, S: w > $o, T: w > $o] : "
431
+ "( ( ( sel @ X @ S ) & ( sel @ X @ T ) ) => "
432
+ "( ( ! [U: w] : ( ( S @ U ) => ( T @ U ) ) ) "
433
+ "| ( ! [U: w] : ( ( T @ U ) => ( S @ U ) ) ) ) ) ) )).",
434
+ "thf(weakly_centered_type, type, ( weakly_centered : $o )).",
435
+ "thf(weakly_centered_def, definition, ( weakly_centered <=> "
436
+ "( ! [X: w] : "
437
+ "( ( ? [S: w > $o] : ( ( sel @ X @ S ) & ( S @ X ) ) ) "
438
+ "& ( ! [S: w > $o] : ( ( sel @ X @ S ) => "
439
+ "( ( ? [U: w] : ( S @ U ) ) => ( S @ X ) ) ) ) ) ) )).",
440
+ "thf(strongly_centered_type, type, ( strongly_centered : $o )).",
441
+ "thf(strongly_centered_def, definition, ( strongly_centered <=> "
442
+ "( ! [X: w] : ( sel @ X @ ( ^ [U: w] : ( U = X ) ) ) ) )).",
443
+ ]
444
+
445
+ #: Level -> the extra THF premise, or ``None`` for Lewis's bare V (no premise).
446
+ #: ``nested``/``weakly_centered``/``strongly_centered`` are nullary here (see
447
+ #: the comment above ``_THF_PRELUDE``), so unlike ``_CENTERING_PREMISE`` the
448
+ #: premise names carry no ``@ sel``.
449
+ _THF_CENTERING_PREMISE: Dict[str, Optional[str]] = {
450
+ "none": None,
451
+ "weak": "weakly_centered",
452
+ "strong": "strongly_centered",
453
+ }
454
+
455
+
456
+ def _thf_would_at(left: Node, right: Node, world: str,
457
+ names: ThfNames, depth: int) -> str:
458
+ """The world-threaded rendering of ``left □→ right`` (CondC) at ``world``.
459
+
460
+ CondC's own case split (:func:`_encode`'s ``Would`` clause via
461
+ ``_CONNECTIVES``, unchanged): some sphere of ``world`` permits ``left``
462
+ and has ``right`` throughout its ``left``-worlds, or no sphere of
463
+ ``world`` holds a ``left``-world at all (the vacuous case). ``S``/``U``
464
+ are named from ``depth`` for the same reason
465
+ :func:`unicode_logic_kit.hol.isabelle_relevant._thf_implies_at`'s ``Y``/``Z``
466
+ are: sibling ``□→`` nodes may reuse the token freely, while a
467
+ NESTED one (reached through ``left``/``right``, always recursed at
468
+ ``depth + 1``) never reuses an ancestor's.
469
+
470
+ Also for the same reason ``_thf_implies_at`` gives: this deliberately
471
+ parallels :mod:`unicode_logic_kit.hol.ho_modal`'s plain ``f"W{depth}"`` world
472
+ binders, not :func:`~unicode_logic_kit.hol._ho_common.bound_token`, which
473
+ renames a USER-level binder apart from other same-named user binders in
474
+ scope -- a case that cannot arise here, since :func:`_thf_encode` rejects
475
+ every node with a user-level binder (``Quantifier``/``Variable``) before
476
+ reaching a ``Would``. ``S{depth}``/``U{depth}`` name this embedding's OWN
477
+ sphere/world quantifiers, the same role ``ho_modal.py``'s ``W{depth}``
478
+ plays for its own.
479
+ """
480
+ s, u = f"S{depth}", f"U{depth}"
481
+ left_at_u = _thf_encode(left, u, names, depth + 1)
482
+ right_at_u = _thf_encode(right, u, names, depth + 1)
483
+ nonvacuous = (f"( ? [{s}: w > $o] : "
484
+ f"( ( sel @ {world} @ {s} ) "
485
+ f"& ( ? [{u}: w] : ( ( {s} @ {u} ) & {left_at_u} ) ) "
486
+ f"& ( ! [{u}: w] : ( ( {s} @ {u} ) => "
487
+ f"( {left_at_u} => {right_at_u} ) ) ) ) )")
488
+ vacuous = (f"( ! [{s}: w > $o] : ( ( sel @ {world} @ {s} ) => "
489
+ f"( ! [{u}: w] : ( ( {s} @ {u} ) => ( ~ {left_at_u} ) ) ) ) )")
490
+ return f"( {nonvacuous} | {vacuous} )"
491
+
492
+
493
+ def _thf_encode(formula: Node, world: str, names: ThfNames, depth: int = 0) -> str:
494
+ """Recursive worker for :func:`to_thf_conditional`: ``formula`` rendered TRUE AT ``world``.
495
+
496
+ Mirrors :func:`_encode`'s own recursion (same refusals, same ``Xor``/
497
+ ``Might`` desugaring) but threads ``world`` instead of building a
498
+ free-standing ``tau`` term -- see the comment above ``_THF_PRELUDE``.
499
+ """
500
+ if isinstance(formula, Atom):
501
+ constant = _truth_value(formula)
502
+ if constant is not None:
503
+ return "( $true )" if constant else "( $false )" # the same at every world
504
+ reject_equality(formula, "to_thf_conditional", _EQUALITY_ROUTE,
505
+ atom_reading=_EQUALITY_ATOM_READING,
506
+ instead=_EQUALITY_INSTEAD)
507
+ if not _atom_is_propositional(formula):
508
+ raise NotImplementedError(
509
+ "to_thf_conditional: atom with a free variable is first-order; "
510
+ "the sphere embedding is propositional.")
511
+ functor = names.functor("predicate", key_text(formula))
512
+ return f"( {functor} @ {world} )"
513
+ if isinstance(formula, (Quantifier, Variable)):
514
+ raise NotImplementedError(
515
+ "to_thf_conditional: quantifiers/variables are first-order; the "
516
+ "sphere embedding is propositional.")
517
+ if isinstance(formula, Not):
518
+ return f"( ~ {_thf_encode(formula.formula, world, names, depth)} )"
519
+ if isinstance(formula, And):
520
+ return (f"( {_thf_encode(formula.left, world, names, depth)} & "
521
+ f"{_thf_encode(formula.right, world, names, depth)} )")
522
+ if isinstance(formula, Or):
523
+ return (f"( {_thf_encode(formula.left, world, names, depth)} | "
524
+ f"{_thf_encode(formula.right, world, names, depth)} )")
525
+ if isinstance(formula, Implies):
526
+ return (f"( {_thf_encode(formula.left, world, names, depth)} => "
527
+ f"{_thf_encode(formula.right, world, names, depth)} )")
528
+ if isinstance(formula, Iff):
529
+ return (f"( {_thf_encode(formula.left, world, names, depth)} <=> "
530
+ f"{_thf_encode(formula.right, world, names, depth)} )")
531
+ if isinstance(formula, Xor):
532
+ return (f"( ~ ( {_thf_encode(formula.left, world, names, depth)} <=> "
533
+ f"{_thf_encode(formula.right, world, names, depth)} ) )")
534
+ if isinstance(formula, Would):
535
+ return _thf_would_at(formula.left, formula.right, world, names, depth)
536
+ if isinstance(formula, Might):
537
+ # ◇→ is the dual ¬(A □→ ¬B) -- no case of its own, matching
538
+ # _encode's derivation of it exactly.
539
+ return f"( ~ {_thf_would_at(formula.left, Not(formula.right), world, names, depth)} )"
540
+ raise NotImplementedError(
541
+ f"to_thf_conditional: unsupported node type "
542
+ f"{type(formula).__name__}. The sphere semantics reads a similarity "
543
+ "ordering, not an accessibility relation, so the modal operators "
544
+ "belong to hol.thf_modal instead.")
545
+
546
+
547
+ def to_thf_conditional(formula: Node, *, centering: str = "weak") -> str:
548
+ r"""Emit a self-contained THF (TH0) problem asserting ``formula``'s counterfactual validity.
549
+
550
+ The THF sibling of :func:`isabelle_conditional_theory`: the same shallow
551
+ embedding over the sphere system ``Sel``, for a higher-order ATP (Leo-III,
552
+ Vampire-THF, Satallax) instead of Isabelle. The conjecture is
553
+ ``nested => <centering premise> => ![X:w] : φ_at(X)`` -- validity over
554
+ every nested sphere system meeting the requested centering level, matching
555
+ :func:`isabelle_conditional_theory`'s goal and
556
+ :func:`unicode_logic_kit.semantics.conditional.cf_valid` (see the comment
557
+ above ``_THF_PRELUDE`` for why ``φ_at(X)`` is rendered inline rather than
558
+ as a combinator applied to ``X``, and why the centering premises are
559
+ nullary). As elsewhere in the toolkit, this function only emits the
560
+ problem; it does not run a prover.
561
+
562
+ Args:
563
+ formula: the AST node to encode -- ``¬ ∧ ∨ → ↔ □→ ◇→`` over nullary
564
+ propositional atoms (``Xor`` desugars to ``¬(A ↔ B)``, ``◇→`` to
565
+ ``¬(A □→ ¬B)``, exactly as :func:`to_isabelle_conditional` derives
566
+ them).
567
+ centering: ``"none"`` (V) / ``"weak"`` (VW, default) / ``"strong"``
568
+ (VC) -- see :func:`isabelle_conditional_theory`. The default
569
+ matches :func:`~unicode_logic_kit.semantics.conditional.cf_valid`'s.
570
+
571
+ Raises:
572
+ ValueError: on an unknown ``centering`` level.
573
+ NotImplementedError: propagated from :func:`_thf_encode` -- a
574
+ quantifier, a first-order atom, or a modal operator, mirroring
575
+ :func:`to_isabelle_conditional`'s own refusal.
576
+ """
577
+ check_centering(centering)
578
+ names = ThfNames(reserved=_THF_RESERVED)
579
+ body = _thf_encode(formula, "X", names)
580
+ AtomKeys("to_thf_conditional", "refuse").letters([formula])
581
+ lines = list(_THF_PRELUDE)
582
+ for label in sorted({key_text(atom) for atom in formula.atoms()
583
+ if _truth_value(atom) is None}):
584
+ functor = names.functor("predicate", label)
585
+ lines.append(f"thf({functor}_type, type, ( {functor} : w > $o )).")
586
+ conclusion = f"( ! [X: w] : {body} )"
587
+ extra = _THF_CENTERING_PREMISE[centering]
588
+ if extra is None:
589
+ goal_body = f"( nested => {conclusion} )"
590
+ else:
591
+ goal_body = f"( nested => ( {extra} => {conclusion} ) )"
592
+ lines.append(f"thf(goal, conjecture, {goal_body}).")
593
+ return "\n".join(lines) + "\n"