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,941 @@
1
+ """Full-family modal THF export — a Benzmüller-style higher-order shallow embedding.
2
+
3
+ This module extends :func:`unicode_logic_kit.fol.qml.to_thf_modal` (which only handles
4
+ the *alethic* ``□`` / ``◇`` fragment plus object quantifiers and **raises** on the
5
+ other modalities) to the **full modal family** the toolkit's AST can express:
6
+
7
+ - alethic ``□φ`` / ``◇φ`` over relation ``r``;
8
+ - epistemic ``K_a φ`` (``Knows(agent, φ)``) over an AGENT-INDEXED ``rk``;
9
+ - doxastic ``B_a φ`` (``Believes(agent, φ)``) over an AGENT-INDEXED ``rb``;
10
+ - assertive ``Say_a φ`` (``Says(agent, φ)``) over an AGENT-INDEXED ``rs``;
11
+ - bouletic ``Want_a φ`` (``Wants(agent, φ)``) over an AGENT-INDEXED ``rw``;
12
+ - deontic ``Oφ`` / ``Pφ`` over a SERIAL relation ``d``;
13
+ - temporal ``Gφ`` / ``Fφ`` / ``Xφ`` over ``t`` / ``tnext``, with the
14
+ past mirrors ``⒣`` / ``⒫`` / ``⒴`` over the CONVERSE relations and strong
15
+ ``Ⓤ`` / ``⒮`` as impredicative least fixpoints (``muntil`` / ``msince``);
16
+ - hybrid nominals ``i`` and ``@i φ`` as ``mu`` world constants.
17
+
18
+ It is a genuine higher-order shallow embedding (SSE): a modal proposition is a
19
+ function ``mu > $o`` (world → bool), the modalities are λ-lifted quantifiers over the
20
+ relevant accessibility relation, and object quantifiers are ``existsAt``-guarded
21
+ (actualist). The emitted THF problem is **complete and self-contained** — type
22
+ declarations, every lifted operator, frame axioms (per relation where relevant),
23
+ domain axioms, and the conjecture ``mvalid @ ⟨formula⟩`` — ready for a higher-order
24
+ ATP (Leo-III, Satallax). As elsewhere in the toolkit, this module only *emits* the
25
+ problem; it does **not** run any prover.
26
+
27
+ Faithfulness to :func:`unicode_logic_kit.semantics.kripke.satisfies_modal`:
28
+
29
+ - epistemic/doxastic accessibility is **agent-indexed**. The agent of ``Knows`` /
30
+ ``Believes`` is a first-class TERM (Variable or Constant). In THF it becomes a real
31
+ ``$i`` argument of the relation: ``rk : $i > mu > mu > $o`` and
32
+ ``mknows = ^[A:$i, Phi:mu>$o, W:mu] : ![V:mu] : (rk @ A @ W @ V) => Phi @ V``. A bound
33
+ object variable in agent position (``∀x (Student(x) → K_x φ)``) is bound by the
34
+ embedding's ``mforall`` (which binds an ``$i`` variable), so it correctly quantifies
35
+ over agents — exactly mirroring ``satisfies_modal``'s per-agent ``"K:"+agent`` /
36
+ ``"B:"+agent`` relations. Named agents become ordinary ``$i`` constants (the same
37
+ individual sort as object-quantifier variables), so an agent can also be an object.
38
+ - deontic ``O`` / ``P`` are a box / diamond over a **serial** relation ``d`` (Standard
39
+ Deontic Logic, KD), matching the evaluator's serial ``"deontic"`` relation; the
40
+ serial axiom is always emitted for ``d``.
41
+ - temporal ``G`` / ``F`` / ``X`` are box / diamond / box over ``t`` / ``tnext`` — but
42
+ **note** the one-step-vs-closure caveat below.
43
+ - equality ``=`` / ``≠`` is **rigid identity**, THF's own ``=`` with no world argument —
44
+ the reading of :func:`unicode_logic_kit.fol.qml.qml_is_valid` (and of
45
+ :func:`unicode_logic_kit.fol.qml.to_thf_modal`, byte-compatible on the conjecture).
46
+ See "Equality" below. (``satisfies_modal`` has no term semantics and REFUSES an
47
+ equality atom by name; it is not the oracle for identity.)
48
+
49
+ Equality
50
+ --------
51
+ An identity atom ``t₁ = t₂`` is lifted through one extra macro, ``meq``, emitted only
52
+ when the formula contains identity:
53
+ ``meq = ^ [A: $i, B: $i, W: mu] : ( A = B )`` — THF's native ``=`` over the individual
54
+ sort ``$i``, with a world binder ``W`` the body never mentions, so it takes **no
55
+ world argument** and cannot vary by world. ``t₁ ≠ t₂`` is lowered to ``¬(t₁ = t₂)``
56
+ first, exactly as :func:`unicode_logic_kit.fol.qml.qml_translate` does (the lowering is
57
+ :func:`unicode_logic_kit.fol.qml._st_equality` itself, reached through
58
+ :func:`unicode_logic_kit.hol.isabelle_modal._lower_identity`), and a non-binary ``=`` /
59
+ ``≠`` atom raises ``ValueError``, as in ``qml``. Nothing is declared for identity — no
60
+ ``feq`` / ``fneq`` functor exists in the emitted problem — so a prover gets
61
+ reflexivity, symmetry, transitivity and congruence (hence Leibniz's law, for ``mbox``
62
+ too) from its own equality, not from an axiom.
63
+
64
+ Identity ranges over the whole individual sort ``$i`` and is **not** existence-guarded
65
+ (``qml``'s documented varying-domain choice): ``a = a`` is a theorem at a world where
66
+ ``a`` does not exist, whereas ``∃x (x = c)`` — which goes through the
67
+ ``existsAt``-guarded ``mexists`` — is a theorem under ``constant`` / ``possibilist``
68
+ and not under the actualist modes. ``a = b → □(a = b)``, ``a ≠ b → □(a ≠ b)`` and
69
+ ``◇(a = b) → a = b`` are theorems under ``frame='K'``; ``□(a = b) → a = b`` is NOT
70
+ (a dead-end world) and is one exactly when the frame is reflexive or serial. The
71
+ ordering atoms ``<`` ``>`` ``≤`` ``≥`` remain ordinary world-relativized predicates, as in
72
+ ``qml``.
73
+
74
+ CAVEAT — temporal closure. ``satisfies_modal`` reads ``G``/``F`` over the
75
+ *reflexive-transitive closure* of the temporal relation, and ``X`` over the immediate
76
+ successors. A first-order / higher-order shallow embedding cannot express transitive
77
+ closure (it is not first-order definable). This module therefore embeds:
78
+
79
+ - ``Xφ`` as a box over the **one-step** relation ``tnext`` — faithful to
80
+ ``satisfies_modal`` on the *universal* "all next states" reading of ``Next``;
81
+ - ``Gφ`` / ``Fφ`` as box / diamond over ``t`` **made reflexive and transitive** by the
82
+ emitted ``t_refl`` / ``t_trans`` frame axioms. On a frame whose ``t`` is already its
83
+ own reflexive-transitive closure the embedding coincides with the evaluator; in
84
+ general the embedding's ``G``/``F`` quantify over the *transitive closure of the
85
+ emitted ``t``*, which is the standard SSE rendering of LTL ``G``/``F`` but is only an
86
+ **approximation** of ``satisfies_modal``'s closure over the caller's raw temporal
87
+ edges. ``Until`` / ``Since`` **are supported**: TH0 quantifies over predicates, so
88
+ strong Until/Since embed directly as the impredicative Knaster–Tarski least
89
+ fixpoints ``muntil`` / ``msince`` over the one-step ``tnext`` — the same least
90
+ fixpoint Isabelle's ``inductive muntil`` compiles to.
91
+
92
+ The two temporal relations are **linked** by the emitted ``tnext_in_t`` inclusion
93
+ axiom ``![W,V]: (tnext @ W @ V) => (t @ W @ V)``. In ``satisfies_modal`` ``X`` reads
94
+ the immediate successors of the *single* ``"temporal"`` relation while ``G``/``F``
95
+ read its reflexive-transitive closure, so every one-step ``X``-successor is reachable
96
+ by ``G``/``F``; the inclusion axiom reproduces exactly that containment in the
97
+ embedding. Without it ``Gφ→Xφ`` — valid for the evaluator — was a non-theorem of the
98
+ emitted problem (``t`` and ``tnext`` were unconstrained relative to each other); with
99
+ it ``Gφ→Xφ`` (and ``Xφ→Fφ`` wherever the evaluator agrees) is now a theorem of the
100
+ embedding.
101
+
102
+ Cross-family bridges (``bridges=``). ``frame=`` and ``systems=`` each constrain ONE
103
+ relation; a *bridge* relates two relations of different families and is therefore a
104
+ separate, opt-in option — ``knowledge_implies_belief`` (``K_a φ → B_a φ``, condition
105
+ ``rb ⊆ rk``, axiom ``rb_in_rk``), ``sincerity`` (``Say_a φ → B_a φ``, ``rb ⊆ rs``,
106
+ ``rb_in_rs``) and ``ought_implies_can`` (``Oφ → ◇φ``, ``∀W ∃V. d W V ∧ r W V``,
107
+ ``d_meets_r``). The names, the required families and the axiom names are
108
+ single-sourced from :mod:`unicode_logic_kit.hol.isabelle_modal`, so the two HOL routes
109
+ cannot drift apart. Each condition is the *exact* correspondent of its schema,
110
+ measured against :func:`satisfies_modal` over every frame on ≤ 2 worlds — in
111
+ particular ``ought_implies_can`` is deliberately NOT the folklore inclusion
112
+ ``d ⊆ r``, which on its own does not validate ``Oφ → ◇φ`` at all and which, with
113
+ seriality, additionally validates the strictly stronger ``□φ → Oφ``. Requesting a
114
+ bridge whose partner family does not occur in the formula raises ``ValueError``
115
+ here too — see :func:`_thf_bridge_axioms` for why this route refuses even though
116
+ its relations are all declared unconditionally.
117
+
118
+ UNDECIDABILITY / honesty. First-order modal logic is **undecidable** — though the
119
+ standard systems emitted here (K/T/S4/S5, deontic KD) are still *semi-decidable*:
120
+ validity is recursively enumerable. (Higher-order modal logic and full second-order
121
+ logic are *not even semi-decidable*.) "Emit a problem a prover may discharge" is the
122
+ honest claim — never "decide every instance". The propositional
123
+ fragments (modal K/T/S4/S5, deontic KD) are decidable, but this export targets the
124
+ quantified family, so a ``Theorem`` verdict depends on an external HOL ATP and is not
125
+ guaranteed to terminate.
126
+
127
+ Public API: :func:`to_thf_modal_full`, plus the introspection helpers
128
+ :func:`thf_full_definitions` and :func:`thf_full_frame_axioms`.
129
+ """
130
+
131
+ from typing import Dict, List, Optional, Sequence, Tuple
132
+
133
+ from ..fol.nodes import (
134
+ Node, Variable, Constant, Number, Function,
135
+ Atom, Not, And, Or, Xor, Implies, Iff, Quantifier, Contrast,
136
+ Box, Diamond, Knows, Believes, Says, Wants,
137
+ Obligatory, Permitted, Always, Eventually, Next, Until, Since,
138
+ Historically, Once, Previous, Nominal, At,
139
+ SortedQuantifier,
140
+ )
141
+ # Down (the ↓ binder, N1) is not yet re-exported through fol.nodes / fol's
142
+ # public __init__ / the top-level unicode_logic_kit package (that three-file
143
+ # edit is outside this change's file ownership — see the change's own
144
+ # report); imported directly from its defining module in the meantime, the
145
+ # same class object either import path would give.
146
+ from ..fol._hybrid_nodes import Down
147
+
148
+ # Re-use qml.py's THF helpers verbatim so the two exports stay byte-compatible on the
149
+ # overlapping (alethic + object-quantifier + equality) fragment.
150
+ from ..fol.qml import (
151
+ _FRAMES, _CONSTANT_MODES, _ACTUALIST_MODES,
152
+ _THF_FRAME, _THF_DOMAIN, _THF_PRED_ALIAS, _THF_RESERVED,
153
+ _thf_name, _thf_term, _thf_signature, _ThfNames,
154
+ _FORALL, _EQUALITY_PREDICATES,
155
+ )
156
+ from ..fol._free_parameters import free_parameter_names
157
+ from ..fol._numeral_symbols import numerals_as_constants, prefixed_numeral_name
158
+ from ..fol._symbol_names import dedupe
159
+ from ..fol._truth_constants import truth_value
160
+ from ..fol._msfl_nodes import nonempty_sort_axioms, sort_membership_axioms
161
+
162
+ # The cross-family bridge REGISTRY is single-sourced from the Isabelle route: the
163
+ # names, the families each bridge needs, and the fact names are shared, so the two
164
+ # HOL routes cannot answer differently on the same input (the divergence this
165
+ # option exists to remove). Only the axiom TEXT is route-local (_THF_BRIDGE_LINES).
166
+ from .isabelle_modal import (
167
+ BRIDGES, _BRIDGES as _BRIDGE_SPEC, _validate_bridges,
168
+ _has_identity, _lower_identity,
169
+ )
170
+
171
+ __all__ = ["to_thf_modal_full", "thf_full_definitions", "thf_full_frame_axioms",
172
+ "BRIDGES"]
173
+
174
+
175
+ # Accessibility-relation THF functors, one per modal family. Alethic ``r`` keeps the
176
+ # qml.py name so the alethic fragment is identical across both exports.
177
+ _R_ALETHIC = "r" # mu > mu > $o
178
+ _R_KNOWS = "rk" # $i > mu > mu > $o (AGENT-indexed)
179
+ _R_BELIEVES = "rb" # $i > mu > mu > $o (AGENT-indexed)
180
+ _R_DEONTIC = "d" # mu > mu > $o (serial)
181
+ _R_TEMPORAL = "t" # mu > mu > $o (G/F)
182
+ _R_NEXT = "tnext" # mu > mu > $o (X, one-step)
183
+ _R_SAYS = "rs" # $i > mu > mu > $o (AGENT-indexed, Says, plain K)
184
+ _R_WANTS = "rw" # $i > mu > mu > $o (AGENT-indexed, Wants, plain K)
185
+
186
+ # Every fixed functor of the FULL export (qml's core set plus the extra relations
187
+ # and lifted operators of this module). A user symbol sanitising onto one of
188
+ # these is pushed to a suffixed variant by the resolver — a predicate literally
189
+ # named "t" or "muntil" must not silently re-declare a built-in at another type.
190
+ _THF_RESERVED_FULL = _THF_RESERVED | frozenset({
191
+ _R_KNOWS, _R_BELIEVES, _R_DEONTIC, _R_TEMPORAL, _R_NEXT, _R_SAYS, _R_WANTS,
192
+ "mknows", "mbelieves", "mobl", "mperm", "malways", "meventually", "mnext",
193
+ "msays", "mwants", "mhistorically", "monce", "mprevious", "muntil", "msince",
194
+ })
195
+
196
+
197
+ # ---------------------------------------------------------------------------
198
+ # Lifted operators (the SSE macro definitions).
199
+ # ---------------------------------------------------------------------------
200
+ #
201
+ # The classical / alethic / object-quantifier / mvalid macros are copied from
202
+ # qml.py's _THF_DEFS (kept inline rather than imported so the emitted block is a
203
+ # single self-contained string and so adding the extra families does not depend on
204
+ # qml.py's private layout). The new macros are:
205
+ #
206
+ # mknows / mbelieves : agent-indexed box, AGENT is a real $i argument so a bound
207
+ # object variable in agent position quantifies over agents.
208
+ # mobl / mperm : deontic box / diamond over the serial relation d.
209
+ # malways / meventually : temporal box / diamond over t (refl-trans).
210
+ # mnext : temporal box over the one-step relation tnext.
211
+ _THF_DEFS_FULL = """\
212
+ thf(mnot, definition, ( mnot = ( ^ [Phi: mu>$o, W: mu] : ~ ( Phi @ W ) ) )).
213
+ thf(mand, definition, ( mand = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ( ( Phi @ W ) & ( Psi @ W ) ) ) )).
214
+ thf(mor, definition, ( mor = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ( ( Phi @ W ) | ( Psi @ W ) ) ) )).
215
+ thf(mimplies, definition, ( mimplies = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ( ( Phi @ W ) => ( Psi @ W ) ) ) )).
216
+ thf(mequiv, definition, ( mequiv = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ( ( Phi @ W ) <=> ( Psi @ W ) ) ) )).
217
+ thf(mbox, definition, ( mbox = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( r @ W @ V ) => ( Phi @ V ) ) ) )).
218
+ thf(mdia, definition, ( mdia = ( ^ [Phi: mu>$o, W: mu] : ? [V: mu] : ( ( r @ W @ V ) & ( Phi @ V ) ) ) )).
219
+ thf(mknows, definition, ( mknows = ( ^ [A: $i, Phi: mu>$o, W: mu] : ! [V: mu] : ( ( rk @ A @ W @ V ) => ( Phi @ V ) ) ) )).
220
+ thf(mbelieves, definition, ( mbelieves = ( ^ [A: $i, Phi: mu>$o, W: mu] : ! [V: mu] : ( ( rb @ A @ W @ V ) => ( Phi @ V ) ) ) )).
221
+ thf(mobl, definition, ( mobl = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( d @ W @ V ) => ( Phi @ V ) ) ) )).
222
+ thf(mperm, definition, ( mperm = ( ^ [Phi: mu>$o, W: mu] : ? [V: mu] : ( ( d @ W @ V ) & ( Phi @ V ) ) ) )).
223
+ thf(malways, definition, ( malways = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( t @ W @ V ) => ( Phi @ V ) ) ) )).
224
+ thf(meventually, definition, ( meventually = ( ^ [Phi: mu>$o, W: mu] : ? [V: mu] : ( ( t @ W @ V ) & ( Phi @ V ) ) ) )).
225
+ thf(mnext, definition, ( mnext = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( tnext @ W @ V ) => ( Phi @ V ) ) ) )).
226
+ thf(msays, definition, ( msays = ( ^ [A: $i, Phi: mu>$o, W: mu] : ! [V: mu] : ( ( rs @ A @ W @ V ) => ( Phi @ V ) ) ) )).
227
+ thf(mwants, definition, ( mwants = ( ^ [A: $i, Phi: mu>$o, W: mu] : ! [V: mu] : ( ( rw @ A @ W @ V ) => ( Phi @ V ) ) ) )).
228
+ thf(mhistorically, definition, ( mhistorically = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( t @ V @ W ) => ( Phi @ V ) ) ) )).
229
+ thf(monce, definition, ( monce = ( ^ [Phi: mu>$o, W: mu] : ? [V: mu] : ( ( t @ V @ W ) & ( Phi @ V ) ) ) )).
230
+ thf(mprevious, definition, ( mprevious = ( ^ [Phi: mu>$o, W: mu] : ! [V: mu] : ( ( tnext @ V @ W ) => ( Phi @ V ) ) ) )).
231
+ thf(muntil, definition, ( muntil = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ! [S: mu>$o] : ( ( ( ! [V: mu] : ( ( Psi @ V ) => ( S @ V ) ) ) & ( ! [V: mu, U: mu] : ( ( ( Phi @ V ) & ( tnext @ V @ U ) & ( S @ U ) ) => ( S @ V ) ) ) ) => ( S @ W ) ) ) )).
232
+ thf(msince, definition, ( msince = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : ! [S: mu>$o] : ( ( ( ! [V: mu] : ( ( Psi @ V ) => ( S @ V ) ) ) & ( ! [V: mu, U: mu] : ( ( ( Phi @ V ) & ( tnext @ U @ V ) & ( S @ U ) ) => ( S @ V ) ) ) ) => ( S @ W ) ) ) )).
233
+ thf(mforall, definition, ( mforall = ( ^ [Phi: $i>(mu>$o), W: mu] : ! [X: $i] : ( ( existsAt @ X @ W ) => ( Phi @ X @ W ) ) ) )).
234
+ thf(mexists, definition, ( mexists = ( ^ [Phi: $i>(mu>$o), W: mu] : ? [X: $i] : ( ( existsAt @ X @ W ) & ( Phi @ X @ W ) ) ) )).
235
+ thf(mvalid, definition, ( mvalid = ( ^ [Phi: mu>$o] : ! [W: mu] : ( Phi @ W ) ) )).\
236
+ """
237
+
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Rigid identity.
241
+ # ---------------------------------------------------------------------------
242
+ #
243
+ # Object identity is NOT a world-indexed predicate (fol.qml's "Equality is rigid"): it
244
+ # is THF's own `=` over `$i`, lifted by a macro whose world binder is unused. The macro
245
+ # is emitted only when the formula contains identity, so an equality-free problem is
246
+ # byte-for-byte what it was before; `to_thf_modal` (fol.qml) emits the SAME line, so
247
+ # the two exports stay byte-compatible on the alethic + equality fragment.
248
+ _THF_RIGID_EQ = "meq"
249
+ _THF_RIGID_EQ_DEF = ("thf(meq, definition, "
250
+ "( meq = ( ^ [A: $i, B: $i, W: mu] : ( A = B ) ) )).")
251
+
252
+
253
+ class _RigidNames(_ThfNames):
254
+ """:class:`~unicode_logic_kit.fol.qml._ThfNames` that reads ``=`` as the ``meq`` macro.
255
+
256
+ Identity is not a predicate: it gets no entry in ``pred`` (so ``_thf_signature``
257
+ declares no ``feq``) and ``atom`` names the macro. ``meq`` is reserved only when the
258
+ formula contains identity, so a user predicate / constant / function literally named
259
+ ``meq`` is pushed to ``meq_2`` exactly then and not otherwise. (As before, the
260
+ resolver still lets ``=`` / ``≠`` claim ``feq`` / ``fneq`` first, which only
261
+ matters to a user symbol spelled that way; it stays unique.)
262
+ """
263
+
264
+ #: Nominal name -> its world constant, filled by :func:`_resolve_names`.
265
+ nominal: Dict[str, str]
266
+
267
+ def __init__(self, formula: Node, reserved=_THF_RESERVED):
268
+ if _has_identity(formula):
269
+ reserved = frozenset(reserved) | {_THF_RIGID_EQ}
270
+ super().__init__(formula, reserved=reserved)
271
+ for key in [k for k in self.pred if k[0] in _EQUALITY_PREDICATES]:
272
+ del self.pred[key]
273
+
274
+ def atom(self, node: Atom) -> str:
275
+ if node.predicate == "=":
276
+ return _THF_RIGID_EQ
277
+ return super().atom(node)
278
+
279
+
280
+ # Frame axioms for the *temporal* relation t. G/F are read over a reflexive-transitive
281
+ # t (see the module-level caveat), so t carries refl+trans; X uses a separate one-step
282
+ # relation tnext. The `tnext ⊆ t` inclusion links the two relations: every one-step
283
+ # successor (over which X quantifies) is reachable by the henceforth relation t (over
284
+ # which G/F quantify). Without it Gφ→Xφ — VALID for satisfies_modal, where X's
285
+ # immediate "temporal" successors are a subset of G's reflexive-transitive closure of
286
+ # the *same* "temporal" relation — would be a non-theorem of the embedding (t and tnext
287
+ # would be unconstrained relative to each other). With it Gφ→Xφ (and Xφ→Fφ wherever the
288
+ # oracle agrees) becomes a theorem, aligning the embedding with the evaluator.
289
+ _THF_TEMPORAL_AXIOMS = [
290
+ "thf(t_refl, axiom, ( ! [W: mu] : ( t @ W @ W ) )).",
291
+ "thf(t_trans, axiom, ( ! [W: mu, V: mu, U: mu] : "
292
+ "( ( ( t @ W @ V ) & ( t @ V @ U ) ) => ( t @ W @ U ) ) )).",
293
+ "thf(tnext_in_t, axiom, ( ! [W: mu, V: mu] : "
294
+ "( ( tnext @ W @ V ) => ( t @ W @ V ) ) )).",
295
+ ]
296
+
297
+ # Deontic d is serial (Standard Deontic Logic / KD): every world has a d-successor.
298
+ _THF_DEONTIC_AXIOM = "thf(d_serial, axiom, ( ! [W: mu] : ? [V: mu] : ( d @ W @ V ) ))."
299
+
300
+
301
+ # Optional per-system frame axioms for the AGENT-INDEXED epistemic/doxastic relations.
302
+ # Each is universally quantified over the agent A as well as the worlds, so the
303
+ # property holds per agent — matching the per-agent relations of satisfies_modal.
304
+ # "S5" (refl+trans+sym) is the usual logic of knowledge; "KD45" (serial+trans+eucl)
305
+ # the usual logic of belief.
306
+ def _agent_frame_axioms(rel: str, conds: Sequence[str], tag: str) -> List[str]:
307
+ """THF frame axioms for an agent-indexed relation ``rel`` (each ∀ over the agent A).
308
+
309
+ Raises on a condition with no per-agent schema (Löb / directed / connected):
310
+ silently dropping one would emit a WEAKER logic than the caller requested.
311
+ """
312
+ unsupported = [c for c in conds
313
+ if c not in ("refl", "trans", "sym", "serial", "eucl")]
314
+ if unsupported:
315
+ raise NotImplementedError(
316
+ f"to_thf_modal_full: the requested agent-indexed system needs the frame "
317
+ f"condition(s) {unsupported}, which have no per-agent axiom schema here; "
318
+ "use frame= on the alethic relation for those systems.")
319
+ out: List[str] = []
320
+ if "refl" in conds:
321
+ out.append(f"thf({tag}_refl, axiom, ( ! [A: $i, W: mu] : ( {rel} @ A @ W @ W ) )).")
322
+ if "trans" in conds:
323
+ out.append(
324
+ f"thf({tag}_trans, axiom, ( ! [A: $i, W: mu, V: mu, U: mu] : "
325
+ f"( ( ( {rel} @ A @ W @ V ) & ( {rel} @ A @ V @ U ) ) "
326
+ f"=> ( {rel} @ A @ W @ U ) ) )).")
327
+ if "sym" in conds:
328
+ out.append(
329
+ f"thf({tag}_sym, axiom, ( ! [A: $i, W: mu, V: mu] : "
330
+ f"( ( {rel} @ A @ W @ V ) => ( {rel} @ A @ V @ W ) ) )).")
331
+ if "serial" in conds:
332
+ out.append(
333
+ f"thf({tag}_serial, axiom, ( ! [A: $i, W: mu] : ? [V: mu] : "
334
+ f"( {rel} @ A @ W @ V ) )).")
335
+ if "eucl" in conds:
336
+ out.append(
337
+ f"thf({tag}_eucl, axiom, ( ! [A: $i, W: mu, V: mu, U: mu] : "
338
+ f"( ( ( {rel} @ A @ W @ V ) & ( {rel} @ A @ W @ U ) ) "
339
+ f"=> ( {rel} @ A @ V @ U ) ) )).")
340
+ return out
341
+
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # Cross-family bridge axioms (opt-in, ``bridges=``).
345
+ # ---------------------------------------------------------------------------
346
+ #
347
+ # Only the axiom TEXT lives here; the bridge names and the families each one needs
348
+ # come from hol.isabelle_modal's registry, so `grep rb_in_rk` finds both routes and
349
+ # neither can gain a bridge the other lacks. The TPTP formula names are the SAME
350
+ # identifiers as the Isabelle fact names (rb_in_rk / rb_in_rs / d_meets_r) and
351
+ # cannot collide with the signature declarations, which all carry a `_decl` suffix.
352
+ #
353
+ # Types check out unconditionally: rk / rb / rs are `$i > mu > mu > $o` and d / r
354
+ # are `mu > mu > $o`, and this route declares every relation whether or not its
355
+ # operators occur (see the comment at the declaration block), so a bridge axiom is
356
+ # always WELL-FORMED here. That is exactly why the refusal below is a policy
357
+ # decision rather than a syntactic necessity.
358
+ _THF_BRIDGE_LINES = {
359
+ "knowledge_implies_belief": [
360
+ "thf(rb_in_rk, axiom, ( ! [A: $i, W: mu, V: mu] : "
361
+ "( ( rb @ A @ W @ V ) => ( rk @ A @ W @ V ) ) ))."],
362
+ "sincerity": [
363
+ "thf(rb_in_rs, axiom, ( ! [A: $i, W: mu, V: mu] : "
364
+ "( ( rb @ A @ W @ V ) => ( rs @ A @ W @ V ) ) ))."],
365
+ "ought_implies_can": [
366
+ "thf(d_meets_r, axiom, ( ! [W: mu] : ? [V: mu] : "
367
+ "( ( d @ W @ V ) & ( r @ W @ V ) ) ))."],
368
+ }
369
+
370
+
371
+ def _thf_bridge_axioms(used: Dict[str, bool], bridges) -> List[str]:
372
+ """THF axioms for the requested cross-family bridges, in registry order.
373
+
374
+ Each is the exact frame correspondent of the schema its option is named after
375
+ (``rb ⊆ rk``, ``rb ⊆ rs``, ``∀W ∃V. d W V ∧ r W V``), measured — not the
376
+ folklore ``d ⊆ r``, which fails to validate ``Oφ → ◇φ`` alone and, with
377
+ seriality, over-validates ``□φ → Oφ``. Emission order follows the shared
378
+ registry, not the caller's set-iteration order, so the output is deterministic.
379
+
380
+ Honest contract — why this route REFUSES a bridge whose partner family does not
381
+ occur in the formula, even though it could emit it. Unlike the Isabelle route,
382
+ every relation here is declared unconditionally (the ``_THF_DEFS_FULL`` macro
383
+ block references them all), so ``d_meets_r`` for a ``□``-only formula would be
384
+ perfectly well-formed THF. Emitting it anyway would nonetheless be wrong twice
385
+ over: it would make this route DISAGREE with
386
+ :func:`~unicode_logic_kit.hol.isabelle_modal.to_isabelle_modal` on the same input
387
+ — the exact divergence class the ``bridges=`` option exists to remove — and it
388
+ is not conservative, since ``d_meets_r`` entails seriality of the alethic ``r``
389
+ and so silently turns ``□P → ◇P`` from a non-theorem into a theorem under
390
+ ``frame='K'``. Silently skipping it would be worse still (a weaker logic than
391
+ requested, with no signal). So: ``ValueError``, same wording as the Isabelle
392
+ route modulo the function name.
393
+ """
394
+ requested = _validate_bridges(bridges, "to_thf_modal_full")
395
+ if not requested:
396
+ return []
397
+ out: List[str] = []
398
+ for name, spec in _BRIDGE_SPEC.items():
399
+ if name not in requested:
400
+ continue
401
+ missing = [op for fam, op in spec["needs"] if not used[fam]]
402
+ if missing:
403
+ raise ValueError(
404
+ f"to_thf_modal_full: the bridge {name!r} relates {spec['rels']}, "
405
+ f"but the formula contains no {' / '.join(missing)} operator. This "
406
+ "route declares every relation unconditionally, so the axiom would "
407
+ "be well-formed here — but emitting it would make this route "
408
+ "disagree with hol.isabelle_modal on the same input (the exact "
409
+ "divergence this option exists to remove), and it is not "
410
+ "conservative either (d_meets_r entails seriality of the alethic "
411
+ "r, which would silently make []P -> <>P valid under frame='K'); "
412
+ "while skipping it would emit a weaker logic than requested. Drop "
413
+ "the bridge, or state the formula in both families.")
414
+ out += _THF_BRIDGE_LINES[name]
415
+ return out
416
+
417
+
418
+ # ---------------------------------------------------------------------------
419
+ # Lifting the formula to a THF term of type mu > $o.
420
+ # ---------------------------------------------------------------------------
421
+ def _lift(node: Node, names: "_RigidNames") -> str:
422
+ """Render a full-family modal formula as a THF term of type ``mu > $o``.
423
+
424
+ Classical connectives, alethic □/◇, object quantifiers and atoms are handled
425
+ exactly as in :func:`unicode_logic_kit.fol.qml._thf_lift`; the additional families
426
+ are lifted through the macros defined in :data:`_THF_DEFS_FULL`. ``names`` is the
427
+ per-formula de-colliding functor resolver (so distinct symbols never collapse).
428
+ """
429
+ if isinstance(node, Atom):
430
+ # An identity atom reaches here as `=` (`≠` was lowered to `¬(=)` up front) and
431
+ # `names.atom` answers the `meq` macro, so it lifts as `( meq @ t1 @ t2 )`.
432
+ constant = truth_value(node)
433
+ if constant is not None:
434
+ # `$true` / `$false`: the proposition true (false) at every world.
435
+ return "( ^ [W: mu] : $true )" if constant else "( ^ [W: mu] : $false )"
436
+ head = names.atom(node)
437
+ if not node.args:
438
+ return head
439
+ return "( " + " @ ".join([head] + [_thf_term(a, names) for a in node.args]) + " )"
440
+ if isinstance(node, Not):
441
+ return f"( mnot @ {_lift(node.formula, names)} )"
442
+ if isinstance(node, And):
443
+ return f"( mand @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
444
+ if isinstance(node, Or):
445
+ return f"( mor @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
446
+ if isinstance(node, Xor):
447
+ # Xor is ¬(φ ⇔ ψ): no dedicated macro, build it from mnot/mequiv.
448
+ return f"( mnot @ ( mequiv @ {_lift(node.left, names)} @ {_lift(node.right, names)} ) )"
449
+ if isinstance(node, Implies):
450
+ return f"( mimplies @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
451
+ if isinstance(node, Iff):
452
+ return f"( mequiv @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
453
+
454
+ if isinstance(node, Box):
455
+ return f"( mbox @ {_lift(node.formula, names)} )"
456
+ if isinstance(node, Diamond):
457
+ return f"( mdia @ {_lift(node.formula, names)} )"
458
+
459
+ if isinstance(node, Contrast):
460
+ # Truth-functionally conjunction (Contrast's own contract).
461
+ return f"( mand @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
462
+
463
+ # Agent-indexed epistemic / doxastic / assertive / bouletic: the agent is a
464
+ # real $i argument.
465
+ if isinstance(node, Knows):
466
+ return f"( mknows @ {_thf_term(node.agent, names)} @ {_lift(node.formula, names)} )"
467
+ if isinstance(node, Believes):
468
+ return f"( mbelieves @ {_thf_term(node.agent, names)} @ {_lift(node.formula, names)} )"
469
+ if isinstance(node, Says):
470
+ return f"( msays @ {_thf_term(node.agent, names)} @ {_lift(node.formula, names)} )"
471
+ if isinstance(node, Wants):
472
+ return f"( mwants @ {_thf_term(node.agent, names)} @ {_lift(node.formula, names)} )"
473
+
474
+ if isinstance(node, Obligatory):
475
+ return f"( mobl @ {_lift(node.formula, names)} )"
476
+ if isinstance(node, Permitted):
477
+ return f"( mperm @ {_lift(node.formula, names)} )"
478
+
479
+ if isinstance(node, Always):
480
+ return f"( malways @ {_lift(node.formula, names)} )"
481
+ if isinstance(node, Eventually):
482
+ return f"( meventually @ {_lift(node.formula, names)} )"
483
+ if isinstance(node, Next):
484
+ return f"( mnext @ {_lift(node.formula, names)} )"
485
+
486
+ if isinstance(node, Quantifier):
487
+ x = node.variable.name.upper()
488
+ binder = "mforall" if node.type in (_FORALL, "forall") else "mexists"
489
+ return f"( {binder} @ ( ^ [{x}: $i] : {_lift(node.formula, names)} ) )"
490
+
491
+ if isinstance(node, Historically):
492
+ # Box over the CONVERSE of t (faithful to satisfies_modal's past reading).
493
+ return f"( mhistorically @ {_lift(node.formula, names)} )"
494
+ if isinstance(node, Once):
495
+ return f"( monce @ {_lift(node.formula, names)} )"
496
+ if isinstance(node, Previous):
497
+ return f"( mprevious @ {_lift(node.formula, names)} )"
498
+
499
+ if isinstance(node, Until):
500
+ # Strong Until as the impredicative Knaster–Tarski least fixpoint over
501
+ # the one-step tnext — TH0 quantifies over predicates, so the same least
502
+ # fixpoint Isabelle's `inductive muntil` compiles to is directly shallow-
503
+ # embeddable (the earlier claim that it is not was simply wrong).
504
+ return f"( muntil @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
505
+ if isinstance(node, Since):
506
+ # The converse-relation mirror of muntil (backward one-step paths).
507
+ return f"( msince @ {_lift(node.left, names)} @ {_lift(node.right, names)} )"
508
+
509
+ if isinstance(node, Nominal):
510
+ # True at exactly the named world: the reserved nom_ world constant, the
511
+ # same convention as standard_translation and isabelle_modal. The functor
512
+ # comes from the resolver's per-formula nominal map, so two DISTINCT
513
+ # nominals whose names sanitise alike ('A' / 'a') stay distinct worlds.
514
+ return f"( ^ [W: mu] : ( W = {names.nominal[node.name]} ) )"
515
+ if isinstance(node, At):
516
+ return (f"( ^ [W: mu] : ( {_lift(node.formula, names)} "
517
+ f"@ {names.nominal[node.nominal.name]} ) )")
518
+ if isinstance(node, Down):
519
+ raise NotImplementedError(
520
+ "to_thf_modal_full: the ↓ binder is not supported by this HOL "
521
+ "shallow embedding — H(@,↓) validity is undecidable, and this "
522
+ "emitter's job (a TPTP THF conjecture for an ATP) assumes a goal "
523
+ "shape a prover can be expected to close, not an open research "
524
+ "question. Use unicode_logic_kit.fol.modal_translation.down_is_valid "
525
+ "(Z3, PROVED-only) or unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend "
526
+ "/ modal_enum_search (bounded search, REFUTED-only) instead.")
527
+
528
+ if isinstance(node, SortedQuantifier):
529
+ raise NotImplementedError(
530
+ "to_thf_modal_full: SortedQuantifier is not supported; use a plain ∀x/∃x.")
531
+ if type(node).__name__ in ("Would", "Might"):
532
+ raise NotImplementedError(
533
+ "to_thf_modal_full: the counterfactuals □→/◇→ read a similarity "
534
+ "ordering (Lewis spheres), not an accessibility relation — use "
535
+ "hol.isabelle_conditional / isabelle_decide_counterfactual.")
536
+ raise NotImplementedError(
537
+ f"to_thf_modal_full: unsupported node type {type(node).__name__}.")
538
+
539
+
540
+ def _nominal_names(formula: Node):
541
+ """All nominal names occurring in ``formula`` (Nominal and At sites), sorted."""
542
+ return sorted({n.name for n in formula.walk() if isinstance(n, Nominal)})
543
+
544
+
545
+ def _sort_functors(original: Node, names: "_ThfNames") -> List[str]:
546
+ """THF functors of the sorts ``original`` quantifies over, in first-occurrence order.
547
+
548
+ Takes the UNrelativized formula: afterwards a sort guard is indistinguishable
549
+ from any other unary predicate. Reuses
550
+ :func:`~unicode_logic_kit.fol._msfl_nodes.nonempty_sort_axioms`'s own scan — as
551
+ :func:`~unicode_logic_kit.fol.qml._sort_names_used` and
552
+ :func:`~unicode_logic_kit.hol.isabelle_modal._sort_consts` do — so the three
553
+ routes can never disagree about which sorts a formula uses, then maps each
554
+ through the de-colliding resolver so the axiom names the same functor the
555
+ conjecture does. A sort with no unary atom in the relativized formula has no
556
+ type declaration, so it is skipped rather than referenced undeclared.
557
+ """
558
+ out: List[str] = []
559
+ for axiom in nonempty_sort_axioms(original):
560
+ assert isinstance(axiom, Quantifier) and isinstance(axiom.formula, Atom) # ∃x S(x)
561
+ functor = names.pred.get((axiom.formula.predicate, 1))
562
+ if functor is not None and functor not in out:
563
+ out.append(functor)
564
+ return out
565
+
566
+
567
+ def _sort_members(original: Node, names: "_ThfNames") -> List[Tuple[str, str]]:
568
+ """``(guard functor, individual functor)`` of every sorted constant of ``original``.
569
+
570
+ One pair per distinct ``c:S``, in first-occurrence order (the order of
571
+ :func:`~unicode_logic_kit.fol._msfl_nodes.sort_membership_axioms`), each name taken
572
+ from ``names`` — which must come from :func:`_resolve_names` called with the same
573
+ ``original``, so the guard of a sort that occurs only through a sorted constant is
574
+ declared and the pair names exactly the functors the conjecture uses.
575
+ """
576
+ out: List[Tuple[str, str]] = []
577
+ for atom in sort_membership_axioms(original):
578
+ assert isinstance(atom, Atom) and isinstance(atom.args[0], Constant) # ``S(c)``
579
+ pair = (names.pred[(atom.predicate, 1)], names.constant(atom.args[0].name))
580
+ if pair not in out:
581
+ out.append(pair)
582
+ return out
583
+
584
+
585
+ def _resolve_names(formula: Node, original: Optional[Node] = None) -> "_RigidNames":
586
+ """Build the per-formula functor resolver, including a de-colliding nominal map.
587
+
588
+ User symbols are resolved against the full export's reserved functor set;
589
+ then each nominal name gets a ``nom_``-prefixed ``mu`` constant deduped
590
+ against BOTH the user functors and the other nominals. Without this, two
591
+ distinct nominals sanitising alike (``'A'`` / ``'a'``) would silently
592
+ collapse to the SAME world constant — the emitted problem would load but no
593
+ longer mean what the source formula meant (and a user constant literally
594
+ named ``nom_a`` would conflate with the nominal ``a``'s world).
595
+
596
+ ``original`` is the many-sorted formula ``formula`` was relativized from. In
597
+ ``formula`` a sorted constant ``c:S`` is the plain ``c`` and the guard ``S`` is
598
+ mentioned only if a sorted QUANTIFIER used it; resolving over ``formula``
599
+ together with the membership atom ``S(c)`` of every sorted constant of
600
+ ``original`` gives a sort that occurs only through a sorted constant its guard
601
+ functor and type declaration too. Without a sorted constant the resolver is
602
+ built over ``formula`` itself, so every name is what it was.
603
+ """
604
+ scope = formula
605
+ if original is not None:
606
+ for atom in sort_membership_axioms(original):
607
+ scope = And(scope, atom)
608
+ formula = scope
609
+ names = _RigidNames(formula, reserved=_THF_RESERVED_FULL)
610
+ taken = (set(_THF_RESERVED_FULL)
611
+ | set(names.pred.values()) | set(names.const.values())
612
+ | set(names.func.values()))
613
+ names.nominal = {}
614
+ for raw in _nominal_names(formula):
615
+ names.nominal[raw] = dedupe("nom_" + _thf_name(raw), taken)
616
+ return names
617
+
618
+
619
+ # ---------------------------------------------------------------------------
620
+ # Which families occur — so we only declare/axiomatise the relations we use.
621
+ # ---------------------------------------------------------------------------
622
+ def _families_used(formula: Node) -> Dict[str, bool]:
623
+ """Scan ``formula`` for which modal families occur (controls what gets emitted)."""
624
+ used = {"alethic": False, "epistemic": False, "doxastic": False,
625
+ "assertive": False, "bouletic": False,
626
+ "deontic": False, "temporal": False, "tnext": False}
627
+ for n in formula.walk():
628
+ if isinstance(n, (Box, Diamond)):
629
+ used["alethic"] = True
630
+ elif isinstance(n, Knows):
631
+ used["epistemic"] = True
632
+ elif isinstance(n, Believes):
633
+ used["doxastic"] = True
634
+ elif isinstance(n, Says):
635
+ used["assertive"] = True
636
+ elif isinstance(n, Wants):
637
+ used["bouletic"] = True
638
+ elif isinstance(n, (Obligatory, Permitted)):
639
+ used["deontic"] = True
640
+ elif isinstance(n, (Always, Eventually, Historically, Once)):
641
+ # ⒣/⒫ read the CONVERSE of the same henceforth t, whose refl/trans
642
+ # axioms constrain the past readings equally.
643
+ used["temporal"] = True
644
+ elif isinstance(n, (Next, Previous, Until, Since)):
645
+ # ⒴ reads the converse of tnext; the muntil/msince fixpoints step
646
+ # over tnext — all need the one-step relation's link into t.
647
+ used["tnext"] = True
648
+ return used
649
+
650
+
651
+ def thf_full_definitions() -> str:
652
+ """Return the block of lifted-operator THF definitions used by the full export."""
653
+ return _THF_DEFS_FULL
654
+
655
+
656
+ # Agent-indexed families that systems= may constrain, with their relation + tag.
657
+ _AGENT_SYSTEM_FAMILIES = (
658
+ ("epistemic", _R_KNOWS, "rk"),
659
+ ("doxastic", _R_BELIEVES, "rb"),
660
+ ("assertive", _R_SAYS, "rs"),
661
+ ("bouletic", _R_WANTS, "rw"),
662
+ )
663
+
664
+
665
+ def thf_full_frame_axioms(frame: str = "K",
666
+ systems: Optional[Dict[str, str]] = None,
667
+ temporal_closure: bool = True,
668
+ bridges: Optional[Sequence[str]] = None) -> List[str]:
669
+ """Return the frame axioms the full export would emit (for inspection / testing).
670
+
671
+ ``frame`` constrains the alethic relation ``r`` (K/T/S4/S5/KD/KD45); ``systems`` is
672
+ an optional mapping that constrains the agent-indexed epistemic / doxastic /
673
+ assertive / bouletic relations, e.g. ``{"epistemic": "S5", "doxastic": "KD45"}``.
674
+ The deontic relation ``d`` is always serial; the temporal relation ``t`` is
675
+ reflexive-transitive unless ``temporal_closure=False`` (which keeps only the
676
+ ``tnext ⊆ t`` inclusion, leaving ``t`` an arbitrary relation — mirroring
677
+ ``isabelle_modal_theory``'s parameter of the same name). ``bridges`` appends the
678
+ cross-family bridge axioms (see :func:`_thf_bridge_axioms`).
679
+
680
+ ASYMMETRY, stated so this helper does not look like it contradicts the emitter:
681
+ it takes no formula, so it cannot know which families occur and emits every
682
+ requested axiom UNCONDITIONALLY — exactly as it already emits the ``systems=``
683
+ axioms and ``d_serial`` unconditionally while :func:`to_thf_modal_full` gates
684
+ them on the families actually used. Unknown bridge names are still rejected
685
+ here, so a typo is caught in the inspection path too; the "family absent"
686
+ refusal can only be made by the real emitter.
687
+ """
688
+ if frame not in _FRAMES:
689
+ raise ValueError(f"to_thf_modal_full: unknown frame {frame!r}.")
690
+ systems = systems or {}
691
+ axioms: List[str] = [_THF_FRAME[c] for c in _FRAMES[frame]]
692
+ for fam, rel, tag in _AGENT_SYSTEM_FAMILIES:
693
+ sys = systems.get(fam)
694
+ if sys is not None:
695
+ if sys not in _FRAMES:
696
+ raise ValueError(
697
+ f"to_thf_modal_full: unknown system {sys!r} for {fam} "
698
+ f"(use one of {sorted(_FRAMES)}).")
699
+ axioms += _agent_frame_axioms(rel, _FRAMES[sys], tag)
700
+ axioms.append(_THF_DEONTIC_AXIOM)
701
+ axioms += _THF_TEMPORAL_AXIOMS if temporal_closure else _THF_TEMPORAL_AXIOMS[-1:]
702
+ requested = _validate_bridges(bridges, "to_thf_modal_full")
703
+ for name in _BRIDGE_SPEC: # registry order, not the caller's set order
704
+ if name in requested:
705
+ axioms += _THF_BRIDGE_LINES[name]
706
+ return axioms
707
+
708
+
709
+ def to_thf_modal_full(formula: Node, mode: str = "constant", frame: str = "K",
710
+ systems: Optional[Dict[str, str]] = None,
711
+ temporal_closure: bool = True,
712
+ bridges: Optional[Sequence[str]] = None) -> str:
713
+ """Emit a complete Benzmüller-style **THF** shallow embedding of a full-family modal formula.
714
+
715
+ Unlike :func:`unicode_logic_kit.fol.qml.to_thf_modal` (alethic-only), this covers the
716
+ whole modal family the AST expresses: alethic ``□``/``◇``, agent-indexed epistemic
717
+ ``K_a`` and doxastic ``B_a``, deontic ``O``/``P`` (serial / KD), and temporal
718
+ ``G``/``F``/``X``. It produces a self-contained THF problem — type declarations,
719
+ every lifted operator, the relevant frame axioms, the ``existsAt`` domain axioms for
720
+ ``mode``, and the conjecture ``mvalid @ ⟨formula⟩`` — ready for a higher-order ATP
721
+ (Leo-III / Satallax). The toolkit only emits the problem; it does not run a prover.
722
+
723
+ A many-sorted formula also gets its sort facts, as ``axiom`` lines beside the
724
+ conjecture: ``nonempty_sort<i>`` (the sort is non-empty at every world) and, per
725
+ sorted constant ``c:S``, ``sort_member<i>`` (``! [W: mu] : ( S @ c @ W )``: ``c`` is
726
+ an element of ``S`` at EVERY world, unguarded by ``existsAt``, because a constant is
727
+ rigid and may lie outside the domain of a world). A sort that occurs only through
728
+ a sorted constant gets its guard declared too. A formula without a sorted constant
729
+ gets no ``sort_member`` line and its text is unchanged.
730
+
731
+ Parameters:
732
+ formula: the modal formula to embed.
733
+ mode: object-quantifier domain regime — ``"constant"`` / ``"possibilist"``
734
+ (unrelativised) or ``"varying"`` / ``"increasing"`` / ``"cumulative"`` /
735
+ ``"decreasing"`` (actualist, ``existsAt``-guarded).
736
+ frame: constrains the ALETHIC relation ``r`` (K/T/S4/S5/KD/KD45).
737
+ systems: optional per-family system selection for the AGENT-INDEXED epistemic /
738
+ doxastic / assertive / bouletic relations, e.g. ``{"epistemic": "S5",
739
+ "doxastic": "KD45"}``. Each value is a frame name from
740
+ ``{K,T,S4,S5,KD,KD45}``; the property is asserted ``∀A`` over the agent, so
741
+ it holds per agent.
742
+ temporal_closure: emit the ``t_refl`` / ``t_trans`` axioms making ``t``
743
+ reflexive-transitive (default). ``False`` keeps only the ``tnext ⊆ t``
744
+ inclusion, so ``t`` denotes an arbitrary relation — mirroring
745
+ ``isabelle_modal_theory``'s parameter of the same name.
746
+ bridges: optional collection of CROSS-family bridge names (:data:`BRIDGES`,
747
+ shared verbatim with ``hol.isabelle_modal``), **off by default**:
748
+ ``"knowledge_implies_belief"`` (``K_a φ → B_a φ``, axiom ``rb_in_rk``,
749
+ condition ``rb ⊆ rk``), ``"sincerity"`` (``Say_a φ → B_a φ``,
750
+ ``rb_in_rs``, ``rb ⊆ rs``) and ``"ought_implies_can"`` (``Oφ → ◇φ``,
751
+ ``d_meets_r``, ``∀W ∃V. d W V ∧ r W V`` — NOT the folklore ``d ⊆ r``).
752
+ Each condition is the exact correspondent of its schema, so a bridge
753
+ adds that principle and nothing stronger. Requesting a bridge whose
754
+ partner family does not occur in the formula raises ``ValueError``,
755
+ matching ``to_isabelle_modal`` even though the axiom would be
756
+ well-formed here — see :func:`_thf_bridge_axioms`.
757
+
758
+ Agent-indexedness (faithful to ``satisfies_modal``): the agent of ``Knows`` /
759
+ ``Believes`` / ``Says`` / ``Wants`` is a first-class TERM and is carried into
760
+ ``rk`` / ``rb`` / ``rs`` / ``rw`` as a real ``$i`` argument, so a bound object
761
+ variable in agent position quantifies over agents
762
+ (``∀x (Student(x) → K_x φ)``). A named agent becomes an ordinary ``$i`` constant.
763
+
764
+ Caveats: ``Until`` / ``Since`` ARE supported — as the impredicative
765
+ Knaster–Tarski least-fixpoint macros ``muntil`` / ``msince`` over the one-step
766
+ ``tnext`` (TH0 quantifies over predicates, so the least fixpoint is directly
767
+ expressible). Temporal ``G``/``F`` are read over a reflexive-transitive ``t`` and
768
+ only approximate ``satisfies_modal``'s closure over the caller's raw temporal
769
+ edges (see the module docstring). Equality ``=`` / ``≠`` is RIGID identity — THF's
770
+ own ``=`` with no world argument, through the ``meq`` macro (``≠`` lowered to
771
+ ``¬(=)``) — the reading of :func:`~unicode_logic_kit.fol.qml.qml_is_valid`, so
772
+ ``a = a`` and ``a = b → □(a = b)`` are theorems and ``□(a = b) → a = b`` is one
773
+ exactly under a reflexive / serial ``frame`` (module docstring, "Equality").
774
+ First-order/higher-order modal logic is undecidable, so a ``Theorem`` verdict
775
+ requires an external HOL ATP and is not guaranteed to terminate.
776
+ """
777
+ if frame not in _FRAMES:
778
+ raise ValueError(f"to_thf_modal_full: unknown frame {frame!r}.")
779
+ if mode not in _ACTUALIST_MODES and mode not in _CONSTANT_MODES:
780
+ raise ValueError(
781
+ f"to_thf_modal_full: unknown mode {mode!r} (use one of "
782
+ f"{sorted(_ACTUALIST_MODES | _CONSTANT_MODES)}).")
783
+ # Reject a bare string / an unknown bridge name before any emission work.
784
+ _validate_bridges(bridges, "to_thf_modal_full")
785
+
786
+ # A many-sorted formula (SortedQuantifier / SortedConstant) is relativized
787
+ # ONCE, here, before anything scans or lifts it — same choice, and same
788
+ # reason, as hol.isabelle_modal.isabelle_modal_theory and fol.qml's
789
+ # qml_translate: the resulting sort-guard atom is an ordinary predicate
790
+ # the existing signature scan / lift already handle, and doing it once,
791
+ # up front, also catches a SortedConstant anywhere in the formula, not
792
+ # only directly under a SortedQuantifier.
793
+ #
794
+ # A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
795
+ # a user constant spelled like it is refused, not merged with it.
796
+ [formula], _ = numerals_as_constants([formula], where="to_thf_modal_full",
797
+ spell=prefixed_numeral_name)
798
+ original = formula
799
+ formula = formula._relativize([])
800
+ # Rigid identity (qml's reading): `≠` -> `¬(=)`, a non-binary `=` refused. A formula
801
+ # without an identity atom is returned unchanged, so it emits byte-for-byte as before.
802
+ formula = _lower_identity(formula, "to_thf_modal_full")
803
+
804
+ used = _families_used(formula)
805
+ systems = systems or {}
806
+
807
+ lines: List[str] = [
808
+ f"% Full-family shallow embedding of a quantified modal formula "
809
+ f"(mode={mode}, frame={frame}).",
810
+ "% Families: alethic r, epistemic rk(agent), doxastic rb(agent), "
811
+ "deontic d(serial), temporal t.",
812
+ "% Conjecture is 'Theorem' iff the formula is valid under this regime "
813
+ "(external HOL ATP required).",
814
+ "thf(mu_type, type, ( mu : $tType )).",
815
+ ]
816
+
817
+ # --- relation type declarations ---
818
+ # The lifted-operator definitions block (_THF_DEFS_FULL) is emitted WHOLE and its
819
+ # macros (mbox/mdia, mknows/mbelieves, mobl/mperm, mnext) reference every relation
820
+ # r/rk/rb/d/tnext. So all of them must be declared unconditionally — otherwise a
821
+ # Box-only formula would still emit mknows etc. referencing an undeclared rk, and a
822
+ # strict THF parser (Leo-III/Satallax) would reject the problem. Unused declared
823
+ # symbols are harmless.
824
+ lines.append("thf(r_decl, type, ( r : ( mu > mu > $o ) )).")
825
+ lines.append("thf(rk_decl, type, ( rk : ( $i > mu > mu > $o ) )).")
826
+ lines.append("thf(rb_decl, type, ( rb : ( $i > mu > mu > $o ) )).")
827
+ lines.append("thf(d_decl, type, ( d : ( mu > mu > $o ) )).")
828
+ lines.append("thf(t_decl, type, ( t : ( mu > mu > $o ) )).")
829
+ lines.append("thf(tnext_decl, type, ( tnext : ( mu > mu > $o ) )).")
830
+ lines.append("thf(rs_decl, type, ( rs : ( $i > mu > mu > $o ) )).")
831
+ lines.append("thf(rw_decl, type, ( rw : ( $i > mu > mu > $o ) )).")
832
+ lines.append("thf(existsAt_decl, type, ( existsAt : ( $i > mu > $o ) )).")
833
+
834
+ # --- signature of the object-level predicates / constants / functions ---
835
+ # (named agents fall out here as ordinary $i constants, matching rk/rb's $i slot).
836
+ # The de-colliding resolver guarantees distinct source symbols never share a functor
837
+ # (e.g. Ab / ab) and never shadow a built-in (a predicate named "r"/"mbox" is pushed
838
+ # to r_2/mbox_2), so a non-valid formula can neither collapse to a tautology nor
839
+ # re-declare an export-internal relation at a conflicting type.
840
+ names = _resolve_names(formula, original)
841
+ # One mu constant per nominal (reserved nom_ prefix; true at exactly the
842
+ # world it names — matching standard_translation / isabelle_modal). The map
843
+ # is deduped, so nominals 'A'/'a' get distinct constants (nom_a / nom_a_2).
844
+ for raw in _nominal_names(formula):
845
+ f = names.nominal[raw]
846
+ lines.append(f"thf({f}_decl, type, ( {f} : mu )).")
847
+ lines += _thf_signature(formula, names)
848
+
849
+ # --- the lifted-operator definitions (one self-contained block) ---
850
+ lines.append(_THF_DEFS_FULL)
851
+ if _has_identity(formula):
852
+ lines.append(_THF_RIGID_EQ_DEF)
853
+
854
+ # --- standing axiom: every world has an existing individual ---
855
+ lines.append(
856
+ "thf(nonempty_dom, axiom, ( ! [W: mu] : ? [X: $i] : ( existsAt @ X @ W ) )).")
857
+
858
+ # --- standing axiom per SORT: every sort is non-empty at every world ---
859
+ # _relativize left each sort guard an ordinary WORLD-RELATIVE predicate, so
860
+ # without this nothing forces S to hold of anything and ∀x:S P(x) → ∃x:S P(x)
861
+ # would be unprovable here while the classical many-sorted routes call it
862
+ # valid (fol._msfl_nodes.nonempty_sort_axioms states the same convention
863
+ # there, fol.qml.qml_axioms the same per-world version, and
864
+ # hol.isabelle_modal._nonempty_sort_axioms the same lines for the sibling
865
+ # route). Under an actualist mode the witness must also EXIST at the world,
866
+ # or it could not instantiate the existsAt-guarded mexists.
867
+ for i, sort in enumerate(_sort_functors(original, names)):
868
+ body = (f"( ( existsAt @ X @ W ) & ( {sort} @ X @ W ) )"
869
+ if mode in _ACTUALIST_MODES else f"( {sort} @ X @ W )")
870
+ lines.append(
871
+ f"thf(nonempty_sort{i}, axiom, ( ! [W: mu] : ? [X: $i] : {body} )).")
872
+
873
+ # --- standing axiom per SORTED CONSTANT: it is an element of its sort ---
874
+ # ``c:S`` denotes an element of ``S`` (the reading every many-sorted route of the
875
+ # kit shares, fol._msfl_nodes.sort_membership_axioms). A constant is a rigid
876
+ # designator, so ``S @ c @ W`` holds at EVERY world -- and it is NOT guarded by
877
+ # ``existsAt``, in any mode: a constant may lie outside the local domain (the
878
+ # reading fol.qml documents), and a guarded fact would never fire in the modes
879
+ # where that matters. As an AXIOM it is part of the problem, not of the
880
+ # conjecture, so it can only be used, never proved. The names are distinct from
881
+ # the ``nonempty_sort`` family above.
882
+ for i, (sort, const) in enumerate(_sort_members(original, names)):
883
+ lines.append(
884
+ f"thf(sort_member{i}, axiom, ( ! [W: mu] : ( {sort} @ {const} @ W ) )).")
885
+
886
+ # --- alethic frame axioms (frame) ---
887
+ for cond in _FRAMES[frame]:
888
+ lines.append(_THF_FRAME[cond])
889
+
890
+ # --- agent-indexed epistemic/doxastic/assertive/bouletic system axioms ---
891
+ for fam, rel, tag in _AGENT_SYSTEM_FAMILIES:
892
+ if not used[fam]:
893
+ continue
894
+ sys = systems.get(fam)
895
+ if sys is None:
896
+ continue
897
+ if sys not in _FRAMES:
898
+ raise ValueError(
899
+ f"to_thf_modal_full: unknown system {sys!r} for {fam} "
900
+ f"(use one of {sorted(_FRAMES)}).")
901
+ for ax in _agent_frame_axioms(rel, _FRAMES[sys], tag):
902
+ lines.append(ax)
903
+
904
+ # --- deontic seriality (Standard Deontic Logic, KD) ---
905
+ if used["deontic"]:
906
+ lines.append(_THF_DEONTIC_AXIOM)
907
+
908
+ # --- temporal frame axioms: refl/trans for G/F (see caveat) plus the tnext ⊆ t
909
+ # inclusion linking X's one-step relation to G/F's henceforth relation. Emitted
910
+ # whenever EITHER the temporal (G/F) or next (X) family occurs, since the linking
911
+ # axiom is exactly what makes a mixed formula such as Gφ→Xφ — valid for
912
+ # satisfies_modal — a theorem of the embedding. (t and tnext are declared
913
+ # unconditionally, so the axiom is always well-formed.)
914
+ if used["temporal"] or used["tnext"]:
915
+ lines += _THF_TEMPORAL_AXIOMS if temporal_closure else _THF_TEMPORAL_AXIOMS[-1:]
916
+
917
+ # --- opt-in cross-family bridge axioms (raises when a partner family is absent) ---
918
+ lines += _thf_bridge_axioms(used, bridges)
919
+
920
+ # --- object-quantifier domain regime ---
921
+ if mode in _THF_DOMAIN:
922
+ lines.append(_THF_DOMAIN[mode])
923
+ elif mode not in ("varying",) and mode not in _CONSTANT_MODES:
924
+ raise ValueError(f"to_thf_modal_full: unknown mode {mode!r}.")
925
+
926
+ # --- the conjecture ---
927
+ # A free variable is a parameter (one unknown individual), bound in front of the whole
928
+ # conjecture exactly as fol.qml.to_thf_modal binds it: THF has no free variables, and
929
+ # for a conjecture that stands alone "for the parameter" and "for every individual"
930
+ # are one validity. Under an actualist regime the individual exists at the world of
931
+ # evaluation.
932
+ goal = _lift(formula, names)
933
+ parameters = free_parameter_names([formula])
934
+ if mode in _ACTUALIST_MODES:
935
+ for name in reversed(parameters):
936
+ goal = f"( mimplies @ ( existsAt @ {names.variable(name)} ) @ {goal} )"
937
+ conjecture = f"mvalid @ {goal}"
938
+ for name in reversed(parameters):
939
+ conjecture = f"! [{names.variable(name)}: $i] : ( {conjecture} )"
940
+ lines.append(f"thf(goal, conjecture, ( {conjecture} )).")
941
+ return "\n".join(lines) + "\n"