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,331 @@
|
|
|
1
|
+
"""Relevant logic — the basic affixing system **B**, via simplified Routley–Meyer semantics.
|
|
2
|
+
|
|
3
|
+
Relevance logics reject the classical "paradoxes of material implication": in **B**
|
|
4
|
+
an implication ``A → B`` is a genuine entailment claim, so ``P → (Q → P)`` (an
|
|
5
|
+
irrelevant antecedent) and ``(P ∧ ¬P) → Q`` (ex falso quodlibet) are *not* theorems.
|
|
6
|
+
The semantics implemented here is the Priest–Sylvan **simplified** Routley–Meyer
|
|
7
|
+
semantics (Priest & Sylvan 1992, "Simplified semantics for basic relevant logics",
|
|
8
|
+
*J. Philosophical Logic*; Priest, *Introduction to Non-Classical Logic*, 2nd ed.,
|
|
9
|
+
ch. 10), which is sound and complete for B.
|
|
10
|
+
|
|
11
|
+
An interpretation is ``⟨W, N, *, R, V⟩``:
|
|
12
|
+
|
|
13
|
+
- ``W`` — a set of worlds, with ``N ⊆ W`` a **nonempty** set of *normal* worlds;
|
|
14
|
+
- ``*`` — the Routley star, an involution on worlds (``w** = w``), interpreting ``¬``;
|
|
15
|
+
- ``R`` — a ternary relation sourced only at NON-normal worlds
|
|
16
|
+
(``R ⊆ (W∖N) × W × W``), interpreting ``→`` at non-normal worlds;
|
|
17
|
+
- ``V`` — a valuation mapping each propositional atom to the set of worlds where
|
|
18
|
+
it holds.
|
|
19
|
+
|
|
20
|
+
Truth at a world ``w``:
|
|
21
|
+
|
|
22
|
+
- ``w ⊨ p`` iff ``w ∈ V(p)``
|
|
23
|
+
- ``w ⊨ A ∧ B`` iff ``w ⊨ A`` and ``w ⊨ B``
|
|
24
|
+
- ``w ⊨ A ∨ B`` iff ``w ⊨ A`` or ``w ⊨ B``
|
|
25
|
+
- ``w ⊨ ¬A`` iff ``w* ⊭ A`` (Routley star)
|
|
26
|
+
- ``w ⊨ A → B``, w ∈ N iff for ALL ``x ∈ W``: ``x ⊨ A`` ⇒ ``x ⊨ B``
|
|
27
|
+
- ``w ⊨ A → B``, w ∉ N iff for all ``x, y`` with ``R(w, x, y)``: ``x ⊨ A`` ⇒ ``y ⊨ B``
|
|
28
|
+
|
|
29
|
+
``A ↔ B`` abbreviates ``(A → B) ∧ (B → A)``. A formula is **valid** iff it is true
|
|
30
|
+
at every normal world of every interpretation. Formulas are the classical
|
|
31
|
+
propositional nodes of the default ``MSFLParser()`` — ``And``, ``Or``, ``Not``,
|
|
32
|
+
``Implies``, ``Iff`` over nullary ``Atom``s — reused with relevant semantics,
|
|
33
|
+
exactly as :mod:`.intuitionistic` reuses classical syntax over Kripke models.
|
|
34
|
+
|
|
35
|
+
Public API: :class:`RelevantModel`, :func:`rel_satisfies`,
|
|
36
|
+
:func:`rel_countermodel`, :func:`rel_valid`.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from collections.abc import Mapping as MappingABC
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from itertools import chain, combinations, permutations, product
|
|
42
|
+
from typing import Dict, FrozenSet, Iterator, List, Mapping, Optional, Tuple
|
|
43
|
+
|
|
44
|
+
from ..fol.nodes import Node, Atom, Not, And, Or, Implies, Iff
|
|
45
|
+
from ..fol._msfl_nodes import key_text
|
|
46
|
+
from ..fol._truth_constants import refuse_truth_constants
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _FrozenMap(MappingABC):
|
|
50
|
+
"""An immutable, hashable mapping: dict-like lookup over a frozen tuple of pairs."""
|
|
51
|
+
|
|
52
|
+
__slots__ = ("_dict", "_pairs")
|
|
53
|
+
|
|
54
|
+
def __init__(self, mapping):
|
|
55
|
+
items = mapping.items() if isinstance(mapping, MappingABC) else mapping
|
|
56
|
+
self._dict: Dict = dict(items)
|
|
57
|
+
# Sorted by key so equal mappings hash equally regardless of insertion order.
|
|
58
|
+
self._pairs: Tuple = tuple(sorted(self._dict.items(), key=lambda kv: kv[0]))
|
|
59
|
+
|
|
60
|
+
def __getitem__(self, key):
|
|
61
|
+
return self._dict[key]
|
|
62
|
+
|
|
63
|
+
def __iter__(self) -> Iterator:
|
|
64
|
+
return iter(self._dict)
|
|
65
|
+
|
|
66
|
+
def __len__(self) -> int:
|
|
67
|
+
return len(self._dict)
|
|
68
|
+
|
|
69
|
+
def __hash__(self) -> int:
|
|
70
|
+
return hash(self._pairs)
|
|
71
|
+
|
|
72
|
+
def __repr__(self) -> str:
|
|
73
|
+
return repr(self._dict)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class RelevantModel:
|
|
78
|
+
"""A simplified Routley–Meyer interpretation ``⟨W, N, *, R, V⟩`` for the logic B.
|
|
79
|
+
|
|
80
|
+
Plain containers are welcome — everything is copied and frozen in
|
|
81
|
+
``__post_init__`` (the constructor ergonomics of
|
|
82
|
+
:class:`~unicode_logic_kit.semantics.kripke.KripkeModel`),
|
|
83
|
+
and the frozen model is hashable.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
worlds: the worlds ``W`` (an iterable of names; order kept, duplicates
|
|
87
|
+
collapse).
|
|
88
|
+
normal: the **nonempty** set ``N ⊆ W`` of normal worlds.
|
|
89
|
+
star: the Routley star, a mapping ``w → w*`` that must be total on ``W``
|
|
90
|
+
and involutive (``w** = w``). ``None`` (the default) means the
|
|
91
|
+
identity. Stored hashable (a frozen mapping) with dict-like lookup:
|
|
92
|
+
``model.star["w0"]``.
|
|
93
|
+
R: the ternary relation — a set of triples ``(w, x, y)`` whose source
|
|
94
|
+
``w`` must be NON-normal (``R ⊆ (W∖N) × W × W``). Consulted only when
|
|
95
|
+
evaluating ``→`` at a non-normal world. Defaults to empty.
|
|
96
|
+
valuation: maps an atom key (its rendered form, e.g. ``"P"``) to the set
|
|
97
|
+
of worlds where the atom holds. A missing key is false everywhere.
|
|
98
|
+
Dict-like lookup: ``model.valuation.get("P", frozenset())``.
|
|
99
|
+
|
|
100
|
+
Raises ValueError from ``__post_init__`` if ``normal`` is empty or not a
|
|
101
|
+
subset of ``worlds``, if ``star`` is not a total involution on ``worlds``,
|
|
102
|
+
if some ``R``-triple is sourced at a normal world or mentions an unknown
|
|
103
|
+
world, or if the valuation mentions an unknown world.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
worlds: Tuple[str, ...]
|
|
107
|
+
normal: FrozenSet[str]
|
|
108
|
+
star: Optional[Mapping[str, str]] = None
|
|
109
|
+
R: FrozenSet[Tuple[str, str, str]] = frozenset()
|
|
110
|
+
valuation: Mapping[str, FrozenSet[str]] = field(default_factory=dict)
|
|
111
|
+
|
|
112
|
+
def __post_init__(self):
|
|
113
|
+
worlds = tuple(dict.fromkeys(self.worlds)) # dedupe, keep order
|
|
114
|
+
wset = frozenset(worlds)
|
|
115
|
+
normal = frozenset(self.normal)
|
|
116
|
+
star = (_FrozenMap({w: w for w in worlds}) if self.star is None
|
|
117
|
+
else _FrozenMap(self.star))
|
|
118
|
+
relation = frozenset(tuple(t) for t in self.R)
|
|
119
|
+
val_in = self.valuation
|
|
120
|
+
val_items = val_in.items() if isinstance(val_in, MappingABC) else val_in
|
|
121
|
+
valuation = _FrozenMap({key: frozenset(ws) for key, ws in val_items})
|
|
122
|
+
|
|
123
|
+
if not normal:
|
|
124
|
+
raise ValueError("RelevantModel: the set N of normal worlds must be nonempty.")
|
|
125
|
+
if not normal <= wset:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"RelevantModel: normal worlds {sorted(normal - wset)} are not in worlds.")
|
|
128
|
+
if frozenset(star) != wset or not frozenset(star.values()) <= wset:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"RelevantModel: star must be a total map W → W; got domain "
|
|
131
|
+
f"{sorted(star)} with values {sorted(set(star.values()))} over "
|
|
132
|
+
f"worlds {sorted(wset)}.")
|
|
133
|
+
for w in worlds:
|
|
134
|
+
if star[star[w]] != w:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"RelevantModel: star is not an involution (w** = w fails): "
|
|
137
|
+
f"{w!r}* = {star[w]!r} but {star[w]!r}* = {star[star[w]]!r}.")
|
|
138
|
+
for triple in relation:
|
|
139
|
+
if len(triple) != 3:
|
|
140
|
+
raise ValueError(f"RelevantModel: R entries must be triples; got {triple!r}.")
|
|
141
|
+
a, b, c = triple
|
|
142
|
+
if a not in wset or b not in wset or c not in wset:
|
|
143
|
+
raise ValueError(f"RelevantModel: R{triple!r} mentions a world not in W.")
|
|
144
|
+
if a in normal:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"RelevantModel: R must be sourced at NON-normal worlds only "
|
|
147
|
+
f"(R ⊆ (W∖N) × W × W); R{triple!r} starts at the normal world {a!r}.")
|
|
148
|
+
for key, ws in valuation.items():
|
|
149
|
+
if not ws <= wset:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"RelevantModel: valuation for {key!r} mentions worlds "
|
|
152
|
+
f"{sorted(set(ws) - wset)} not in W.")
|
|
153
|
+
|
|
154
|
+
object.__setattr__(self, "worlds", worlds)
|
|
155
|
+
object.__setattr__(self, "normal", normal)
|
|
156
|
+
object.__setattr__(self, "star", star)
|
|
157
|
+
object.__setattr__(self, "R", relation)
|
|
158
|
+
object.__setattr__(self, "valuation", valuation)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _reject_non_propositional(formula: Node) -> None:
|
|
162
|
+
"""Raise TypeError unless ``formula`` is ∧ ∨ ¬ → ↔ over nullary atoms.
|
|
163
|
+
|
|
164
|
+
The B semantics interprets exactly the classical propositional connectives;
|
|
165
|
+
quantifiers, modalities, lambda terms, Xor and non-nullary atoms are rejected
|
|
166
|
+
(there is no agreed relevant reading for them here).
|
|
167
|
+
"""
|
|
168
|
+
if not isinstance(formula, Node):
|
|
169
|
+
raise TypeError(
|
|
170
|
+
f"relevant: expected a formula Node, got {type(formula).__name__}.")
|
|
171
|
+
refuse_truth_constants(
|
|
172
|
+
[formula], "relevant",
|
|
173
|
+
"relevant logic has two truths and two falsities, an additive pair and a "
|
|
174
|
+
"multiplicative pair, which differ in B, and TPTP's $true / $false names "
|
|
175
|
+
"neither pair", error=TypeError)
|
|
176
|
+
for node in formula.walk():
|
|
177
|
+
if isinstance(node, Atom):
|
|
178
|
+
if node.args:
|
|
179
|
+
raise TypeError(
|
|
180
|
+
"relevant: only nullary propositional atoms are supported; "
|
|
181
|
+
f"got the atom {node.to_unicode_str()!r} with arguments.")
|
|
182
|
+
elif not isinstance(node, (Not, And, Or, Implies, Iff)):
|
|
183
|
+
raise TypeError(
|
|
184
|
+
f"relevant: unsupported node {type(node).__name__} in "
|
|
185
|
+
f"{formula.to_unicode_str()!r}; the B semantics interprets only "
|
|
186
|
+
"the propositional connectives ∧ ∨ ¬ → ↔ over nullary atoms "
|
|
187
|
+
"(no quantifiers, modalities, or lambda terms).")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _sat(model: RelevantModel, world: str, formula: Node) -> bool:
|
|
191
|
+
"""The simplified Routley–Meyer truth clauses (input already validated)."""
|
|
192
|
+
if isinstance(formula, Atom):
|
|
193
|
+
return world in model.valuation.get(key_text(formula), frozenset())
|
|
194
|
+
if isinstance(formula, And):
|
|
195
|
+
return _sat(model, world, formula.left) and _sat(model, world, formula.right)
|
|
196
|
+
if isinstance(formula, Or):
|
|
197
|
+
return _sat(model, world, formula.left) or _sat(model, world, formula.right)
|
|
198
|
+
if isinstance(formula, Not):
|
|
199
|
+
# w ⊨ ¬A iff w* ⊭ A (Routley star).
|
|
200
|
+
return not _sat(model, model.star[world], formula.formula)
|
|
201
|
+
if isinstance(formula, Iff):
|
|
202
|
+
# A ↔ B is defined as (A → B) ∧ (B → A), with the relevant →.
|
|
203
|
+
return (_sat(model, world, Implies(formula.left, formula.right))
|
|
204
|
+
and _sat(model, world, Implies(formula.right, formula.left)))
|
|
205
|
+
if isinstance(formula, Implies):
|
|
206
|
+
if world in model.normal:
|
|
207
|
+
# Normal world: w ⊨ A → B iff EVERY world satisfying A satisfies B.
|
|
208
|
+
return all(_sat(model, x, formula.right)
|
|
209
|
+
for x in model.worlds if _sat(model, x, formula.left))
|
|
210
|
+
# Non-normal world: quantify over the R-successor pairs of w.
|
|
211
|
+
return all(_sat(model, y, formula.right)
|
|
212
|
+
for (a, x, y) in model.R
|
|
213
|
+
if a == world and _sat(model, x, formula.left))
|
|
214
|
+
raise TypeError( # unreachable after _reject_non_propositional
|
|
215
|
+
f"relevant: unsupported node {type(formula).__name__}.")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def rel_satisfies(model: RelevantModel, world: str, formula: Node) -> bool:
|
|
219
|
+
"""Return whether ``formula`` is true at ``world`` in ``model``.
|
|
220
|
+
|
|
221
|
+
Implements the simplified Routley–Meyer clauses: atoms via the valuation,
|
|
222
|
+
``∧``/``∨`` pointwise, ``¬A`` true at ``w`` iff ``A`` is false at ``w*``,
|
|
223
|
+
``A → B`` at a *normal* world iff every world satisfying ``A`` satisfies
|
|
224
|
+
``B``, and at a *non-normal* world iff ``x ⊨ A ⇒ y ⊨ B`` for every triple
|
|
225
|
+
``R(w, x, y)``. ``A ↔ B`` unfolds to ``(A → B) ∧ (B → A)``.
|
|
226
|
+
|
|
227
|
+
Raises TypeError for non-propositional input (quantifiers, modalities,
|
|
228
|
+
lambdas, non-nullary atoms) and ValueError for an unknown world.
|
|
229
|
+
"""
|
|
230
|
+
_reject_non_propositional(formula)
|
|
231
|
+
if world not in model.worlds:
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"rel_satisfies: unknown world {world!r}; the model's worlds are "
|
|
234
|
+
f"{model.worlds}.")
|
|
235
|
+
return _sat(model, world, formula)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _atom_keys(formula: Node) -> List[str]:
|
|
239
|
+
"""Distinct atom keys in ``formula``, in first-seen order (the atoms are nullary here,
|
|
240
|
+
so a key is the name of the predicate)."""
|
|
241
|
+
keys: List[str] = []
|
|
242
|
+
seen = set()
|
|
243
|
+
for node in formula.walk():
|
|
244
|
+
if isinstance(node, Atom):
|
|
245
|
+
key = key_text(node)
|
|
246
|
+
if key not in seen:
|
|
247
|
+
seen.add(key)
|
|
248
|
+
keys.append(key)
|
|
249
|
+
return keys
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _powerset(items) -> Iterator[tuple]:
|
|
253
|
+
"""Every subset of ``items`` as a tuple, smallest first."""
|
|
254
|
+
seq = list(items)
|
|
255
|
+
return chain.from_iterable(combinations(seq, r) for r in range(len(seq) + 1))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _involutions(worlds: Tuple[str, ...]) -> Iterator[Dict[str, str]]:
|
|
259
|
+
"""Every involutive permutation of ``worlds`` (each ``w** = w``) as a dict."""
|
|
260
|
+
for perm in permutations(worlds):
|
|
261
|
+
mapping = dict(zip(worlds, perm))
|
|
262
|
+
if all(mapping[mapping[w]] == w for w in worlds):
|
|
263
|
+
yield mapping
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def rel_countermodel(formula: Node,
|
|
267
|
+
max_worlds: int = 2) -> Optional[Tuple[RelevantModel, str]]:
|
|
268
|
+
"""Return ``(model, world)`` where ``formula`` fails at a normal world, or None.
|
|
269
|
+
|
|
270
|
+
EXHAUSTIVE search over every simplified Routley–Meyer interpretation with
|
|
271
|
+
``|W| ≤ max_worlds`` worlds and exactly ONE normal world ``"w0"``: every
|
|
272
|
+
involution for ``*``, every ``R ⊆ (W∖N) × W × W``, and every valuation of the
|
|
273
|
+
formula's atoms. The returned countermodel is verified with
|
|
274
|
+
:func:`rel_satisfies` before it is returned (the search's failure check *is*
|
|
275
|
+
that call), so a non-None result definitively refutes validity in B.
|
|
276
|
+
|
|
277
|
+
One normal world is WLOG for countermodel *existence*: if ``φ`` fails at a
|
|
278
|
+
normal world ``w0`` of any interpretation, demote every other normal world
|
|
279
|
+
``w`` to non-normal and add the triples ``R(w, x, x)`` for all ``x`` — the
|
|
280
|
+
demoted →-clause with those triples coincides with the normal-world clause,
|
|
281
|
+
the other clauses never mention ``N``, so every truth value is preserved and
|
|
282
|
+
``w0`` (relabelled first) still refutes ``φ``. The search enumerates ALL
|
|
283
|
+
``R``, so it covers that transformed interpretation.
|
|
284
|
+
|
|
285
|
+
The space is EXPONENTIAL: about ``inv(n) · 2^((n-1)·n²) · 2^(n·a)``
|
|
286
|
+
interpretations for ``n`` worlds and ``a`` atoms. ``max_worlds=2`` with ≤ 3
|
|
287
|
+
atoms is ~2·16·64 ≈ 2000 models (well under a second); ``max_worlds=3`` with
|
|
288
|
+
3 atoms is already ≈ 5·10⁸ — keep the bound tiny.
|
|
289
|
+
"""
|
|
290
|
+
_reject_non_propositional(formula)
|
|
291
|
+
atoms = _atom_keys(formula)
|
|
292
|
+
for n in range(1, max_worlds + 1):
|
|
293
|
+
worlds = tuple(f"w{i}" for i in range(n))
|
|
294
|
+
w0 = worlds[0]
|
|
295
|
+
normal = frozenset({w0})
|
|
296
|
+
non_normal = worlds[1:]
|
|
297
|
+
triples = [(w, x, y) for w in non_normal for x in worlds for y in worlds]
|
|
298
|
+
world_subsets = [frozenset(s) for s in _powerset(worlds)]
|
|
299
|
+
for star in _involutions(worlds):
|
|
300
|
+
for r_subset in _powerset(triples):
|
|
301
|
+
relation = frozenset(r_subset)
|
|
302
|
+
for choice in product(world_subsets, repeat=len(atoms)):
|
|
303
|
+
model = RelevantModel(worlds, normal, star, relation,
|
|
304
|
+
dict(zip(atoms, choice)))
|
|
305
|
+
# rel_satisfies IS the verification: a False here certifies
|
|
306
|
+
# that the model refutes the formula at its normal world.
|
|
307
|
+
if not rel_satisfies(model, w0, formula):
|
|
308
|
+
return model, w0
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def rel_valid(formula: Node, max_worlds: int = 2) -> bool:
|
|
313
|
+
"""Return True iff no B-countermodel to ``formula`` is found within the bound.
|
|
314
|
+
|
|
315
|
+
HONEST CONTRACT (mirroring
|
|
316
|
+
:func:`~unicode_logic_kit.semantics.intuitionistic.int_valid`'s first-order
|
|
317
|
+
contract): ``False`` is *definitive* — it is backed by an explicit,
|
|
318
|
+
:func:`rel_satisfies`-verified countermodel from :func:`rel_countermodel`,
|
|
319
|
+
so the formula is certainly not a theorem of B. ``True`` means only "no
|
|
320
|
+
countermodel with at most ``max_worlds`` worlds": B is decidable, but this
|
|
321
|
+
search is *bounded*, so a non-theorem whose smallest refuting interpretation
|
|
322
|
+
needs more worlds than the bound is (spuriously) reported valid — for a
|
|
323
|
+
certified positive verdict use
|
|
324
|
+
:func:`~unicode_logic_kit.hol.isabelle_runner.isabelle_decide_relevant`, whose
|
|
325
|
+
proof battery is a real validity proof over EVERY interpretation satisfying
|
|
326
|
+
B's frame conditions (N nonempty, the Routley star a total involution, R
|
|
327
|
+
sourced only at non-normal worlds) — not just those with at most
|
|
328
|
+
``max_worlds`` worlds. Raising ``max_worlds`` never turns a ``False`` into a
|
|
329
|
+
``True``; it is exponentially more expensive (see :func:`rel_countermodel`).
|
|
330
|
+
"""
|
|
331
|
+
return rel_countermodel(formula, max_worlds) is None
|