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,342 @@
|
|
|
1
|
+
"""Finite-valued logical matrices: a generalisation of the K3 / LP three-valued layer.
|
|
2
|
+
|
|
3
|
+
A *logical matrix* is a finite set of truth values, a distinguished subset of
|
|
4
|
+
**designated** values (those that count as assertible), and a truth table for each
|
|
5
|
+
connective. Validity is "designated under every assignment", satisfiability is
|
|
6
|
+
"designated under some assignment", and Γ ⊨ φ is "every assignment that designates
|
|
7
|
+
all of Γ designates φ". This module makes that schema first-class so any finite
|
|
8
|
+
many-valued logic — not just the hard-wired ``{0, ½, 1}`` of
|
|
9
|
+
:mod:`unicode_logic_kit.semantics.manyvalued` — can be evaluated and decided.
|
|
10
|
+
|
|
11
|
+
Three matrices ship built in:
|
|
12
|
+
|
|
13
|
+
- :data:`K3_MATRIX` — Kleene's strong three-valued logic (designate ``{1}``);
|
|
14
|
+
- :data:`LP_MATRIX` — Priest's Logic of Paradox (designate ``{½, 1}``);
|
|
15
|
+
- :data:`FDE_MATRIX` — the Belnap–Dunn four-valued logic **FDE** (first-degree
|
|
16
|
+
entailment), values ``T``/``F``/``N`` (neither)/``B`` (both), designate the
|
|
17
|
+
*true-containing* values ``{T, B}``. FDE is both paraconsistent (``p ∧ ¬p ⊭ q``)
|
|
18
|
+
and paracomplete (``p ⊭ q ∨ ¬q``). Like K3, and unlike LP, it has no logical truth built
|
|
19
|
+
from letters alone: the valuation that gives every letter ``N`` (K3: ``½``) gives every
|
|
20
|
+
such formula that value, which is not designated (``p → p`` takes the value ``N`` at
|
|
21
|
+
``N``). With the truth constants it has some: ``⊤``, ``⊥ → p`` and ``p → ⊤`` are
|
|
22
|
+
FDE-valid.
|
|
23
|
+
|
|
24
|
+
K3 and LP are re-expressed here as matrices, so this layer reproduces the existing
|
|
25
|
+
three-valued decisions exactly (cross-checked in the tests). Build your own with
|
|
26
|
+
:meth:`TruthMatrix.from_functions`.
|
|
27
|
+
|
|
28
|
+
Public API: :class:`TruthMatrix`, :func:`matrix_value`, :func:`matrix_is_valid`,
|
|
29
|
+
:func:`matrix_is_satisfiable`, :func:`matrix_entails`, and the matrices
|
|
30
|
+
:data:`K3_MATRIX`, :data:`LP_MATRIX`, :data:`FDE_MATRIX`. :data:`MATRICES` maps the
|
|
31
|
+
names ``"K3"`` / ``"LP"`` / ``"FDE"`` to them.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from itertools import product
|
|
36
|
+
from typing import Callable, Dict, FrozenSet, Hashable, List, Optional, Sequence, Tuple
|
|
37
|
+
|
|
38
|
+
from ..fol.nodes import Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier
|
|
39
|
+
from ..fol._atom_keys import AtomKeys, atom_key, find_own_key
|
|
40
|
+
from ..fol._truth_constants import truth_value as _truth_value
|
|
41
|
+
from . import manyvalued as _manyvalued
|
|
42
|
+
from .manyvalued import (_atom_keys, _instantiate, _ground, _parameter_instances,
|
|
43
|
+
_reject_if_unsupported, MAX_MODELS)
|
|
44
|
+
|
|
45
|
+
Value = Hashable
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class TruthMatrix:
|
|
50
|
+
"""A finite logical matrix: values, designated values, and connective tables.
|
|
51
|
+
|
|
52
|
+
The connective tables are plain dicts (``neg[v]``, ``conj[(a, b)]``, …) so a
|
|
53
|
+
matrix is fully data-driven; build one directly, or — more conveniently — with
|
|
54
|
+
:meth:`from_functions` from value-level operations. ``∀`` / ``∃`` are read as the
|
|
55
|
+
iterated ``conj`` / ``disj`` over a finite domain (a generalised min / max), so
|
|
56
|
+
the matrix needs no separate quantifier tables.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
name: str
|
|
60
|
+
values: Tuple[Value, ...]
|
|
61
|
+
designated: FrozenSet[Value]
|
|
62
|
+
neg: Dict[Value, Value]
|
|
63
|
+
conj: Dict[Tuple[Value, Value], Value]
|
|
64
|
+
disj: Dict[Tuple[Value, Value], Value]
|
|
65
|
+
impl: Dict[Tuple[Value, Value], Value]
|
|
66
|
+
iff: Dict[Tuple[Value, Value], Value]
|
|
67
|
+
xor: Dict[Tuple[Value, Value], Value]
|
|
68
|
+
top: Optional[Value] = None
|
|
69
|
+
bottom: Optional[Value] = None
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def from_functions(
|
|
73
|
+
name: str,
|
|
74
|
+
values: Sequence[Value],
|
|
75
|
+
designated: Sequence[Value],
|
|
76
|
+
neg: Callable[[Value], Value],
|
|
77
|
+
conj: Callable[[Value, Value], Value],
|
|
78
|
+
disj: Callable[[Value, Value], Value],
|
|
79
|
+
impl: Optional[Callable[[Value, Value], Value]] = None,
|
|
80
|
+
top: Optional[Value] = None,
|
|
81
|
+
bottom: Optional[Value] = None,
|
|
82
|
+
) -> "TruthMatrix":
|
|
83
|
+
"""Materialise a matrix from value-level operations.
|
|
84
|
+
|
|
85
|
+
``impl`` defaults to the **material** conditional ``¬a ∨ b``; the
|
|
86
|
+
biconditional is ``(a→b) ∧ (b→a)`` and exclusive-or is ``¬(a↔b)``. Every
|
|
87
|
+
operation is checked to land back in ``values`` (a closed matrix), and every
|
|
88
|
+
designated value to be a value, so a malformed table is caught at build time.
|
|
89
|
+
|
|
90
|
+
``top`` and ``bottom`` are the values of the truth constants ``$true`` and
|
|
91
|
+
``$false`` (``⊤`` and ``⊥``). A matrix does not determine them (a finite
|
|
92
|
+
logic may have several candidates, or none), so they are declared, and a
|
|
93
|
+
matrix that declares neither refuses a formula that uses the constants. A
|
|
94
|
+
declared one must be a value of the matrix.
|
|
95
|
+
"""
|
|
96
|
+
values = tuple(values)
|
|
97
|
+
vset = set(values)
|
|
98
|
+
for label, constant in (("top", top), ("bottom", bottom)):
|
|
99
|
+
if constant is not None and constant not in vset:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"TruthMatrix {name!r}: {label} value {constant!r} is not a value.")
|
|
102
|
+
if impl is None:
|
|
103
|
+
impl = lambda a, b: disj(neg(a), b)
|
|
104
|
+
iff = lambda a, b: conj(impl(a, b), impl(b, a))
|
|
105
|
+
xor = lambda a, b: neg(iff(a, b))
|
|
106
|
+
|
|
107
|
+
def _u(fn):
|
|
108
|
+
return {v: _check(fn(v), vset, name) for v in values}
|
|
109
|
+
|
|
110
|
+
def _b(fn):
|
|
111
|
+
return {(a, b): _check(fn(a, b), vset, name) for a in values for b in values}
|
|
112
|
+
|
|
113
|
+
for d in designated:
|
|
114
|
+
if d not in vset:
|
|
115
|
+
raise ValueError(f"TruthMatrix {name!r}: designated value {d!r} is not a value.")
|
|
116
|
+
return TruthMatrix(
|
|
117
|
+
name=name, values=values, designated=frozenset(designated),
|
|
118
|
+
neg=_u(neg), conj=_b(conj), disj=_b(disj),
|
|
119
|
+
impl=_b(impl), iff=_b(iff), xor=_b(xor),
|
|
120
|
+
top=top, bottom=bottom,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def is_designated(self, v: Value) -> bool:
|
|
124
|
+
"""True iff ``v`` is a designated (assertible) value of this matrix."""
|
|
125
|
+
return v in self.designated
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _check(v: Value, vset, name: str) -> Value:
|
|
129
|
+
"""Verify a connective result is a legal value of the matrix, else raise."""
|
|
130
|
+
if v not in vset:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"TruthMatrix {name!r}: a connective produced {v!r}, which is not one of "
|
|
133
|
+
f"the matrix values {sorted(map(str, vset))}.")
|
|
134
|
+
return v
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def matrix_value(formula: Node, valuation: Dict[str, Value],
|
|
138
|
+
matrix: TruthMatrix, domain: Optional[Sequence[str]] = None) -> Value:
|
|
139
|
+
"""Evaluate ``formula`` to a matrix value under ``valuation``.
|
|
140
|
+
|
|
141
|
+
``valuation`` maps each ground atom's key (the text it prints as, with every constant
|
|
142
|
+
written by its name: ``'P(a)'``; the text of the atom as a formula, ``"P('a')"``, is
|
|
143
|
+
read as the same key) to a value of ``matrix``. Quantifiers fold ``conj`` (∀)
|
|
144
|
+
/ ``disj`` (∃) over ``domain`` (a set
|
|
145
|
+
of constant names), generalising min/max. A missing atom key raises ``KeyError``;
|
|
146
|
+
a valuation that holds both spellings of one atom with different values raises
|
|
147
|
+
``ValueError``, and so does a value outside the matrix; a modal/fuzzy/sorted/lambda
|
|
148
|
+
node is rejected with the same message :func:`kleene_value` uses, and so is a
|
|
149
|
+
sorted constant inside an atom and a pair of different atoms that print alike (the
|
|
150
|
+
numeral ``1`` and a constant named ``1``, a free variable ``x`` and a constant
|
|
151
|
+
named ``x``), which one key of the valuation could not tell apart. The atom ``P(x)``
|
|
152
|
+
of a free variable is one more key of the valuation.
|
|
153
|
+
"""
|
|
154
|
+
return _matrix_value(formula, valuation, matrix, domain,
|
|
155
|
+
AtomKeys("matrix_value", "refuse"))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _matrix_value(formula: Node, valuation: Dict[str, Value], matrix: TruthMatrix,
|
|
159
|
+
domain: Optional[Sequence[str]], keys: Optional[AtomKeys]) -> Value:
|
|
160
|
+
"""The body of :func:`matrix_value`; ``keys`` records and checks the key of every atom
|
|
161
|
+
reached, and is ``None`` where the atoms were checked already."""
|
|
162
|
+
if isinstance(formula, Atom):
|
|
163
|
+
constant = _truth_value(formula)
|
|
164
|
+
if constant is not None:
|
|
165
|
+
value = matrix.top if constant else matrix.bottom
|
|
166
|
+
if value is None:
|
|
167
|
+
raise NotImplementedError(
|
|
168
|
+
f"matrix_value: the matrix {matrix.name!r} declares no "
|
|
169
|
+
f"{'top' if constant else 'bottom'} value, so it has no reading "
|
|
170
|
+
f"of the truth constant {formula.predicate}; declare one with "
|
|
171
|
+
"TruthMatrix.from_functions(..., top=..., bottom=...), or write "
|
|
172
|
+
"the formula without the constant.")
|
|
173
|
+
return value
|
|
174
|
+
found = (find_own_key(valuation, formula) if keys is None
|
|
175
|
+
else keys.find(valuation, formula))
|
|
176
|
+
if found is None:
|
|
177
|
+
raise KeyError(f"No value for ground atom {atom_key(formula)!r} in the valuation.")
|
|
178
|
+
v = valuation[found]
|
|
179
|
+
return _check(v, set(matrix.values), matrix.name)
|
|
180
|
+
if isinstance(formula, Not):
|
|
181
|
+
return matrix.neg[_matrix_value(formula.formula, valuation, matrix, domain, keys)]
|
|
182
|
+
if isinstance(formula, (And, Or, Xor, Implies, Iff)):
|
|
183
|
+
a = _matrix_value(formula.left, valuation, matrix, domain, keys)
|
|
184
|
+
b = _matrix_value(formula.right, valuation, matrix, domain, keys)
|
|
185
|
+
table = {And: matrix.conj, Or: matrix.disj, Implies: matrix.impl,
|
|
186
|
+
Iff: matrix.iff, Xor: matrix.xor}[type(formula)]
|
|
187
|
+
return table[(a, b)]
|
|
188
|
+
if isinstance(formula, Quantifier):
|
|
189
|
+
if not domain:
|
|
190
|
+
raise ValueError("Evaluating a Quantifier requires a non-empty 'domain'.")
|
|
191
|
+
vals = [_matrix_value(_ground(formula.formula, formula.variable.name, d),
|
|
192
|
+
valuation, matrix, domain, keys) for d in domain]
|
|
193
|
+
combine = matrix.conj if formula.type in ("∀", "forall") else matrix.disj
|
|
194
|
+
acc = vals[0]
|
|
195
|
+
for nxt in vals[1:]:
|
|
196
|
+
acc = combine[(acc, nxt)]
|
|
197
|
+
return acc
|
|
198
|
+
_reject_if_unsupported(formula)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _prepare(formulas: Sequence[Node], matrix: TruthMatrix,
|
|
202
|
+
domain: Optional[Sequence[str]], route: str = "matrix"):
|
|
203
|
+
"""Ground quantifiers, collect ground-atom keys, and bound the enumeration."""
|
|
204
|
+
grounded: List[Node] = []
|
|
205
|
+
for f in formulas:
|
|
206
|
+
if f.count(Quantifier):
|
|
207
|
+
if not domain:
|
|
208
|
+
raise ValueError("Deciding a quantified formula requires a non-empty 'domain'.")
|
|
209
|
+
grounded.append(_instantiate(f, set(domain)))
|
|
210
|
+
else:
|
|
211
|
+
grounded.append(f)
|
|
212
|
+
keys = _atom_keys(*grounded, route=route)
|
|
213
|
+
total = len(matrix.values) ** len(keys)
|
|
214
|
+
# Read where the message says to set it: a name imported into this module would be a
|
|
215
|
+
# copy that a caller's ``manyvalued.MAX_MODELS = ...`` does not reach.
|
|
216
|
+
bound = _manyvalued.MAX_MODELS
|
|
217
|
+
if total > bound:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Matrix enumeration would visit {len(matrix.values)}**{len(keys)} = "
|
|
220
|
+
f"{total} assignments, above MAX_MODELS = {bound}. Reduce the number "
|
|
221
|
+
"of distinct ground atoms, or raise manyvalued.MAX_MODELS.")
|
|
222
|
+
return keys, grounded
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def matrix_is_valid(formula: Node, matrix: TruthMatrix,
|
|
226
|
+
domain: Optional[Sequence[str]] = None) -> bool:
|
|
227
|
+
"""True iff ``formula`` is designated under *every* assignment over ``matrix``.
|
|
228
|
+
|
|
229
|
+
Enumerates ``len(matrix.values) ** n`` assignments of the ``n`` distinct ground
|
|
230
|
+
atoms (after grounding quantifiers over ``domain``). Exponential in ``n``; capped
|
|
231
|
+
at :data:`manyvalued.MAX_MODELS`. A variable that is free in a quantified formula is a
|
|
232
|
+
parameter: one element of ``domain``, and the formula is valid when it is designated
|
|
233
|
+
under every assignment of the domain's elements to its free variables as well (see
|
|
234
|
+
:mod:`~unicode_logic_kit.semantics.manyvalued`). A sorted constant, and two different
|
|
235
|
+
atoms that print alike, are refused by name with ``NotImplementedError``.
|
|
236
|
+
"""
|
|
237
|
+
for instance in _parameter_instances([formula], domain):
|
|
238
|
+
keys, (grounded,) = _prepare(instance, matrix, domain, "matrix_is_valid")
|
|
239
|
+
if not all(
|
|
240
|
+
_matrix_value(grounded, dict(zip(keys, combo)), matrix, domain, None)
|
|
241
|
+
in matrix.designated
|
|
242
|
+
for combo in product(matrix.values, repeat=len(keys))
|
|
243
|
+
):
|
|
244
|
+
return False
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def matrix_is_satisfiable(formula: Node, matrix: TruthMatrix,
|
|
249
|
+
domain: Optional[Sequence[str]] = None) -> bool:
|
|
250
|
+
"""True iff ``formula`` is designated under *some* assignment over ``matrix``.
|
|
251
|
+
|
|
252
|
+
A variable that is free in a quantified formula is a parameter over ``domain``: the
|
|
253
|
+
formula is satisfiable when it is under some assignment of the domain's elements to
|
|
254
|
+
its free variables (see :func:`matrix_is_valid`).
|
|
255
|
+
"""
|
|
256
|
+
for instance in _parameter_instances([formula], domain):
|
|
257
|
+
keys, (grounded,) = _prepare(instance, matrix, domain, "matrix_is_satisfiable")
|
|
258
|
+
if any(
|
|
259
|
+
_matrix_value(grounded, dict(zip(keys, combo)), matrix, domain, None)
|
|
260
|
+
in matrix.designated
|
|
261
|
+
for combo in product(matrix.values, repeat=len(keys))
|
|
262
|
+
):
|
|
263
|
+
return True
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def matrix_entails(premises: Sequence[Node], conclusion: Node, matrix: TruthMatrix,
|
|
268
|
+
domain: Optional[Sequence[str]] = None) -> bool:
|
|
269
|
+
"""True iff every assignment designating all ``premises`` designates ``conclusion``.
|
|
270
|
+
|
|
271
|
+
Matrix consequence: ``Γ ⊨ φ`` holds when no assignment over ``matrix`` makes every
|
|
272
|
+
premise designated yet the conclusion undesignated. A variable that is free in some
|
|
273
|
+
formula of a problem that has a quantifier is a parameter: ONE element of ``domain``,
|
|
274
|
+
the same in the premises and the conclusion, and the conclusion must follow under every
|
|
275
|
+
assignment of the domain's elements to the free variables (see :func:`matrix_is_valid`).
|
|
276
|
+
"""
|
|
277
|
+
formulas = list(premises) + [conclusion]
|
|
278
|
+
for instance in _parameter_instances(formulas, domain):
|
|
279
|
+
keys, grounded = _prepare(instance, matrix, domain, "matrix_entails")
|
|
280
|
+
prem, concl = grounded[:-1], grounded[-1]
|
|
281
|
+
for combo in product(matrix.values, repeat=len(keys)):
|
|
282
|
+
val = dict(zip(keys, combo))
|
|
283
|
+
if all(_matrix_value(p, val, matrix, domain, None) in matrix.designated
|
|
284
|
+
for p in prem):
|
|
285
|
+
if _matrix_value(concl, val, matrix, domain, None) not in matrix.designated:
|
|
286
|
+
return False
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# --------------------------------------------------------------------------- #
|
|
291
|
+
# Built-in matrices.
|
|
292
|
+
# --------------------------------------------------------------------------- #
|
|
293
|
+
|
|
294
|
+
# K3 / LP over {0, ½, 1} with the strong-Kleene tables — the same semantics as
|
|
295
|
+
# semantics.manyvalued, re-expressed as matrices (cross-checked in the tests).
|
|
296
|
+
_THREE = (0.0, 0.5, 1.0)
|
|
297
|
+
_k_neg = lambda x: 1.0 - x
|
|
298
|
+
_k_conj = min
|
|
299
|
+
_k_disj = max
|
|
300
|
+
_k_impl = lambda a, b: max(1.0 - a, b)
|
|
301
|
+
|
|
302
|
+
K3_MATRIX = TruthMatrix.from_functions(
|
|
303
|
+
"K3", _THREE, designated=(1.0,),
|
|
304
|
+
neg=_k_neg, conj=_k_conj, disj=_k_disj, impl=_k_impl, top=1.0, bottom=0.0)
|
|
305
|
+
LP_MATRIX = TruthMatrix.from_functions(
|
|
306
|
+
"LP", _THREE, designated=(0.5, 1.0),
|
|
307
|
+
neg=_k_neg, conj=_k_conj, disj=_k_disj, impl=_k_impl, top=1.0, bottom=0.0)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# Belnap–Dunn FDE: each value is a (has-true, has-false) bit pair.
|
|
311
|
+
# T = told-true-only, F = told-false-only, N = told-neither, B = told-both.
|
|
312
|
+
_FDE_BITS = {"T": (1, 0), "F": (0, 1), "N": (0, 0), "B": (1, 1)}
|
|
313
|
+
_FDE_OF = {bits: name for name, bits in _FDE_BITS.items()}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _fde_neg(x):
|
|
317
|
+
t, f = _FDE_BITS[x]
|
|
318
|
+
return _FDE_OF[(f, t)]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _fde_conj(a, b):
|
|
322
|
+
at, af = _FDE_BITS[a]
|
|
323
|
+
bt, bf = _FDE_BITS[b]
|
|
324
|
+
return _FDE_OF[(at & bt, af | bf)]
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _fde_disj(a, b):
|
|
328
|
+
at, af = _FDE_BITS[a]
|
|
329
|
+
bt, bf = _FDE_BITS[b]
|
|
330
|
+
return _FDE_OF[(at | bt, af & bf)]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
FDE_MATRIX = TruthMatrix.from_functions(
|
|
334
|
+
"FDE", ("F", "N", "T", "B"), designated=("T", "B"),
|
|
335
|
+
neg=_fde_neg, conj=_fde_conj, disj=_fde_disj, # material → via ¬a ∨ b
|
|
336
|
+
top="T", bottom="F") # the extremes of the truth order: told-true-only, told-false-only
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
#: The built-in truth matrices by name — ``"K3"``, ``"LP"`` and ``"FDE"``.
|
|
340
|
+
#: What :func:`matrix_is_valid` and friends accept in place of a
|
|
341
|
+
#: :class:`TruthMatrix` instance.
|
|
342
|
+
MATRICES = {"K3": K3_MATRIX, "LP": LP_MATRIX, "FDE": FDE_MATRIX}
|