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,332 @@
1
+ """Leo-III backend: mono-modal alethic decisions via NXF + the Leo-III prover.
2
+
3
+ `Leo-III <https://github.com/leoprover/Leo-III>`_ is a higher-order ATP with
4
+ native support for TPTP's Non-Classical Logic dialect (NXF/NHF, see
5
+ :mod:`atp.tptp_ncl`): handed an NXF problem with a ``logic`` role statement,
6
+ it decides the formula in exactly that non-classical logic rather than a
7
+ first-order approximation of it. This module is the thin
8
+ :class:`~atp.protocol.ProverBackend` adapter around that: it folds
9
+ ``premises ⊨ formula`` into one NXF conjecture (:mod:`atp.tptp_ncl`), spawns
10
+ ``java -jar leo3.jar <problem> -t <seconds>``, and reads the result back
11
+ through the shared TPTP-family SZS reader (:mod:`atp.tstp`) — the identical
12
+ plumbing shape :mod:`atp.vampire_entailment` uses for classical Vampire runs,
13
+ generalised to Leo-III's non-classical mode.
14
+
15
+ Scope: exactly what :func:`atp.tptp_ncl.to_tptp_ncl` can render — the
16
+ mono-modal alethic PROPOSITIONAL fragment, frames K/T/S4/S5. A formula
17
+ outside that fragment (a quantifier, a non-nullary atom, or a non-alethic
18
+ modal family) is reported as ``UNKNOWN``/``"unsupported"`` rather than a
19
+ malformed or silently wrong NXF file ever reaching the subprocess — see
20
+ :func:`Leo3Backend.decide`.
21
+
22
+ **The mandatory modal-tableau cross-check**, mirroring
23
+ :class:`atp.nanocop_backend.NanocopBackend`'s soundness-alarm policy (an
24
+ external prover's claim earns trust only when an in-kit route backs it up):
25
+ every PROVED/REFUTED verdict is re-decided by this kit's own
26
+ ``modal-tableau`` (:class:`atp.protocol.ModalTableauBackend`) on the SAME
27
+ ``formula``/``premises``/``frame`` before it is returned (UNKNOWN/ERROR
28
+ verdicts skip the check — there is nothing definitive to cross-check yet).
29
+ Unlike nanocop's policy this one is **symmetric**: nanocop's first-order
30
+ modal fragment is undecidable, so only its ``Theorem`` direction has a
31
+ complete second opinion available (a tableau REFUTATION alarms; a tableau
32
+ UNKNOWN never can, because completeness does not exist there to demand it
33
+ of). Leo-III's fragment here, in contrast, is the mono-modal alethic
34
+ PROPOSITIONAL fragment over K/T/S4/S5 — and ``modal-tableau`` is a *sound
35
+ and complete* decision procedure for exactly that fragment (labelled
36
+ tableaux with blocking decide propositional K/T/S4/S5; see
37
+ :mod:`atp.modal_tableau`'s own docstring), strictly larger than what
38
+ Leo-III accepts (multi-relation Knows/Believes/PAL, and the frames
39
+ D/B/K4/K45/KD45 besides). So a disagreement in EITHER direction —
40
+ Leo-III PROVED vs. tableau REFUTED, or Leo-III REFUTED vs. tableau PROVED —
41
+ is a genuine soundness alarm (``ERROR``/``"infra"``), not merely one
42
+ engine outrunning the other's power. The only escape hatch is the
43
+ tableau's own resource bound (``UNKNOWN``/``"bound_hit"``): that is a
44
+ budget limit, not an incompleteness of the fragment, so it never alarms —
45
+ Leo-III's own verdict is kept, with the cross-check outcome
46
+ (``"confirmed"``/``"inconclusive"``) appended to ``detail``, and — when
47
+ Leo-III's REFUTED is confirmed — the tableau's verified Kripke witness is
48
+ attached as ``Verdict.countermodel`` (see
49
+ :meth:`atp.protocol.ModalTableauBackend.decide`). The cross-check itself is
50
+ best-effort infrastructure: an exception from it (not a tableau
51
+ disagreement) is swallowed and never masks Leo-III's own answer — see
52
+ :meth:`Leo3Backend._cross_check`.
53
+
54
+ Discovery is deliberately narrow and explicit, matching
55
+ :class:`atp.protocol.Prover9Backend`/:class:`atp.protocol.VampireBackend`'s
56
+ own env-var-then-PATH convention: ``$UFK_LEO3`` must point to either a
57
+ Leo-III ``.jar`` (invoked as ``java -jar <path> ...``) or an already-executable
58
+ wrapper script/binary (invoked directly, no ``java -jar`` — for a machine
59
+ where Leo-III is wrapped in a shell/batch launcher instead of a bare jar), AND
60
+ ``java`` must be resolvable on ``PATH``. Both are required for
61
+ :meth:`Leo3Backend.available` to return ``True`` — this kit's own
62
+ :mod:`atp.protocol` module explicitly documents that a "known but
63
+ unavailable" backend must raise :class:`~atp.protocol.BackendUnavailable`
64
+ rather than silently vanish from a chain, and this backend follows that
65
+ contract.
66
+
67
+ NOT independently verified on this machine: no local Leo-III install was
68
+ available to actually run against (see the module docstring of
69
+ ``tests/test_tptp_ncl.py`` for what IS/is-not exercised). The live test below
70
+ is skipped unless ``$UFK_LEO3`` is set, and documents the intended path for
71
+ whoever next runs it against a real Leo-III.
72
+ """
73
+
74
+ import os
75
+ import shutil
76
+ import subprocess
77
+ import tempfile
78
+ import time
79
+ from typing import Optional, Sequence
80
+
81
+ from ..fol.nodes import And, Implies, Node
82
+ from .protocol import (
83
+ ERROR, PROVED, REFUTED, UNKNOWN, BackendUnavailable, ProverBackend, Verdict,
84
+ )
85
+ from .tptp_ncl import to_tptp_ncl
86
+ from .tstp import extract_szs_status, szs_to_verdict_fields
87
+
88
+ __all__ = ["Leo3Backend"]
89
+
90
+ # How much of Leo-III's stdout+stderr to keep on an ERROR verdict (no SZS
91
+ # status line found at all) — mirrors atp.vampire_entailment's _EXCERPT_CHARS
92
+ # convention: enough to diagnose a syntax/CLI-usage failure without dumping
93
+ # an unbounded proof search log into a Verdict.
94
+ _EXCERPT_CHARS = 2000
95
+
96
+ # How many EXTRA seconds beyond the prover's own ``-t <seconds>`` budget the
97
+ # subprocess itself is allowed before this backend gives up waiting — Leo-III
98
+ # is asked to stop at ``timeout_sec`` internally, so the outer subprocess
99
+ # timeout is only a safety net against a prover that ignores ``-t`` (a hung
100
+ # JVM, a malformed CLI invocation that silently no-ops the flag, ...).
101
+ _SUBPROCESS_SLACK_SECONDS = 10
102
+
103
+
104
+ def _fold_premises(formula: Node, premises: Sequence[Node]) -> Node:
105
+ """Fold ``premises ⊨ formula`` into the single formula ``(∧premises) → formula``.
106
+
107
+ A local copy of :mod:`atp.protocol`'s private ``_implication`` helper
108
+ (that module must not be modified or imported from beyond its public
109
+ surface — see this kit's contribution conventions): NXF has no separate
110
+ premise list, so every :class:`ProverBackend` that speaks it must fold
111
+ premises into one conjecture itself, same as
112
+ :class:`atp.protocol.Z3Backend`/``ModalTableauBackend``/``QmlBackend`` do
113
+ via the module they live in.
114
+ """
115
+ premises = list(premises)
116
+ if not premises:
117
+ return formula
118
+ conj = premises[0]
119
+ for p in premises[1:]:
120
+ conj = And(conj, p)
121
+ return Implies(conj, formula)
122
+
123
+
124
+ class Leo3Backend(ProverBackend):
125
+ """Mono-modal alethic decisions (K/T/S4/S5, propositional fragment) via Leo-III.
126
+
127
+ See the module docstring for scope, discovery, and the SZS-reading
128
+ contract. ``premises`` is the standard LOCAL consequence reading shared
129
+ by every modal backend in this kit (see
130
+ :class:`atp.protocol.ProverBackend`'s docstring): ``⊨ (∧premises) →
131
+ formula``.
132
+ """
133
+
134
+ name = "leo3"
135
+ logics = frozenset({"modal"})
136
+ external = True
137
+
138
+ @staticmethod
139
+ def _jar_or_wrapper() -> Optional[str]:
140
+ """Return ``$UFK_LEO3`` (a leo3.jar path or executable wrapper), or ``None``."""
141
+ return os.environ.get("UFK_LEO3") or None
142
+
143
+ @staticmethod
144
+ def _java() -> Optional[str]:
145
+ """Return the resolved ``java`` binary on PATH, or ``None``."""
146
+ return shutil.which("java")
147
+
148
+ def available(self) -> bool:
149
+ """True iff both ``$UFK_LEO3`` is set and ``java`` resolves on PATH.
150
+
151
+ Pure discovery (env var + PATH lookup only) — deliberately does NOT
152
+ check that ``$UFK_LEO3`` actually points at an existing file: neither
153
+ :class:`atp.protocol.Prover9Backend` nor ``VampireBackend`` do that
154
+ check either, leaving a stale/typo'd path to surface as a genuine
155
+ ``FileNotFoundError`` from the subprocess call instead (caught and
156
+ reported as an ``ERROR``/``"infra"`` verdict — see :meth:`decide`).
157
+ """
158
+ return self._jar_or_wrapper() is not None and self._java() is not None
159
+
160
+ def decide(self, formula: Node, premises: Sequence[Node] = (),
161
+ timeout: int = 10000, **options) -> Verdict:
162
+ """Decide ``premises ⊨ formula`` in the given alethic modal frame via Leo-III.
163
+
164
+ Args:
165
+ formula: the conclusion; together with ``premises`` folded into
166
+ one NXF conjecture by :func:`_fold_premises` +
167
+ :func:`atp.tptp_ncl.to_tptp_ncl`.
168
+ premises: local-consequence premises (see class docstring).
169
+ timeout: milliseconds, per the :class:`ProverBackend` contract;
170
+ converted to whole seconds (minimum 1) for Leo-III's own
171
+ ``-t <seconds>`` flag.
172
+ **options: ``frame`` (default ``"K"``) and ``domains`` (default
173
+ ``"constant"``) are forwarded to
174
+ :func:`atp.tptp_ncl.to_tptp_ncl`; no other options are read.
175
+
176
+ Returns:
177
+ A :class:`~atp.protocol.Verdict` with ``logic="modal"``:
178
+
179
+ * the formula is outside the supported NXF fragment (a
180
+ :class:`ValueError`/:class:`NotImplementedError` from
181
+ :func:`to_tptp_ncl`) → ``UNKNOWN``/``"unsupported"``, with no
182
+ subprocess ever spawned;
183
+ * the subprocess exceeds its safety-net timeout → ``UNKNOWN``/
184
+ ``"timeout"``;
185
+ * the subprocess cannot even be started (bad ``$UFK_LEO3``/java
186
+ path) → ``ERROR``/``"infra"``;
187
+ * output has no ``% SZS status`` line at all → ``ERROR``/
188
+ ``"infra"``, with a trailing excerpt of the raw output in
189
+ ``detail`` (Leo-III printed SOMETHING but not in the expected
190
+ form — a genuine infrastructure surprise, not a decided
191
+ ``UNKNOWN``);
192
+ * otherwise, the SZS status is mapped through
193
+ :func:`atp.tstp.szs_to_verdict_fields` with
194
+ ``query="conjecture"`` (the problem carries exactly one
195
+ ``conjecture``-role formula — see :func:`to_tptp_ncl`'s
196
+ docstring) into ``PROVED``/``REFUTED``/``UNKNOWN``, with the
197
+ raw SZS token kept verbatim in ``szs_status``; a ``PROVED`` or
198
+ ``REFUTED`` verdict is then re-decided by ``modal-tableau`` on
199
+ the same ``formula``/``premises``/``frame`` (see the module
200
+ docstring's cross-check section and :meth:`_cross_check`):
201
+
202
+ - the tableau DISAGREES (a definitive PROVED vs. REFUTED
203
+ clash, in either direction) → ``ERROR``/``"infra"`` with
204
+ ``"SOUNDNESS ALARM"`` in ``detail`` — two definitive answers
205
+ may never disagree silently;
206
+ - the tableau agrees, or is ``UNKNOWN``/unreachable → Leo-III's
207
+ own ``PROVED``/``REFUTED`` is kept, with
208
+ ``"; cross-check modal-tableau: confirmed"`` or
209
+ ``"; cross-check modal-tableau: inconclusive"`` appended to
210
+ ``detail``; when Leo-III's ``REFUTED`` is confirmed, the
211
+ tableau's ``satisfies_modal``-verified Kripke witness is
212
+ attached as ``countermodel`` (``None`` otherwise).
213
+
214
+ Raises:
215
+ BackendUnavailable: neither ``$UFK_LEO3`` nor ``java`` (or
216
+ neither) was found — mirrors
217
+ :class:`atp.protocol.Prover9Backend`/``VampireBackend``'s own
218
+ defensive re-check inside ``decide()`` (in case a caller
219
+ invokes this backend directly rather than through
220
+ :func:`atp.protocol.run_backend`, which already enforces
221
+ this).
222
+ """
223
+ jar = self._jar_or_wrapper()
224
+ java = self._java()
225
+ if jar is None or java is None:
226
+ raise BackendUnavailable(
227
+ "leo3: needs $UFK_LEO3 (path to leo3.jar or an executable wrapper) "
228
+ "and 'java' on PATH — set $UFK_LEO3 and/or put 'java' on PATH.")
229
+
230
+ frame = options.pop("frame", "K")
231
+ domains = options.pop("domains", "constant")
232
+ goal = _fold_premises(formula, premises)
233
+
234
+ try:
235
+ problem_text = to_tptp_ncl(goal, frame=frame, domains=domains,
236
+ conjecture_name="c")
237
+ except (ValueError, NotImplementedError) as exc:
238
+ return Verdict(UNKNOWN, self.name, logic="modal", reason="unsupported",
239
+ detail=str(exc))
240
+
241
+ timeout_sec = max(1, timeout // 1000)
242
+ start = time.perf_counter()
243
+
244
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".p", delete=False,
245
+ encoding="utf-8") as problem_file:
246
+ problem_file.write(problem_text)
247
+ problem_path = problem_file.name
248
+
249
+ try:
250
+ if jar.lower().endswith(".jar"):
251
+ command = [java, "-jar", jar, problem_path, "-t", str(timeout_sec)]
252
+ else:
253
+ command = [jar, problem_path, "-t", str(timeout_sec)]
254
+ try:
255
+ result = subprocess.run(
256
+ command, capture_output=True, text=True,
257
+ timeout=timeout_sec + _SUBPROCESS_SLACK_SECONDS)
258
+ except subprocess.TimeoutExpired:
259
+ elapsed = time.perf_counter() - start
260
+ return Verdict(UNKNOWN, self.name, logic="modal", reason="timeout",
261
+ wall_time=elapsed,
262
+ detail=f"leo3 exceeded {timeout_sec + _SUBPROCESS_SLACK_SECONDS}s "
263
+ "(prover -t budget plus safety-net slack)")
264
+ except OSError as exc:
265
+ elapsed = time.perf_counter() - start
266
+ return Verdict(ERROR, self.name, logic="modal", reason="infra",
267
+ wall_time=elapsed, detail=f"{type(exc).__name__}: {exc}")
268
+ finally:
269
+ try:
270
+ os.unlink(problem_path)
271
+ except OSError:
272
+ pass
273
+
274
+ elapsed = time.perf_counter() - start
275
+ output = result.stdout + "\n" + result.stderr
276
+ szs = extract_szs_status(output)
277
+ if szs is None:
278
+ excerpt = output[-_EXCERPT_CHARS:]
279
+ return Verdict(ERROR, self.name, logic="modal", reason="infra",
280
+ wall_time=elapsed,
281
+ detail=f"no 'SZS status' line in leo3 output: {excerpt!r}")
282
+
283
+ status, reason = szs_to_verdict_fields(szs, query="conjecture")
284
+ base_detail = f"leo3 SZS status {szs} (frame={frame})"
285
+
286
+ if status not in (PROVED, REFUTED):
287
+ # Nothing definitive to cross-check yet — matches nanocop's own
288
+ # "skip on UNKNOWN/ERROR" pattern (see the module docstring).
289
+ return Verdict(status, self.name, logic="modal", reason=reason,
290
+ wall_time=elapsed, szs_status=szs, detail=base_detail)
291
+
292
+ check = self._cross_check(formula, premises, frame, timeout)
293
+ if status == PROVED and check is not None and check.status == REFUTED:
294
+ return Verdict(
295
+ ERROR, self.name, logic="modal", reason="infra", wall_time=elapsed,
296
+ detail=f"SOUNDNESS ALARM: leo3 says Theorem but modal-tableau "
297
+ f"(frame {frame}) refutes it — refusing to answer.")
298
+ if status == REFUTED and check is not None and check.status == PROVED:
299
+ # The symmetric alarm nanocop cannot build (its FO fragment has no
300
+ # complete second engine for a Non-Theorem claim) — see the module
301
+ # docstring: modal-tableau fully decides Leo-III's own fragment.
302
+ return Verdict(
303
+ ERROR, self.name, logic="modal", reason="infra", wall_time=elapsed,
304
+ detail=f"SOUNDNESS ALARM: leo3 says Non-Theorem but modal-tableau "
305
+ f"(frame {frame}) proves it — refusing to answer.")
306
+
307
+ agreement = ("confirmed" if check is not None and check.status == status
308
+ else "inconclusive")
309
+ countermodel = (check.countermodel
310
+ if status == REFUTED and check is not None
311
+ and check.status == REFUTED else None)
312
+ return Verdict(status, self.name, logic="modal", reason=reason,
313
+ wall_time=elapsed, szs_status=szs, countermodel=countermodel,
314
+ detail=f"{base_detail}; cross-check modal-tableau: {agreement}")
315
+
316
+ @staticmethod
317
+ def _cross_check(formula: Node, premises: Sequence[Node], frame: str,
318
+ timeout: int) -> Optional[Verdict]:
319
+ """Run the REAL ``modal-tableau`` backend on the same problem; ``None`` on any error.
320
+
321
+ A local, ``modal-tableau``-only counterpart of
322
+ :meth:`atp.nanocop_backend.NanocopBackend._cross_check` — best-effort
323
+ infrastructure: ITS failure must not mask Leo-III's own answer, only
324
+ its definitive disagreement may (see the module docstring).
325
+ """
326
+ from .protocol import get_backend
327
+
328
+ try:
329
+ return get_backend("modal-tableau").decide(
330
+ formula, list(premises), frame=frame, timeout=timeout)
331
+ except Exception: # noqa: BLE001
332
+ return None