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,308 @@
1
+ """Hybrid-logic node classes: nominals, the satisfaction operator @, and ↓.
2
+
3
+ Hybrid logic H(@) extends modal logic with NOMINALS — atomic formulas i, j, …
4
+ that are true at EXACTLY ONE world, naming it — and the satisfaction operator
5
+ ``@i φ`` (*at the world named i, φ holds*). Both parse in the modal mode
6
+ (``MSFLParser(modal=True)``): a bare lowercase name in formula position is a
7
+ nominal, and ``@i φ`` applies the satisfaction operator. H(@) over K stays
8
+ DECIDABLE.
9
+
10
+ Adding the ``↓`` binder gives the full hybrid language H(@,↓): ``↓x.φ`` binds
11
+ the state variable ``x`` to the CURRENT world, then evaluates ``φ`` (which may
12
+ refer back to ``x`` as an ordinary nominal or via ``@x``). H(@,↓) validity is
13
+ UNDECIDABLE (Areces, Blackburn & Marx 1999) — but the standard translation
14
+ into classical FOL stays MEANING-PRESERVING for it (that correspondence is
15
+ exactly what defines the "bounded fragment"), so validity is co-r.e.: a Z3
16
+ route can still PROVE it (never REFUTE it presentably — see
17
+ ``fol.modal_translation.down_is_valid``), and the Kripke evaluator
18
+ (``satisfies_modal``) still evaluates ``↓`` on any finite model, including
19
+ inside a bounded finite-model search that REFUTES it
20
+ (``atp.kripke_enum.KripkeEnumBackend``). No route here DECIDES full H(@,↓);
21
+ a route that stays sound only by staying bounded to a fragment it can fully
22
+ search (the labelled modal tableau, the QML embedding, the HOL shallow
23
+ embeddings) refuses a ``↓``-containing formula BY NAME instead of silently
24
+ mis-scoping.
25
+
26
+ Reasoning routes: the Kripke evaluator (``KripkeModel(nominals={...})`` +
27
+ ``satisfies_modal``) evaluates hybrid formulas directly (route A, always
28
+ terminating), and the standard translation maps them to classical FOL (a
29
+ nominal becomes a world-equality with a fresh world constant; ``↓x`` becomes a
30
+ local rebinding of ``x`` to the current-world term, with NO fresh quantifier),
31
+ so ``hybrid_is_valid`` (H(@) only) / ``down_is_valid`` (H(@,↓), PROVED-only) /
32
+ ``KripkeEnumBackend`` (bounded search, REFUTED-only) decide validity with Z3 /
33
+ finite enumeration per frame (route B). The direct classical exporters reject
34
+ (a nominal — and ``↓`` doubly so — is world-relative).
35
+ """
36
+
37
+ from dataclasses import dataclass
38
+
39
+ from ._fol_nodes import (
40
+ Node, Z3Env,
41
+ NODE_CLASSES, register_operator, register_parser_op,
42
+ )
43
+
44
+ # Shared rejection message: hybrid constructs are world-relative, so they have no
45
+ # direct classical export; the standard translation is the sanctioned route.
46
+ _NO_HYBRID_EXPORT = (
47
+ "Hybrid-logic constructs (nominals / the satisfaction operator @) are "
48
+ "world-relative and have no direct first-order export. Use the standard "
49
+ "translation (unicode_logic_kit.standard_translation) or hybrid_is_valid, or "
50
+ "evaluate in a KripkeModel with a nominal assignment."
51
+ )
52
+
53
+ # Rejection message for Down specifically: same world-relativity as Nominal/At,
54
+ # PLUS H(@,↓) validity is undecidable, so the sanctioned routes are narrower
55
+ # than hybrid_is_valid's (which itself refuses ↓ — see modal_translation.py).
56
+ _NO_DOWN_EXPORT = (
57
+ "The ↓ binder is world-relative (like every hybrid-logic construct) and "
58
+ "H(@,↓) validity is undecidable, so it has no direct first-order export "
59
+ "and no bare-bool validity check. Use "
60
+ "unicode_logic_kit.fol.modal_translation.down_is_valid (Z3, PROVED-only) or "
61
+ "unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend (bounded search, "
62
+ "REFUTED-only) for validity, or evaluate directly in a KripkeModel with "
63
+ "unicode_logic_kit.semantics.kripke.satisfies_modal."
64
+ )
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class Nominal(Node):
69
+ """A nominal ``i`` — an atomic formula true at exactly one world (naming it).
70
+
71
+ ``name`` is a lowercase identifier (a legal NAME token, so it round-trips).
72
+ In a :class:`~unicode_logic_kit.semantics.kripke.KripkeModel` the assignment
73
+ ``nominals={"i": world}`` fixes which world each nominal names; the nominal
74
+ is then true at that world and false everywhere else.
75
+ """
76
+
77
+ name: str
78
+
79
+ def _tree_parts(self):
80
+ """Return the nominal's name as an atomic tree label."""
81
+ return self.name, []
82
+
83
+ def to_dict(self):
84
+ """Serialise to dict with the type tag and the nominal's name."""
85
+ return {"_type": "Nominal", "name": self.name}
86
+
87
+ @staticmethod
88
+ def from_dict(d):
89
+ """Deserialise a Nominal from a dict produced by to_dict."""
90
+ return Nominal(d["name"])
91
+
92
+ def to_z3(self, env: Z3Env = None):
93
+ """Reject direct Z3 export: nominals are world-relative."""
94
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
95
+
96
+ def to_prover9(self) -> str:
97
+ """Reject direct Prover9 export: nominals are world-relative."""
98
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
99
+
100
+ def to_tptp(self) -> str:
101
+ """Reject direct TPTP export: nominals are world-relative."""
102
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class At(Node):
107
+ """The satisfaction operator ``@i φ`` — at the world named ``i``, ``φ`` holds.
108
+
109
+ ``nominal`` is the :class:`Nominal` naming the evaluation world (a bare
110
+ string is coerced); ``formula`` is evaluated THERE, regardless of the
111
+ current world. ``@`` is self-dual (``@i φ ↔ ¬@i ¬φ``) and a normal
112
+ modality.
113
+ """
114
+
115
+ nominal: Nominal
116
+ formula: Node
117
+
118
+ def __post_init__(self):
119
+ """Coerce a bare string to a Nominal and validate the field types."""
120
+ if isinstance(self.nominal, str):
121
+ object.__setattr__(self, "nominal", Nominal(self.nominal))
122
+ if not isinstance(self.nominal, Nominal):
123
+ raise ValueError("At: nominal must be a Nominal (or a bare string).")
124
+
125
+ @property
126
+ def agent(self):
127
+ """The nominal, exposed under the generic agent-prefix renderer contract.
128
+
129
+ The Unicode/LaTeX renderers drive every ``agent_prefix`` operator
130
+ generically via ``node.agent`` (as for ``K_a`` / ``B_a``); exposing the
131
+ nominal here lets ``@i φ`` render with zero renderer special-casing.
132
+ """
133
+ return self.nominal
134
+
135
+ def _tree_parts(self):
136
+ """Return the @-label (with the nominal) and the formula child."""
137
+ return f"@{self.nominal.name}", [self.formula]
138
+
139
+ def to_dict(self):
140
+ """Serialise to dict with the nominal and the serialised formula."""
141
+ return {"_type": "At", "nominal": self.nominal.to_dict(),
142
+ "formula": self.formula.to_dict()}
143
+
144
+ @staticmethod
145
+ def from_dict(d):
146
+ """Deserialise an At from a dict produced by to_dict."""
147
+ return At(Node.from_dict(d["nominal"]), Node.from_dict(d["formula"]))
148
+
149
+ def to_z3(self, env: Z3Env = None):
150
+ """Reject direct Z3 export: @ is world-relative."""
151
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
152
+
153
+ def to_prover9(self) -> str:
154
+ """Reject direct Prover9 export: @ is world-relative."""
155
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
156
+
157
+ def to_tptp(self) -> str:
158
+ """Reject direct TPTP export: @ is world-relative."""
159
+ raise NotImplementedError(_NO_HYBRID_EXPORT)
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class Down(Node):
164
+ """The ↓ binder ``↓x.φ`` — binds the state variable ``x`` to the CURRENT
165
+ world, then evaluates ``φ``, in which ``x`` may occur as an ordinary
166
+ :class:`Nominal` or via :class:`At`. Nesting ``↓`` under a modality lets a
167
+ formula "look back" at a world named earlier — e.g. ``↓x.□¬x``
168
+ (irreflexivity: no successor of the current world IS the current world)
169
+ or ``↓x.◇x`` (reflexivity) — expressive power plain H(@) does not have
170
+ (a nominal only ever names a world FIXED IN ADVANCE by the model, never
171
+ "whichever world evaluation happens to be at").
172
+
173
+ ``variable`` is a :class:`Nominal` naming the bound state variable — this
174
+ reuses the Nominal shape rather than adding a new leaf kind, since ↓
175
+ binds exactly the same kind of name (a lowercase, NAME-legal identifier)
176
+ that a plain nominal already is; a bare string is coerced, matching
177
+ :class:`At`'s own convention. ``formula`` is the scope.
178
+
179
+ Binding is by NAME, not by a separate de Bruijn/alpha-renaming layer
180
+ (unlike :class:`~unicode_logic_kit.fol._fol_nodes.Quantifier`'s object
181
+ variable): an occurrence of ``Nominal(x)`` or ``At(Nominal(x), …)``
182
+ anywhere in ``formula`` NOT itself inside a nested ``Down(Nominal(x), …)``
183
+ refers to THIS binder — a nested ``↓x`` of the same name shadows it
184
+ exactly the way a nested ``∀x`` would shadow an outer one, so
185
+ ``↓x.↓x.φ`` makes the outer binding entirely inert. This is deliberately
186
+ NOT wired into the generic ``_subst`` / capture-avoiding ``replace`` /
187
+ alpha-renaming machinery those FOL binders use (``fol/_msfl_nodes.py``):
188
+ a nominal is never a first-order term, so no first-order substitution
189
+ ever needs to reach inside one, and the two evaluators that DO give ↓ its
190
+ meaning (:func:`~unicode_logic_kit.semantics.kripke.satisfies_modal` and
191
+ :func:`~unicode_logic_kit.fol.modal_translation.standard_translation`) each
192
+ implement this by-name (re)binding directly, as a small environment keyed
193
+ on the name — see their own docstrings.
194
+ """
195
+
196
+ variable: Nominal
197
+ formula: Node
198
+
199
+ def __post_init__(self):
200
+ """Coerce a bare string to a Nominal and validate the field types."""
201
+ if isinstance(self.variable, str):
202
+ object.__setattr__(self, "variable", Nominal(self.variable))
203
+ if not isinstance(self.variable, Nominal):
204
+ raise ValueError("Down: variable must be a Nominal (or a bare string).")
205
+
206
+ def _tree_parts(self):
207
+ """Return the ↓-label (with the bound name) and the formula child."""
208
+ return f"↓{self.variable.name}", [self.formula]
209
+
210
+ def to_dict(self):
211
+ """Serialise to dict with the bound variable and the serialised formula."""
212
+ return {"_type": "Down", "variable": self.variable.to_dict(),
213
+ "formula": self.formula.to_dict()}
214
+
215
+ @staticmethod
216
+ def from_dict(d):
217
+ """Deserialise a Down from a dict produced by to_dict."""
218
+ return Down(Node.from_dict(d["variable"]), Node.from_dict(d["formula"]))
219
+
220
+ def to_z3(self, env: Z3Env = None):
221
+ """Reject direct Z3 export: ↓ is world-relative and undecidable."""
222
+ raise NotImplementedError(_NO_DOWN_EXPORT)
223
+
224
+ def to_prover9(self) -> str:
225
+ """Reject direct Prover9 export: ↓ is world-relative and undecidable."""
226
+ raise NotImplementedError(_NO_DOWN_EXPORT)
227
+
228
+ def to_tptp(self) -> str:
229
+ """Reject direct TPTP export: ↓ is world-relative and undecidable."""
230
+ raise NotImplementedError(_NO_DOWN_EXPORT)
231
+
232
+
233
+ NODE_CLASSES.update({"Nominal": Nominal, "At": At, "Down": Down})
234
+
235
+
236
+ # =========================
237
+ # Renderer + parser registration (modal mode)
238
+ # =========================
239
+ #
240
+ # @ registers as a regular agent_prefix operator (the nominal doubles as the
241
+ # "agent", see At.agent), so the Unicode/LaTeX renderers need no new branch.
242
+ # Nominal is atomic and rendered by an explicit branch in _msfl_nodes (bare name).
243
+
244
+ register_operator(At, "agent_prefix", "@", "@", 4)
245
+
246
+
247
+ def _nominal_transform(items):
248
+ """Build a Nominal from a NAME token (or the Constant the base pass made of it)."""
249
+ first = items[0]
250
+ name = getattr(first, "name", None) or str(first)
251
+ return Nominal(name)
252
+
253
+
254
+ def _at_transform(items):
255
+ """Build an At from [ATNOM token '@i', body] (strip the leading '@')."""
256
+ return At(Nominal(str(items[0])[1:]), items[1])
257
+
258
+
259
+ # A bare lowercase name in formula position is a nominal; @i binds like ¬ / K_a.
260
+ # Both token classes are accepted because the lexer splits lowercase identifiers
261
+ # by shape: single letters (+digits) are VARIABLE, multi-letter names are NAME —
262
+ # and the standard hybrid nominals i, j, k fall in the VARIABLE class. ATNOM has
263
+ # lexer priority 5 so '@i' lexes as one token.
264
+ #
265
+ # The bare-name form is emitted as a SEPARATE rule at grammar priority -1 (injected
266
+ # via the terminal-defs hook) rather than an inline prefix alternative. A lambda
267
+ # application argument (``?app_arg: formula | atom_term``) is also a bare name, so
268
+ # without the lowered priority Earley would resolve ``(λx. P(x))(y)`` to the nominal
269
+ # reading and silently turn the term argument into Nominal('y'); the -1 priority
270
+ # makes the term (atom_term) reading win wherever the two overlap, while a nominal
271
+ # still parses everywhere a formula is actually required (``i``, ``P ∧ i``, ``@i j``).
272
+ register_parser_op(Nominal, "modal", "prefix", "nominal_", "nominal",
273
+ _nominal_transform,
274
+ terminal_name="_nominal_rule",
275
+ terminal_def="?nominal.-1: (NAME | VARIABLE)")
276
+ register_parser_op(At, "modal", "prefix", "at_", "ATNOM prefix",
277
+ _at_transform,
278
+ terminal_name="ATNOM", terminal_def="ATNOM.5: /@[a-z][a-zA-Z0-9]*/")
279
+
280
+
281
+ def _down_transform(items):
282
+ """Build a Down from [DOWNARROW token, bound name (Variable/Constant), body]."""
283
+ bound = items[1]
284
+ name = getattr(bound, "name", None) or str(bound)
285
+ return Down(Nominal(name), items[2])
286
+
287
+
288
+ # ↓ binds at the SAME "quantifier" grammar level as ∀/∃/the counting quantifier
289
+ # (structurally identical to the already-shipped Count registration: same level,
290
+ # same named-terminal-glyph mechanism), so ``↓x.□¬x`` parses with the same tight
291
+ # binding a plain quantifier gets, extending as far right as possible through
292
+ # ``prefix``. The bound name accepts BOTH lexer classes, ``(NAME | VARIABLE)`` —
293
+ # not just VARIABLE — for the same reason the bare-nominal rule above does: a
294
+ # hybrid nominal name is legally either a single-letter VARIABLE-class token
295
+ # (the standard i, j, k) or a multi-letter NAME-class one, and ↓'s bound name is
296
+ # the very same kind of name (it IS a Nominal, see Down's docstring), so
297
+ # restricting it to VARIABLE alone would let ``i`` be bound by ↓ but not
298
+ # ``world1`` — an arbitrary asymmetry with the unbound nominal syntax. No
299
+ # priority annotation is needed here (unlike the bare-nominal rule's -1): DOWNARROW
300
+ # is a distinguishing token that starts this alternative, so it cannot share an
301
+ # LALR state with the term/atom_term alternatives the bare-nominal rule's
302
+ # priority exists to disambiguate (verified empirically in
303
+ # tests/test_modal_lalr_fallback.py, which — because "modal" is an LALR-first,
304
+ # Earley-fallback mode regardless, see msflparser.py's _HYBRID_MODES — also
305
+ # guarantees correctness even if that empirical claim ever stopped holding).
306
+ register_parser_op(Down, "modal", "quantifier", "down_",
307
+ 'DOWNARROW (NAME | VARIABLE) "." prefix', _down_transform,
308
+ terminal_name="DOWNARROW", terminal_def='DOWNARROW: "↓"')