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,339 @@
|
|
|
1
|
+
"""HETS (the Heterogeneous Tool Set) as an opt-in prover backend.
|
|
2
|
+
|
|
3
|
+
:class:`HetsBackend` (registry name ``"hets"``) decides classical FOL and
|
|
4
|
+
MSFOL entailment by exporting the problem to CASL
|
|
5
|
+
(:mod:`unicode_logic_kit.fol.casl_export` — premises become axioms, the goal a
|
|
6
|
+
``%implied`` conjecture), uploading it to a running ``hets-server`` REST
|
|
7
|
+
instance (:mod:`unicode_logic_kit.hets`), and mapping the per-goal result back
|
|
8
|
+
onto the kit's :class:`~unicode_logic_kit.atp.protocol.Verdict`:
|
|
9
|
+
|
|
10
|
+
=========== ========== =====================================================
|
|
11
|
+
Hets result Verdict Notes
|
|
12
|
+
=========== ========== =====================================================
|
|
13
|
+
``Proved`` PROVED ``detail`` carries reasoner + comorphism provenance
|
|
14
|
+
``Disproved`` REFUTED any reasoner may report it (live-verified: the
|
|
15
|
+
default SPASS route disproves simple non-theorems
|
|
16
|
+
too); ``darwin-non-fd`` is the DEDICATED finite
|
|
17
|
+
model finder for when the others stay Open
|
|
18
|
+
``Open`` UNKNOWN ``reason="incomplete"`` — NEVER mapped to refuted
|
|
19
|
+
(this image's eprover/Vampire wrappers are broken
|
|
20
|
+
and always report Open; see ``hets/docker.py``)
|
|
21
|
+
=========== ========== =====================================================
|
|
22
|
+
|
|
23
|
+
What this buys: the Hets reasoner stable (SPASS, the darwins, MathServe
|
|
24
|
+
brokered provers, …) behind one backend, each answer stamped with the
|
|
25
|
+
comorphism Hets actually used (e.g. ``CASL2TPTP_FOF`` for SPASS,
|
|
26
|
+
``CASL2SoftFOL`` for darwin) — and native many-sorted CASL, so kit MSFOL
|
|
27
|
+
problems go through WITHOUT the single-sort collapse every TPTP route needs.
|
|
28
|
+
|
|
29
|
+
**A typed reading that differs from the kit's is refused.** The CASL export is a
|
|
30
|
+
typed text: it infers the sort of every position (an unannotated constant, or the
|
|
31
|
+
value of a function, is declared at the sort of the position it is used in) and its
|
|
32
|
+
sorts are disjoint types, while the kit has ONE universe, sorts that are subsets of
|
|
33
|
+
it, and constants and function values that are in a sort only when a formula says
|
|
34
|
+
so. :class:`HetsBackend` decides, so for a problem on which the two readings differ
|
|
35
|
+
it answers ``UNKNOWN`` / ``"unsupported"`` with the reason in ``detail`` (the
|
|
36
|
+
checks are those of :func:`~unicode_logic_kit.atp.tptp_tff.check_typed_reading`, shared
|
|
37
|
+
with the TF0 and NXF writers) instead of a verdict about another problem; the
|
|
38
|
+
fof route (``backends=["vampire"], tff=False`` and the like) asks the kit's question
|
|
39
|
+
for those. The export itself (``to_casl_spec``) keeps writing the typed reading.
|
|
40
|
+
The sort the export gives every unsorted position is ``Thing``, or ``Thing1``, ``Thing2``, ...
|
|
41
|
+
when the problem has a sort spelled like it: a user sort of the default sort's name would
|
|
42
|
+
be one sort with it.
|
|
43
|
+
|
|
44
|
+
Cost model and chain policy: a Docker container start is minutes-expensive
|
|
45
|
+
(image pull even more so), exactly like the Isabelle route — so this backend
|
|
46
|
+
is NEVER part of any default chain and ``decide()`` NEVER starts a container
|
|
47
|
+
itself. It only talks to an already-running server found by
|
|
48
|
+
:func:`unicode_logic_kit.hets.discover_hets_url` (``$UFK_HETS_URL``, then
|
|
49
|
+
``localhost:8000``); nothing reachable raises ``BackendUnavailable`` with
|
|
50
|
+
the start-it-yourself instructions, never a silent skip. Callers who want
|
|
51
|
+
managed lifecycle wrap their session in
|
|
52
|
+
:class:`unicode_logic_kit.hets.HetsContainer`.
|
|
53
|
+
|
|
54
|
+
``decide()`` options (all keyword, via ``**options``): ``reasoner`` (Hets
|
|
55
|
+
prover identifier, default ``"SPASS"`` — the one verified reliable in the
|
|
56
|
+
official image; ``"darwin-non-fd"`` is the dedicated finite model finder
|
|
57
|
+
for disproof when other reasoners stay Open), ``translation`` (a comorphism
|
|
58
|
+
name from ``HetsClient.translations``, default: let Hets choose), ``url``
|
|
59
|
+
(skip discovery and use this server). The gate in
|
|
60
|
+
:func:`~unicode_logic_kit.atp.protocol.run_backend` asks
|
|
61
|
+
:meth:`HetsBackend.available_for` with the options of the call, so a call that
|
|
62
|
+
names ``url=`` is answered for THAT server (``GET /version`` on it), not for
|
|
63
|
+
``$UFK_HETS_URL`` / localhost, which are only probed when the call names no
|
|
64
|
+
``url=``.
|
|
65
|
+
|
|
66
|
+
:meth:`HetsBackend.check_consistency` is the extra non-protocol route over
|
|
67
|
+
``POST /consistency-check``: it asks whether the PREMISES have a model at
|
|
68
|
+
all. It deliberately returns a plain dict (``consistent`` True/False/None +
|
|
69
|
+
provenance), not a :class:`Verdict` — Verdict's status axis answers
|
|
70
|
+
"entailed?", and overloading PROVED to mean "consistent" would corrupt
|
|
71
|
+
every consumer that treats PROVED as "goal follows".
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
import time
|
|
75
|
+
from typing import Dict, Optional, Sequence
|
|
76
|
+
|
|
77
|
+
from ..fol.nodes import Node
|
|
78
|
+
from .protocol import ProverBackend, Verdict, PROVED, REFUTED, UNKNOWN, ERROR
|
|
79
|
+
|
|
80
|
+
__all__ = ["HetsBackend"]
|
|
81
|
+
|
|
82
|
+
#: The reasoner used when the caller does not choose one. SPASS is the
|
|
83
|
+
#: verified-working prover of the official image (translation CASL2TPTP_FOF);
|
|
84
|
+
#: eprover and Vampire are broken there (always Open) — see hets/docker.py.
|
|
85
|
+
_DEFAULT_REASONER = "SPASS"
|
|
86
|
+
|
|
87
|
+
_SPEC_NAME = "KitProblem"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _default_sort_for(formulas: Sequence[Node]) -> str:
|
|
91
|
+
"""The sort the CASL export gives every unsorted position: ``Thing``, or the first of
|
|
92
|
+
``Thing1``, ``Thing2``, ... that no sort of ``formulas`` is spelled like. A user sort of
|
|
93
|
+
the default sort's name would be one sort with it (the export refuses that), and the
|
|
94
|
+
kit's reading of an unsorted position is the whole universe, not a sort of the user's."""
|
|
95
|
+
taken = {n.sort for f in formulas for n in f.walk() if isinstance(getattr(n, "sort", None), str)}
|
|
96
|
+
candidate, i = "Thing", 0
|
|
97
|
+
while candidate in taken:
|
|
98
|
+
i += 1
|
|
99
|
+
candidate = f"Thing{i}"
|
|
100
|
+
return candidate
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Solver-version provenance (K1). HETS is an HTTP server, not a spawned
|
|
104
|
+
# binary, so the analogue of atp.protocol._binary_version's subprocess memo
|
|
105
|
+
# is one GET /version per discovered base_url, memoized process-wide via
|
|
106
|
+
# the already-existing but (until now) unused HetsClient.version().
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
_VERSION_CACHE: Dict[str, Optional[str]] = {}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _hets_version(client, url: str) -> Optional[str]:
|
|
113
|
+
"""``client.version()`` (``GET /version``), memoized per ``url`` for the
|
|
114
|
+
life of this process — every call after the first for the same
|
|
115
|
+
``base_url`` returns the cached string (or cached ``None``) without a
|
|
116
|
+
second HTTP round trip. A failure degrades to ``None`` rather than
|
|
117
|
+
raising — this is best-effort provenance, never grounds to turn a sound
|
|
118
|
+
decide() result into an ERROR verdict, so this catches broadly: a real
|
|
119
|
+
``HetsClient.version()`` only ever raises ``RuntimeError`` (server
|
|
120
|
+
dropped between the availability check and this call, a malformed
|
|
121
|
+
response — see ``hets.client``'s own contract), but ``client`` here is
|
|
122
|
+
whatever :meth:`HetsBackend.decide`/:meth:`HetsBackend.solver_version`
|
|
123
|
+
constructed (or, in a test double that stands in for the whole
|
|
124
|
+
``HetsClient`` surface but has not modelled this one extra method,
|
|
125
|
+
might not even implement ``version()`` at all — an ``AttributeError``
|
|
126
|
+
must degrade exactly like a ``RuntimeError`` does, not propagate and
|
|
127
|
+
turn a working ``decide()`` call into a crash over a provenance nicety).
|
|
128
|
+
"""
|
|
129
|
+
if url in _VERSION_CACHE:
|
|
130
|
+
return _VERSION_CACHE[url]
|
|
131
|
+
try:
|
|
132
|
+
version: Optional[str] = client.version()
|
|
133
|
+
except Exception: # noqa: BLE001 - best-effort provenance, must not sink a sound verdict
|
|
134
|
+
version = None
|
|
135
|
+
_VERSION_CACHE[url] = version
|
|
136
|
+
return version
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _prover_id(goal: dict):
|
|
140
|
+
"""The reasoner identifier out of a normalized goal dict.
|
|
141
|
+
|
|
142
|
+
``used_prover`` arrives as ``{"identifier": …, "name": …}`` from the
|
|
143
|
+
server; fall back to the raw value (or None) if the shape ever changes.
|
|
144
|
+
"""
|
|
145
|
+
prover = goal.get("used_prover")
|
|
146
|
+
if isinstance(prover, dict):
|
|
147
|
+
return prover.get("identifier", prover.get("name"))
|
|
148
|
+
return prover
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class HetsBackend(ProverBackend):
|
|
152
|
+
"""CASL export + hets-server REST prove, results as kit Verdicts."""
|
|
153
|
+
|
|
154
|
+
name = "hets"
|
|
155
|
+
logics = frozenset({"fol"})
|
|
156
|
+
external = True # needs Docker (or a remote hets-server)
|
|
157
|
+
|
|
158
|
+
def available(self) -> bool:
|
|
159
|
+
"""True iff a hets-server answers right now (no container start)."""
|
|
160
|
+
from ..hets import hets_available
|
|
161
|
+
|
|
162
|
+
return hets_available()
|
|
163
|
+
|
|
164
|
+
def available_for(self, options: dict) -> bool:
|
|
165
|
+
"""Whether the server :meth:`decide` will talk to answers: the one named by
|
|
166
|
+
``url=`` when the call names one (``decide`` then skips discovery), else
|
|
167
|
+
:meth:`available`. Never starts a container."""
|
|
168
|
+
url = options.get("url")
|
|
169
|
+
if url is None:
|
|
170
|
+
return self.available()
|
|
171
|
+
from ..hets.docker import _probe_health
|
|
172
|
+
|
|
173
|
+
return _probe_health(url)
|
|
174
|
+
|
|
175
|
+
def solver_version(self) -> Optional[str]:
|
|
176
|
+
"""The reachable server's ``GET /version`` banner, via
|
|
177
|
+
:meth:`~unicode_logic_kit.hets.client.HetsClient.version` — memoized
|
|
178
|
+
per discovered ``base_url`` for the life of the process (see
|
|
179
|
+
:func:`_hets_version`). ``None`` when no server is currently
|
|
180
|
+
reachable (the same "never guess" discipline as :meth:`available`),
|
|
181
|
+
so callers such as
|
|
182
|
+
:func:`~unicode_logic_kit.eval.batch.batch_decide`'s cache key can
|
|
183
|
+
call this unconditionally, with no prior availability check.
|
|
184
|
+
"""
|
|
185
|
+
from ..hets import HetsClient, discover_hets_url
|
|
186
|
+
from .protocol import BackendUnavailable
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
url, _container = discover_hets_url()
|
|
190
|
+
except BackendUnavailable:
|
|
191
|
+
return None
|
|
192
|
+
return _hets_version(HetsClient(url), url)
|
|
193
|
+
|
|
194
|
+
def decide(self, formula: Node, premises: Sequence[Node] = (),
|
|
195
|
+
timeout: int = 10000, **options) -> Verdict:
|
|
196
|
+
from ..fol.casl_export import to_casl_spec
|
|
197
|
+
from ..hets import HetsClient, discover_hets_url
|
|
198
|
+
from .tptp_tff import check_typed_reading
|
|
199
|
+
|
|
200
|
+
reasoner = options.pop("reasoner", _DEFAULT_REASONER)
|
|
201
|
+
translation = options.pop("translation", None)
|
|
202
|
+
url = options.pop("url", None)
|
|
203
|
+
|
|
204
|
+
# Solver-version provenance (K1): resolve it up front ONLY when the
|
|
205
|
+
# caller passed an explicit `url=` override — they already opted
|
|
206
|
+
# into contacting THAT specific server (see the module docstring's
|
|
207
|
+
# `url=` CAVEAT), so looking its version up before the CASL-fragment
|
|
208
|
+
# check below adds no network touch beyond what was already asked
|
|
209
|
+
# for, and even an out-of-fragment formula's UNKNOWN/"unsupported"
|
|
210
|
+
# verdict then carries it. Without an explicit `url=`, this stays
|
|
211
|
+
# None here: resolving it would mean running discover_hets_url()'s
|
|
212
|
+
# own network health probe for a formula that will never reach a
|
|
213
|
+
# prover, which test_out_of_fragment_is_unsupported_before_any_network
|
|
214
|
+
# (tests/test_hets_backend.py, unowned) deliberately forbids —
|
|
215
|
+
# discovery must never run before the fragment check for the
|
|
216
|
+
# default-discovery path (see that test's own docstring). In that
|
|
217
|
+
# path solver_version is instead resolved below, right after
|
|
218
|
+
# discover_hets_url() succeeds.
|
|
219
|
+
solver_version = _hets_version(HetsClient(url), url) if url is not None else None
|
|
220
|
+
|
|
221
|
+
default_sort = _default_sort_for(list(premises) + [formula])
|
|
222
|
+
try:
|
|
223
|
+
spec = to_casl_spec(list(premises), conjectures=[formula],
|
|
224
|
+
spec_name=_SPEC_NAME, default_sort=default_sort)
|
|
225
|
+
# The export is a typed text: it gives an unannotated constant or a
|
|
226
|
+
# function value the sort of the position it is used at, and its sorts
|
|
227
|
+
# are disjoint types. A decision must answer the kit's question, so a
|
|
228
|
+
# problem for which that reading differs is refused (see
|
|
229
|
+
# check_typed_reading), not answered about something else.
|
|
230
|
+
check_typed_reading(list(premises) + [formula], writer="hets",
|
|
231
|
+
unsorted_type=default_sort)
|
|
232
|
+
except (ValueError, NotImplementedError) as exc:
|
|
233
|
+
# Outside the CASL FOL/MSFOL fragment (modal node, sort
|
|
234
|
+
# conflict, free variable, …), or a typed reading that differs from
|
|
235
|
+
# the kit's: honestly unsupported, never a silent mistranslation.
|
|
236
|
+
return Verdict(UNKNOWN, self.name, reason="unsupported",
|
|
237
|
+
solver_version=solver_version,
|
|
238
|
+
detail=f"{type(exc).__name__}: {exc}")
|
|
239
|
+
|
|
240
|
+
if url is None:
|
|
241
|
+
url, _ = discover_hets_url() # raises BackendUnavailable
|
|
242
|
+
# Memoized per base_url (see _hets_version) — resolved only now
|
|
243
|
+
# (not above) because discovery itself must not run before the
|
|
244
|
+
# fragment check just above.
|
|
245
|
+
solver_version = _hets_version(HetsClient(url), url)
|
|
246
|
+
|
|
247
|
+
# Hets takes its budget in whole seconds; the HTTP timeout must
|
|
248
|
+
# outlast it (upload + translation + prover startup).
|
|
249
|
+
time_limit = max(1, timeout // 1000)
|
|
250
|
+
client = HetsClient(url, timeout=float(time_limit + 30))
|
|
251
|
+
# Memoized per base_url (see _hets_version) — the first decide() (or
|
|
252
|
+
# solver_version()) call for this server pays one GET /version, every
|
|
253
|
+
# later one for the same url is free (including the lookup above,
|
|
254
|
+
# under either path: this is the SAME cache, keyed by url).
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
start = time.perf_counter()
|
|
258
|
+
iri = client.upload(spec, "kit_problem.casl")
|
|
259
|
+
goals = client.prove(iri, _SPEC_NAME, reasoner=reasoner,
|
|
260
|
+
translation=translation,
|
|
261
|
+
time_limit=time_limit)
|
|
262
|
+
elapsed = time.perf_counter() - start
|
|
263
|
+
except RuntimeError as exc:
|
|
264
|
+
return Verdict(ERROR, self.name, reason="infra",
|
|
265
|
+
solver_version=solver_version,
|
|
266
|
+
detail=f"{type(exc).__name__}: {exc}")
|
|
267
|
+
|
|
268
|
+
if len(goals) != 1:
|
|
269
|
+
# Exactly one %implied conjecture was emitted, so anything else
|
|
270
|
+
# means the server answered a different question than asked.
|
|
271
|
+
return Verdict(ERROR, self.name, reason="infra",
|
|
272
|
+
solver_version=solver_version,
|
|
273
|
+
detail=f"hets returned {len(goals)} goal results "
|
|
274
|
+
"for a single-conjecture spec")
|
|
275
|
+
|
|
276
|
+
goal = goals[0]
|
|
277
|
+
provenance = (f"reasoner={_prover_id(goal)}, "
|
|
278
|
+
f"translation={goal.get('used_translation')}")
|
|
279
|
+
result = (goal.get("result") or "").strip()
|
|
280
|
+
|
|
281
|
+
if result == "Proved":
|
|
282
|
+
return Verdict(PROVED, self.name, wall_time=elapsed,
|
|
283
|
+
solver_version=solver_version, detail=provenance)
|
|
284
|
+
if result == "Disproved":
|
|
285
|
+
return Verdict(REFUTED, self.name, wall_time=elapsed,
|
|
286
|
+
solver_version=solver_version, detail=provenance)
|
|
287
|
+
# "Open" and anything unrecognised: not settled. Open is NOT a
|
|
288
|
+
# refutation (and in this image often just a broken wrapper).
|
|
289
|
+
tail = (goal.get("prover_output") or "").strip()[-200:]
|
|
290
|
+
return Verdict(UNKNOWN, self.name, reason="incomplete",
|
|
291
|
+
wall_time=elapsed, solver_version=solver_version,
|
|
292
|
+
detail=f"{provenance}, result={result or 'absent'}"
|
|
293
|
+
+ (f", output tail: {tail}" if tail else ""))
|
|
294
|
+
|
|
295
|
+
def check_consistency(self, premises: Sequence[Node],
|
|
296
|
+
timeout: int = 10000, **options) -> dict:
|
|
297
|
+
"""Ask ``POST /consistency-check``: do the premises have a model?
|
|
298
|
+
|
|
299
|
+
Returns ``{"consistent": True | False | None, "result": <verbatim>,
|
|
300
|
+
"reasoner": …, "translation": …, "wall_time": …}`` — ``None`` when
|
|
301
|
+
the checker did not settle it. Raises ``BackendUnavailable`` when no
|
|
302
|
+
server is reachable, ``ValueError``/``NotImplementedError`` when
|
|
303
|
+
the premises leave the CASL fragment, and ``RuntimeError`` when the
|
|
304
|
+
server itself fails mid-flight (HTTP error, ``*** Error`` body,
|
|
305
|
+
malformed JSON — ``HetsClient``'s contract, passed through): unlike
|
|
306
|
+
``decide`` there is no Verdict envelope to absorb ANY of these, so
|
|
307
|
+
every failure raises rather than being half-mapped.
|
|
308
|
+
"""
|
|
309
|
+
from ..fol.casl_export import to_casl_spec
|
|
310
|
+
from ..hets import HetsClient, discover_hets_url
|
|
311
|
+
from .tptp_tff import check_typed_reading
|
|
312
|
+
|
|
313
|
+
reasoner = options.pop("reasoner", "darwin-non-fd")
|
|
314
|
+
translation = options.pop("translation", None)
|
|
315
|
+
url = options.pop("url", None)
|
|
316
|
+
|
|
317
|
+
default_sort = _default_sort_for(list(premises))
|
|
318
|
+
spec = to_casl_spec(list(premises), spec_name=_SPEC_NAME, default_sort=default_sort)
|
|
319
|
+
# Same refusal as decide(): a consistency answer about a typed reading that
|
|
320
|
+
# differs from the kit's is an answer about another problem.
|
|
321
|
+
check_typed_reading(list(premises), writer="hets", unsorted_type=default_sort)
|
|
322
|
+
if url is None:
|
|
323
|
+
url, _ = discover_hets_url()
|
|
324
|
+
time_limit = max(1, timeout // 1000)
|
|
325
|
+
client = HetsClient(url, timeout=float(time_limit + 30))
|
|
326
|
+
|
|
327
|
+
start = time.perf_counter()
|
|
328
|
+
iri = client.upload(spec, "kit_consistency.casl")
|
|
329
|
+
goals = client.consistency_check(iri, _SPEC_NAME, reasoner=reasoner,
|
|
330
|
+
time_limit=time_limit)
|
|
331
|
+
elapsed = time.perf_counter() - start
|
|
332
|
+
|
|
333
|
+
result = (goals[0].get("result") or "").strip() if goals else ""
|
|
334
|
+
consistent = {"Consistent": True, "Inconsistent": False}.get(result)
|
|
335
|
+
first = goals[0] if goals else {}
|
|
336
|
+
return {"consistent": consistent, "result": result,
|
|
337
|
+
"reasoner": _prover_id(first),
|
|
338
|
+
"translation": first.get("used_translation"),
|
|
339
|
+
"wall_time": elapsed}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""The ↓ binder's "route B" combinator: down_is_valid + KripkeEnumBackend (N1).
|
|
2
|
+
|
|
3
|
+
``fol.modal_translation.down_is_valid`` and ``atp.kripke_enum.KripkeEnumBackend``
|
|
4
|
+
are each already a complete, independently-correct decision route on their own
|
|
5
|
+
half of the verdict space for full hybrid logic H(@,↓):
|
|
6
|
+
|
|
7
|
+
* ``down_is_valid`` (the standard translation + a direct Z3 ``Solver()`` call)
|
|
8
|
+
can PROVE validity (Z3 ``unsat`` of the negated goal) but, by design, never
|
|
9
|
+
claims REFUTED — see its own docstring for why that is a deliberate
|
|
10
|
+
completeness sacrifice, not a soundness one.
|
|
11
|
+
* ``KripkeEnumBackend`` (bounded finite-Kripke-model enumeration) can REFUTE
|
|
12
|
+
validity with a concrete, ``satisfies_modal``-verified countermodel, but
|
|
13
|
+
exhausting its search space never proves anything (H(@,↓) — indeed modal
|
|
14
|
+
logic generally — has no small-model property bounding a countermodel to
|
|
15
|
+
``max_worlds`` worlds).
|
|
16
|
+
|
|
17
|
+
Together they cover exactly the two halves of what H(@,↓) validity's
|
|
18
|
+
undecidability still leaves DECIDABLE-in-the-relevant-direction: co-r.e.
|
|
19
|
+
(refutable, via search) and — for the instances Z3 actually closes —
|
|
20
|
+
provable (via the meaning-preserving standard translation, Areces/
|
|
21
|
+
Blackburn/Marx 1999). Neither route alone can decide the fragment; running
|
|
22
|
+
BOTH and reporting whichever one succeeds is the honest, maximal answer this
|
|
23
|
+
kit can give in one call, and it is exactly what
|
|
24
|
+
``docs/guide/hybrid.md``'s existing tableau-vs-Z3 pattern for H(@) already
|
|
25
|
+
models for the decidable fragment. See ``fol._hybrid_nodes``' module
|
|
26
|
+
docstring for the three-route architecture this module's function is the
|
|
27
|
+
"route B" entry point for (route A is ``semantics.kripke.satisfies_modal``
|
|
28
|
+
on a model the caller already has).
|
|
29
|
+
|
|
30
|
+
This is a thin combinator over two already-complete, already-tested routes:
|
|
31
|
+
no new semantic rule is added here, and no ``Verdict`` this module returns
|
|
32
|
+
carries a status either underlying route would not have produced on its own.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from typing import Dict, Optional
|
|
36
|
+
|
|
37
|
+
from ..fol.nodes import Node
|
|
38
|
+
from ..fol.modal_translation import down_is_valid
|
|
39
|
+
from .kripke_enum import KripkeEnumBackend
|
|
40
|
+
from .protocol import PROVED, REFUTED, UNKNOWN, Verdict
|
|
41
|
+
|
|
42
|
+
__all__ = ["down_decide"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def down_decide(formula: Node, frame: str = "K", timeout: int = 10000,
|
|
46
|
+
*, systems: Optional[Dict[str, str]] = None,
|
|
47
|
+
max_worlds: int = 3, max_atoms: Optional[int] = None,
|
|
48
|
+
max_models: int = 200000) -> Verdict:
|
|
49
|
+
"""Decide a hybrid-modal ``formula`` (H(@,↓), ``Down``/↓ included) over ``frame``.
|
|
50
|
+
|
|
51
|
+
Tries :func:`~unicode_logic_kit.fol.modal_translation.down_is_valid` first
|
|
52
|
+
(fast: one Z3 call on a small translated formula). If it returns
|
|
53
|
+
``PROVED``, that Verdict is returned as-is. Otherwise — ``UNKNOWN`` from
|
|
54
|
+
``down_is_valid``, which NEVER claims ``REFUTED`` on its own — this falls
|
|
55
|
+
back to :class:`~unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend`'s
|
|
56
|
+
bounded finite-model search for an actual countermodel:
|
|
57
|
+
|
|
58
|
+
* a countermodel found → ``REFUTED``, carrying that model (already
|
|
59
|
+
independently confirmed by ``satisfies_modal`` — see
|
|
60
|
+
:func:`~unicode_logic_kit.atp.kripke_enum.modal_enum_search`'s own
|
|
61
|
+
soundness argument — this function performs no re-verification of its
|
|
62
|
+
own beyond calling that route);
|
|
63
|
+
* the search space exhausted, or its budget ran out, or the formula falls
|
|
64
|
+
outside the propositional/ground modal fragment ``satisfies_modal``
|
|
65
|
+
understands → ``UNKNOWN``, with BOTH routes' ``detail`` text combined
|
|
66
|
+
(so a caller can see whether Z3 timed out / found nothing to prove, and
|
|
67
|
+
separately why the bounded search did not find a countermodel either).
|
|
68
|
+
|
|
69
|
+
This never returns ``REFUTED`` for a formula ``down_is_valid`` alone
|
|
70
|
+
reported ``PROVED`` for (only one of the two branches ever runs — see
|
|
71
|
+
above), and it never returns ``PROVED`` from anywhere but
|
|
72
|
+
``down_is_valid`` (:class:`KripkeEnumBackend` is refutation-only and never
|
|
73
|
+
reports ``PROVED`` either).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
formula: the hybrid-modal formula (with or without ``Down``/↓ — a
|
|
77
|
+
plain H(@) formula is handled identically, just more
|
|
78
|
+
conservatively on the PROVED side than
|
|
79
|
+
:func:`~unicode_logic_kit.fol.modal_translation.hybrid_is_valid`
|
|
80
|
+
would be for that decidable fragment).
|
|
81
|
+
frame: the alethic frame name, shared by both routes (``down_is_valid``
|
|
82
|
+
for the frame axioms, ``KripkeEnumBackend`` for the relation
|
|
83
|
+
conditions it enumerates over — see :mod:`unicode_logic_kit.fol.frames`).
|
|
84
|
+
timeout: milliseconds, the limit of the whole call. ``down_is_valid``'s
|
|
85
|
+
Z3 call runs under it, and the bounded search that follows is
|
|
86
|
+
given what is left of it (at least one millisecond), ending at a
|
|
87
|
+
candidate model with ``reason="timeout"`` when that runs out — on
|
|
88
|
+
top of its own budget, ``max_worlds``/``max_atoms``/``max_models``,
|
|
89
|
+
below.
|
|
90
|
+
systems, max_worlds, max_atoms, max_models: forwarded verbatim to
|
|
91
|
+
:class:`KripkeEnumBackend`'s ``decide`` (equivalently,
|
|
92
|
+
:func:`~unicode_logic_kit.atp.kripke_enum.modal_enum_search`) — see
|
|
93
|
+
their docstrings.
|
|
94
|
+
"""
|
|
95
|
+
proved = down_is_valid(formula, frame=frame, timeout=timeout)
|
|
96
|
+
if proved.status == PROVED:
|
|
97
|
+
return proved
|
|
98
|
+
|
|
99
|
+
enum_options = {"frame": frame, "max_worlds": max_worlds,
|
|
100
|
+
"max_atoms": max_atoms, "max_models": max_models}
|
|
101
|
+
if systems is not None:
|
|
102
|
+
enum_options["systems"] = systems
|
|
103
|
+
left = max(1, int(timeout - proved.wall_time * 1000))
|
|
104
|
+
enum_verdict = KripkeEnumBackend().decide(formula, timeout=left, **enum_options)
|
|
105
|
+
|
|
106
|
+
if enum_verdict.status == REFUTED:
|
|
107
|
+
return Verdict(
|
|
108
|
+
REFUTED, "down_decide", logic="hybrid",
|
|
109
|
+
wall_time=proved.wall_time + enum_verdict.wall_time,
|
|
110
|
+
countermodel=enum_verdict.countermodel,
|
|
111
|
+
detail=(f"down_is_valid: {proved.reason} ({proved.detail}); "
|
|
112
|
+
f"kripke-enum: {enum_verdict.detail}"),
|
|
113
|
+
)
|
|
114
|
+
return Verdict(
|
|
115
|
+
UNKNOWN, "down_decide", logic="hybrid",
|
|
116
|
+
reason=enum_verdict.reason or proved.reason,
|
|
117
|
+
wall_time=proved.wall_time + enum_verdict.wall_time,
|
|
118
|
+
detail=(f"down_is_valid: {proved.reason} ({proved.detail}); "
|
|
119
|
+
f"kripke-enum: {enum_verdict.reason} ({enum_verdict.detail})"),
|
|
120
|
+
)
|