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,921 @@
|
|
|
1
|
+
"""Finite many-valued propositional logic -> HOL export (K3 / LP / FDE / any TruthMatrix).
|
|
2
|
+
|
|
3
|
+
A many-valued *shallow semantical embedding* (SSE) in the Benzmueller/Steen
|
|
4
|
+
style, generalised over the toolkit's finite-matrix layer
|
|
5
|
+
:mod:`unicode_logic_kit.semantics.matrix`: any :class:`~unicode_logic_kit.semantics.matrix.TruthMatrix`
|
|
6
|
+
(the three-valued ``K3_MATRIX`` / ``LP_MATRIX``, the four-valued Belnap-Dunn
|
|
7
|
+
``FDE_MATRIX``, or a custom matrix built with ``TruthMatrix.from_functions``)
|
|
8
|
+
can be reified as an N-element HOL type, with the connectives defined as total
|
|
9
|
+
functions over that type matching the matrix's own operation tables, and
|
|
10
|
+
*validity* read as "the formula's value is **designated** under every
|
|
11
|
+
valuation". Concretely, for a matrix with values ``v1, ..., vN``:
|
|
12
|
+
|
|
13
|
+
* ``N`` distinct, pairwise-**distinct** and jointly **exhaustive** constants
|
|
14
|
+
reify the truth values (so an ATP/Isabelle can finitely case-split);
|
|
15
|
+
* one **ground equation per truth-table cell** pins each connective the
|
|
16
|
+
matrix actually tabulates (arity-1 for negation, arity-2 for the rest;
|
|
17
|
+
only over connectives with a *non-empty* table -- a formula using a
|
|
18
|
+
connective the matrix has no table for is rejected up front, not emitted
|
|
19
|
+
as a dangling symbol);
|
|
20
|
+
* a **designated** predicate ``des`` picks out the matrix's designated set;
|
|
21
|
+
* the conjecture/lemma is ``! [valuation] : des @ eval(formula)``, a
|
|
22
|
+
**Theorem** exactly when ``formula`` is valid in the matrix.
|
|
23
|
+
|
|
24
|
+
This is the data-driven core: :func:`to_thf_matrix` / :func:`to_isabelle_matrix`
|
|
25
|
+
(and their ``*_entailment`` counterparts) take **any** finite
|
|
26
|
+
:class:`~unicode_logic_kit.semantics.matrix.TruthMatrix` and need nothing hardcoded
|
|
27
|
+
about K3/LP/FDE. The legacy K3/LP-only entry points --
|
|
28
|
+
:func:`to_thf_k3lp`, :func:`to_isabelle_k3lp`, :func:`to_thf_k3lp_entailment`,
|
|
29
|
+
:func:`to_isabelle_k3lp_entailment` -- are now thin wrappers that look up
|
|
30
|
+
``K3_MATRIX`` / ``LP_MATRIX`` and delegate, passing the historical constant/
|
|
31
|
+
function names (``tT``/``tB``/``tF``, ``kneg``/``kand``/``kor``/``kimp``/
|
|
32
|
+
``kiff``/``kxor``) so their emitted text keeps its shape; this is a
|
|
33
|
+
compatibility convenience, not a promise about a specific matrix's naming.
|
|
34
|
+
|
|
35
|
+
Truth-value reification and naming. Each matrix value gets a constant name:
|
|
36
|
+
a caller may pin exact names via ``value_names`` (a ``{value: name}`` dict --
|
|
37
|
+
used by the K3/LP wrappers to reproduce ``tT``/``tB``/``tF``), and any value
|
|
38
|
+
left unnamed gets the default ``"t" + sanitised(str(value))`` (e.g. FDE's
|
|
39
|
+
``"T"`` -> ``"tT"``, K3's ``0.5`` -> ``"t0_5"``), de-collided exactly like the
|
|
40
|
+
module's atom-variable naming (:func:`_assign_var_names`). Connective function/
|
|
41
|
+
predicate symbols are similarly overridable via ``conn_names`` (default: the
|
|
42
|
+
:class:`TruthMatrix` field name itself -- ``neg``/``conj``/``disj``/``impl``/
|
|
43
|
+
``iff``/``xor``).
|
|
44
|
+
|
|
45
|
+
Honesty / scope. The toolkit *emits* a problem/theory; it does **not** run
|
|
46
|
+
Leo-III, Satallax, Isabelle, or Sledgehammer. Any *finite* matrix's validity is
|
|
47
|
+
**decidable** (finite truth tables), so the emitted problem is in principle
|
|
48
|
+
fully dischargeable by any sound+complete HOL prover -- and for the Isabelle
|
|
49
|
+
export the emit-time verdict (and, when invalid/non-entailing, the witness
|
|
50
|
+
used in the proof) comes from the toolkit's own oracle,
|
|
51
|
+
:func:`unicode_logic_kit.semantics.matrix.matrix_is_valid` /
|
|
52
|
+
:func:`~unicode_logic_kit.semantics.matrix.matrix_entails`, so the emitted lemma
|
|
53
|
+
cannot drift from the decision procedure it is meant to certify. This module
|
|
54
|
+
covers the **propositional** fragment only: quantifiers and all modal /
|
|
55
|
+
Lukasiewicz / sorted / lambda nodes are out of scope and raise
|
|
56
|
+
``NotImplementedError`` -- exactly the nodes the toolkit's evaluators reject,
|
|
57
|
+
plus ``Quantifier`` (a first-order many-valued validity problem is not a
|
|
58
|
+
finite truth table and is not handled by this propositional export).
|
|
59
|
+
|
|
60
|
+
Public API: :func:`to_thf_matrix`, :func:`to_isabelle_matrix`,
|
|
61
|
+
:func:`to_thf_matrix_entailment`, :func:`to_isabelle_matrix_entailment`, and
|
|
62
|
+
the K3/LP-specific :func:`to_thf_k3lp`, :func:`to_isabelle_k3lp`,
|
|
63
|
+
:func:`to_thf_k3lp_entailment`, :func:`to_isabelle_k3lp_entailment`, and the
|
|
64
|
+
constant :data:`SYSTEMS`.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
from itertools import product
|
|
68
|
+
from typing import Dict, List, Optional, Sequence, Type
|
|
69
|
+
|
|
70
|
+
from ..fol.nodes import (
|
|
71
|
+
Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
72
|
+
Variable, Constant, Number, Function,
|
|
73
|
+
)
|
|
74
|
+
from ..fol._atom_keys import AtomKeys
|
|
75
|
+
from ..fol._msfl_nodes import key_text
|
|
76
|
+
from ..fol._truth_constants import truth_value as _truth_value
|
|
77
|
+
from ..semantics.matrix import (
|
|
78
|
+
TruthMatrix, Value, matrix_value, matrix_is_valid, matrix_entails,
|
|
79
|
+
K3_MATRIX, LP_MATRIX,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# The two legacy systems and their designated-value sets, kept in lock-step
|
|
83
|
+
# with unicode_logic_kit.semantics.manyvalued.DESIGNATED (K3 designates only
|
|
84
|
+
# "true"; LP designates "true" and "both") -- and, since 0.17, re-expressed as
|
|
85
|
+
# TruthMatrix instances (semantics.matrix.K3_MATRIX / LP_MATRIX).
|
|
86
|
+
SYSTEMS = ("K3", "LP")
|
|
87
|
+
|
|
88
|
+
_K3LP_MATRICES = {"K3": K3_MATRIX, "LP": LP_MATRIX}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _check_system(system: str) -> str:
|
|
92
|
+
if system not in SYSTEMS:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Unknown three-valued system {system!r}; choose one of {list(SYSTEMS)} "
|
|
95
|
+
"(K3 designates {T}; LP designates {T, B}). For any other finite "
|
|
96
|
+
"logical matrix (e.g. FDE), use to_thf_matrix / to_isabelle_matrix "
|
|
97
|
+
"with a unicode_logic_kit.semantics.matrix.TruthMatrix directly."
|
|
98
|
+
)
|
|
99
|
+
return system
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _check_propositional(formula: Node) -> None:
|
|
103
|
+
"""Reject any node outside the propositional {neg,and,or,xor,imp,iff} fragment.
|
|
104
|
+
|
|
105
|
+
Recurses the *connective* structure only, treating an ``Atom`` as a leaf:
|
|
106
|
+
an atom's term arguments (Variable/Constant/Number/Function -- e.g. the ``a``
|
|
107
|
+
in ``P(a)``) are legitimate and are not inspected; an atom is just a key into
|
|
108
|
+
the valuation.
|
|
109
|
+
|
|
110
|
+
Shared by every export in this module (K3/LP and the generic matrix
|
|
111
|
+
exporters alike): a quantified or modal/Lukasiewicz/sorted/lambda node has
|
|
112
|
+
no many-valued propositional reading here, and this export's validity is a
|
|
113
|
+
finite truth table, so ``Quantifier`` is out of scope too.
|
|
114
|
+
"""
|
|
115
|
+
if isinstance(formula, Atom):
|
|
116
|
+
return
|
|
117
|
+
if isinstance(formula, Not):
|
|
118
|
+
_check_propositional(formula.formula)
|
|
119
|
+
return
|
|
120
|
+
if isinstance(formula, (And, Or, Xor, Implies, Iff)):
|
|
121
|
+
_check_propositional(formula.left)
|
|
122
|
+
_check_propositional(formula.right)
|
|
123
|
+
return
|
|
124
|
+
if isinstance(formula, Quantifier):
|
|
125
|
+
raise NotImplementedError(
|
|
126
|
+
"many-valued HOL export: Quantifier is outside the propositional "
|
|
127
|
+
"fragment; first-order many-valued validity is not a finite truth "
|
|
128
|
+
"table. Use unicode_logic_kit.semantics.manyvalued.is_valid or "
|
|
129
|
+
"semantics.matrix.matrix_is_valid with a domain instead."
|
|
130
|
+
)
|
|
131
|
+
if isinstance(formula, (Variable, Constant, Number, Function)):
|
|
132
|
+
raise NotImplementedError(
|
|
133
|
+
f"many-valued HOL export: {type(formula).__name__} is a term, not "
|
|
134
|
+
"a propositional formula."
|
|
135
|
+
)
|
|
136
|
+
raise NotImplementedError(
|
|
137
|
+
f"many-valued HOL export: {type(formula).__name__} is not in the "
|
|
138
|
+
"propositional fragment {not, and, or, xor, implies, iff}; this "
|
|
139
|
+
"export has no many-valued reading for it."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# Atom keys -> THF / Isabelle valuation-variable names
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
#
|
|
147
|
+
# An *atom* of the propositional formula is keyed by its canonical
|
|
148
|
+
# rendering with every constant written by its bare name (``key_text``) -- EXACTLY
|
|
149
|
+
# the key the toolkit's evaluators use (e.g. ``valuation[key_text(atom)]`` in
|
|
150
|
+
# matrix_value), and not the text of the formula, which writes a constant such as
|
|
151
|
+
# ``k2`` in quotes. Distinct atoms become distinct universally-quantified
|
|
152
|
+
# valuation variables.
|
|
153
|
+
|
|
154
|
+
def _atom_keys(*formulas: Node) -> List[str]:
|
|
155
|
+
"""Distinct atom keys (canonical ``key_text``) in first-seen order, over all
|
|
156
|
+
``formulas`` of one problem.
|
|
157
|
+
|
|
158
|
+
``$true`` / ``$false`` are the matrix's top / bottom value, not letters. Two different atoms
|
|
159
|
+
that print alike (the numeral ``1`` and a constant named ``1``) would be ONE valuation
|
|
160
|
+
variable and the exported problem another one, and a sorted constant has no reading in a
|
|
161
|
+
matrix, so both are refused by name with ``NotImplementedError``.
|
|
162
|
+
"""
|
|
163
|
+
return AtomKeys("many-valued HOL export", "refuse").letters(formulas)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _safe_name(key: str) -> str:
|
|
167
|
+
"""Turn an arbitrary key into a safe alnum/underscore suffix for a name."""
|
|
168
|
+
return "".join(c if (c.isalnum() or c == "_") else "_" for c in key)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _assign_var_names(keys: List[str]) -> dict:
|
|
172
|
+
"""Map each atom key to a distinct uppercase THF/Isabelle variable name.
|
|
173
|
+
|
|
174
|
+
``P(a)`` -> ``V_P_A`` etc.; on a collision after sanitisation a numeric
|
|
175
|
+
suffix is appended so distinct atoms never share a valuation variable.
|
|
176
|
+
"""
|
|
177
|
+
names_by_key = {}
|
|
178
|
+
used = set()
|
|
179
|
+
for key in keys:
|
|
180
|
+
base = "V_" + _safe_name(key)
|
|
181
|
+
base = base.upper()
|
|
182
|
+
name = base
|
|
183
|
+
i = 0
|
|
184
|
+
while name in used:
|
|
185
|
+
i += 1
|
|
186
|
+
name = f"{base}_{i}"
|
|
187
|
+
used.add(name)
|
|
188
|
+
names_by_key[key] = name
|
|
189
|
+
return names_by_key
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ===========================================================================
|
|
193
|
+
# Generic matrix machinery: naming, op-availability, and the emit-time oracle.
|
|
194
|
+
# ===========================================================================
|
|
195
|
+
#
|
|
196
|
+
# Canonical (node class, TruthMatrix field name, arity) triples, in the fixed
|
|
197
|
+
# order every export below declares/axiomatises them -- same order the K3/LP
|
|
198
|
+
# export always used (neg, conj, disj, impl, iff, xor).
|
|
199
|
+
_CANONICAL_OPS = [
|
|
200
|
+
(Not, "neg", 1), (And, "conj", 2), (Or, "disj", 2),
|
|
201
|
+
(Implies, "impl", 2), (Iff, "iff", 2), (Xor, "xor", 2),
|
|
202
|
+
]
|
|
203
|
+
_CANONICAL_OP_INFO = {cls: (attr, arity) for cls, attr, arity in _CANONICAL_OPS}
|
|
204
|
+
# Default connective symbol names are the TruthMatrix field name prefixed with
|
|
205
|
+
# "mv_" (many-valued) -- NOT the bare field name. Isabelle/HOL's prelude
|
|
206
|
+
# (`imports Main`) already binds `conj`/`disj` as the underlying constants of
|
|
207
|
+
# `\<and>`/`\<or>`; a bare `fun conj` or `fun disj` would collide with those.
|
|
208
|
+
# The legacy K3/LP export already dodged exactly this with its "k" prefix
|
|
209
|
+
# (kneg/kand/kor/kimp/kiff/kxor); "mv_" generalises the same precaution to any
|
|
210
|
+
# matrix's default names. A caller may still override via ``conn_names``.
|
|
211
|
+
_DEFAULT_CONN_NAMES = {cls: f"mv_{attr}" for cls, attr, _ in _CANONICAL_OPS}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _matrix_defined_ops(matrix: TruthMatrix):
|
|
215
|
+
"""The canonical ``(node class, table attr, arity)`` triples ``matrix`` actually
|
|
216
|
+
tabulates (a non-empty dict for that connective), in the fixed declaration order."""
|
|
217
|
+
return [(cls, attr, arity) for cls, attr, arity in _CANONICAL_OPS if getattr(matrix, attr)]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _collect_used_ops(node: Node, acc: set) -> None:
|
|
221
|
+
"""Record which connective *classes* appear in the propositional structure of ``node``."""
|
|
222
|
+
if isinstance(node, Atom):
|
|
223
|
+
return
|
|
224
|
+
if isinstance(node, Not):
|
|
225
|
+
acc.add(Not)
|
|
226
|
+
_collect_used_ops(node.formula, acc)
|
|
227
|
+
return
|
|
228
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff)):
|
|
229
|
+
acc.add(type(node))
|
|
230
|
+
_collect_used_ops(node.left, acc)
|
|
231
|
+
_collect_used_ops(node.right, acc)
|
|
232
|
+
return
|
|
233
|
+
# Any other node class is already rejected by _check_propositional.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _check_matrix_supports(formulas: Sequence[Node], matrix: TruthMatrix) -> None:
|
|
237
|
+
"""Raise ``ValueError`` if some formula uses a connective ``matrix`` has no table for.
|
|
238
|
+
|
|
239
|
+
``matrix``'s ``neg``/``conj``/``disj``/``impl``/``iff``/``xor`` fields are
|
|
240
|
+
plain dicts; an empty dict means "this matrix does not define this
|
|
241
|
+
connective" (``TruthMatrix.from_functions`` always fills all six, but a
|
|
242
|
+
hand-built matrix may legitimately omit one). Emitting a THF/Isabelle
|
|
243
|
+
symbol with no axioms would be unsound (an uninterpreted function the
|
|
244
|
+
prover could give any meaning), so this is checked eagerly instead.
|
|
245
|
+
"""
|
|
246
|
+
used: set = set()
|
|
247
|
+
for f in formulas:
|
|
248
|
+
_collect_used_ops(f, used)
|
|
249
|
+
for cls in used:
|
|
250
|
+
attr, _ = _CANONICAL_OP_INFO[cls]
|
|
251
|
+
if not getattr(matrix, attr):
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"TruthMatrix {matrix.name!r} does not define {attr!r} (its "
|
|
254
|
+
f"table is empty), but the formula uses {cls.__name__}. This "
|
|
255
|
+
"export only reifies connectives the matrix actually "
|
|
256
|
+
f"tabulates -- supply a matrix with a non-empty {attr!r} "
|
|
257
|
+
f"table, or remove {cls.__name__} from the formula."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _default_value_name(value: Value, used: set) -> str:
|
|
262
|
+
"""Default constant name for a matrix value: ``"t" + sanitised(str(value))``,
|
|
263
|
+
de-collided with a numeric suffix (mirrors :func:`_assign_var_names`)."""
|
|
264
|
+
base = "t" + _safe_name(str(value))
|
|
265
|
+
name = base
|
|
266
|
+
i = 0
|
|
267
|
+
while name in used:
|
|
268
|
+
i += 1
|
|
269
|
+
name = f"{base}_{i}"
|
|
270
|
+
return name
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _resolve_value_names(matrix: TruthMatrix,
|
|
274
|
+
value_names: Optional[Dict[Value, str]]) -> Dict[Value, str]:
|
|
275
|
+
"""Merge caller-supplied constant names with defaults for the rest.
|
|
276
|
+
|
|
277
|
+
The returned dict's iteration order is significant: it is the order
|
|
278
|
+
constants/constructors are declared in the emitted text. It is the
|
|
279
|
+
caller's ``value_names`` keys, in the order given (letting a caller pin a
|
|
280
|
+
specific declaration order), followed by any remaining ``matrix.values``
|
|
281
|
+
in the matrix's own order. Every value resolves to a distinct name.
|
|
282
|
+
"""
|
|
283
|
+
provided = dict(value_names or {})
|
|
284
|
+
for v in provided:
|
|
285
|
+
if v not in matrix.values:
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"value_names key {v!r} is not one of matrix {matrix.name!r}'s "
|
|
288
|
+
f"values {matrix.values!r}.")
|
|
289
|
+
resolved: Dict[Value, str] = {}
|
|
290
|
+
used = set()
|
|
291
|
+
for v, nm in provided.items():
|
|
292
|
+
resolved[v] = nm
|
|
293
|
+
used.add(nm)
|
|
294
|
+
for v in matrix.values:
|
|
295
|
+
if v in resolved:
|
|
296
|
+
continue
|
|
297
|
+
name = _default_value_name(v, used)
|
|
298
|
+
resolved[v] = name
|
|
299
|
+
used.add(name)
|
|
300
|
+
return resolved
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _resolve_conn_names(conn_names: Optional[Dict[Type[Node], str]]) -> Dict[Type[Node], str]:
|
|
304
|
+
"""Merge caller-supplied connective symbol names with the field-name defaults."""
|
|
305
|
+
resolved = dict(_DEFAULT_CONN_NAMES)
|
|
306
|
+
if conn_names:
|
|
307
|
+
resolved.update(conn_names)
|
|
308
|
+
return resolved
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _constant_names(matrix: TruthMatrix, names: Dict[Value, str]) -> Dict[bool, Optional[str]]:
|
|
312
|
+
"""The value constants that stand for the truth constants ``$true`` / ``$false``.
|
|
313
|
+
|
|
314
|
+
``True`` maps to the name of ``matrix.top`` and ``False`` to the name of
|
|
315
|
+
``matrix.bottom``; ``None`` for a value the matrix does not declare.
|
|
316
|
+
"""
|
|
317
|
+
return {True: names[matrix.top] if matrix.top is not None else None,
|
|
318
|
+
False: names[matrix.bottom] if matrix.bottom is not None else None}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _constant_term(constants: Optional[Dict[bool, Optional[str]]], truth: bool,
|
|
322
|
+
node: Atom, route: str) -> str:
|
|
323
|
+
"""The value constant for the truth constant ``node``, or a refusal by name."""
|
|
324
|
+
name = (constants or {}).get(truth)
|
|
325
|
+
if name is None:
|
|
326
|
+
raise NotImplementedError(
|
|
327
|
+
f"{route}: the matrix declares no {'top' if truth else 'bottom'} value, so "
|
|
328
|
+
f"it has no reading of the truth constant {node.predicate}; declare one "
|
|
329
|
+
"with TruthMatrix.from_functions(..., top=..., bottom=...), or write the "
|
|
330
|
+
"formula without the constant.")
|
|
331
|
+
return name
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _matrix_falsifying_assignment(formula: Node, matrix: TruthMatrix, keys: List[str]):
|
|
335
|
+
"""A ``matrix``-value assignment (in ``keys`` order) making ``formula`` non-designated.
|
|
336
|
+
|
|
337
|
+
Only called after :func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`
|
|
338
|
+
has already reported ``formula`` invalid (which enumerates the identical
|
|
339
|
+
assignment space and bounds it by ``MAX_MODELS``), so a witness is
|
|
340
|
+
guaranteed to exist and this re-enumeration is safe.
|
|
341
|
+
"""
|
|
342
|
+
for combo in product(matrix.values, repeat=len(keys)):
|
|
343
|
+
asg = dict(zip(keys, combo))
|
|
344
|
+
if matrix_value(formula, asg, matrix) not in matrix.designated:
|
|
345
|
+
return combo
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _matrix_entailment_countermodel(premises: Sequence[Node], conclusion: Node,
|
|
350
|
+
matrix: TruthMatrix, keys: List[str]):
|
|
351
|
+
"""A ``matrix``-value assignment designating every premise but not the conclusion.
|
|
352
|
+
|
|
353
|
+
Only called after :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`
|
|
354
|
+
has already reported non-entailment (same enumeration, same bound), so a
|
|
355
|
+
countermodel is guaranteed to exist.
|
|
356
|
+
"""
|
|
357
|
+
for combo in product(matrix.values, repeat=len(keys)):
|
|
358
|
+
asg = dict(zip(keys, combo))
|
|
359
|
+
if (all(matrix_value(p, asg, matrix) in matrix.designated for p in premises)
|
|
360
|
+
and matrix_value(conclusion, asg, matrix) not in matrix.designated):
|
|
361
|
+
return combo
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ===========================================================================
|
|
366
|
+
# (A) THF export
|
|
367
|
+
# ===========================================================================
|
|
368
|
+
#
|
|
369
|
+
# Type ``tv`` with |values| exhaustive, pairwise-distinct constants; connective
|
|
370
|
+
# functions matching the matrix's own tables, one ground equation per cell.
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _thf_preamble(matrix: TruthMatrix, names: Dict[Value, str], conns: Dict[Type[Node], str],
|
|
374
|
+
type_name: str, predicate_name: str) -> List[str]:
|
|
375
|
+
"""Type/constant/connective *declarations* plus distinctness + exhaustiveness."""
|
|
376
|
+
ordered = list(names.keys())
|
|
377
|
+
lines = [f"thf({type_name}_type, type, ( {type_name} : $tType ))."]
|
|
378
|
+
for v in ordered:
|
|
379
|
+
cname = names[v]
|
|
380
|
+
lines.append(f"thf({cname}_decl, type, ( {cname} : {type_name} )).")
|
|
381
|
+
lines.append(
|
|
382
|
+
f"thf({predicate_name}_decl, type, ( {predicate_name} : ( {type_name} > $o ) )).")
|
|
383
|
+
for cls, attr, arity in _matrix_defined_ops(matrix):
|
|
384
|
+
cname = conns[cls]
|
|
385
|
+
ty = (f"( {type_name} > {type_name} )" if arity == 1
|
|
386
|
+
else f"( {type_name} > {type_name} > {type_name} )")
|
|
387
|
+
lines.append(f"thf({cname}_decl, type, ( {cname} : {ty} )).")
|
|
388
|
+
if len(ordered) > 1:
|
|
389
|
+
pairs = [f"( {names[a]} != {names[b]} )"
|
|
390
|
+
for i, a in enumerate(ordered) for b in ordered[i + 1:]]
|
|
391
|
+
lines.append(f"thf({type_name}_distinct, axiom, ( " + " & ".join(pairs) + " )).")
|
|
392
|
+
disj = " | ".join(f"( X = {names[v]} )" for v in ordered)
|
|
393
|
+
lines.append(
|
|
394
|
+
f"thf({type_name}_exhaust, axiom, ( ! [X: {type_name}] : ( {disj} ) )).")
|
|
395
|
+
return lines
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _thf_op_axioms(matrix: TruthMatrix, names: Dict[Value, str],
|
|
399
|
+
conns: Dict[Type[Node], str]) -> List[str]:
|
|
400
|
+
"""One THF ground equation per truth-table cell, for every op ``matrix`` defines."""
|
|
401
|
+
ordered = list(names.keys())
|
|
402
|
+
lines: List[str] = []
|
|
403
|
+
for cls, attr, arity in _matrix_defined_ops(matrix):
|
|
404
|
+
cname = conns[cls]
|
|
405
|
+
table = getattr(matrix, attr)
|
|
406
|
+
if arity == 1:
|
|
407
|
+
for v in ordered:
|
|
408
|
+
out = names[table[v]]
|
|
409
|
+
lines.append(
|
|
410
|
+
f"thf({cname}_{names[v]}, axiom, "
|
|
411
|
+
f"( ( {cname} @ {names[v]} ) = {out} )).")
|
|
412
|
+
else:
|
|
413
|
+
for a in ordered:
|
|
414
|
+
for b in ordered:
|
|
415
|
+
out = names[table[(a, b)]]
|
|
416
|
+
lines.append(
|
|
417
|
+
f"thf({cname}_{names[a]}_{names[b]}, axiom, "
|
|
418
|
+
f"( ( {cname} @ {names[a]} @ {names[b]} ) = {out} )).")
|
|
419
|
+
return lines
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _thf_designated_predicate(matrix: TruthMatrix, names: Dict[Value, str],
|
|
423
|
+
type_name: str, predicate_name: str) -> str:
|
|
424
|
+
"""The ``des : tv > $o`` predicate, true exactly of the matrix's designated values."""
|
|
425
|
+
ordered = list(names.keys())
|
|
426
|
+
terms = [f"( D = {names[v]} )" for v in ordered if v in matrix.designated]
|
|
427
|
+
if not terms:
|
|
428
|
+
raise ValueError(f"TruthMatrix {matrix.name!r} has an empty designated set.")
|
|
429
|
+
body = terms[0] if len(terms) == 1 else "( " + " | ".join(terms) + " )"
|
|
430
|
+
return (f"thf({predicate_name}_def, definition, "
|
|
431
|
+
f"( {predicate_name} = ( ^ [D: {type_name}] : {body} ) )).")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _thf_eval_matrix(node: Node, names_by_key: dict, conns: Dict[Type[Node], str],
|
|
435
|
+
constants: Optional[Dict[bool, Optional[str]]] = None) -> str:
|
|
436
|
+
"""Render the formula as a truth-value-typed THF term using the connective fns.
|
|
437
|
+
|
|
438
|
+
``constants`` names the value constants of the truth constants ``$true`` /
|
|
439
|
+
``$false`` (see :func:`_constant_names`).
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(node, Atom):
|
|
442
|
+
truth = _truth_value(node)
|
|
443
|
+
if truth is not None:
|
|
444
|
+
return _constant_term(constants, truth, node, "to_thf_matrix")
|
|
445
|
+
return names_by_key[key_text(node)]
|
|
446
|
+
if isinstance(node, Not):
|
|
447
|
+
return f"( {conns[Not]} @ {_thf_eval_matrix(node.formula, names_by_key, conns, constants)} )"
|
|
448
|
+
for cls in (And, Or, Xor, Implies, Iff):
|
|
449
|
+
if isinstance(node, cls):
|
|
450
|
+
return (f"( {conns[cls]} @ {_thf_eval_matrix(node.left, names_by_key, conns, constants)} "
|
|
451
|
+
f"@ {_thf_eval_matrix(node.right, names_by_key, conns, constants)} )")
|
|
452
|
+
raise NotImplementedError(
|
|
453
|
+
f"to_thf_matrix: unsupported node {type(node).__name__}.")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def to_thf_matrix(formula: Node, matrix: TruthMatrix, *,
|
|
457
|
+
value_names: Optional[Dict[Value, str]] = None,
|
|
458
|
+
conn_names: Optional[Dict[Type[Node], str]] = None,
|
|
459
|
+
type_name: str = "tv",
|
|
460
|
+
predicate_name: str = "des") -> str:
|
|
461
|
+
"""Emit a complete THF problem whose theorem-hood is ``matrix``-validity of ``formula``.
|
|
462
|
+
|
|
463
|
+
Builds a self-contained TPTP **THF** problem: an ``|matrix.values|``-element
|
|
464
|
+
truth-value type (one constant per value, distinctness + exhaustiveness
|
|
465
|
+
axioms), the connective functions ``matrix`` tabulates -- defined by full
|
|
466
|
+
case analysis, one ground equation per cell -- the designated predicate
|
|
467
|
+
``des`` (true of exactly ``matrix.designated``), and the conjecture
|
|
468
|
+
``! [valuation] : des @ <formula>`` where each propositional atom is a
|
|
469
|
+
universally-quantified valuation variable. The conjecture is a **Theorem**
|
|
470
|
+
iff ``formula`` is valid in ``matrix`` (:func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`).
|
|
471
|
+
|
|
472
|
+
Propositional fragment only (``not/and/or/xor/implies/iff`` over atoms); a
|
|
473
|
+
``Quantifier`` or any modal/Lukasiewicz/sorted/lambda node raises
|
|
474
|
+
``NotImplementedError``. A formula using a connective ``matrix`` has no
|
|
475
|
+
table for raises ``ValueError`` (see :func:`_check_matrix_supports`).
|
|
476
|
+
|
|
477
|
+
``value_names`` / ``conn_names`` let a caller pin exact constant/function
|
|
478
|
+
names (and, via a dict's key order, the declaration order); anything
|
|
479
|
+
unnamed gets a data-driven default. ``type_name`` / ``predicate_name``
|
|
480
|
+
rename the truth-value type and the designated predicate.
|
|
481
|
+
|
|
482
|
+
Note: the toolkit emits this problem; it does **not** run a prover. A
|
|
483
|
+
finite matrix's validity is decidable, so a sound+complete HOL ATP
|
|
484
|
+
(Leo-III, Satallax) can in principle discharge it.
|
|
485
|
+
"""
|
|
486
|
+
_check_propositional(formula)
|
|
487
|
+
_check_matrix_supports([formula], matrix)
|
|
488
|
+
names = _resolve_value_names(matrix, value_names)
|
|
489
|
+
conns = _resolve_conn_names(conn_names)
|
|
490
|
+
keys = _atom_keys(formula)
|
|
491
|
+
names_by_key = _assign_var_names(keys)
|
|
492
|
+
|
|
493
|
+
designated_names = [names[v] for v in names if v in matrix.designated]
|
|
494
|
+
lines = [
|
|
495
|
+
f"% {matrix.name} many-valued shallow embedding of a propositional formula.",
|
|
496
|
+
f"% Values: {[names[v] for v in names]}; designated: {designated_names}. "
|
|
497
|
+
"Conjecture is 'Theorem' iff the formula is valid in this matrix.",
|
|
498
|
+
f"% Formula: {formula.to_unicode_str()}",
|
|
499
|
+
]
|
|
500
|
+
lines.extend(_thf_preamble(matrix, names, conns, type_name, predicate_name))
|
|
501
|
+
lines.extend(_thf_op_axioms(matrix, names, conns))
|
|
502
|
+
lines.append(_thf_designated_predicate(matrix, names, type_name, predicate_name))
|
|
503
|
+
|
|
504
|
+
body = _thf_eval_matrix(formula, names_by_key, conns, _constant_names(matrix, names))
|
|
505
|
+
if keys:
|
|
506
|
+
binder = ", ".join(f"{names_by_key[k]}: {type_name}" for k in keys)
|
|
507
|
+
conj = f"! [{binder}] : ( {predicate_name} @ {body} )"
|
|
508
|
+
else:
|
|
509
|
+
conj = f"( {predicate_name} @ {body} )"
|
|
510
|
+
lines.append(f"thf(goal, conjecture, ( {conj} )).")
|
|
511
|
+
return "\n".join(lines) + "\n"
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def to_thf_matrix_entailment(premises: Sequence[Node], conclusion: Node, matrix: TruthMatrix, *,
|
|
515
|
+
value_names: Optional[Dict[Value, str]] = None,
|
|
516
|
+
conn_names: Optional[Dict[Type[Node], str]] = None,
|
|
517
|
+
type_name: str = "tv",
|
|
518
|
+
predicate_name: str = "des") -> str:
|
|
519
|
+
"""Emit a THF problem whose theorem-hood is ``matrix``-entailment of ``conclusion`` by ``premises``.
|
|
520
|
+
|
|
521
|
+
Designation-preserving entailment: ``premises |= conclusion`` iff every
|
|
522
|
+
valuation that designates **all** premises also designates the
|
|
523
|
+
conclusion. The conjecture is
|
|
524
|
+
``! [valuation] : ( (des p1 & ... & des pn) => des c )``, matching
|
|
525
|
+
:func:`~unicode_logic_kit.semantics.matrix.matrix_entails`.
|
|
526
|
+
|
|
527
|
+
Same propositional fragment, rejection surface, and naming knobs as
|
|
528
|
+
:func:`to_thf_matrix`.
|
|
529
|
+
"""
|
|
530
|
+
premises = list(premises)
|
|
531
|
+
for f in premises:
|
|
532
|
+
_check_propositional(f)
|
|
533
|
+
_check_propositional(conclusion)
|
|
534
|
+
_check_matrix_supports([*premises, conclusion], matrix)
|
|
535
|
+
names = _resolve_value_names(matrix, value_names)
|
|
536
|
+
conns = _resolve_conn_names(conn_names)
|
|
537
|
+
|
|
538
|
+
keys = _atom_keys(*premises, conclusion)
|
|
539
|
+
names_by_key = _assign_var_names(keys)
|
|
540
|
+
|
|
541
|
+
lines = [
|
|
542
|
+
f"% {matrix.name} many-valued entailment.",
|
|
543
|
+
"% Conjecture is 'Theorem' iff the premises entail the conclusion.",
|
|
544
|
+
"% Premises: " + ("; ".join(p.to_unicode_str() for p in premises) or "(none)"),
|
|
545
|
+
f"% Conclusion: {conclusion.to_unicode_str()}",
|
|
546
|
+
]
|
|
547
|
+
lines.extend(_thf_preamble(matrix, names, conns, type_name, predicate_name))
|
|
548
|
+
lines.extend(_thf_op_axioms(matrix, names, conns))
|
|
549
|
+
lines.append(_thf_designated_predicate(matrix, names, type_name, predicate_name))
|
|
550
|
+
|
|
551
|
+
consts = _constant_names(matrix, names)
|
|
552
|
+
concl = f"( {predicate_name} @ {_thf_eval_matrix(conclusion, names_by_key, conns, consts)} )"
|
|
553
|
+
if premises:
|
|
554
|
+
prem = " & ".join(
|
|
555
|
+
f"( {predicate_name} @ {_thf_eval_matrix(p, names_by_key, conns, consts)} )"
|
|
556
|
+
for p in premises)
|
|
557
|
+
goal_body = f"( ( {prem} ) => {concl} )"
|
|
558
|
+
else:
|
|
559
|
+
goal_body = concl
|
|
560
|
+
if keys:
|
|
561
|
+
binder = ", ".join(f"{names_by_key[k]}: {type_name}" for k in keys)
|
|
562
|
+
conj = f"! [{binder}] : {goal_body}"
|
|
563
|
+
else:
|
|
564
|
+
conj = goal_body
|
|
565
|
+
lines.append(f"thf(goal, conjecture, ( {conj} )).")
|
|
566
|
+
return "\n".join(lines) + "\n"
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ===========================================================================
|
|
570
|
+
# (B) Isabelle/HOL export
|
|
571
|
+
# ===========================================================================
|
|
572
|
+
#
|
|
573
|
+
# A self-contained, loadable theory: a datatype with |values| constructors,
|
|
574
|
+
# fun definitions for the connectives matching the matrix's tables, the
|
|
575
|
+
# designated predicate, and a lemma provable iff the formula is matrix-valid.
|
|
576
|
+
# Discharge with `by eval` / `by (cases ...)` / Sledgehammer.
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _isa_datatype_line(matrix: TruthMatrix, names: Dict[Value, str], type_name: str) -> str:
|
|
580
|
+
ordered = list(names.keys())
|
|
581
|
+
return f"datatype {type_name} = " + " | ".join(names[v] for v in ordered)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _isa_neg_def_matrix(matrix: TruthMatrix, names: Dict[Value, str],
|
|
585
|
+
conns: Dict[Type[Node], str], type_name: str) -> str:
|
|
586
|
+
cname = conns[Not]
|
|
587
|
+
ordered = list(names.keys())
|
|
588
|
+
eqs = [f' "{cname} {names[v]} = {names[matrix.neg[v]]}"' for v in ordered]
|
|
589
|
+
return (f'fun {cname} :: "{type_name} \\<Rightarrow> {type_name}" where\n'
|
|
590
|
+
+ " |\n".join(eqs))
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _isa_binop_def_matrix(matrix: TruthMatrix, names: Dict[Value, str], conns: Dict[Type[Node], str],
|
|
594
|
+
cls: type, attr: str, type_name: str) -> str:
|
|
595
|
+
cname = conns[cls]
|
|
596
|
+
table = getattr(matrix, attr)
|
|
597
|
+
ordered = list(names.keys())
|
|
598
|
+
eqs = []
|
|
599
|
+
for a in ordered:
|
|
600
|
+
for b in ordered:
|
|
601
|
+
out = names[table[(a, b)]]
|
|
602
|
+
eqs.append(f' "{cname} {names[a]} {names[b]} = {out}"')
|
|
603
|
+
return (f'fun {cname} :: "{type_name} \\<Rightarrow> {type_name} \\<Rightarrow> {type_name}" where\n'
|
|
604
|
+
+ " |\n".join(eqs))
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _isa_designated_def_matrix(matrix: TruthMatrix, names: Dict[Value, str],
|
|
608
|
+
predicate_name: str, type_name: str) -> str:
|
|
609
|
+
ordered = list(names.keys())
|
|
610
|
+
ds = [names[v] for v in ordered if v in matrix.designated]
|
|
611
|
+
if not ds:
|
|
612
|
+
raise ValueError(f"TruthMatrix {matrix.name!r} has an empty designated set.")
|
|
613
|
+
rhs = f"(d = {ds[0]})" if len(ds) == 1 else "(" + " \\<or> ".join(f"d = {n}" for n in ds) + ")"
|
|
614
|
+
return (f'definition {predicate_name} :: "{type_name} \\<Rightarrow> bool" where\n'
|
|
615
|
+
f' "{predicate_name} d \\<equiv> {rhs}"')
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _isa_matrix_prelude(theory_name: str, matrix: TruthMatrix, names: Dict[Value, str],
|
|
619
|
+
conns: Dict[Type[Node], str], type_name: str, predicate_name: str,
|
|
620
|
+
header_comments: List[str]) -> List[str]:
|
|
621
|
+
"""The shared theory header: datatype, connective funs, designated-predicate definition.
|
|
622
|
+
|
|
623
|
+
Returns the lines from ``theory ... begin`` down to (and including) the
|
|
624
|
+
designated-predicate definition; the caller appends its lemma(s) and the
|
|
625
|
+
final ``end``.
|
|
626
|
+
"""
|
|
627
|
+
parts = [
|
|
628
|
+
f"theory {theory_name}",
|
|
629
|
+
" imports Main",
|
|
630
|
+
"begin",
|
|
631
|
+
"",
|
|
632
|
+
]
|
|
633
|
+
parts += header_comments
|
|
634
|
+
parts += ["", _isa_datatype_line(matrix, names, type_name), ""]
|
|
635
|
+
for cls, attr, arity in _matrix_defined_ops(matrix):
|
|
636
|
+
if arity == 1:
|
|
637
|
+
parts.append(_isa_neg_def_matrix(matrix, names, conns, type_name))
|
|
638
|
+
else:
|
|
639
|
+
parts.append(_isa_binop_def_matrix(matrix, names, conns, cls, attr, type_name))
|
|
640
|
+
parts.append("")
|
|
641
|
+
parts.append(_isa_designated_def_matrix(matrix, names, predicate_name, type_name))
|
|
642
|
+
parts.append("")
|
|
643
|
+
return parts
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _isa_eval_matrix(node: Node, names_by_key: dict, conns: Dict[Type[Node], str],
|
|
647
|
+
constants: Optional[Dict[bool, Optional[str]]] = None) -> str:
|
|
648
|
+
if isinstance(node, Atom):
|
|
649
|
+
truth = _truth_value(node)
|
|
650
|
+
if truth is not None:
|
|
651
|
+
return _constant_term(constants, truth, node, "to_isabelle_matrix")
|
|
652
|
+
return names_by_key[key_text(node)].lower()
|
|
653
|
+
if isinstance(node, Not):
|
|
654
|
+
return f"({conns[Not]} {_isa_eval_matrix(node.formula, names_by_key, conns, constants)})"
|
|
655
|
+
for cls in (And, Or, Xor, Implies, Iff):
|
|
656
|
+
if isinstance(node, cls):
|
|
657
|
+
return (f"({conns[cls]} {_isa_eval_matrix(node.left, names_by_key, conns, constants)} "
|
|
658
|
+
f"{_isa_eval_matrix(node.right, names_by_key, conns, constants)})")
|
|
659
|
+
raise NotImplementedError(
|
|
660
|
+
f"to_isabelle_matrix: unsupported node {type(node).__name__}.")
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _isa_forall_proof(isa_vars: List[str], predicate_name: str = "des") -> str:
|
|
664
|
+
"""Discharge ``\\<forall>vars. des (...)`` by exhausting the finite value-type cases.
|
|
665
|
+
|
|
666
|
+
``simp`` cannot reduce ``fn v`` while ``v`` is an abstract variable (e.g.
|
|
667
|
+
on the LP-valid ``p \\<or> \\<not>p``), so a bare
|
|
668
|
+
``by (simp add: <predicate>_def)`` fails. Splitting every quantified
|
|
669
|
+
variable into its constructors first makes each leaf ground, and
|
|
670
|
+
``simp_all`` then evaluates the ``fun`` tables.
|
|
671
|
+
"""
|
|
672
|
+
if not isa_vars:
|
|
673
|
+
return f" by (simp add: {predicate_name}_def)"
|
|
674
|
+
cases = "; ".join(f"case_tac {v}" for v in isa_vars)
|
|
675
|
+
return (" apply (intro allI)\n"
|
|
676
|
+
f" apply ({cases})\n"
|
|
677
|
+
f" by (simp_all add: {predicate_name}_def)")
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _isa_exists_proof(witness: List[str], predicate_name: str = "des") -> str:
|
|
681
|
+
"""Discharge ``\\<exists>vars. \\<not> des (...)`` by supplying the witness.
|
|
682
|
+
|
|
683
|
+
``witness`` is the counter-valuation (a value-type constructor per
|
|
684
|
+
quantified variable, in binder order) computed at emit time; each
|
|
685
|
+
``rule exI`` instantiates one existential, then ``simp`` evaluates the
|
|
686
|
+
now-ground body.
|
|
687
|
+
"""
|
|
688
|
+
if not witness:
|
|
689
|
+
return f" by (simp add: {predicate_name}_def)"
|
|
690
|
+
exs = ", ".join(f"rule exI[where x={w}]" for w in witness)
|
|
691
|
+
return f" by ({exs}, simp add: {predicate_name}_def)"
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def to_isabelle_matrix(formula: Node, matrix: TruthMatrix, *,
|
|
695
|
+
name: str = "Matrix_Validity",
|
|
696
|
+
lemma_name: str = "matrix_validity",
|
|
697
|
+
value_names: Optional[Dict[Value, str]] = None,
|
|
698
|
+
conn_names: Optional[Dict[Type[Node], str]] = None,
|
|
699
|
+
type_name: str = "tv",
|
|
700
|
+
predicate_name: str = "des") -> str:
|
|
701
|
+
"""Emit a complete, loadable Isabelle/HOL theory text for ``matrix``-validity of ``formula``.
|
|
702
|
+
|
|
703
|
+
Produces a ``theory <name> ... begin ... end`` snippet: an
|
|
704
|
+
``|matrix.values|``-constructor ``datatype``, ``fun`` definitions of every
|
|
705
|
+
connective ``matrix`` tabulates, the designated predicate (true of exactly
|
|
706
|
+
``matrix.designated``), and a ``lemma`` that **always loads**:
|
|
707
|
+
|
|
708
|
+
* if ``formula`` is valid in ``matrix``
|
|
709
|
+
(:func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`), the
|
|
710
|
+
lemma is ``\\<forall> valuation. des (<formula>)``;
|
|
711
|
+
* if it is **invalid**, the lemma is the *refutation*
|
|
712
|
+
``\\<exists> valuation. \\<not> des (<formula>)`` (which then holds).
|
|
713
|
+
|
|
714
|
+
Either way the lemma encodes the validity verdict and is **discharged by a
|
|
715
|
+
real proof** Isabelle accepts: the ``\\<forall>`` form by exhausting each
|
|
716
|
+
quantified variable's constructors (``case_tac`` + ``simp_all``); the
|
|
717
|
+
``\\<exists>`` refutation form by supplying the counter-valuation
|
|
718
|
+
(computed by :func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`'s
|
|
719
|
+
sibling enumeration) as ``rule exI`` witnesses, then ``simp``. A leading
|
|
720
|
+
comment states the verdict so the reader knows which form was emitted.
|
|
721
|
+
|
|
722
|
+
Same propositional fragment, rejection surface, and naming knobs
|
|
723
|
+
(``value_names``/``conn_names``/``type_name``/``predicate_name``) as
|
|
724
|
+
:func:`to_thf_matrix`; ``name`` is the Isabelle theory name and
|
|
725
|
+
``lemma_name`` the lemma's label.
|
|
726
|
+
|
|
727
|
+
Note: the toolkit emits the theory; it does **not** run Isabelle. The
|
|
728
|
+
verdict and witness come from the toolkit's own decision procedure
|
|
729
|
+
(:mod:`unicode_logic_kit.semantics.matrix`), cross-checked in the tests.
|
|
730
|
+
"""
|
|
731
|
+
_check_propositional(formula)
|
|
732
|
+
_check_matrix_supports([formula], matrix)
|
|
733
|
+
names = _resolve_value_names(matrix, value_names)
|
|
734
|
+
conns = _resolve_conn_names(conn_names)
|
|
735
|
+
keys = _atom_keys(formula)
|
|
736
|
+
names_by_key = _assign_var_names(keys)
|
|
737
|
+
isa_vars = [names_by_key[k].lower() for k in keys]
|
|
738
|
+
|
|
739
|
+
valid = matrix_is_valid(formula, matrix)
|
|
740
|
+
body = _isa_eval_matrix(formula, names_by_key, conns, _constant_names(matrix, names))
|
|
741
|
+
if keys:
|
|
742
|
+
binder = " ".join(isa_vars)
|
|
743
|
+
if valid:
|
|
744
|
+
lemma = f" \"\\<forall>{binder}. {predicate_name} ({body})\""
|
|
745
|
+
proof = _isa_forall_proof(isa_vars, predicate_name)
|
|
746
|
+
else:
|
|
747
|
+
lemma = f" \"\\<exists>{binder}. \\<not> {predicate_name} ({body})\""
|
|
748
|
+
witness_values = _matrix_falsifying_assignment(formula, matrix, keys)
|
|
749
|
+
proof = _isa_exists_proof([names[v] for v in witness_values], predicate_name)
|
|
750
|
+
else:
|
|
751
|
+
lemma = (f" \"{predicate_name} ({body})\"" if valid
|
|
752
|
+
else f" \"\\<not> {predicate_name} ({body})\"")
|
|
753
|
+
proof = f" by (simp add: {predicate_name}_def)"
|
|
754
|
+
|
|
755
|
+
verdict = "VALID" if valid else "INVALID (lemma is the refutation)"
|
|
756
|
+
designated_names = [names[v] for v in names if v in matrix.designated]
|
|
757
|
+
header = [
|
|
758
|
+
f"(* {matrix.name} many-valued shallow embedding of a propositional formula. *)",
|
|
759
|
+
f"(* Designated: {designated_names}. Verdict: {verdict}. *)",
|
|
760
|
+
f"(* Formula: {formula.to_unicode_str()} *)",
|
|
761
|
+
]
|
|
762
|
+
parts = _isa_matrix_prelude(name, matrix, names, conns, type_name, predicate_name, header)
|
|
763
|
+
parts.append(f"lemma {lemma_name}:")
|
|
764
|
+
parts.append(lemma)
|
|
765
|
+
parts.append(proof)
|
|
766
|
+
parts.append("")
|
|
767
|
+
parts.append("end")
|
|
768
|
+
return "\n".join(parts) + "\n"
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def to_isabelle_matrix_entailment(premises: Sequence[Node], conclusion: Node, matrix: TruthMatrix, *,
|
|
772
|
+
name: str = "Matrix_Entailment",
|
|
773
|
+
lemma_name: str = "matrix_entailment",
|
|
774
|
+
value_names: Optional[Dict[Value, str]] = None,
|
|
775
|
+
conn_names: Optional[Dict[Type[Node], str]] = None,
|
|
776
|
+
type_name: str = "tv",
|
|
777
|
+
predicate_name: str = "des") -> str:
|
|
778
|
+
"""Emit a loadable Isabelle/HOL theory whose lemma is ``matrix``-entailment.
|
|
779
|
+
|
|
780
|
+
Emits a lemma that **always loads**, mirroring
|
|
781
|
+
:func:`~unicode_logic_kit.semantics.matrix.matrix_entails`:
|
|
782
|
+
|
|
783
|
+
* if ``premises |= conclusion`` in ``matrix``, the lemma is
|
|
784
|
+
``\\<forall> valuation. (des p1 \\<and> ... ) \\<longrightarrow> des c``;
|
|
785
|
+
* otherwise it is the countermodel statement
|
|
786
|
+
``\\<exists> valuation. (des p1 \\<and> ... ) \\<and> \\<not> des c``.
|
|
787
|
+
|
|
788
|
+
The verdict and (when non-entailing) the countermodel witness come from
|
|
789
|
+
:func:`~unicode_logic_kit.semantics.matrix.matrix_entails`'s enumeration.
|
|
790
|
+
|
|
791
|
+
Same propositional fragment, rejection surface, and naming knobs as
|
|
792
|
+
:func:`to_isabelle_matrix`.
|
|
793
|
+
"""
|
|
794
|
+
premises = list(premises)
|
|
795
|
+
for f in premises:
|
|
796
|
+
_check_propositional(f)
|
|
797
|
+
_check_propositional(conclusion)
|
|
798
|
+
_check_matrix_supports([*premises, conclusion], matrix)
|
|
799
|
+
names = _resolve_value_names(matrix, value_names)
|
|
800
|
+
conns = _resolve_conn_names(conn_names)
|
|
801
|
+
|
|
802
|
+
keys = _atom_keys(*premises, conclusion)
|
|
803
|
+
names_by_key = _assign_var_names(keys)
|
|
804
|
+
isa_vars = [names_by_key[k].lower() for k in keys]
|
|
805
|
+
|
|
806
|
+
valid = matrix_entails(premises, conclusion, matrix)
|
|
807
|
+
consts = _constant_names(matrix, names)
|
|
808
|
+
concl = f"{predicate_name} ({_isa_eval_matrix(conclusion, names_by_key, conns, consts)})"
|
|
809
|
+
if premises:
|
|
810
|
+
prem = " \\<and> ".join(
|
|
811
|
+
f"{predicate_name} ({_isa_eval_matrix(p, names_by_key, conns, consts)})"
|
|
812
|
+
for p in premises)
|
|
813
|
+
if valid:
|
|
814
|
+
body = f"({prem}) \\<longrightarrow> {concl}"
|
|
815
|
+
else:
|
|
816
|
+
body = f"({prem}) \\<and> \\<not> {concl}"
|
|
817
|
+
else:
|
|
818
|
+
body = concl if valid else f"\\<not> {concl}"
|
|
819
|
+
if keys:
|
|
820
|
+
binder = " ".join(isa_vars)
|
|
821
|
+
q = "\\<forall>" if valid else "\\<exists>"
|
|
822
|
+
lemma = f" \"{q}{binder}. {body}\""
|
|
823
|
+
if valid:
|
|
824
|
+
proof = _isa_forall_proof(isa_vars, predicate_name)
|
|
825
|
+
else:
|
|
826
|
+
witness_values = _matrix_entailment_countermodel(premises, conclusion, matrix, keys)
|
|
827
|
+
proof = _isa_exists_proof([names[v] for v in witness_values], predicate_name)
|
|
828
|
+
else:
|
|
829
|
+
lemma = f" \"{body}\""
|
|
830
|
+
proof = f" by (simp add: {predicate_name}_def)"
|
|
831
|
+
|
|
832
|
+
verdict = "ENTAILS" if valid else "DOES NOT ENTAIL (lemma is a countermodel)"
|
|
833
|
+
header = [
|
|
834
|
+
f"(* {matrix.name} many-valued entailment. *)",
|
|
835
|
+
f"(* Verdict: {verdict}. *)",
|
|
836
|
+
"(* Premises: " + ("; ".join(p.to_unicode_str() for p in premises) or "(none)") + " *)",
|
|
837
|
+
f"(* Conclusion: {conclusion.to_unicode_str()} *)",
|
|
838
|
+
]
|
|
839
|
+
parts = _isa_matrix_prelude(name, matrix, names, conns, type_name, predicate_name, header)
|
|
840
|
+
parts.append(f"lemma {lemma_name}:")
|
|
841
|
+
parts.append(lemma)
|
|
842
|
+
parts.append(proof)
|
|
843
|
+
parts.append("")
|
|
844
|
+
parts.append("end")
|
|
845
|
+
return "\n".join(parts) + "\n"
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
# ===========================================================================
|
|
849
|
+
# (C) Legacy K3/LP-only entry points: thin delegation to the generic exports.
|
|
850
|
+
# ===========================================================================
|
|
851
|
+
#
|
|
852
|
+
# K3_MATRIX / LP_MATRIX (semantics.matrix) reproduce the strong-Kleene tables
|
|
853
|
+
# exactly (cross-checked in tests/test_matrix.py); these wrappers just pin the
|
|
854
|
+
# historical constant/function names so the emitted text keeps its old shape.
|
|
855
|
+
|
|
856
|
+
_K3LP_VALUE_NAMES: Dict[Value, str] = {1.0: "tT", 0.5: "tB", 0.0: "tF"}
|
|
857
|
+
_K3LP_CONN_NAMES = {
|
|
858
|
+
Not: "kneg", And: "kand", Or: "kor", Implies: "kimp", Iff: "kiff", Xor: "kxor",
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def to_thf_k3lp(formula: Node, system: str = "K3") -> str:
|
|
863
|
+
"""Emit a complete THF problem whose theorem-hood is K3/LP validity of ``formula``.
|
|
864
|
+
|
|
865
|
+
A thin wrapper around :func:`to_thf_matrix` with ``system``'s matrix
|
|
866
|
+
(``K3_MATRIX`` / ``LP_MATRIX`` from :mod:`unicode_logic_kit.semantics.matrix`)
|
|
867
|
+
and the historical constant/function names
|
|
868
|
+
(``tT``/``tB``/``tF``, ``kneg``/``kand``/``kor``/``kimp``/``kiff``/``kxor``).
|
|
869
|
+
See :func:`to_thf_matrix` for the full contract; ``system`` is ``"K3"``
|
|
870
|
+
(designates {T}) or ``"LP"`` (designates {T, B}).
|
|
871
|
+
"""
|
|
872
|
+
_check_system(system)
|
|
873
|
+
return to_thf_matrix(formula, _K3LP_MATRICES[system],
|
|
874
|
+
value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def to_thf_k3lp_entailment(premises, conclusion, system: str = "K3") -> str:
|
|
878
|
+
"""Emit a THF problem whose theorem-hood is the K3/LP *entailment* of ``conclusion`` by ``premises``.
|
|
879
|
+
|
|
880
|
+
A thin wrapper around :func:`to_thf_matrix_entailment` with ``system``'s
|
|
881
|
+
matrix and the historical K3/LP constant/function names. This is where K3
|
|
882
|
+
and LP diverge: the explosion ``[P, ~P] |= Q`` is a **Theorem** under K3
|
|
883
|
+
(vacuously -- P and ~P are never jointly designated) but **not** under LP
|
|
884
|
+
(at P = B the premises are designated yet Q may be F), so LP is
|
|
885
|
+
paraconsistent. Mirrors :func:`unicode_logic_kit.semantics.manyvalued.entails`.
|
|
886
|
+
"""
|
|
887
|
+
_check_system(system)
|
|
888
|
+
return to_thf_matrix_entailment(premises, conclusion, _K3LP_MATRICES[system],
|
|
889
|
+
value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def to_isabelle_k3lp(formula: Node, system: str = "K3") -> str:
|
|
893
|
+
"""Emit a complete, loadable Isabelle/HOL theory text for K3/LP validity.
|
|
894
|
+
|
|
895
|
+
A thin wrapper around :func:`to_isabelle_matrix` with ``system``'s matrix,
|
|
896
|
+
the historical K3/LP constant/function names, and the historical theory/
|
|
897
|
+
lemma names (``K3LP_Validity`` / ``k3lp_validity``). See
|
|
898
|
+
:func:`to_isabelle_matrix` for the full contract (verdict-dependent lemma
|
|
899
|
+
shape, proof-by-case-split/witness, honesty note).
|
|
900
|
+
"""
|
|
901
|
+
_check_system(system)
|
|
902
|
+
return to_isabelle_matrix(formula, _K3LP_MATRICES[system],
|
|
903
|
+
name="K3LP_Validity", lemma_name="k3lp_validity",
|
|
904
|
+
value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def to_isabelle_k3lp_entailment(premises, conclusion, system: str = "K3") -> str:
|
|
908
|
+
"""Emit a loadable Isabelle/HOL theory whose lemma is the K3/LP entailment.
|
|
909
|
+
|
|
910
|
+
A thin wrapper around :func:`to_isabelle_matrix_entailment` with
|
|
911
|
+
``system``'s matrix, the historical K3/LP constant/function names, and the
|
|
912
|
+
historical theory/lemma names (``K3LP_Entailment`` / ``k3lp_entailment``).
|
|
913
|
+
The K3/LP split shows up here -- explosion ``[P, ~P] |= Q`` is valid under
|
|
914
|
+
K3 (the ``\\<forall>``/``\\<longrightarrow>`` form) but invalid under LP
|
|
915
|
+
(the ``\\<exists>`` countermodel form).
|
|
916
|
+
"""
|
|
917
|
+
_check_system(system)
|
|
918
|
+
return to_isabelle_matrix_entailment(
|
|
919
|
+
premises, conclusion, _K3LP_MATRICES[system],
|
|
920
|
+
name="K3LP_Entailment", lemma_name="k3lp_entailment",
|
|
921
|
+
value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
|