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,884 @@
|
|
|
1
|
+
r"""Isabelle/HOL export for ILL and Lambek-calculus derivations — proof by REPLAY.
|
|
2
|
+
|
|
3
|
+
Unlike the *shallow* semantical embeddings elsewhere in :mod:`unicode_logic_kit.hol`
|
|
4
|
+
(e.g. :mod:`~unicode_logic_kit.hol.isabelle_modal`), this module needs no semantics at
|
|
5
|
+
all. :mod:`unicode_logic_kit.atp.linear` (ILL) and :mod:`unicode_logic_kit.atp.lambek` (the
|
|
6
|
+
Lambek calculus L) are already **complete, sound, cut-free sequent-calculus decision
|
|
7
|
+
procedures**: a formula's :class:`~unicode_logic_kit.atp.linear.ILLDerivation` /
|
|
8
|
+
:class:`~unicode_logic_kit.atp.lambek.LambekDerivation` tree, once found, already *is* a
|
|
9
|
+
complete proof — Isabelle only needs to be shown, at every node, which rule fired and
|
|
10
|
+
why its premises license its conclusion. So this module:
|
|
11
|
+
|
|
12
|
+
1. **Deep-embeds** the formula grammar as an Isabelle ``datatype`` (``ill`` / ``lam``,
|
|
13
|
+
one constructor per connective, prefixed ``I``/``L`` to dodge any clash with an
|
|
14
|
+
existing HOL identifier — mirroring the ``tv = tT | tB | tF`` prefixing convention
|
|
15
|
+
in :mod:`~unicode_logic_kit.hol.manyvalued`);
|
|
16
|
+
2. Defines the sequent-calculus derivability judgement **1:1** as an
|
|
17
|
+
``inductive derivable`` predicate, one intro rule per rule of the Python calculus,
|
|
18
|
+
each named after and transcribed verbatim from the corresponding ``_r_*`` checker
|
|
19
|
+
in :mod:`~unicode_logic_kit.atp.linear` / :mod:`~unicode_logic_kit.atp.lambek`;
|
|
20
|
+
3. States the queried sequent as a ``lemma`` and **replays** the Python-found
|
|
21
|
+
derivation tree as an explicit, fully mechanical Isar proof — a ``have`` step per
|
|
22
|
+
tree node, closed by exactly the rule that node used (``by (rule <IntroName>)``,
|
|
23
|
+
``using`` the already-established premise facts). No search, no automation beyond
|
|
24
|
+
the purely computational bookkeeping described next: the *replay itself* is the
|
|
25
|
+
proof.
|
|
26
|
+
|
|
27
|
+
ILL's antecedent: ``list``, not ``multiset`` — and why
|
|
28
|
+
--------------------------------------------------------
|
|
29
|
+
ILL's antecedent Γ is mathematically a **multiset** (:mod:`atp.linear`'s own
|
|
30
|
+
docstring says so, and the natural Isabelle rendering is
|
|
31
|
+
``inductive derivable :: "ill multiset ⇒ ill ⇒ bool"`` after
|
|
32
|
+
``imports "HOL-Library.Multiset"``). This module does **not** do that, for a concrete,
|
|
33
|
+
checkable reason: the theories it emits are built and checked with
|
|
34
|
+
:func:`unicode_logic_kit.hol.isabelle_runner.check_theory`, whose scratch session ROOT is
|
|
35
|
+
fixed as ``session S = HOL + ... theories <name>`` — it extends only the base ``HOL``
|
|
36
|
+
session, with no ``sessions "HOL-Library"`` dependency declaration, so
|
|
37
|
+
``imports "HOL-Library.Multiset"`` fails to resolve (confirmed empirically: Isabelle
|
|
38
|
+
reports *"Bad import of theory 'HOL-Library.Multiset': need to include sessions
|
|
39
|
+
'HOL-Library' in ROOT"*). Since ``check_theory`` is used exactly as instructed and
|
|
40
|
+
lives outside this module, this module instead represents Γ as an ``ill list`` and
|
|
41
|
+
makes the ONE structural fact a multiset gets "for free" — exchange, i.e. every
|
|
42
|
+
reordering of Γ is derivability-preserving — an **explicit extra intro rule**,
|
|
43
|
+
``Exch``. This is mathematically exact: "multiset" and "list quotiented by the
|
|
44
|
+
transitive closure of adjacent transpositions" are the same equivalence, so
|
|
45
|
+
``derivable`` here decides *exactly* the same relation as the Python ``Counter``-based
|
|
46
|
+
:func:`~unicode_logic_kit.atp.linear.ill_prove` — it is simply not literally typed as
|
|
47
|
+
``multiset``. Every place the replay needs two lists that are the same multiset in a
|
|
48
|
+
different order (which happens often — Python's own antecedent tuples are sorted by
|
|
49
|
+
render text, not by proof structure) it emits an explicit, finite chain of ``Exch``
|
|
50
|
+
applications computed by an adjacent-transposition sort (see ``_reorder_swaps``),
|
|
51
|
+
never asking ``simp``/``auto`` to guess a permutation. The Lambek side needs no such
|
|
52
|
+
device: L's antecedent genuinely IS an ordered list (no exchange), so
|
|
53
|
+
``lam list`` is the literally correct type, ``imports Main`` alone suffices, and the
|
|
54
|
+
replay never reorders anything — only re-associates ``@``, always by a trivial,
|
|
55
|
+
fully mechanical ``simp`` step (pure ground computation, since every list involved is,
|
|
56
|
+
by construction, fully concrete).
|
|
57
|
+
|
|
58
|
+
Public API
|
|
59
|
+
----------
|
|
60
|
+
:func:`to_isabelle_ill` / :func:`to_isabelle_lambek` (run the Python prover
|
|
61
|
+
internally, raise ``ValueError`` if the sequent is not derivable) and
|
|
62
|
+
:func:`ill_derivation_theory` / :func:`lambek_derivation_theory` (take an
|
|
63
|
+
already-found — and checker-validated — derivation object directly).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
from collections import Counter
|
|
67
|
+
from typing import Iterable, List, Optional, Sequence, Tuple
|
|
68
|
+
|
|
69
|
+
from ..fol.nodes import (
|
|
70
|
+
Node, Atom, Tensor, With, OPlus, LinearImplies, OfCourse, One,
|
|
71
|
+
Product, Under, Over,
|
|
72
|
+
)
|
|
73
|
+
from ..fol._linear_nodes import Top, Zero
|
|
74
|
+
from ..fol._msfl_nodes import key_text
|
|
75
|
+
from ..fol._truth_constants import refuse_truth_constants
|
|
76
|
+
from ..atp.linear import (
|
|
77
|
+
ILLDerivation, ILLSequent, ill_prove, check_ill_proof,
|
|
78
|
+
_NO_TRUTH_CONSTANT_WHY as _ILL_NO_CONSTANT_WHY,
|
|
79
|
+
)
|
|
80
|
+
from ..atp.lambek import (
|
|
81
|
+
LambekDerivation, LambekSequent, lambek_prove, check_lambek_proof,
|
|
82
|
+
_NO_TRUTH_CONSTANT_WHY as _LAMBEK_NO_CONSTANT_WHY,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"to_isabelle_ill", "ill_derivation_theory",
|
|
87
|
+
"to_isabelle_lambek", "lambek_derivation_theory",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# =============================================================================
|
|
92
|
+
# Shared low-level helpers: Isabelle string literals, name bookkeeping.
|
|
93
|
+
# =============================================================================
|
|
94
|
+
|
|
95
|
+
def _isa_str(s: str) -> str:
|
|
96
|
+
"""Render a Python string as an Isabelle ``string`` literal ``''...''``.
|
|
97
|
+
|
|
98
|
+
ILL/Lambek atoms in this kit are propositional letters / atomic categories
|
|
99
|
+
(``A``, ``NP``, ...); their text never legitimately contains a
|
|
100
|
+
single quote, so — rather than silently mis-escaping — an embedded ``'``
|
|
101
|
+
is rejected outright with a message pointing at why. The text of an atom
|
|
102
|
+
is its key (``key_text``: every constant by its bare name), not the text of
|
|
103
|
+
the formula: that writes a constant such as ``k2`` in quotes, which would
|
|
104
|
+
turn an export that works into this refusal.
|
|
105
|
+
"""
|
|
106
|
+
if "'" in s:
|
|
107
|
+
raise NotImplementedError(
|
|
108
|
+
f"to_isabelle_ill/to_isabelle_lambek: atom text {s!r} contains a "
|
|
109
|
+
"single quote, which the Isabelle string-literal encoding used "
|
|
110
|
+
"here does not escape; rename the atom.")
|
|
111
|
+
return "''" + s + "''"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class _Emitter:
|
|
115
|
+
"""Accumulates the Isar proof-script lines of one lemma and hands out
|
|
116
|
+
fresh, collision-free fact names (``h1``, ``h2``, ...)."""
|
|
117
|
+
|
|
118
|
+
def __init__(self):
|
|
119
|
+
self.lines: List[str] = []
|
|
120
|
+
self._n = 0
|
|
121
|
+
|
|
122
|
+
def fresh(self) -> str:
|
|
123
|
+
self._n += 1
|
|
124
|
+
return f"h{self._n}"
|
|
125
|
+
|
|
126
|
+
def have(self, stmt: str, using: Sequence[str], method: str) -> str:
|
|
127
|
+
"""Emit ``have hN: "stmt" using f1 f2 ... by (method)`` and return hN."""
|
|
128
|
+
name = self.fresh()
|
|
129
|
+
using_clause = f"using {' '.join(using)} " if using else ""
|
|
130
|
+
self.lines.append(f' have {name}: "{stmt}"\n {using_clause}by {method}')
|
|
131
|
+
return name
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# The general list-reordering primitive (ILL only — see the module docstring).
|
|
136
|
+
# =============================================================================
|
|
137
|
+
|
|
138
|
+
def _reorder_swaps(src: Sequence[Node], dst: Sequence[Node]) -> List[int]:
|
|
139
|
+
"""Adjacent-transposition sequence turning ``src`` into ``dst`` (same multiset).
|
|
140
|
+
|
|
141
|
+
Returns a list of positions ``k`` (swap ``cur[k]``/``cur[k+1]``), to be applied
|
|
142
|
+
to ``src`` **in order**, ending at ``dst``. Standard selection-style algorithm:
|
|
143
|
+
for each target position ``i`` (left to right), find the first not-yet-placed
|
|
144
|
+
occurrence of ``dst[i]`` at or after ``i`` and bubble it left into place —
|
|
145
|
+
O(n^2) swaps worst case, ample for the antecedents this module handles.
|
|
146
|
+
"""
|
|
147
|
+
cur = list(src)
|
|
148
|
+
n = len(cur)
|
|
149
|
+
swaps: List[int] = []
|
|
150
|
+
for i in range(n):
|
|
151
|
+
if cur[i] == dst[i]:
|
|
152
|
+
continue
|
|
153
|
+
j = i + 1
|
|
154
|
+
while cur[j] != dst[i]:
|
|
155
|
+
j += 1
|
|
156
|
+
while j > i:
|
|
157
|
+
swaps.append(j - 1)
|
|
158
|
+
cur[j - 1], cur[j] = cur[j], cur[j - 1]
|
|
159
|
+
j -= 1
|
|
160
|
+
assert cur == list(dst), "internal error: _reorder_swaps did not reach dst"
|
|
161
|
+
return swaps
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _remove_one(lst: List[Node], value: Node) -> List[Node]:
|
|
165
|
+
"""Return ``lst`` with the first element equal to ``value`` deleted."""
|
|
166
|
+
out = list(lst)
|
|
167
|
+
for i, x in enumerate(out):
|
|
168
|
+
if x == value:
|
|
169
|
+
del out[i]
|
|
170
|
+
return out
|
|
171
|
+
raise AssertionError(f"internal error: {value!r} not found in {lst!r}")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _counter_diff(a: Sequence[Node], b: Sequence[Node]) -> Counter:
|
|
175
|
+
"""``Counter(a) - Counter(b)``, positive entries only (what ``a`` has extra)."""
|
|
176
|
+
c = Counter(a)
|
|
177
|
+
c.subtract(Counter(b))
|
|
178
|
+
return Counter({k: v for k, v in c.items() if v > 0})
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _the_one(c: Counter) -> Node:
|
|
182
|
+
"""Return the single key of a one-entry-count-1 Counter (asserts the shape)."""
|
|
183
|
+
assert len(c) == 1 and next(iter(c.values())) == 1, f"internal error: not a singleton diff: {c!r}"
|
|
184
|
+
return next(iter(c))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# =============================================================================
|
|
188
|
+
# ILL: deep embedding.
|
|
189
|
+
# =============================================================================
|
|
190
|
+
|
|
191
|
+
ILL_DATATYPE = (
|
|
192
|
+
"datatype ill = IAtom string | ITensor ill ill | IWith ill ill | IOPlus ill ill "
|
|
193
|
+
"| ILImp ill ill | IBang ill | IOne | ITop | IZero"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# One entry per Isabelle intro rule, in emission order. 19 mirror the 19 rules of
|
|
197
|
+
# atp.linear._ILL_RULES one-for-one (named identically up to Isabelle-legal
|
|
198
|
+
# spelling — see _ILL_RULE_TO_ISA); "Exch" is the one addition, the explicit
|
|
199
|
+
# structural rule that stands in for "antecedent is a multiset" (see the module
|
|
200
|
+
# docstring). 20 rules total.
|
|
201
|
+
_ILL_INTROS: List[Tuple[str, str]] = [
|
|
202
|
+
("Ax", 'derivable [A] A'),
|
|
203
|
+
("Exch", 'derivable (G1 @ [B, A] @ G2) C \\<Longrightarrow> derivable (G1 @ [A, B] @ G2) C'),
|
|
204
|
+
("OneR", 'derivable [] IOne'),
|
|
205
|
+
("OneL", 'derivable G C \\<Longrightarrow> derivable (G @ [IOne]) C'),
|
|
206
|
+
("TensorL", 'derivable (G @ [A, B]) C \\<Longrightarrow> derivable (G @ [ITensor A B]) C'),
|
|
207
|
+
("TensorR", 'derivable G A \\<Longrightarrow> derivable D B \\<Longrightarrow> derivable (G @ D) (ITensor A B)'),
|
|
208
|
+
("LImpL", 'derivable G A \\<Longrightarrow> derivable (D @ [B]) C \\<Longrightarrow> '
|
|
209
|
+
'derivable (G @ D @ [ILImp A B]) C'),
|
|
210
|
+
("LImpR", 'derivable (G @ [A]) B \\<Longrightarrow> derivable G (ILImp A B)'),
|
|
211
|
+
("WithL1", 'derivable (G @ [A]) C \\<Longrightarrow> derivable (G @ [IWith A B]) C'),
|
|
212
|
+
("WithL2", 'derivable (G @ [B]) C \\<Longrightarrow> derivable (G @ [IWith A B]) C'),
|
|
213
|
+
("WithR", 'derivable G A \\<Longrightarrow> derivable G B \\<Longrightarrow> derivable G (IWith A B)'),
|
|
214
|
+
("OPlusL", 'derivable (G @ [A]) C \\<Longrightarrow> derivable (G @ [B]) C \\<Longrightarrow> '
|
|
215
|
+
'derivable (G @ [IOPlus A B]) C'),
|
|
216
|
+
("OPlusR1", 'derivable G A \\<Longrightarrow> derivable G (IOPlus A B)'),
|
|
217
|
+
("OPlusR2", 'derivable G B \\<Longrightarrow> derivable G (IOPlus A B)'),
|
|
218
|
+
("BangW", 'derivable G C \\<Longrightarrow> derivable (G @ [IBang A]) C'),
|
|
219
|
+
("BangC", 'derivable (G @ [IBang A, IBang A]) C \\<Longrightarrow> derivable (G @ [IBang A]) C'),
|
|
220
|
+
("BangD", 'derivable (G @ [A]) C \\<Longrightarrow> derivable (G @ [IBang A]) C'),
|
|
221
|
+
("BangP", 'derivable (map IBang G0) A \\<Longrightarrow> derivable (map IBang G0) (IBang A)'),
|
|
222
|
+
("TopR", 'derivable G ITop'),
|
|
223
|
+
("ZeroL", 'derivable (G @ [IZero]) C'),
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
# Python atp.linear rule name -> Isabelle intro name (Exch has no Python
|
|
227
|
+
# counterpart — see the module docstring).
|
|
228
|
+
_ILL_RULE_TO_ISA = {
|
|
229
|
+
"Ax": "Ax", "1R": "OneR", "1L": "OneL",
|
|
230
|
+
"⊗L": "TensorL", "⊗R": "TensorR",
|
|
231
|
+
"⊸L": "LImpL", "⊸R": "LImpR",
|
|
232
|
+
"&L1": "WithL1", "&L2": "WithL2", "&R": "WithR",
|
|
233
|
+
"⊕L": "OPlusL", "⊕R1": "OPlusR1", "⊕R2": "OPlusR2",
|
|
234
|
+
"!W": "BangW", "!C": "BangC", "!D": "BangD", "!P": "BangP",
|
|
235
|
+
"⊤R": "TopR", "0L": "ZeroL",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _lift_ill(node: Node) -> str:
|
|
240
|
+
"""Lift an ILL formula to its ``ill``-datatype Isabelle term."""
|
|
241
|
+
if isinstance(node, Atom):
|
|
242
|
+
refuse_truth_constants([node], "to_isabelle_ill", _ILL_NO_CONSTANT_WHY)
|
|
243
|
+
return f"(IAtom {_isa_str(key_text(node))})"
|
|
244
|
+
if isinstance(node, Tensor):
|
|
245
|
+
return f"(ITensor {_lift_ill(node.left)} {_lift_ill(node.right)})"
|
|
246
|
+
if isinstance(node, With):
|
|
247
|
+
return f"(IWith {_lift_ill(node.left)} {_lift_ill(node.right)})"
|
|
248
|
+
if isinstance(node, OPlus):
|
|
249
|
+
return f"(IOPlus {_lift_ill(node.left)} {_lift_ill(node.right)})"
|
|
250
|
+
if isinstance(node, LinearImplies):
|
|
251
|
+
return f"(ILImp {_lift_ill(node.left)} {_lift_ill(node.right)})"
|
|
252
|
+
if isinstance(node, OfCourse):
|
|
253
|
+
return f"(IBang {_lift_ill(node.formula)})"
|
|
254
|
+
if isinstance(node, One):
|
|
255
|
+
return "IOne"
|
|
256
|
+
if isinstance(node, Top):
|
|
257
|
+
return "ITop"
|
|
258
|
+
if isinstance(node, Zero):
|
|
259
|
+
return "IZero"
|
|
260
|
+
raise NotImplementedError(f"to_isabelle_ill: unsupported node {type(node).__name__}.")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _flat_ill(nodes: Sequence[Node]) -> str:
|
|
264
|
+
"""Bracket-list Isabelle text for a list of ILL formulas, e.g. ``[A, B]``."""
|
|
265
|
+
if not nodes:
|
|
266
|
+
return "[]"
|
|
267
|
+
return "[" + ", ".join(_lift_ill(n) for n in nodes) + "]"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# ILL replay engine.
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
#
|
|
274
|
+
# Contract of _emit_ill(d, emitter) -> (fact_name, antecedent_list): fact_name
|
|
275
|
+
# names a fact, already emitted into emitter.lines, whose statement is EXACTLY
|
|
276
|
+
# "derivable <_flat_ill(antecedent_list)> <_lift_ill(d.conclusion.succedent)>".
|
|
277
|
+
# antecedent_list is SOME list of Node with that multiset (not necessarily
|
|
278
|
+
# d.conclusion.antecedent's own order — this module never needs to match that
|
|
279
|
+
# specific order, only to be internally consistent; see the module docstring).
|
|
280
|
+
|
|
281
|
+
def _reorder_bridge(fact: str, src: List[Node], dst: List[Node],
|
|
282
|
+
succ_isa: str, emitter: _Emitter) -> str:
|
|
283
|
+
"""Reshape a flat fact for ``src`` into one for ``dst`` (same multiset), via an
|
|
284
|
+
explicit ``Exch`` chain. Returns ``fact`` unchanged if ``src == dst`` already."""
|
|
285
|
+
if src == dst:
|
|
286
|
+
return fact
|
|
287
|
+
cur = list(src)
|
|
288
|
+
cur_fact = fact
|
|
289
|
+
for k in _reorder_swaps(src, dst):
|
|
290
|
+
x, y = cur[k], cur[k + 1]
|
|
291
|
+
g1, g2 = cur[:k], cur[k + 2:]
|
|
292
|
+
decomp_before = f"{_flat_ill(g1)} @ {_flat_ill([x, y])} @ {_flat_ill(g2)}"
|
|
293
|
+
h_shaped = emitter.have(
|
|
294
|
+
f'derivable ({decomp_before}) {succ_isa}', [cur_fact], "simp")
|
|
295
|
+
decomp_after = f"{_flat_ill(g1)} @ {_flat_ill([y, x])} @ {_flat_ill(g2)}"
|
|
296
|
+
h_swapped = emitter.have(
|
|
297
|
+
f'derivable ({decomp_after}) {succ_isa}', [h_shaped], "(rule Exch)")
|
|
298
|
+
cur[k], cur[k + 1] = y, x
|
|
299
|
+
cur_fact = emitter.have(
|
|
300
|
+
f'derivable {_flat_ill(cur)} {succ_isa}', [h_swapped], "simp")
|
|
301
|
+
assert cur == dst
|
|
302
|
+
return cur_fact
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _decompose(fact: str, full: List[Node], pieces: List[List[Node]],
|
|
306
|
+
succ_isa: str, emitter: _Emitter) -> str:
|
|
307
|
+
"""Reshape a flat fact for ``full`` (== the concatenation of ``pieces``) into
|
|
308
|
+
an explicit ``@``-joined form matching ``pieces`` — a pure re-association
|
|
309
|
+
(never a reorder), always closed by a trivial ground ``simp``."""
|
|
310
|
+
text = " @ ".join(_flat_ill(p) for p in pieces) if pieces else "[]"
|
|
311
|
+
flat_text = _flat_ill(full)
|
|
312
|
+
if text == flat_text:
|
|
313
|
+
return fact
|
|
314
|
+
return emitter.have(f'derivable ({text}) {succ_isa}', [fact], "simp")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _emit_ill(d: ILLDerivation, emitter: _Emitter) -> Tuple[str, List[Node]]:
|
|
318
|
+
"""Recursively replay one ILL derivation node; see the contract above."""
|
|
319
|
+
succ_isa = _lift_ill(d.conclusion.succedent)
|
|
320
|
+
rule = d.rule
|
|
321
|
+
|
|
322
|
+
if rule == "Ax":
|
|
323
|
+
lst = [d.conclusion.antecedent[0]]
|
|
324
|
+
fact = emitter.have(f'derivable {_flat_ill(lst)} {succ_isa}', [], "(rule Ax)")
|
|
325
|
+
return fact, lst
|
|
326
|
+
|
|
327
|
+
if rule == "1R":
|
|
328
|
+
fact = emitter.have(f'derivable [] {succ_isa}', [], "(rule OneR)")
|
|
329
|
+
return fact, []
|
|
330
|
+
|
|
331
|
+
if rule == "⊤R":
|
|
332
|
+
lst = list(d.conclusion.antecedent)
|
|
333
|
+
fact = emitter.have(f'derivable {_flat_ill(lst)} {succ_isa}', [], "(rule TopR)")
|
|
334
|
+
return fact, lst
|
|
335
|
+
|
|
336
|
+
if rule == "0L":
|
|
337
|
+
lst_all = list(d.conclusion.antecedent)
|
|
338
|
+
idx = next(i for i, x in enumerate(lst_all) if isinstance(x, Zero))
|
|
339
|
+
rest = lst_all[:idx] + lst_all[idx + 1:]
|
|
340
|
+
target = rest + [lst_all[idx]]
|
|
341
|
+
stmt = f'({_flat_ill(rest)} @ {_flat_ill([lst_all[idx]])}) {succ_isa}'
|
|
342
|
+
fact = emitter.have(f'derivable {stmt}', [], "(rule ZeroL)")
|
|
343
|
+
return fact, target
|
|
344
|
+
|
|
345
|
+
# --- single-premise rules -------------------------------------------------
|
|
346
|
+
if rule in ("1L", "⊗L", "⊸R", "&L1", "&L2", "!W", "!C", "!D"):
|
|
347
|
+
child = d.premises[0]
|
|
348
|
+
cfact, clist = _emit_ill(child, emitter)
|
|
349
|
+
csucc = _lift_ill(child.conclusion.succedent)
|
|
350
|
+
|
|
351
|
+
if rule == "1L":
|
|
352
|
+
extract, tail = [], [One()]
|
|
353
|
+
elif rule == "⊗L":
|
|
354
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, clist))
|
|
355
|
+
extract, tail = [compound.left, compound.right], [compound]
|
|
356
|
+
elif rule == "⊸R":
|
|
357
|
+
a = _the_one(_counter_diff(clist, d.conclusion.antecedent))
|
|
358
|
+
extract, tail = [a], []
|
|
359
|
+
elif rule in ("&L1", "&L2"):
|
|
360
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, clist))
|
|
361
|
+
extract = [compound.left if rule == "&L1" else compound.right]
|
|
362
|
+
tail = [compound]
|
|
363
|
+
elif rule == "!W":
|
|
364
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, clist))
|
|
365
|
+
extract, tail = [], [compound]
|
|
366
|
+
elif rule == "!C":
|
|
367
|
+
compound = _the_one(_counter_diff(clist, d.conclusion.antecedent))
|
|
368
|
+
extract, tail = [compound, compound], [compound]
|
|
369
|
+
else: # "!D"
|
|
370
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, clist))
|
|
371
|
+
extract, tail = [compound.formula], [compound]
|
|
372
|
+
|
|
373
|
+
rest = list(clist)
|
|
374
|
+
for v in extract:
|
|
375
|
+
rest = _remove_one(rest, v)
|
|
376
|
+
target = rest + extract
|
|
377
|
+
flat_fact = _reorder_bridge(cfact, clist, target, csucc, emitter)
|
|
378
|
+
if extract:
|
|
379
|
+
# This rule's Isabelle premise pattern needs "G @ [extracted...]" —
|
|
380
|
+
# reshape into that explicit decomposition.
|
|
381
|
+
premise_fact = _decompose(flat_fact, target, [rest, extract], csucc, emitter)
|
|
382
|
+
else:
|
|
383
|
+
# 1L / !W: the premise pattern is bare "G" (no tail at all — the new
|
|
384
|
+
# formula is added only in the CONCLUSION) — flat_fact (== rest,
|
|
385
|
+
# nothing was extracted) is already exactly that; no reshape needed.
|
|
386
|
+
premise_fact = flat_fact
|
|
387
|
+
|
|
388
|
+
concl_list = rest + tail
|
|
389
|
+
concl_stmt = f'({_flat_ill(rest)} @ {_flat_ill(tail)}) {succ_isa}'
|
|
390
|
+
isa_rule = _ILL_RULE_TO_ISA[rule]
|
|
391
|
+
hraw = emitter.have(f'derivable {concl_stmt}', [premise_fact], f"(rule {isa_rule})")
|
|
392
|
+
# hraw's antecedent is the DECOMPOSED "rest @ tail" (or, for LImpR/⊸R
|
|
393
|
+
# where tail == [], "rest @ []"); flatten to the plain bracket-list form
|
|
394
|
+
# every _emit_ill caller expects (a pure ground computation, always
|
|
395
|
+
# simp-provable — never a reorder).
|
|
396
|
+
hflat = emitter.have(f'derivable {_flat_ill(concl_list)} {succ_isa}', [hraw], "simp")
|
|
397
|
+
return hflat, concl_list
|
|
398
|
+
|
|
399
|
+
# --- BangP (its own shape: image over the SAME antecedent) --------------
|
|
400
|
+
if rule == "!P":
|
|
401
|
+
child = d.premises[0]
|
|
402
|
+
cfact, clist = _emit_ill(child, emitter)
|
|
403
|
+
csucc = _lift_ill(child.conclusion.succedent)
|
|
404
|
+
g0 = [f.formula for f in clist] # every element of clist is OfCourse(...)
|
|
405
|
+
map_text = f"map IBang {_flat_ill(g0)}"
|
|
406
|
+
h_mapshape = emitter.have(f'derivable ({map_text}) {csucc}', [cfact], "simp")
|
|
407
|
+
hraw = emitter.have(f'derivable ({map_text}) {succ_isa}', [h_mapshape],
|
|
408
|
+
"(rule BangP)")
|
|
409
|
+
hflat = emitter.have(f'derivable {_flat_ill(clist)} {succ_isa}', [hraw], "simp")
|
|
410
|
+
return hflat, list(clist)
|
|
411
|
+
|
|
412
|
+
# --- two-premise rules ----------------------------------------------------
|
|
413
|
+
if rule == "⊗R":
|
|
414
|
+
c0, c1 = d.premises
|
|
415
|
+
f0, l0 = _emit_ill(c0, emitter)
|
|
416
|
+
f1, l1 = _emit_ill(c1, emitter)
|
|
417
|
+
target = l0 + l1
|
|
418
|
+
stmt = f'({_flat_ill(l0)} @ {_flat_ill(l1)}) {succ_isa}'
|
|
419
|
+
hraw = emitter.have(f'derivable {stmt}', [f0, f1], "(rule TensorR)")
|
|
420
|
+
hflat = emitter.have(f'derivable {_flat_ill(target)} {succ_isa}', [hraw], "simp")
|
|
421
|
+
return hflat, target
|
|
422
|
+
|
|
423
|
+
if rule == "&R":
|
|
424
|
+
c0, c1 = d.premises
|
|
425
|
+
f0, l0 = _emit_ill(c0, emitter)
|
|
426
|
+
f1, l1 = _emit_ill(c1, emitter)
|
|
427
|
+
csucc1 = _lift_ill(c1.conclusion.succedent)
|
|
428
|
+
f1m = _reorder_bridge(f1, l1, l0, csucc1, emitter)
|
|
429
|
+
stmt = f'derivable {_flat_ill(l0)} {succ_isa}'
|
|
430
|
+
hraw = emitter.have(stmt, [f0, f1m], "(rule WithR)")
|
|
431
|
+
return hraw, l0
|
|
432
|
+
|
|
433
|
+
if rule in ("⊕R1", "⊕R2"):
|
|
434
|
+
child = d.premises[0]
|
|
435
|
+
cfact, clist = _emit_ill(child, emitter)
|
|
436
|
+
stmt = f'derivable {_flat_ill(clist)} {succ_isa}'
|
|
437
|
+
isa_rule = "OPlusR1" if rule == "⊕R1" else "OPlusR2"
|
|
438
|
+
hraw = emitter.have(stmt, [cfact], f"(rule {isa_rule})")
|
|
439
|
+
return hraw, list(clist)
|
|
440
|
+
|
|
441
|
+
if rule == "⊸L":
|
|
442
|
+
c0, c1 = d.premises # c0: Γ⊢A ; c1: Δ,B⊢C
|
|
443
|
+
f0, l0 = _emit_ill(c0, emitter)
|
|
444
|
+
f1, l1 = _emit_ill(c1, emitter)
|
|
445
|
+
csucc1 = _lift_ill(c1.conclusion.succedent)
|
|
446
|
+
b = _the_one(_counter_diff(l1, d.conclusion.antecedent))
|
|
447
|
+
d1 = list(l1)
|
|
448
|
+
d1 = _remove_one(d1, b)
|
|
449
|
+
target1 = d1 + [b]
|
|
450
|
+
flat1 = _reorder_bridge(f1, l1, target1, csucc1, emitter)
|
|
451
|
+
premise1 = _decompose(flat1, target1, [d1, [b]], csucc1, emitter)
|
|
452
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, l0 + d1))
|
|
453
|
+
concl_list = l0 + d1 + [compound]
|
|
454
|
+
stmt = (f'derivable ({_flat_ill(l0)} @ {_flat_ill(d1)} @ {_flat_ill([compound])}) '
|
|
455
|
+
f'{succ_isa}')
|
|
456
|
+
hraw = emitter.have(stmt, [f0, premise1], "(rule LImpL)")
|
|
457
|
+
hflat = emitter.have(f'derivable {_flat_ill(concl_list)} {succ_isa}', [hraw], "simp")
|
|
458
|
+
return hflat, concl_list
|
|
459
|
+
|
|
460
|
+
if rule == "⊕L":
|
|
461
|
+
c0, c1 = d.premises # c0: Γ,A⊢C ; c1: Γ,B⊢C
|
|
462
|
+
f0, l0 = _emit_ill(c0, emitter)
|
|
463
|
+
f1, l1 = _emit_ill(c1, emitter)
|
|
464
|
+
csucc0 = _lift_ill(c0.conclusion.succedent)
|
|
465
|
+
csucc1 = _lift_ill(c1.conclusion.succedent)
|
|
466
|
+
a = _the_one(_counter_diff(l0, d.conclusion.antecedent))
|
|
467
|
+
b = _the_one(_counter_diff(l1, d.conclusion.antecedent))
|
|
468
|
+
rest0 = _remove_one(list(l0), a)
|
|
469
|
+
rest1 = _remove_one(list(l1), b)
|
|
470
|
+
target0 = rest0 + [a]
|
|
471
|
+
flat0 = _reorder_bridge(f0, l0, target0, csucc0, emitter)
|
|
472
|
+
premise0 = _decompose(flat0, target0, [rest0, [a]], csucc0, emitter)
|
|
473
|
+
# Reconcile rest1 (proving Γ,B⊢C) to rest0's chosen order for Γ.
|
|
474
|
+
target1 = rest0 + [b]
|
|
475
|
+
flat1_native = rest1 + [b]
|
|
476
|
+
flat1 = _reorder_bridge(f1, l1, flat1_native, csucc1, emitter)
|
|
477
|
+
flat1 = _reorder_bridge(flat1, flat1_native, target1, csucc1, emitter)
|
|
478
|
+
premise1 = _decompose(flat1, target1, [rest0, [b]], csucc1, emitter)
|
|
479
|
+
compound = _the_one(_counter_diff(d.conclusion.antecedent, rest0))
|
|
480
|
+
concl_list = rest0 + [compound]
|
|
481
|
+
stmt = f'derivable ({_flat_ill(rest0)} @ {_flat_ill([compound])}) {succ_isa}'
|
|
482
|
+
hraw = emitter.have(stmt, [premise0, premise1], "(rule OPlusL)")
|
|
483
|
+
hflat = emitter.have(f'derivable {_flat_ill(concl_list)} {succ_isa}', [hraw], "simp")
|
|
484
|
+
return hflat, concl_list
|
|
485
|
+
|
|
486
|
+
raise NotImplementedError(f"to_isabelle_ill: unsupported derivation rule {rule!r}.")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# ---------------------------------------------------------------------------
|
|
490
|
+
# ILL public API.
|
|
491
|
+
# ---------------------------------------------------------------------------
|
|
492
|
+
|
|
493
|
+
def ill_derivation_theory(derivation: ILLDerivation,
|
|
494
|
+
theory_name: str = "ILLDerivation") -> str:
|
|
495
|
+
"""Emit a complete, loadable Isabelle/HOL theory replaying an already-found
|
|
496
|
+
(and checker-validated) :class:`~unicode_logic_kit.atp.linear.ILLDerivation`.
|
|
497
|
+
|
|
498
|
+
The returned theory: declares the deep-embedded ``ill`` datatype, defines
|
|
499
|
+
``derivable`` as an ``inductive`` predicate with one intro rule per rule of
|
|
500
|
+
the Python calculus PLUS ``Exch`` (see the module docstring), states the
|
|
501
|
+
derivation's end-sequent as ``lemma ill_goal`` with its antecedent in the
|
|
502
|
+
ORIGINAL order recorded on ``derivation.conclusion`` (a final ``Exch``
|
|
503
|
+
chain re-orders the replay's own working order to match it), and proves it
|
|
504
|
+
by replaying ``derivation`` node-for-node — one ``have`` per tree node,
|
|
505
|
+
closed by exactly the rule that node used.
|
|
506
|
+
|
|
507
|
+
Raises:
|
|
508
|
+
ValueError: if ``derivation`` does not check
|
|
509
|
+
(:func:`~unicode_logic_kit.atp.linear.check_ill_proof`) — there is
|
|
510
|
+
nothing sound to replay.
|
|
511
|
+
"""
|
|
512
|
+
if not check_ill_proof(derivation):
|
|
513
|
+
raise ValueError(
|
|
514
|
+
"ill_derivation_theory: the given ILLDerivation does not check "
|
|
515
|
+
"(unicode_logic_kit.check_ill_proof / verify_ill_proof rejects it) "
|
|
516
|
+
"— use ill_prove/ill_derivable to find a valid derivation first.")
|
|
517
|
+
|
|
518
|
+
emitter = _Emitter()
|
|
519
|
+
root_fact, root_list = _emit_ill(derivation, emitter)
|
|
520
|
+
wanted = list(derivation.conclusion.antecedent)
|
|
521
|
+
succ_isa = _lift_ill(derivation.conclusion.succedent)
|
|
522
|
+
final_fact = _reorder_bridge(root_fact, root_list, wanted, succ_isa, emitter)
|
|
523
|
+
|
|
524
|
+
lines: List[str] = []
|
|
525
|
+
lines.append(f"theory {theory_name}")
|
|
526
|
+
lines.append(" imports Main")
|
|
527
|
+
lines.append("begin")
|
|
528
|
+
lines.append("")
|
|
529
|
+
lines.append("(* Deep embedding + derivation REPLAY of an ILL sequent. The toolkit's own")
|
|
530
|
+
lines.append(" cut-free search (unicode_logic_kit.ill_prove) already found this proof; this")
|
|
531
|
+
lines.append(" theory transcribes it into Isabelle, one intro rule per tree node -- no")
|
|
532
|
+
lines.append(" automation searches for the proof. See the module docstring of")
|
|
533
|
+
lines.append(" unicode_logic_kit.hol.isabelle_substructural for why the antecedent is an")
|
|
534
|
+
lines.append(" \"ill list\" with an explicit Exch rule rather than \"ill multiset\". *)")
|
|
535
|
+
lines.append(f"(* Sequent: {derivation.conclusion} *)")
|
|
536
|
+
lines.append("")
|
|
537
|
+
lines.append(ILL_DATATYPE)
|
|
538
|
+
lines.append("")
|
|
539
|
+
lines.append("inductive derivable :: \"ill list \\<Rightarrow> ill \\<Rightarrow> bool\" where")
|
|
540
|
+
for i, (name, body) in enumerate(_ILL_INTROS):
|
|
541
|
+
prefix = " " if i == 0 else "| "
|
|
542
|
+
lines.append(f"{prefix}{name}: \"{body}\"")
|
|
543
|
+
lines.append("")
|
|
544
|
+
lines.append(f'lemma ill_goal: "derivable {_flat_ill(wanted)} {succ_isa}"')
|
|
545
|
+
lines.append("proof -")
|
|
546
|
+
lines.extend(emitter.lines)
|
|
547
|
+
lines.append(f" show ?thesis using {final_fact} by simp")
|
|
548
|
+
lines.append("qed")
|
|
549
|
+
lines.append("")
|
|
550
|
+
lines.append("end")
|
|
551
|
+
return "\n".join(lines) + "\n"
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def to_isabelle_ill(premises: Iterable[Node], goal: Node,
|
|
555
|
+
theory_name: str = "ILLDerivation",
|
|
556
|
+
max_depth: Optional[int] = None,
|
|
557
|
+
max_steps: int = 200000) -> str:
|
|
558
|
+
"""Run :func:`~unicode_logic_kit.atp.linear.ill_prove` on ``premises ⊢ goal`` and,
|
|
559
|
+
if derivable, emit a complete Isabelle/HOL theory that replays the proof
|
|
560
|
+
(see :func:`ill_derivation_theory`).
|
|
561
|
+
|
|
562
|
+
Raises:
|
|
563
|
+
ValueError: if the sequent is not (found) derivable — an underivable
|
|
564
|
+
sequent has no derivation to replay; use ``ill_prove`` /
|
|
565
|
+
``ill_derivable`` directly first to investigate why (for a
|
|
566
|
+
``!``-sequent, ``None`` may mean only "not found within the
|
|
567
|
+
default search bound" — see :func:`~unicode_logic_kit.atp.linear.ill_prove`).
|
|
568
|
+
"""
|
|
569
|
+
ants = list(premises)
|
|
570
|
+
derivation = ill_prove(ants, goal, max_depth=max_depth, max_steps=max_steps)
|
|
571
|
+
if derivation is None:
|
|
572
|
+
raise ValueError(
|
|
573
|
+
f"to_isabelle_ill: {ILLSequent(tuple(ants), goal)} was not found "
|
|
574
|
+
"derivable by ill_prove — there is no derivation to replay. Call "
|
|
575
|
+
"unicode_logic_kit.ill_prove / ill_derivable first (with an explicit "
|
|
576
|
+
"max_depth for a !-sequent if needed) to confirm derivability.")
|
|
577
|
+
return ill_derivation_theory(derivation, theory_name=theory_name)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# =============================================================================
|
|
581
|
+
# Lambek calculus L: deep embedding.
|
|
582
|
+
# =============================================================================
|
|
583
|
+
#
|
|
584
|
+
# L's antecedent is genuinely ORDERED (no exchange), so "lam list" is the
|
|
585
|
+
# literally correct type — no Exch-style device is needed here, and the
|
|
586
|
+
# replay never reorders anything: every list constructed below is either the
|
|
587
|
+
# direct concatenation of already-known sub-lists, or a known list split at a
|
|
588
|
+
# known index (found by comparing a conclusion's antecedent tuple against its
|
|
589
|
+
# premises' — see _find_split_left / _find_split_right), so the only "bridge"
|
|
590
|
+
# ever needed is a pure "@"-re-association, always closed by a trivial,
|
|
591
|
+
# fully-ground `simp` (never a permutation).
|
|
592
|
+
|
|
593
|
+
LAMBEK_DATATYPE = "datatype lam = LAtom string | LProduct lam lam | LUnder lam lam | LOver lam lam"
|
|
594
|
+
|
|
595
|
+
# One entry per Isabelle intro rule; 7 total, 1:1 with atp.lambek's 7 rules
|
|
596
|
+
# (Ax, •L, •R, \L, \R, /L, /R — see _LAMBEK_RULE_TO_ISA). Unlike ILL, nothing
|
|
597
|
+
# extra is added: L's list type needs no structural-rule stand-in.
|
|
598
|
+
_LAMBEK_INTROS: List[Tuple[str, str]] = [
|
|
599
|
+
("Ax", 'derivable [A] A'),
|
|
600
|
+
("ProdL", 'derivable (G1 @ [A, B] @ G2) C \\<Longrightarrow> derivable (G1 @ [LProduct A B] @ G2) C'),
|
|
601
|
+
("ProdR", 'derivable G A \\<Longrightarrow> derivable D B \\<Longrightarrow> derivable (G @ D) (LProduct A B)'),
|
|
602
|
+
("UnderL", 'G \\<noteq> [] \\<Longrightarrow> derivable G A \\<Longrightarrow> derivable (D1 @ [B] @ D2) C \\<Longrightarrow> '
|
|
603
|
+
'derivable (D1 @ G @ [LUnder A B] @ D2) C'),
|
|
604
|
+
("UnderR", 'G \\<noteq> [] \\<Longrightarrow> derivable (A # G) B \\<Longrightarrow> derivable G (LUnder A B)'),
|
|
605
|
+
("OverL", 'G \\<noteq> [] \\<Longrightarrow> derivable G A \\<Longrightarrow> derivable (D1 @ [B] @ D2) C \\<Longrightarrow> '
|
|
606
|
+
'derivable (D1 @ [LOver B A] @ G @ D2) C'),
|
|
607
|
+
("OverR", 'G \\<noteq> [] \\<Longrightarrow> derivable (G @ [A]) B \\<Longrightarrow> derivable G (LOver B A)'),
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
_LAMBEK_RULE_TO_ISA = {
|
|
611
|
+
"Ax": "Ax", "•L": "ProdL", "•R": "ProdR",
|
|
612
|
+
"\\L": "UnderL", "\\R": "UnderR", "/L": "OverL", "/R": "OverR",
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _lift_lambek(node: Node) -> str:
|
|
617
|
+
"""Lift a Lambek type/formula to its ``lam``-datatype Isabelle term."""
|
|
618
|
+
if isinstance(node, Atom):
|
|
619
|
+
refuse_truth_constants([node], "to_isabelle_lambek", _LAMBEK_NO_CONSTANT_WHY)
|
|
620
|
+
return f"(LAtom {_isa_str(key_text(node))})"
|
|
621
|
+
if isinstance(node, Product):
|
|
622
|
+
return f"(LProduct {_lift_lambek(node.left)} {_lift_lambek(node.right)})"
|
|
623
|
+
if isinstance(node, Under):
|
|
624
|
+
return f"(LUnder {_lift_lambek(node.left)} {_lift_lambek(node.right)})"
|
|
625
|
+
if isinstance(node, Over):
|
|
626
|
+
return f"(LOver {_lift_lambek(node.left)} {_lift_lambek(node.right)})"
|
|
627
|
+
raise NotImplementedError(f"to_isabelle_lambek: unsupported node {type(node).__name__}.")
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _flat_lambek(nodes: Sequence[Node]) -> str:
|
|
631
|
+
"""Bracket-list Isabelle text for a list of Lambek types, e.g. ``[A, B]``."""
|
|
632
|
+
if not nodes:
|
|
633
|
+
return "[]"
|
|
634
|
+
return "[" + ", ".join(_lift_lambek(n) for n in nodes) + "]"
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
# ---------------------------------------------------------------------------
|
|
638
|
+
# Lambek replay engine.
|
|
639
|
+
# ---------------------------------------------------------------------------
|
|
640
|
+
#
|
|
641
|
+
# Contract of _emit_lambek(d, emitter) -> fact_name: fact_name names a fact,
|
|
642
|
+
# already emitted into emitter.lines, whose statement is EXACTLY
|
|
643
|
+
# "derivable <_flat_lambek(d.conclusion.antecedent)> <_lift_lambek(d.conclusion.succedent)>"
|
|
644
|
+
# — i.e. (unlike ILL) always in EXACTLY d.conclusion.antecedent's own order,
|
|
645
|
+
# since that order is already the semantically correct one and there is
|
|
646
|
+
# nothing to reorder to.
|
|
647
|
+
|
|
648
|
+
def _nonempty(flat_text: str, emitter: _Emitter) -> str:
|
|
649
|
+
"""Emit (and return the fact name for) ``<flat_text> \\<noteq> []``.
|
|
650
|
+
|
|
651
|
+
Used for the ``Γ ≠ []`` side condition of UnderL/UnderR/OverL/OverR
|
|
652
|
+
(Lambek's restriction). ``flat_text`` is always a concrete, nonempty
|
|
653
|
+
bracket-list literal here (guaranteed by
|
|
654
|
+
:func:`~unicode_logic_kit.atp.lambek.check_lambek_proof`, which every
|
|
655
|
+
derivation this module replays has already passed), so this is always a
|
|
656
|
+
trivial, purely computational ``simp``.
|
|
657
|
+
"""
|
|
658
|
+
return emitter.have(f'{flat_text} \\<noteq> []', [], "simp")
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _lcp_len(a: Sequence, b: Sequence) -> int:
|
|
662
|
+
"""Length of the longest common prefix of ``a`` and ``b``."""
|
|
663
|
+
n = 0
|
|
664
|
+
while n < len(a) and n < len(b) and a[n] == b[n]:
|
|
665
|
+
n += 1
|
|
666
|
+
return n
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _emit_lambek(d: LambekDerivation, emitter: _Emitter) -> str:
|
|
670
|
+
"""Recursively replay one Lambek derivation node; see the contract above."""
|
|
671
|
+
concl_list = list(d.conclusion.antecedent)
|
|
672
|
+
succ_isa = _lift_lambek(d.conclusion.succedent)
|
|
673
|
+
rule = d.rule
|
|
674
|
+
|
|
675
|
+
if rule == "Ax":
|
|
676
|
+
return emitter.have(f'derivable {_flat_lambek(concl_list)} {succ_isa}', [], "(rule Ax)")
|
|
677
|
+
|
|
678
|
+
if rule == "•L":
|
|
679
|
+
child = d.premises[0]
|
|
680
|
+
cfact = _emit_lambek(child, emitter)
|
|
681
|
+
clist = list(child.conclusion.antecedent)
|
|
682
|
+
i = _lcp_len(clist, concl_list) # concl_list[i] is the Product; clist[i:i+2] = (A, B)
|
|
683
|
+
g1, g2 = concl_list[:i], concl_list[i + 1:]
|
|
684
|
+
a, b = clist[i], clist[i + 1]
|
|
685
|
+
decomp = f"{_flat_lambek(g1)} @ {_flat_lambek([a, b])} @ {_flat_lambek(g2)}"
|
|
686
|
+
premise = emitter.have(f'derivable ({decomp}) {succ_isa}', [cfact], "simp")
|
|
687
|
+
concl_stmt = (f"derivable ({_flat_lambek(g1)} @ {_flat_lambek([concl_list[i]])} @ "
|
|
688
|
+
f"{_flat_lambek(g2)}) {succ_isa}")
|
|
689
|
+
hraw = emitter.have(concl_stmt, [premise], "(rule ProdL)")
|
|
690
|
+
return emitter.have(f'derivable {_flat_lambek(concl_list)} {succ_isa}', [hraw], "simp")
|
|
691
|
+
|
|
692
|
+
if rule == "•R":
|
|
693
|
+
c0, c1 = d.premises
|
|
694
|
+
f0 = _emit_lambek(c0, emitter)
|
|
695
|
+
f1 = _emit_lambek(c1, emitter)
|
|
696
|
+
l0, l1 = list(c0.conclusion.antecedent), list(c1.conclusion.antecedent)
|
|
697
|
+
stmt = f'derivable ({_flat_lambek(l0)} @ {_flat_lambek(l1)}) {succ_isa}'
|
|
698
|
+
hraw = emitter.have(stmt, [f0, f1], "(rule ProdR)")
|
|
699
|
+
return emitter.have(f'derivable {_flat_lambek(concl_list)} {succ_isa}', [hraw], "simp")
|
|
700
|
+
|
|
701
|
+
if rule == "\\R":
|
|
702
|
+
child = d.premises[0]
|
|
703
|
+
cfact = _emit_lambek(child, emitter)
|
|
704
|
+
csucc = _lift_lambek(child.conclusion.succedent) # the INNER succedent B, not A\B
|
|
705
|
+
clist = list(child.conclusion.antecedent) # clist == [A] + concl_list
|
|
706
|
+
a = clist[0]
|
|
707
|
+
stmt = f'derivable ({_flat_lambek([a])} @ {_flat_lambek(concl_list)}) {csucc}'
|
|
708
|
+
premise = emitter.have(stmt, [cfact], "simp")
|
|
709
|
+
nonempty = _nonempty(_flat_lambek(concl_list), emitter)
|
|
710
|
+
concl_stmt = f'derivable {_flat_lambek(concl_list)} {succ_isa}'
|
|
711
|
+
return emitter.have(concl_stmt, [nonempty, premise], "(rule UnderR)")
|
|
712
|
+
|
|
713
|
+
if rule == "/R":
|
|
714
|
+
child = d.premises[0]
|
|
715
|
+
cfact = _emit_lambek(child, emitter)
|
|
716
|
+
csucc = _lift_lambek(child.conclusion.succedent) # the INNER succedent B, not B/A
|
|
717
|
+
clist = list(child.conclusion.antecedent) # clist == concl_list + [A]
|
|
718
|
+
a = clist[-1]
|
|
719
|
+
stmt = f'derivable ({_flat_lambek(concl_list)} @ {_flat_lambek([a])}) {csucc}'
|
|
720
|
+
premise = emitter.have(stmt, [cfact], "simp")
|
|
721
|
+
nonempty = _nonempty(_flat_lambek(concl_list), emitter)
|
|
722
|
+
concl_stmt = f'derivable {_flat_lambek(concl_list)} {succ_isa}'
|
|
723
|
+
return emitter.have(concl_stmt, [nonempty, premise], "(rule OverR)")
|
|
724
|
+
|
|
725
|
+
if rule == "\\L":
|
|
726
|
+
p_arg, p_main = d.premises
|
|
727
|
+
f_arg = _emit_lambek(p_arg, emitter)
|
|
728
|
+
f_main = _emit_lambek(p_main, emitter)
|
|
729
|
+
gamma = list(p_arg.conclusion.antecedent)
|
|
730
|
+
main_list = list(p_main.conclusion.antecedent) # D1 + [B] + D2
|
|
731
|
+
d1, d2, compound = _find_split_under(concl_list, main_list, gamma,
|
|
732
|
+
p_arg.conclusion.succedent)
|
|
733
|
+
main_stmt = (f'derivable ({_flat_lambek(d1)} @ {_flat_lambek([compound.right])} @ '
|
|
734
|
+
f'{_flat_lambek(d2)}) {succ_isa}')
|
|
735
|
+
main_shaped = emitter.have(main_stmt, [f_main], "simp")
|
|
736
|
+
nonempty = _nonempty(_flat_lambek(gamma), emitter)
|
|
737
|
+
concl_stmt = (f'derivable ({_flat_lambek(d1)} @ {_flat_lambek(gamma)} @ '
|
|
738
|
+
f'{_flat_lambek([compound])} @ {_flat_lambek(d2)}) {succ_isa}')
|
|
739
|
+
hraw = emitter.have(concl_stmt, [nonempty, f_arg, main_shaped], "(rule UnderL)")
|
|
740
|
+
return emitter.have(f'derivable {_flat_lambek(concl_list)} {succ_isa}', [hraw], "simp")
|
|
741
|
+
|
|
742
|
+
if rule == "/L":
|
|
743
|
+
p_arg, p_main = d.premises
|
|
744
|
+
f_arg = _emit_lambek(p_arg, emitter)
|
|
745
|
+
f_main = _emit_lambek(p_main, emitter)
|
|
746
|
+
gamma = list(p_arg.conclusion.antecedent)
|
|
747
|
+
main_list = list(p_main.conclusion.antecedent) # D1 + [B] + D2
|
|
748
|
+
d1, d2, compound = _find_split_over(concl_list, main_list, gamma,
|
|
749
|
+
p_arg.conclusion.succedent)
|
|
750
|
+
main_stmt = (f'derivable ({_flat_lambek(d1)} @ {_flat_lambek([compound.left])} @ '
|
|
751
|
+
f'{_flat_lambek(d2)}) {succ_isa}')
|
|
752
|
+
main_shaped = emitter.have(main_stmt, [f_main], "simp")
|
|
753
|
+
nonempty = _nonempty(_flat_lambek(gamma), emitter)
|
|
754
|
+
concl_stmt = (f'derivable ({_flat_lambek(d1)} @ {_flat_lambek([compound])} @ '
|
|
755
|
+
f'{_flat_lambek(gamma)} @ {_flat_lambek(d2)}) {succ_isa}')
|
|
756
|
+
hraw = emitter.have(concl_stmt, [nonempty, f_arg, main_shaped], "(rule OverL)")
|
|
757
|
+
return emitter.have(f'derivable {_flat_lambek(concl_list)} {succ_isa}', [hraw], "simp")
|
|
758
|
+
|
|
759
|
+
raise NotImplementedError(f"to_isabelle_lambek: unsupported derivation rule {rule!r}.")
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _find_split_under(concl_list: List[Node], main_list: List[Node], gamma: List[Node],
|
|
763
|
+
a_formula: Node) -> Tuple[List[Node], List[Node], Under]:
|
|
764
|
+
"""``\\L``: find ``(D1, D2, compound)`` with ``concl_list == D1+gamma+[compound]+D2``,
|
|
765
|
+
``main_list == D1+[compound.right]+D2``, ``compound`` an ``Under`` with
|
|
766
|
+
``compound.left == a_formula``. Exists and is unique for a valid derivation."""
|
|
767
|
+
n = len(gamma)
|
|
768
|
+
for k in range(0, len(concl_list) - n):
|
|
769
|
+
if concl_list[k:k + n] != gamma:
|
|
770
|
+
continue
|
|
771
|
+
pos = k + n
|
|
772
|
+
if pos >= len(concl_list):
|
|
773
|
+
continue
|
|
774
|
+
compound = concl_list[pos]
|
|
775
|
+
if not isinstance(compound, Under) or compound.left != a_formula:
|
|
776
|
+
continue
|
|
777
|
+
d1, d2 = concl_list[:k], concl_list[pos + 1:]
|
|
778
|
+
if main_list == d1 + [compound.right] + d2:
|
|
779
|
+
return list(d1), list(d2), compound
|
|
780
|
+
raise AssertionError("internal error: no consistent \\L split found")
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def _find_split_over(concl_list: List[Node], main_list: List[Node], gamma: List[Node],
|
|
784
|
+
a_formula: Node) -> Tuple[List[Node], List[Node], Over]:
|
|
785
|
+
"""``/L``: find ``(D1, D2, compound)`` with ``concl_list == D1+[compound]+gamma+D2``,
|
|
786
|
+
``main_list == D1+[compound.left]+D2``, ``compound`` an ``Over`` with
|
|
787
|
+
``compound.right == a_formula``. Exists and is unique for a valid derivation."""
|
|
788
|
+
n = len(gamma)
|
|
789
|
+
for k in range(0, len(concl_list)):
|
|
790
|
+
compound = concl_list[k]
|
|
791
|
+
if not isinstance(compound, Over) or compound.right != a_formula:
|
|
792
|
+
continue
|
|
793
|
+
gstart = k + 1
|
|
794
|
+
if concl_list[gstart:gstart + n] != gamma:
|
|
795
|
+
continue
|
|
796
|
+
d1, d2 = concl_list[:k], concl_list[gstart + n:]
|
|
797
|
+
if main_list == d1 + [compound.left] + d2:
|
|
798
|
+
return list(d1), list(d2), compound
|
|
799
|
+
raise AssertionError("internal error: no consistent /L split found")
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
# ---------------------------------------------------------------------------
|
|
803
|
+
# Lambek public API.
|
|
804
|
+
# ---------------------------------------------------------------------------
|
|
805
|
+
|
|
806
|
+
def lambek_derivation_theory(derivation: LambekDerivation,
|
|
807
|
+
theory_name: str = "LambekDerivation") -> str:
|
|
808
|
+
"""Emit a complete, loadable Isabelle/HOL theory replaying an already-found
|
|
809
|
+
(and checker-validated) :class:`~unicode_logic_kit.atp.lambek.LambekDerivation`.
|
|
810
|
+
|
|
811
|
+
Same shape as :func:`ill_derivation_theory`: deep-embeds the Lambek type
|
|
812
|
+
grammar as ``datatype lam``, defines ``derivable :: "lam list ⇒ lam ⇒ bool"``
|
|
813
|
+
inductively — the **ordered** antecedent (a plain ``list``, no exchange
|
|
814
|
+
rule) is the entire point of the Lambek calculus, unlike ILL's list+Exch
|
|
815
|
+
workaround (see the module docstring) — and replays ``derivation`` node by
|
|
816
|
+
node.
|
|
817
|
+
|
|
818
|
+
Raises:
|
|
819
|
+
ValueError: if ``derivation`` does not check
|
|
820
|
+
(:func:`~unicode_logic_kit.atp.lambek.check_lambek_proof`).
|
|
821
|
+
"""
|
|
822
|
+
if not check_lambek_proof(derivation):
|
|
823
|
+
raise ValueError(
|
|
824
|
+
"lambek_derivation_theory: the given LambekDerivation does not "
|
|
825
|
+
"check (unicode_logic_kit.check_lambek_proof / verify_lambek_proof "
|
|
826
|
+
"rejects it) — use lambek_prove/lambek_derivable to find a valid "
|
|
827
|
+
"derivation first.")
|
|
828
|
+
|
|
829
|
+
emitter = _Emitter()
|
|
830
|
+
root_fact = _emit_lambek(derivation, emitter)
|
|
831
|
+
wanted = list(derivation.conclusion.antecedent)
|
|
832
|
+
succ_isa = _lift_lambek(derivation.conclusion.succedent)
|
|
833
|
+
|
|
834
|
+
lines: List[str] = []
|
|
835
|
+
lines.append(f"theory {theory_name}")
|
|
836
|
+
lines.append(" imports Main")
|
|
837
|
+
lines.append("begin")
|
|
838
|
+
lines.append("")
|
|
839
|
+
lines.append("(* Deep embedding + derivation REPLAY of a Lambek-calculus (L) sequent. The")
|
|
840
|
+
lines.append(" toolkit's own decision procedure (unicode_logic_kit.lambek_prove) already")
|
|
841
|
+
lines.append(" found this proof; this theory transcribes it into Isabelle, one intro rule")
|
|
842
|
+
lines.append(" per tree node -- no automation searches for the proof. The antecedent is a")
|
|
843
|
+
lines.append(" plain \"lam list\": order is the whole point of L, so unlike the ILL export")
|
|
844
|
+
lines.append(" there is no exchange rule at all. *)")
|
|
845
|
+
lines.append(f"(* Sequent: {derivation.conclusion} *)")
|
|
846
|
+
lines.append("")
|
|
847
|
+
lines.append(LAMBEK_DATATYPE)
|
|
848
|
+
lines.append("")
|
|
849
|
+
lines.append("inductive derivable :: \"lam list \\<Rightarrow> lam \\<Rightarrow> bool\" where")
|
|
850
|
+
for i, (name, body) in enumerate(_LAMBEK_INTROS):
|
|
851
|
+
prefix = " " if i == 0 else "| "
|
|
852
|
+
lines.append(f"{prefix}{name}: \"{body}\"")
|
|
853
|
+
lines.append("")
|
|
854
|
+
lines.append(f'lemma lambek_goal: "derivable {_flat_lambek(wanted)} {succ_isa}"')
|
|
855
|
+
lines.append("proof -")
|
|
856
|
+
lines.extend(emitter.lines)
|
|
857
|
+
lines.append(f" show ?thesis using {root_fact} by simp")
|
|
858
|
+
lines.append("qed")
|
|
859
|
+
lines.append("")
|
|
860
|
+
lines.append("end")
|
|
861
|
+
return "\n".join(lines) + "\n"
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def to_isabelle_lambek(sequence: Iterable[Node], goal: Node,
|
|
865
|
+
theory_name: str = "LambekDerivation") -> str:
|
|
866
|
+
"""Run :func:`~unicode_logic_kit.atp.lambek.lambek_prove` on ``sequence ⊢ goal`` and,
|
|
867
|
+
if derivable, emit a complete Isabelle/HOL theory that replays the proof
|
|
868
|
+
(see :func:`lambek_derivation_theory`).
|
|
869
|
+
|
|
870
|
+
Raises:
|
|
871
|
+
ValueError: if the sequent is not derivable in L (a decision
|
|
872
|
+
procedure, so this is a genuine refutation — see
|
|
873
|
+
:func:`~unicode_logic_kit.atp.lambek.lambek_prove`).
|
|
874
|
+
ValueError: (propagated from ``lambek_prove``) if ``sequence`` is empty
|
|
875
|
+
— L requires a nonempty antecedent.
|
|
876
|
+
"""
|
|
877
|
+
seq = list(sequence)
|
|
878
|
+
derivation = lambek_prove(seq, goal)
|
|
879
|
+
if derivation is None:
|
|
880
|
+
raise ValueError(
|
|
881
|
+
f"to_isabelle_lambek: {LambekSequent(tuple(seq), goal)} is not "
|
|
882
|
+
"derivable in L (lambek_prove is a decision procedure — this is "
|
|
883
|
+
"a genuine refutation) — there is no derivation to replay.")
|
|
884
|
+
return lambek_derivation_theory(derivation, theory_name=theory_name)
|