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,811 @@
1
+ """Export to TPTP's Non-Classical Logic dialect (NXF) for the mono-modal alethic fragment,
2
+ now including its native QUANTIFIED sub-fragment.
3
+
4
+ TPTP's *classical* ``fof``/``tff`` syntax (:meth:`Node.to_tptp`) has no way to say
5
+ "this problem is modal" — a non-classical prover needs a companion **logic
6
+ specification** naming the logic and its parameters, plus new connective syntax
7
+ for the modal operators themselves. That companion dialect is **NXF**
8
+ ("Non-Classical TFF"), defined by Steen & Sutcliffe's TPTP-World infrastructure
9
+ for non-classical logics. This module renders one Unicode-kit modal formula as a
10
+ complete, self-contained NXF problem: a ``logic`` role statement, one type
11
+ declaration per sort/constant/predicate the formula actually uses, and a
12
+ ``conjecture`` role statement.
13
+
14
+ Sources (verified against primary/gold material BEFORE writing a line of this
15
+ module — see the individual choices below for what each one settled):
16
+
17
+ * Steen & Sutcliffe, "TPTP World Infrastructure for Non-classical Logics"
18
+ (arXiv:2508.09318, fetched 2026-08-12, re-fetched and cross-checked against
19
+ real ``.p`` files 2026-09-18 — see the correction note below) — the
20
+ logic-specification shape ``tff(name, logic, $modal == [ $domains == ...,
21
+ $designation == ..., $terms == ..., $modalities == ... ] ).`` with those
22
+ four EXACT key names; the long-form connective application
23
+ ``{connective_name} @ (arg1, ..., argn)`` (so ``{$box} @ (p)`` / ``{$dia} @
24
+ (p)``); the SHORT forms for the unparameterised unary modal connectives,
25
+ ``[.]`` for box and ``<.>`` for diamond, used exactly like a classical
26
+ prefix connective; and Table 2's ``$modal_system_X`` literal-token list.
27
+ * ``github.com/TPTPWorld/NonClassicalLogic`` (cloned locally 2026-09-18 to
28
+ read full, real, prover-exercised ``.p`` files rather than a paper's
29
+ abbreviated excerpts — every claim below was checked against this clone,
30
+ not assumed from the paper alone):
31
+ - ``LogicSpecifications/CorrectSpecifications.p`` — confirms the four-key
32
+ logic-spec form is accepted verbatim (``$domains == $constant,
33
+ $designation == $rigid, $terms == $local/$global, $modalities ==
34
+ $modal_system_S5``), and that ``$modal_system_S5`` / ``$modal_axiom_K``
35
+ / ``$modal_axiom_T`` / ``$modal_axiom_5`` are literal system/axiom
36
+ names.
37
+ - ``ProblemBuilding/Archive/ClaudiaNalon/IJCAR2022-TPTP/k45_branch_p/
38
+ k45_branch_p.0001.p`` (a real K45 benchmark, i.e. actually fed to
39
+ provers) — the model this module follows for the PROPOSITIONAL modal
40
+ fragment: ``tff(name,logic, $modal == [ $modalities ==
41
+ $modal_system_K45 ] ).`` (``$domains``/``$designation``/``$terms``
42
+ omitted — moot with no quantifiers/individual terms present), one
43
+ ``tff(<p>_decl,type,<p>: $o).`` per propositional letter, and the
44
+ short-form connectives used as plain prefix operators (``[.] z100``,
45
+ ``<.> [.] y100`` chained with no parens between them) — i.e.
46
+ ``[.]``/``<.>`` sit in the grammar at exactly the same position ``~``
47
+ does (see :mod:`fol.tptp_input`'s ``?unary: "~" unary | ...`` rule).
48
+ - ``ProblemBuilding/QMLTP/7_QMLTPTP/SYM/SYM001_1.p`` — a GENUINE,
49
+ QMLTP-sourced NXF translation of the Barcan-scheme problem this
50
+ module's own test suite uses as a fixture, confirming the NATIVE
51
+ quantified syntax this release adds: ``! [X: $i] : ( {$box} @ (f(X)) )
52
+ => ( {$box} @ (! [X: $i] : f(X)) )`` — a bound variable with NO
53
+ explicit sort defaults to TPTP's BUILT-IN ``$i`` (never a user-declared
54
+ ``$tType``), and a non-nullary predicate gets its own
55
+ ``tff(f_decl,type, f: $i > $o ).`` declaration, confirming NXF/NTF
56
+ stays a genuinely TYPED (TFF-based) dialect even for a
57
+ previously-untyped-looking QMLTP source.
58
+ - ``ProblemBuilding/QMLTP/7_QMLTPTP/APM/APM002_1.p`` — confirms (a) a
59
+ FREE constant of the default sort still needs its own explicit
60
+ declaration (``tff(a_decl,type, a: $i ).`` for a bare constant ``a``,
61
+ not merely left implicit), and (b) ``$modal_system_D`` is the literal
62
+ token for the serial (D/KD) system — the exact evidence this release's
63
+ ``"D"`` addition (see below) is built on.
64
+ - ``Logics/LOG001_4.l`` — the corpus's OWN canonical modal-system-name
65
+ DEFINITIONS file (TPTP v9.0.0): ``tff(modal_system_T,definition, (
66
+ $modal_system_T == ( $modal_system_K & $modal_axiom_T ) ) ).`` --
67
+ confirming ``$modal_system_T`` (not ``$modal_system_M``) is the
68
+ correct, canonical token for the reflexive ("T") frame. No
69
+ ``modal_system_M`` definition exists anywhere in this file or the rest
70
+ of the corpus (checked directly, see below) -- ``$modal_system_M``
71
+ appears only as a USAGE, and only inside
72
+ ``ProblemBuilding/QMLTP/8_QMLTP``, which
73
+ ``ProblemBuilding/QMLTP/README`` itself describes as "expanded
74
+ versions of 7_QMLTP with all combinations of the specification
75
+ parameters" (a derived/generated subtree, not the corpus's canonical
76
+ naming source) -- so those files reference a token this same corpus
77
+ never defines, almost certainly a generator artefact rather than a
78
+ legitimate alternate spelling. ``Tooling/generateSemantics.py`` (the
79
+ corpus's own canonical semantics-specification generator) confirms the
80
+ same thing independently: its ``all_modalities`` tuple is exactly
81
+ ``("$modal_system_K", "$modal_system_T", "$modal_system_D",
82
+ "$modal_system_S4", "$modal_system_S5")`` -- five names, matching this
83
+ exporter's five supported frames one-to-one, with ``$modal_system_M``
84
+ absent. ``ProblemBuilding/QMLTP/SemanticSpecifications/t_*.p`` (all
85
+ eight ``t_<domain>_<designation>.p`` files) likewise all read
86
+ ``$modalities == $modal_system_T``. A prior revision of this module
87
+ briefly "corrected" this token to ``$modal_system_M`` on the strength
88
+ of ``ProblemBuilding/Archive/.../kt_branch_p.0001.p`` and
89
+ ``ProblemBuilding/QMLTP/8_QMLTP/SYM/SYM074_3.121.p`` alone, and a
90
+ docstring claim that a corpus-wide search for ``modal_system_T``
91
+ returned zero hits; that search was not actually re-run against the
92
+ real corpus before being written -- ``modal_system_T`` in fact appears
93
+ in dozens of files including the three canonical sources named above.
94
+ That revision was wrong and is reverted here (adversarial review); the
95
+ accompanying golden test is corrected back to match (see
96
+ ``tests/test_tptp_ncl.py``).
97
+ - ``SampleProblems/PUZ087_1.p`` — a ``$epistemic_modal`` problem using the
98
+ INDEXED long form ``{$knows(#agent)} @ (...)``, confirming that a
99
+ future INDEXED multi-modal export (Knows/Believes/agent-indexed
100
+ alethic box(i)/dia(i)/... — still explicitly out of scope here, see
101
+ "Scope" below) would need the long, not short, connective form. Also
102
+ re-confirms the ``$tType``-declared-sort pattern
103
+ (``tff(agent_type,type,wiseman: $tType).``) this release's
104
+ :class:`~fol.nodes.SortedQuantifier`/:class:`~fol.nodes.SortedConstant`
105
+ support follows.
106
+ * ``fol/_modal_nodes.py`` — confirms ``Box``/``Diamond`` (and every other
107
+ modal-family node) reject the classical ``to_tptp()`` outright, so this
108
+ module cannot delegate to ``Node.to_tptp`` for a formula containing one and
109
+ must render the propositional connectives itself (mirroring their existing
110
+ ``to_tptp`` shape exactly — see :func:`_render`).
111
+ * ``fol/tptp_input.py`` / ``fol/_fol_nodes.py`` — the kit's existing name
112
+ convention (predicate/constant/function names lower-cased on their first
113
+ letter only, a 0-ary atom is a bare lower-case identifier) is reused
114
+ verbatim via ``Node.to_tptp()`` for every propositional letter, term and
115
+ non-nullary atom, rather than re-implementing it here — this release's
116
+ quantified-fragment additions extend that same reuse to
117
+ :class:`~fol.nodes.Quantifier`/``SortedQuantifier``/``SortedConstant``
118
+ arguments (see :func:`_render` / :func:`_term_sort`).
119
+
120
+ Scope — deliberately narrow, and narrowed FURTHER than a "cover everything"
121
+ reading of the sources above once each fragment was checked against real,
122
+ prover-exercised files rather than guessed from the paper's grammar alone:
123
+
124
+ * Supported: :class:`~fol.nodes.Box`, :class:`~fol.nodes.Diamond`,
125
+ :class:`~fol.nodes.Not`, :class:`~fol.nodes.And`, :class:`~fol.nodes.Or`,
126
+ :class:`~fol.nodes.Implies`, :class:`~fol.nodes.Iff`, over an
127
+ :class:`~fol.nodes.Atom` of ANY arity (0-ary propositional letters, and
128
+ now non-nullary ``P(x, y, ...)`` atoms too, PLUS equality/disequality —
129
+ see below) whose arguments are :class:`~fol.nodes.Variable` (bound by a
130
+ :class:`~fol.nodes.Quantifier` / ``SortedQuantifier`` — this module
131
+ renders NATIVE NXF quantifiers, ``! [X: <sort>] : (...)`` / ``? [X:
132
+ <sort>] : (...)``, per ``SYM001_1.p``/PUZ087_1.p above), or
133
+ :class:`~fol.nodes.Constant` / :class:`~fol.nodes.SortedConstant` (each
134
+ free constant gets its own ``tff(<name>_decl,type, <name>: <sort> ).``,
135
+ per ``APM002_1.p`` above) — the mono-modal ALETHIC fragment (the frame
136
+ system is K/T/S4/S5/D; no other modal family). An equality/disequality
137
+ atom (``=``/``≠``) is accepted ONLY when both operands compute to the
138
+ SAME NXF sort token — see the next bullet for why.
139
+ * NOT supported, each raising :class:`NotImplementedError` with a message
140
+ naming the reason (never a silently weaker translation):
141
+ - A free (unbound) :class:`~fol.nodes.Variable` — an Atom/equality
142
+ argument naming a variable with no enclosing
143
+ :class:`~fol.nodes.Quantifier` / ``SortedQuantifier`` (found by
144
+ adversarial review) is refused rather than defaulted to ``$i``: TPTP
145
+ reads a free variable in a ``conjecture`` role as EXISTENTIALLY
146
+ quantified, not universally, so silently typing it would silently
147
+ change the meaning of the exported problem, not merely under-specify
148
+ its sort (see :data:`_UNSUPPORTED_FREE_VARIABLE`).
149
+ - :class:`~fol.nodes.Function` arguments (``P(f(x))``) — a genuine
150
+ function SYMBOL's own NXF type declaration
151
+ (``<name>: (<arg-sorts>) > <result-sort>``) was never found in any
152
+ real file of the cloned corpus (every QMLTP-derived problem checked
153
+ uses only 0-ary/N-ary PREDICATES and free CONSTANTS, never a compound
154
+ function term), so its exact shape is inferred-by-grammar-pattern
155
+ only, not confirmed — refused by name rather than emitted unverified,
156
+ pending a primary-source example.
157
+ - A cross-sort equality/disequality atom (found by adversarial review):
158
+ the kit's own SortedQuantifier/SortedConstant semantics is
159
+ guard-predicate based (one shared classical domain, a sort is just a
160
+ unary predicate over it — see ``fol._msfl_nodes.SortedQuantifier.
161
+ _relativize``), so a cross-sort equality is perfectly meaningful
162
+ there, but native TFF/NXF sorts are disjoint and TFF's polymorphic
163
+ ``=`` requires both operands to share ONE declared type, with no
164
+ subtyping/coercion — emitting one anyway would be TYPE-INCORRECT NXF/
165
+ TFF text, not merely a false formula, so it is refused rather than
166
+ silently emitted (see :data:`_UNSUPPORTED_CROSS_SORT_EQUALITY`).
167
+ - An equality/disequality atom with a bare variable bound by an UNSORTED
168
+ quantifier, in a formula that also uses a user sort. Every operand pair
169
+ of such an atom can be well typed (two ``$i`` variables), but the types
170
+ are disjoint: the unsorted variable ranges over ``$i``, not over the
171
+ one universe the kit's sorts are subsets of, so ``∀x ∀y x = y`` bounds
172
+ ``$i`` and says nothing about a user sort, and a prover would answer a
173
+ different question than the kit's other routes (``∀x ∀y x = y ⊢ ∀x:A
174
+ ∀z:A x = z`` is valid in the kit and is not a theorem of the typed text).
175
+ The refusal is :class:`~unicode_logic_kit.atp.tptp_tff.Tf0Refusal` (also a
176
+ ``NotImplementedError``), the same as the TF0 writer's, with the same
177
+ message; this exporter does not infer a sort for an unannotated constant
178
+ (each occurrence is typed on its own and a constant at two types is
179
+ refused), so that is the only one of the TF0 writer's two typed-reading
180
+ refusals it needs.
181
+ - The arithmetic comparison predicates (``<``/``>``/``≤``/``≥``) and a
182
+ bare :class:`~fol.nodes.Number` literal anywhere in the formula
183
+ (found by adversarial review) — both need one of TPTP's arithmetic
184
+ sorts (``$int``/``$rat``/``$real``), which this exporter never
185
+ declares; defaulting a ``Number`` to ``$i`` (the previous behaviour)
186
+ or applying ``$less``/``$greater``/... to ``$i``-typed operands would
187
+ likewise be type-incorrect NXF/TFF text. Mirrors
188
+ :mod:`atp.tptp_tff`'s identical TF0-only refusal of the same two
189
+ constructs (see that module's ``_ARITH_OUT_OF_SCOPE``) rather than
190
+ inventing a new policy here.
191
+ - Every other modal family (epistemic ``Knows``/``Believes``, doxastic,
192
+ temporal, deontic, counterfactual, PAL) and INDEXED multi-modal
193
+ (agent-/index-indexed box(i)/dia(i)) — each needs the INDEXED
194
+ long-form connective (PUZ087_1.p's ``{{$knows(#a)}} @ (...)``, or the
195
+ generic ``{{$box(#i)}} @ (...)`` form the arXiv paper's Section 2/5
196
+ describe for FOMML) and the corresponding multi-entry
197
+ ``$modalities`` logic-spec list — real examples of the latter show at
198
+ least two non-equivalent accepted spellings for the per-index key
199
+ (``[#a] == system`` in ``CorrectSpecifications.p`` vs.
200
+ ``{{$necessary(#a)}} == system`` in the same file), which is not
201
+ settled precisely enough to commit to one shape here; a follow-up, not
202
+ guessed.
203
+ - Any frame other than K/T/S4/S5/D (:class:`ValueError`, not
204
+ ``NotImplementedError`` — this is a caller input error, not a missing
205
+ feature: these are exactly the frames the rest of this kit's modal
206
+ backends already know how to reason about, K/T/S4/S5 via
207
+ :func:`modal_prove` and D via :func:`~fol.qml.qml_is_valid`'s
208
+ ``frame="D"``/``"KD"``).
209
+ """
210
+
211
+ from typing import Dict, List, Optional, Tuple
212
+
213
+ from ..fol.nodes import (
214
+ Atom, And, Box, Constant, Diamond, Function, Iff, Implies, Node, Not,
215
+ Number, Or, Quantifier, SortedConstant, SortedQuantifier, Variable,
216
+ )
217
+ from ..fol._fol_nodes import tptp_fold_first_letter
218
+ from ..fol._tptp_symbols import is_tptp_boolean_atom
219
+ from .tptp_tff import unsorted_equality_refusal
220
+
221
+ __all__ = ["to_tptp_ncl"]
222
+
223
+ # The frame names this kit's OWN modal routes already reason about (K/T/S4/S5
224
+ # via atp.modal_tableau, D via fol.qml.qml_is_valid(frame="D"/"KD")) map onto
225
+ # the literal $modal_system_<X> tokens — each individually confirmed against
226
+ # the corpus's OWN canonical sources (Logics/LOG001_4.l's definitions,
227
+ # Tooling/generateSemantics.py's all_modalities tuple, and
228
+ # ProblemBuilding/QMLTP/SemanticSpecifications/t_*.p), not merely some
229
+ # prover-exercised .p file from github.com/TPTPWorld/NonClassicalLogic (see
230
+ # the module docstring's "Sources" section for exactly which file proves
231
+ # which token).
232
+ _FRAME_TO_SYSTEM: Dict[str, str] = {
233
+ "K": "$modal_system_K",
234
+ "T": "$modal_system_T",
235
+ "S4": "$modal_system_S4",
236
+ "S5": "$modal_system_S5",
237
+ # D (serial K, a.k.a. KD) -- the alethic SERIALITY axiom ∀w∃v R(w,v),
238
+ # confirmed as $modal_system_D by APM002_1.p / kd_branch_p/*.p in the
239
+ # cloned corpus (see module docstring). NOT to be confused with
240
+ # fol.qml's *deontic* accessibility relation also spelled "D" (qml.py's
241
+ # _R_DEONTIC): that is a wholly different relation this exporter never
242
+ # touches -- this "D" names the ALETHIC frame's seriality condition,
243
+ # the same one fol.qml.qml_is_valid(frame="D") / frame="KD" already
244
+ # reasons about for □/◇.
245
+ "D": "$modal_system_D",
246
+ }
247
+
248
+ # $domains values, confirmed by CorrectSpecifications.p's $domains ==
249
+ # $constant / [$constant, plushie == $varying] examples and the arXiv
250
+ # infrastructure paper's Table 1 enumeration of
251
+ # $constant/$varying/$cumulative/$decreasing. Only the bare (non-per-type)
252
+ # form is exposed here — a per-type domain list is a further extension this
253
+ # module does not implement.
254
+ #
255
+ # What changing this value DOES and does NOT do to the emitted problem, now
256
+ # that native quantifiers exist (this release): NOTHING changes in how a
257
+ # Quantifier/SortedQuantifier is RENDERED -- every value renders the exact
258
+ # same native ``! [X: <sort>] : (...)`` / ``? [X: <sort>] : (...)`` form,
259
+ # with no kit-side existence-guard atom of any kind. That is deliberate, not
260
+ # an oversight: NXF's $domains property is read by the CONSUMING PROVER to
261
+ # constrain how per-world domains relate to each other (SYM001_1.p's own
262
+ # $domains == $constant is exactly what makes its Barcan-scheme conjecture a
263
+ # Theorem for that prover) -- it is declarative logic-specification
264
+ # metadata, not a term-level transformation this exporter would have to
265
+ # perform itself. This is why :func:`to_tptp_ncl` does NOT reuse
266
+ # :func:`fol.qml.qml_translate` (which DOES perform such a transformation,
267
+ # via an explicit existence predicate E(x,w), for its own DIFFERENT
268
+ # technique: a shallow embedding into CLASSICAL logic decided by Z3, not a
269
+ # native non-classical export at all) -- the two routes solve the domain
270
+ # regime in different places on purpose, and conflating them would either
271
+ # double-guard the quantifiers (wrong syntax for NXF, which expects native
272
+ # ``!``/``?``) or silently drop the qml.py guard machinery's own soundness
273
+ # story. So every one of the four values is equally "wired": each selects
274
+ # a distinct token in the emitted ``$domains == ...`` line, which is the
275
+ # ONLY place this parameter has any effect.
276
+ _DOMAIN_WORDS: Dict[str, str] = {
277
+ "constant": "$constant",
278
+ "varying": "$varying",
279
+ "cumulative": "$cumulative",
280
+ "decreasing": "$decreasing",
281
+ }
282
+
283
+ # TPTP's built-in "individual" type -- always available, never separately
284
+ # declared, and non-empty BY TPTP SEMANTICS (the TF0/TFF standard fixes $i to
285
+ # denote a single non-empty domain of individuals; see the TPTP syntax BNF
286
+ # cited in fol/tptp_input.py's own docstring). An untyped Quantifier / a bare
287
+ # Constant defaults to this sort -- confirmed by SYM001_1.p's ``! [X: $i]``
288
+ # and APM002_1.p's ``a: $i`` (see module docstring). A user-declared sort
289
+ # (SortedQuantifier / SortedConstant) instead gets its own
290
+ # ``tff(<name>_type,type, <name>: $tType ).`` declaration -- and a $tType
291
+ # declaration is ITSELF non-empty by the same TPTP semantics (a $tType is a
292
+ # non-empty domain by definition; TPTP has no notion of an empty type), so
293
+ # this exporter never needs -- and does not emit -- a separate
294
+ # "sort S is non-empty" axiom the way fol.qml.qml_axioms's
295
+ # nonempty_sort_axioms does for the classical/shallow-embedding routes: the
296
+ # TYPED DECLARATION ITSELF already carries that guarantee for any consumer
297
+ # that honours TF0/TFF semantics.
298
+ _DEFAULT_SORT = "$i"
299
+
300
+ _UNSUPPORTED_FUNCTION = (
301
+ "to_tptp_ncl: a compound function term {name!r}(...) is outside this "
302
+ "exporter's supported fragment -- a genuine NXF function-SYMBOL type "
303
+ "declaration (as opposed to a predicate's) was never found in any real "
304
+ "problem of the checked github.com/TPTPWorld/NonClassicalLogic corpus, "
305
+ "so its exact shape is not confirmed (see module docstring 'Scope'); "
306
+ "refusing rather than emitting an unverified declaration. Only "
307
+ "Variable/Constant/SortedConstant terms are supported as atom arguments "
308
+ "in this release."
309
+ )
310
+
311
+ _UNSUPPORTED_NUMBER = (
312
+ "to_tptp_ncl: a bare numeral literal ({value!r}) is outside this "
313
+ "exporter's supported fragment -- TPTP types a numeral intrinsically as "
314
+ "an arithmetic sort ($int/$rat/$real), never as the individual sort $i "
315
+ "(or a user sort) this exporter's quantifiers/constants use, and this "
316
+ "exporter never declares an arithmetic sort (mirrors atp.tptp_tff's "
317
+ "identical TF0-only refusal for a Number term -- see that module's "
318
+ "docstring 'Scope: TF0 only'); refusing rather than mistyping the "
319
+ "literal as $i. Only Variable/Constant/SortedConstant terms are "
320
+ "supported as atom arguments in this release."
321
+ )
322
+
323
+ _UNSUPPORTED_FREE_VARIABLE = (
324
+ "to_tptp_ncl: variable {name!r} is not bound by an enclosing Quantifier/"
325
+ "SortedQuantifier (found by adversarial review) -- refusing rather than "
326
+ "defaulting it to $i, because an emitted NXF conjecture with a genuinely "
327
+ "free variable is not equivalent to the source formula: TPTP treats a "
328
+ "free variable in a conjecture role as EXISTENTIALLY quantified, not "
329
+ "universally, which silently changes the meaning of the exported "
330
+ "problem rather than merely defaulting its sort. Bind every variable "
331
+ "with a Quantifier/SortedQuantifier before calling to_tptp_ncl."
332
+ )
333
+
334
+ _UNSUPPORTED_ARITHMETIC_PRED = (
335
+ "to_tptp_ncl: the arithmetic comparison {name!r} is outside this "
336
+ "exporter's supported fragment -- it needs TPTP's arithmetic sorts "
337
+ "($int/$rat/$real), which this exporter never declares (mirrors "
338
+ "atp.tptp_tff's identical TF0-only refusal of the same predicate -- see "
339
+ "that module's docstring 'Scope: TF0 only'); refusing rather than "
340
+ "emitting a $less/$greater/$lesseq/$greatereq atom whose operands are "
341
+ "typed $i (or a user sort) instead of an arithmetic sort."
342
+ )
343
+
344
+ _UNSUPPORTED_CROSS_SORT_EQUALITY = (
345
+ "to_tptp_ncl: an equality/disequality atom ({pred!r}) between an "
346
+ "operand of sort {left!r} and one of sort {right!r} is outside this "
347
+ "exporter's supported fragment -- the kit's own SortedQuantifier/"
348
+ "SortedConstant semantics is guard-predicate based (S(x) is a unary "
349
+ "predicate over one shared classical domain, so cross-sort equality is "
350
+ "meaningful there -- see fol._msfl_nodes.SortedQuantifier._relativize), "
351
+ "but native TFF/NXF types are disjoint and TFF's polymorphic '=' "
352
+ "requires both operands to share the SAME declared type, with no "
353
+ "subtyping/coercion; refusing rather than emitting a type-incorrect "
354
+ "NXF/TFF problem. Give both sides of the equality the same sort."
355
+ )
356
+
357
+ _UNSUPPORTED_NODE = (
358
+ "to_tptp_ncl: {name} is outside the supported mono-modal alethic "
359
+ "fragment (Box/Diamond/¬/∧/∨/→/↔ over atoms, with native ∀/∃ "
360
+ "quantifiers). Every other modal family (epistemic Knows/Believes, "
361
+ "doxastic, temporal, deontic, counterfactual, PAL), and INDEXED "
362
+ "multi-modal box(i)/dia(i), are documented, not-yet-implemented "
363
+ "extensions -- see module docstring 'Scope'."
364
+ )
365
+
366
+
367
+ def _sort_token(sort_name: str) -> str:
368
+ """The NXF identifier for a kit sort NAME (``SortedQuantifier.sort`` /
369
+ ``SortedConstant.sort``) -- the same first-letter fold every other NXF
370
+ identifier in this module goes through (see :func:`_term_sort` /
371
+ :meth:`_NxfSymbols.note_predicate`), so a sort name follows the exact
372
+ same TPTP lower_word convention as everything else this exporter emits.
373
+
374
+ Raises:
375
+ NotImplementedError: ``sort_name`` starts with ``$``. Every such word is one of
376
+ TPTP's own (``$i`` the type of individuals, ``$o`` the booleans, ``$tType``
377
+ the type of types, ``$int`` / ``$rat`` / ``$real`` the numbers, ``$true``,
378
+ ...), and this writer writes a sort name as it is, so the sort would be merged
379
+ with that word, or redeclare it as ``tff($i_type,type, $i: $tType )``. ``$i``
380
+ matters most: it is the sort this writer gives every UNSORTED quantifier and
381
+ constant, so a user sort of that name would be one with everything that has no
382
+ sort.
383
+ """
384
+ if sort_name.startswith("$"):
385
+ raise NotImplementedError(
386
+ f"to_tptp_ncl: a sort named {sort_name!r} cannot be written: a name that starts "
387
+ "with '$' is one of TPTP's own words ($i is the type of individuals, which this "
388
+ "writer gives every unsorted quantifier and constant, $o the booleans, $tType the "
389
+ "type of types, $int/$rat/$real the numbers), so the sort would be merged with "
390
+ "that word or redeclare it. Rename the sort.")
391
+ return tptp_fold_first_letter(sort_name)
392
+
393
+
394
+ class _NxfSymbols:
395
+ """Collects every sort / constant / predicate this export's formula uses,
396
+ in first-occurrence order, while :func:`_render` walks the tree once --
397
+ and refuses (NotImplementedError), rather than silently aliasing, any
398
+ case where two DISTINCT kit symbols would render as the SAME NXF
399
+ identifier, or the same kit symbol would need two DIFFERENT NXF types.
400
+ Both are genuine soundness holes for an emitted problem: a duplicate
401
+ ``type`` declaration for one name, or a real ambiguity a consuming
402
+ prover would resolve arbitrarily, silently changing what is being
403
+ proved. Mirrors the collision-refusal :func:`to_tptp_ncl` already
404
+ applied to 0-ary propositional letters before this release, generalised
405
+ to sorts/constants/predicates of any arity.
406
+ """
407
+
408
+ def __init__(self):
409
+ self.sort_order: List[str] = []
410
+ self.sort_sig: Dict[str, str] = {} # token -> kit sort name
411
+ self.const_order: List[str] = []
412
+ self.const_sig: Dict[str, Tuple[str, str]] = {} # token -> (kit name, sort token)
413
+ self.pred_order: List[str] = []
414
+ self.pred_sig: Dict[str, Tuple[str, tuple]] = {} # token -> (kit name, arg sort tokens)
415
+ # The first equality / disequality atom, as text, that has a bare variable
416
+ # bound by an UNSORTED quantifier as one of its sides (see to_tptp_ncl).
417
+ self.unsorted_equality: Optional[str] = None
418
+
419
+ def note_sort(self, name: str, token: str) -> None:
420
+ """Register one kit sort NAME (``SortedQuantifier.sort`` /
421
+ ``SortedConstant.sort``) under its folded NXF ``token``, refusing
422
+ (NotImplementedError) rather than silently merging when two
423
+ DISTINCT kit sort names would fold to the SAME token -- mirrors
424
+ :meth:`note_constant`/:meth:`note_predicate` exactly (this is the
425
+ sort-level counterpart of both; before this check, two sorts like
426
+ ``"Human"``/``"human"`` collapsed into one ``$tType`` declaration,
427
+ contradicting this class's own docstring)."""
428
+ if token in self.sort_sig:
429
+ prev_name = self.sort_sig[token]
430
+ if prev_name != name:
431
+ raise NotImplementedError(
432
+ f"to_tptp_ncl: distinct sorts {prev_name!r} and {name!r} "
433
+ f"would both render as the NXF identifier {token!r} "
434
+ "(TPTP lower-cases the first letter) -- refusing to "
435
+ "alias them; rename one of the sorts so the export "
436
+ "stays faithful.")
437
+ else:
438
+ self.sort_sig[token] = name
439
+ self.sort_order.append(token)
440
+
441
+ def note_constant(self, name: str, sort_token: str) -> None:
442
+ token = Constant(name).to_tptp()
443
+ if token in self.const_sig:
444
+ prev_name, prev_sort = self.const_sig[token]
445
+ if prev_name != name:
446
+ raise NotImplementedError(
447
+ f"to_tptp_ncl: distinct constants {prev_name!r} and {name!r} "
448
+ f"would both render as the NXF identifier {token!r} (TPTP "
449
+ "lower-cases the first letter) -- refusing to alias them; "
450
+ "rename one of the constants so the export stays faithful.")
451
+ if prev_sort != sort_token:
452
+ raise NotImplementedError(
453
+ f"to_tptp_ncl: the constant {name!r} is used with two "
454
+ f"different sorts ({prev_sort!r} and {sort_token!r}) in "
455
+ "this formula -- TPTP requires one fixed type per symbol; "
456
+ "give it a single, consistent sort throughout.")
457
+ else:
458
+ self.const_sig[token] = (name, sort_token)
459
+ self.const_order.append(token)
460
+
461
+ def note_predicate(self, name: str, arg_sorts: tuple) -> None:
462
+ token = tptp_fold_first_letter(name)
463
+ if token in self.pred_sig:
464
+ prev_name, prev_sorts = self.pred_sig[token]
465
+ if prev_name != name:
466
+ raise NotImplementedError(
467
+ f"to_tptp_ncl: distinct propositional/predicate letters "
468
+ f"{prev_name!r} and {name!r} would both render as the NXF "
469
+ f"identifier {token!r} (TPTP lower-cases the first "
470
+ "letter) -- refusing to alias them; rename one of the "
471
+ "atoms so the export stays faithful.")
472
+ if prev_sorts != arg_sorts:
473
+ raise NotImplementedError(
474
+ f"to_tptp_ncl: the predicate {name!r} is used at two "
475
+ f"different argument types/arities ({prev_sorts!r} and "
476
+ f"{arg_sorts!r}) in this formula -- TPTP requires one "
477
+ "fixed type per symbol; give it a single, consistent "
478
+ "signature throughout.")
479
+ else:
480
+ self.pred_sig[token] = (name, arg_sorts)
481
+ self.pred_order.append(token)
482
+
483
+ def check_no_cross_namespace_collision(self) -> None:
484
+ """Refuse an NXF identifier used as more than one KIND of symbol
485
+ (e.g. a sort named the same as a predicate) -- TPTP identifiers are
486
+ a single flat namespace of ``lower_word`` tokens, so two DIFFERENT
487
+ kit symbols folding to the same token would collide even if neither
488
+ alone triggered :meth:`note_constant` / :meth:`note_predicate`.
489
+
490
+ This exporter returns bare text with no name map, so unlike the
491
+ ``fof``/TF0/TFA problem writers (which rename the function/constant
492
+ side of such a clash and record it in a ``TptpNameMap``) it can only
493
+ refuse -- by name, naming both kit symbols and the writers to use."""
494
+ kinds: Dict[str, List[Tuple[str, str]]] = {}
495
+ for token in self.sort_order:
496
+ kinds.setdefault(token, []).append(("a sort", self.sort_sig[token]))
497
+ for token in self.const_order:
498
+ kinds.setdefault(token, []).append(("a constant", self.const_sig[token][0]))
499
+ for token in self.pred_order:
500
+ kinds.setdefault(token, []).append(("a predicate", self.pred_sig[token][0]))
501
+ for token, seen in kinds.items():
502
+ if len({kind for kind, _ in seen}) > 1:
503
+ described = " and ".join(f"{kind} ({name!r})" for kind, name in sorted(seen))
504
+ raise NotImplementedError(
505
+ f"to_tptp_ncl: the NXF identifier {token!r} would be "
506
+ f"declared as both {described} -- TPTP "
507
+ "identifiers share one flat namespace, and this exporter "
508
+ "has no name map to record a rename in; rename one of "
509
+ "the colliding kit symbols so the export stays faithful "
510
+ "(the classical writers generate_tptp_problem_with_mapping "
511
+ "and generate_tff_problem_with_mapping rename the "
512
+ "function/constant side of such a clash and return the "
513
+ "record).")
514
+
515
+
516
+ # Atom predicates TPTP renders as the genuinely INFIX built-in ('='/'!=') --
517
+ # never user-declared, so _render walks their arguments (for nested
518
+ # constants) without registering the predicate itself, but (unlike a plain
519
+ # user predicate) DOES require both operands to share one NXF sort token,
520
+ # since native TFF's polymorphic '=' has no subtyping/coercion (see
521
+ # _UNSUPPORTED_CROSS_SORT_EQUALITY). Reuses Atom's own table rather than
522
+ # re-listing it, so this set can never drift from what Atom.to_tptp()
523
+ # actually treats as infix.
524
+ _EQUALITY_PREDS = frozenset(Atom.INFIX_PREDS_TPTP)
525
+
526
+ # Arithmetic comparisons (TPTP's $less/$greater/$lesseq/$greatereq
527
+ # dollar-word PREFIX predicates) need TPTP's arithmetic sorts ($int/$rat/
528
+ # $real), which this exporter never declares -- refused by name rather than
529
+ # emitted over $i (or a user sort), mirroring atp.tptp_tff's identical
530
+ # TF0-only refusal of the same predicate set (see that module's
531
+ # _ARITH_OUT_OF_SCOPE). Reuses Atom's own table rather than re-listing it.
532
+ _ARITHMETIC_PREDS = frozenset(Atom.PREFIX_PREDS_TPTP)
533
+
534
+
535
+ def _term_sort(term: Node, scope: Dict[str, str], symbols: _NxfSymbols) -> str:
536
+ """The NXF sort token of one TERM occurrence, registering any
537
+ constant/sort it introduces into ``symbols`` as a side effect.
538
+
539
+ ``scope`` maps a bound Variable's kit NAME to its NXF sort token (set by
540
+ the enclosing Quantifier/SortedQuantifier in :func:`_render`). A
541
+ Variable absent from ``scope`` is a genuinely FREE variable -- directly
542
+ reachable through the public entry point (:func:`to_tptp_ncl` starts
543
+ rendering with an empty scope, so any Atom/equality argument that is a
544
+ Variable with no enclosing Quantifier/SortedQuantifier hits this case,
545
+ found by adversarial review) -- and is refused rather than defaulted to
546
+ ``$i``: TPTP reads a free variable in a conjecture role as EXISTENTIALLY
547
+ quantified, not universally, so silently defaulting its sort would
548
+ silently change the meaning of the exported problem, not merely under-
549
+ specify its type.
550
+
551
+ Raises:
552
+ NotImplementedError: ``term`` is a free (unbound) Variable, a
553
+ :class:`~fol.nodes.Function` (compound function term), or a
554
+ :class:`~fol.nodes.Number` -- see module docstring 'Scope'.
555
+ """
556
+ if isinstance(term, Variable):
557
+ if term.name not in scope:
558
+ raise NotImplementedError(
559
+ _UNSUPPORTED_FREE_VARIABLE.format(name=term.name))
560
+ return scope[term.name]
561
+ if isinstance(term, SortedConstant):
562
+ token = _sort_token(term.sort)
563
+ symbols.note_sort(term.sort, token)
564
+ symbols.note_constant(term.name, token)
565
+ return token
566
+ if isinstance(term, Constant):
567
+ symbols.note_constant(term.name, _DEFAULT_SORT)
568
+ return _DEFAULT_SORT
569
+ if isinstance(term, Number):
570
+ # A bare numeral literal (e.g. "42") is TPTP's own built-in numeral
571
+ # syntax, but it is intrinsically typed to an ARITHMETIC sort
572
+ # ($int/$rat/$real) by the TPTP standard itself -- never to $i (the
573
+ # individual sort) or to any user-declared $tType. This exporter
574
+ # never declares an arithmetic sort (see _UNSUPPORTED_NUMBER /
575
+ # module docstring 'Scope'), so defaulting a Number to $i the way an
576
+ # untyped Constant/Quantifier does would emit a type-incorrect NXF/
577
+ # TFF problem (found by adversarial review; the previous version of
578
+ # this function did exactly that) -- refused instead, mirroring
579
+ # atp.tptp_tff's identical TF0-only refusal of a Number term.
580
+ raise NotImplementedError(_UNSUPPORTED_NUMBER.format(value=term.value))
581
+ if isinstance(term, Function):
582
+ raise NotImplementedError(_UNSUPPORTED_FUNCTION.format(name=term.name))
583
+ raise NotImplementedError(
584
+ f"to_tptp_ncl: unsupported term {type(term).__name__} in the "
585
+ "quantified fragment -- see module docstring 'Scope'.")
586
+
587
+
588
+ def _quantifier_prefix(qtype: str, var_token: str, sort_token: str) -> str:
589
+ if qtype in ("∀", "forall"):
590
+ q = "!"
591
+ elif qtype in ("∃", "exists"):
592
+ q = "?"
593
+ else:
594
+ raise ValueError(f"to_tptp_ncl: unknown quantifier type {qtype!r}.")
595
+ return f"{q} [{var_token}: {sort_token}]"
596
+
597
+
598
+ def _render(node: Node, scope: Dict[str, str], symbols: _NxfSymbols) -> str:
599
+ """Render one node of the supported fragment as an NXF formula string,
600
+ registering every sort/constant/predicate it introduces into ``symbols``.
601
+
602
+ Mirrors the shape :meth:`Node.to_tptp` already uses for the classical
603
+ connectives (``~(...)``, ``(l & r)``, ``(l | r)``, ``(l => r)``,
604
+ ``(l <=> r)`` — see ``fol/_fol_nodes.py``), and extends it with the two
605
+ NXF short-form modal prefix connectives, ``[.]``/``<.>``, and NATIVE NXF
606
+ quantifiers ``! [X: <sort>] : (...)`` / ``? [X: <sort>] : (...)`` (see
607
+ module docstring). Every branch's own output is already a self-delimited
608
+ ``unary`` production (a bare atom, a ``~(...)``-wrapped formula, a fully
609
+ parenthesised binary formula, ``[.]``/``<.>`` applied to one of those, or
610
+ a quantifier whose OWN body is parenthesised — ``! [X: S] : (unary)`` is
611
+ itself exactly the grammar's ``"!" "[" varlist "]" ":" unary`` alternative,
612
+ matching :mod:`fol.tptp_input`'s ``?unary: ... | "!" "[" varlist "]" ":"
613
+ unary | ...`` rule), so nesting one under another NEVER needs extra
614
+ wrapping parentheses.
615
+
616
+ Raises:
617
+ NotImplementedError: ``node`` (or a descendant) is outside the
618
+ supported fragment — a compound function-term, Number, or free
619
+ (unbound) Variable argument, an arithmetic comparison predicate,
620
+ a cross-sort equality, a quantifier of unknown type, or any
621
+ modal-family node other than Box/Diamond.
622
+ """
623
+ if isinstance(node, Atom):
624
+ if node.predicate in _ARITHMETIC_PREDS:
625
+ raise NotImplementedError(
626
+ _UNSUPPORTED_ARITHMETIC_PRED.format(name=node.predicate))
627
+ if node.predicate in _EQUALITY_PREDS:
628
+ if len(node.args) != 2:
629
+ raise ValueError(
630
+ f"to_tptp_ncl: an equality/disequality atom "
631
+ f"({node.predicate!r}) must have exactly 2 arguments, "
632
+ f"got {len(node.args)}.")
633
+ left_sort = _term_sort(node.args[0], scope, symbols)
634
+ right_sort = _term_sort(node.args[1], scope, symbols)
635
+ if left_sort != right_sort:
636
+ raise NotImplementedError(_UNSUPPORTED_CROSS_SORT_EQUALITY.format(
637
+ pred=node.predicate, left=left_sort, right=right_sort))
638
+ if symbols.unsorted_equality is None and any(
639
+ isinstance(a, Variable) and scope[a.name] == _DEFAULT_SORT
640
+ for a in node.args):
641
+ symbols.unsorted_equality = node.to_unicode_str()
642
+ return node.to_tptp()
643
+ if node.predicate.startswith("$") or is_tptp_boolean_atom(node):
644
+ # TPTP's own words, and the truth constants under either spelling
645
+ # (``⊤`` / ``⊥`` are written ``$true`` / ``$false``): no symbol to declare.
646
+ for arg in node.args:
647
+ _term_sort(arg, scope, symbols)
648
+ return node.to_tptp()
649
+ arg_sorts = tuple(_term_sort(arg, scope, symbols) for arg in node.args)
650
+ symbols.note_predicate(node.predicate, arg_sorts)
651
+ return node.to_tptp()
652
+ if isinstance(node, Not):
653
+ return f"~({_render(node.formula, scope, symbols)})"
654
+ if isinstance(node, And):
655
+ return f"({_render(node.left, scope, symbols)} & {_render(node.right, scope, symbols)})"
656
+ if isinstance(node, Or):
657
+ return f"({_render(node.left, scope, symbols)} | {_render(node.right, scope, symbols)})"
658
+ if isinstance(node, Implies):
659
+ return f"({_render(node.left, scope, symbols)} => {_render(node.right, scope, symbols)})"
660
+ if isinstance(node, Iff):
661
+ return f"({_render(node.left, scope, symbols)} <=> {_render(node.right, scope, symbols)})"
662
+ if isinstance(node, Box):
663
+ return f"[.] {_render(node.formula, scope, symbols)}"
664
+ if isinstance(node, Diamond):
665
+ return f"<.> {_render(node.formula, scope, symbols)}"
666
+ if isinstance(node, (Quantifier, SortedQuantifier)):
667
+ sort = node.sort if isinstance(node, SortedQuantifier) else None
668
+ sort_token = _sort_token(sort) if sort is not None else _DEFAULT_SORT
669
+ if sort is not None:
670
+ symbols.note_sort(sort, sort_token)
671
+ var_token = Variable(node.variable.name).to_tptp()
672
+ new_scope = dict(scope)
673
+ new_scope[node.variable.name] = sort_token
674
+ body = _render(node.formula, new_scope, symbols)
675
+ prefix = _quantifier_prefix(node.type, var_token, sort_token)
676
+ return f"{prefix} : ({body})"
677
+ raise NotImplementedError(_UNSUPPORTED_NODE.format(name=type(node).__name__))
678
+
679
+
680
+ def to_tptp_ncl(formula: Node, *, frame: str = "K", domains: str = "constant",
681
+ conjecture_name: str = "c") -> str:
682
+ """Render ``formula`` as a complete NXF (TPTP Non-Classical Logic) problem.
683
+
684
+ Covers the mono-modal ALETHIC fragment: Box/Diamond over
685
+ ``¬``/``∧``/``∨``/``→``/``↔``, atoms of any arity, and native
686
+ ``∀``/``∃`` quantifiers (:class:`~fol.nodes.Quantifier` /
687
+ :class:`~fol.nodes.SortedQuantifier`). See the module docstring for the
688
+ verified syntax sources and the exact scope boundary (compound function
689
+ terms, and non-alethic modal families, are documented, not-yet-
690
+ implemented extensions, not silently mistranslated).
691
+
692
+ The emitted problem is, in order:
693
+
694
+ 1. A ``logic`` role statement fixing the four NXF modal parameters —
695
+ ``$designation`` is fixed to ``$rigid`` and ``$terms`` to ``$global``
696
+ (the only combination the alethic fragment needs); ``$flexible``/
697
+ ``$local`` are not exposed as they would only matter for a
698
+ flexible-designation extension this module does not attempt.
699
+ 2. One ``tff(<sort>_type,type, <sort>: $tType ).`` per DISTINCT
700
+ user-declared sort the formula uses (``SortedQuantifier``/
701
+ ``SortedConstant``), in first-occurrence order — TPTP's built-in
702
+ ``$i`` (the default for every untyped Quantifier/Constant) needs no
703
+ such declaration and gets none (see :data:`_DEFAULT_SORT`).
704
+ 3. One ``tff(<name>_decl,type, <name>: <sort> ).`` per DISTINCT free
705
+ constant the formula uses, in first-occurrence order.
706
+ 4. One ``tff(<name>_decl,type, ...).`` per DISTINCT predicate the
707
+ formula uses, in first-occurrence order — ``<name>: $o`` for a 0-ary
708
+ (propositional) letter, ``<name>: <sort> > $o`` for one argument, or
709
+ ``<name>: (<s1> * <s2> * ...) > $o`` for several — required because
710
+ NXF/NTF is a typed (TFF-based) dialect; an undeclared bare atom is
711
+ not accepted.
712
+ 5. One ``conjecture`` role statement holding the translated ``formula``.
713
+
714
+ Args:
715
+ formula: a modal formula built from Box/Diamond/Not/And/Or/Implies/
716
+ Iff/Quantifier/SortedQuantifier over Atom nodes (the supported
717
+ fragment — see module docstring). Typically the result of
718
+ folding ``premises ⊨ conclusion`` into ``(∧premises) →
719
+ conclusion`` first (:func:`atp.leo3_backend`'s ``decide()`` does
720
+ this), since NXF has no separate premise list — one conjecture
721
+ is the whole problem.
722
+ frame: the alethic modal system — one of ``"K"``, ``"T"``, ``"S4"``,
723
+ ``"S5"``, ``"D"`` (:class:`ValueError` listing the valid names
724
+ otherwise).
725
+ domains: the NXF ``$domains`` value — one of ``"constant"``,
726
+ ``"varying"``, ``"cumulative"``, ``"decreasing"``
727
+ (:class:`ValueError` listing the valid names otherwise). Every
728
+ value selects a distinct ``$domains == ...`` token; NONE of them
729
+ changes how a quantifier is RENDERED (native ``!``/``?`` either
730
+ way) — see :data:`_DOMAIN_WORDS`'s docstring for exactly why
731
+ that is the correct, non-approximated behaviour rather than a
732
+ silent no-op.
733
+ conjecture_name: the TPTP statement name used for the ``conjecture``
734
+ role formula; the ``logic`` role statement is named
735
+ ``<conjecture_name>_logic``.
736
+
737
+ Returns:
738
+ The complete NXF problem text, newline-terminated, ready to write to
739
+ a ``.p`` file for an NXF-aware prover (e.g. Leo-III via
740
+ :class:`atp.leo3_backend.Leo3Backend`).
741
+
742
+ Raises:
743
+ ValueError: ``frame`` or ``domains`` is not one of the recognised
744
+ values, or a Quantifier/SortedQuantifier has an unrecognised
745
+ ``type``.
746
+ NotImplementedError: ``formula`` (or a descendant) is outside the
747
+ supported fragment, or two distinct kit symbols would collide
748
+ under NXF's identifier folding — see :func:`_render` /
749
+ :class:`_NxfSymbols`, or an equality over a variable bound by an
750
+ unsorted quantifier while a user sort occurs (a
751
+ :class:`~unicode_logic_kit.atp.tptp_tff.Tf0Refusal`, which is also
752
+ a ``ValueError``). That includes a predicate (or sort) and a
753
+ constant that fold to one word: this exporter returns bare text,
754
+ has no name map to record a rename in, and so refuses by name,
755
+ where :func:`~unicode_logic_kit.atp._tptp_problem
756
+ .generate_tptp_problem_with_mapping` renames the term side and
757
+ records it.
758
+ """
759
+ if frame not in _FRAME_TO_SYSTEM:
760
+ raise ValueError(
761
+ f"to_tptp_ncl: unknown frame {frame!r} (use one of {sorted(_FRAME_TO_SYSTEM)})")
762
+ if domains not in _DOMAIN_WORDS:
763
+ raise ValueError(
764
+ f"to_tptp_ncl: unknown domains {domains!r} (use one of {sorted(_DOMAIN_WORDS)})")
765
+
766
+ # _render walks the WHOLE tree, collecting every sort/constant/predicate
767
+ # into `symbols` and raising before anything is emitted if any node (or
768
+ # term) is outside the supported fragment.
769
+ symbols = _NxfSymbols()
770
+ body = _render(formula, {}, symbols)
771
+ symbols.check_no_cross_namespace_collision()
772
+ if symbols.unsorted_equality is not None and symbols.sort_order:
773
+ # TFF's types are disjoint, so the unsorted variable of the equation ranges
774
+ # over $i, not over the universe a user sort is a subset of (the same
775
+ # refusal as the TF0 writer's; see unsorted_equality_refusal).
776
+ raise unsorted_equality_refusal(
777
+ "to_tptp_ncl", symbols.unsorted_equality,
778
+ [symbols.sort_sig[token] for token in symbols.sort_order], _DEFAULT_SORT)
779
+
780
+ lines = [
781
+ f"tff({conjecture_name}_logic,logic,",
782
+ " $modal ==",
783
+ f" [ $domains == {_DOMAIN_WORDS[domains]},",
784
+ " $designation == $rigid,",
785
+ " $terms == $global,",
786
+ f" $modalities == {_FRAME_TO_SYSTEM[frame]} ] ).",
787
+ "",
788
+ ]
789
+ for token in symbols.sort_order:
790
+ lines.append(f"tff({token}_type,type,")
791
+ lines.append(f" {token}: $tType ).")
792
+ lines.append("")
793
+ for token in symbols.const_order:
794
+ _, sort_token = symbols.const_sig[token]
795
+ lines.append(f"tff({token}_decl,type,")
796
+ lines.append(f" {token}: {sort_token} ).")
797
+ lines.append("")
798
+ for token in symbols.pred_order:
799
+ _, arg_sorts = symbols.pred_sig[token]
800
+ lines.append(f"tff({token}_decl,type,")
801
+ if not arg_sorts:
802
+ lines.append(f" {token}: $o ).")
803
+ elif len(arg_sorts) == 1:
804
+ lines.append(f" {token}: {arg_sorts[0]} > $o ).")
805
+ else:
806
+ domain = " * ".join(arg_sorts)
807
+ lines.append(f" {token}: ({domain}) > $o ).")
808
+ lines.append("")
809
+ lines.append(f"tff({conjecture_name},conjecture,")
810
+ lines.append(f" {body} ).")
811
+ return "\n".join(lines) + "\n"