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.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- 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
|