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.
Files changed (237) hide show
  1. unicode_logic_kit/__init__.py +385 -0
  2. unicode_logic_kit/__main__.py +520 -0
  3. unicode_logic_kit/_deadline.py +219 -0
  4. unicode_logic_kit/ace/__init__.py +126 -0
  5. unicode_logic_kit/ace/_align.py +135 -0
  6. unicode_logic_kit/ace/chem_lexicon.py +128 -0
  7. unicode_logic_kit/ace/drs_reader.py +570 -0
  8. unicode_logic_kit/ace/mapping.py +666 -0
  9. unicode_logic_kit/ace/reverse_modal.py +138 -0
  10. unicode_logic_kit/ace/runner.py +551 -0
  11. unicode_logic_kit/ace/translate.py +452 -0
  12. unicode_logic_kit/ace/verbalize.py +1070 -0
  13. unicode_logic_kit/api.py +1284 -0
  14. unicode_logic_kit/atp/__init__.py +177 -0
  15. unicode_logic_kit/atp/_ascii_names.py +113 -0
  16. unicode_logic_kit/atp/_html.py +72 -0
  17. unicode_logic_kit/atp/_substructural_input.py +228 -0
  18. unicode_logic_kit/atp/_tff_problem.py +715 -0
  19. unicode_logic_kit/atp/_tptp_problem.py +1111 -0
  20. unicode_logic_kit/atp/_writer_support.py +289 -0
  21. unicode_logic_kit/atp/clingo_backend.py +1180 -0
  22. unicode_logic_kit/atp/cvc5_backend.py +1385 -0
  23. unicode_logic_kit/atp/eprover_backend.py +732 -0
  24. unicode_logic_kit/atp/finite_domain.py +1055 -0
  25. unicode_logic_kit/atp/fitch.py +1547 -0
  26. unicode_logic_kit/atp/fitch_search.py +551 -0
  27. unicode_logic_kit/atp/hets_backend.py +339 -0
  28. unicode_logic_kit/atp/hybrid_down.py +120 -0
  29. unicode_logic_kit/atp/incremental.py +250 -0
  30. unicode_logic_kit/atp/kripke_enum.py +741 -0
  31. unicode_logic_kit/atp/lambek.py +436 -0
  32. unicode_logic_kit/atp/leo3_backend.py +332 -0
  33. unicode_logic_kit/atp/linear.py +738 -0
  34. unicode_logic_kit/atp/lj.py +705 -0
  35. unicode_logic_kit/atp/logic_backends.py +566 -0
  36. unicode_logic_kit/atp/ltl_tableau.py +1084 -0
  37. unicode_logic_kit/atp/minizinc_backend.py +1402 -0
  38. unicode_logic_kit/atp/modal_tableau.py +1382 -0
  39. unicode_logic_kit/atp/nanocop_backend.py +410 -0
  40. unicode_logic_kit/atp/portfolio.py +489 -0
  41. unicode_logic_kit/atp/protocol.py +1803 -0
  42. unicode_logic_kit/atp/prover9_entailment.py +1153 -0
  43. unicode_logic_kit/atp/resolution.py +1376 -0
  44. unicode_logic_kit/atp/resolution_check.py +1114 -0
  45. unicode_logic_kit/atp/sequent.py +1050 -0
  46. unicode_logic_kit/atp/tableau.py +921 -0
  47. unicode_logic_kit/atp/tableau_check.py +543 -0
  48. unicode_logic_kit/atp/tptp_ncl.py +811 -0
  49. unicode_logic_kit/atp/tptp_tff.py +1546 -0
  50. unicode_logic_kit/atp/tstp.py +1333 -0
  51. unicode_logic_kit/atp/tstp_check.py +1096 -0
  52. unicode_logic_kit/atp/twee_backend.py +236 -0
  53. unicode_logic_kit/atp/twee_check.py +711 -0
  54. unicode_logic_kit/atp/twee_entailment.py +953 -0
  55. unicode_logic_kit/atp/vampire_entailment.py +540 -0
  56. unicode_logic_kit/atp/z3_arith.py +470 -0
  57. unicode_logic_kit/atp/z3_equivalence.py +36 -0
  58. unicode_logic_kit/atp/z3_fuzzy.py +362 -0
  59. unicode_logic_kit/atp/z3_input.py +500 -0
  60. unicode_logic_kit/atp/z3_models.py +208 -0
  61. unicode_logic_kit/chem/__init__.py +88 -0
  62. unicode_logic_kit/chem/_naming.py +284 -0
  63. unicode_logic_kit/chem/cache.py +185 -0
  64. unicode_logic_kit/chem/interop.py +244 -0
  65. unicode_logic_kit/chem/mol.py +525 -0
  66. unicode_logic_kit/chem/signature.py +112 -0
  67. unicode_logic_kit/comorphism.py +497 -0
  68. unicode_logic_kit/dl/__init__.py +384 -0
  69. unicode_logic_kit/dl/classification.py +227 -0
  70. unicode_logic_kit/dl/concepts.py +632 -0
  71. unicode_logic_kit/dl/datatypes.py +818 -0
  72. unicode_logic_kit/dl/owl_functional.py +2433 -0
  73. unicode_logic_kit/dl/owl_manchester.py +1637 -0
  74. unicode_logic_kit/dl/owl_reasoner.py +790 -0
  75. unicode_logic_kit/dl/parser.py +391 -0
  76. unicode_logic_kit/dl/tableau.py +4048 -0
  77. unicode_logic_kit/dl/translate.py +2704 -0
  78. unicode_logic_kit/drt/__init__.py +94 -0
  79. unicode_logic_kit/drt/export.py +179 -0
  80. unicode_logic_kit/drt/nodes.py +506 -0
  81. unicode_logic_kit/drt/parser.py +965 -0
  82. unicode_logic_kit/drt/resolve.py +195 -0
  83. unicode_logic_kit/drt/reverse.py +175 -0
  84. unicode_logic_kit/eval/__init__.py +106 -0
  85. unicode_logic_kit/eval/batch.py +382 -0
  86. unicode_logic_kit/eval/canonical.py +663 -0
  87. unicode_logic_kit/eval/chem_batch.py +606 -0
  88. unicode_logic_kit/eval/converses.py +200 -0
  89. unicode_logic_kit/eval/datasets/__init__.py +136 -0
  90. unicode_logic_kit/eval/datasets/_base.py +263 -0
  91. unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
  92. unicode_logic_kit/eval/datasets/c3po.py +678 -0
  93. unicode_logic_kit/eval/datasets/folio.py +158 -0
  94. unicode_logic_kit/eval/datasets/fracas.py +418 -0
  95. unicode_logic_kit/eval/datasets/groves.py +191 -0
  96. unicode_logic_kit/eval/datasets/logicbench.py +467 -0
  97. unicode_logic_kit/eval/datasets/logicnli.py +303 -0
  98. unicode_logic_kit/eval/datasets/malls.py +133 -0
  99. unicode_logic_kit/eval/datasets/pfolio.py +594 -0
  100. unicode_logic_kit/eval/datasets/pmb.py +242 -0
  101. unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
  102. unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
  103. unicode_logic_kit/eval/datasets/proverqa.py +674 -0
  104. unicode_logic_kit/eval/datasets/willow.py +478 -0
  105. unicode_logic_kit/eval/equivalence.py +466 -0
  106. unicode_logic_kit/eval/exercise_gen.py +533 -0
  107. unicode_logic_kit/eval/explain.py +791 -0
  108. unicode_logic_kit/eval/generality.py +750 -0
  109. unicode_logic_kit/eval/metric_hf.py +458 -0
  110. unicode_logic_kit/eval/predicate_match.py +343 -0
  111. unicode_logic_kit/eval/theory_check.py +1170 -0
  112. unicode_logic_kit/eval/validate.py +306 -0
  113. unicode_logic_kit/fol/__init__.py +177 -0
  114. unicode_logic_kit/fol/_atom_keys.py +510 -0
  115. unicode_logic_kit/fol/_fol_nodes.py +3586 -0
  116. unicode_logic_kit/fol/_free_parameters.py +105 -0
  117. unicode_logic_kit/fol/_ho_nodes.py +448 -0
  118. unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
  119. unicode_logic_kit/fol/_identifiers.py +1091 -0
  120. unicode_logic_kit/fol/_lambek_nodes.py +112 -0
  121. unicode_logic_kit/fol/_linear_nodes.py +352 -0
  122. unicode_logic_kit/fol/_modal_nodes.py +1467 -0
  123. unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
  124. unicode_logic_kit/fol/_numeral_symbols.py +231 -0
  125. unicode_logic_kit/fol/_so_nodes.py +200 -0
  126. unicode_logic_kit/fol/_symbol_names.py +81 -0
  127. unicode_logic_kit/fol/_team_nodes.py +181 -0
  128. unicode_logic_kit/fol/_tptp_symbols.py +551 -0
  129. unicode_logic_kit/fol/_truth_constants.py +117 -0
  130. unicode_logic_kit/fol/casl_export.py +1135 -0
  131. unicode_logic_kit/fol/casl_import.py +929 -0
  132. unicode_logic_kit/fol/derivation.py +367 -0
  133. unicode_logic_kit/fol/dialect_detect.py +70 -0
  134. unicode_logic_kit/fol/dialect_repair.py +537 -0
  135. unicode_logic_kit/fol/frames.py +637 -0
  136. unicode_logic_kit/fol/grammars/terminals.lark +31 -0
  137. unicode_logic_kit/fol/lambda_tools.py +297 -0
  138. unicode_logic_kit/fol/latex_input.py +429 -0
  139. unicode_logic_kit/fol/modal_translation.py +944 -0
  140. unicode_logic_kit/fol/msflparser.py +1033 -0
  141. unicode_logic_kit/fol/naming.py +422 -0
  142. unicode_logic_kit/fol/nodes.py +241 -0
  143. unicode_logic_kit/fol/normalforms.py +492 -0
  144. unicode_logic_kit/fol/pal.py +287 -0
  145. unicode_logic_kit/fol/prolog_export.py +566 -0
  146. unicode_logic_kit/fol/prolog_input.py +505 -0
  147. unicode_logic_kit/fol/prover9_input.py +1325 -0
  148. unicode_logic_kit/fol/qml.py +1760 -0
  149. unicode_logic_kit/fol/qmltp_input.py +525 -0
  150. unicode_logic_kit/fol/sanitize.py +221 -0
  151. unicode_logic_kit/fol/serialize.py +79 -0
  152. unicode_logic_kit/fol/signature.py +1290 -0
  153. unicode_logic_kit/fol/simplify_check.py +544 -0
  154. unicode_logic_kit/fol/spans.py +594 -0
  155. unicode_logic_kit/fol/tptp_input.py +1503 -0
  156. unicode_logic_kit/fol/tptp_repair.py +941 -0
  157. unicode_logic_kit/fol/unification.py +157 -0
  158. unicode_logic_kit/fol/verbalize.py +263 -0
  159. unicode_logic_kit/hets/__init__.py +163 -0
  160. unicode_logic_kit/hets/bridge.py +142 -0
  161. unicode_logic_kit/hets/client.py +748 -0
  162. unicode_logic_kit/hets/docker.py +420 -0
  163. unicode_logic_kit/hets/dol.py +712 -0
  164. unicode_logic_kit/hets/haskell_json.py +355 -0
  165. unicode_logic_kit/hets/owl_backend.py +794 -0
  166. unicode_logic_kit/hets/owl_cli.py +598 -0
  167. unicode_logic_kit/hets/symbols.py +512 -0
  168. unicode_logic_kit/hol/__init__.py +140 -0
  169. unicode_logic_kit/hol/_ho_common.py +323 -0
  170. unicode_logic_kit/hol/_isabelle_binders.py +125 -0
  171. unicode_logic_kit/hol/classical.py +812 -0
  172. unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
  173. unicode_logic_kit/hol/deepshallow/_common.py +177 -0
  174. unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
  175. unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
  176. unicode_logic_kit/hol/deepshallow/modal.py +217 -0
  177. unicode_logic_kit/hol/deepshallow/qml.py +406 -0
  178. unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
  179. unicode_logic_kit/hol/free.py +753 -0
  180. unicode_logic_kit/hol/goedel.py +336 -0
  181. unicode_logic_kit/hol/ho_modal.py +1743 -0
  182. unicode_logic_kit/hol/intuitionistic.py +403 -0
  183. unicode_logic_kit/hol/isabelle_conditional.py +593 -0
  184. unicode_logic_kit/hol/isabelle_modal.py +1908 -0
  185. unicode_logic_kit/hol/isabelle_relevant.py +412 -0
  186. unicode_logic_kit/hol/isabelle_runner.py +1147 -0
  187. unicode_logic_kit/hol/isabelle_substructural.py +884 -0
  188. unicode_logic_kit/hol/lean.py +1018 -0
  189. unicode_logic_kit/hol/manyvalued.py +921 -0
  190. unicode_logic_kit/hol/secondorder.py +687 -0
  191. unicode_logic_kit/hol/thf_modal.py +941 -0
  192. unicode_logic_kit/hol/thirdorder.py +397 -0
  193. unicode_logic_kit/ilp/__init__.py +89 -0
  194. unicode_logic_kit/ilp/readback.py +389 -0
  195. unicode_logic_kit/ilp/separation.py +153 -0
  196. unicode_logic_kit/ilp/task.py +730 -0
  197. unicode_logic_kit/logic.py +163 -0
  198. unicode_logic_kit/mcp/__init__.py +28 -0
  199. unicode_logic_kit/mcp/__main__.py +5 -0
  200. unicode_logic_kit/mcp/chem_tools.py +1031 -0
  201. unicode_logic_kit/mcp/server.py +2453 -0
  202. unicode_logic_kit/mcp/syntax_spec.py +681 -0
  203. unicode_logic_kit/prob/__init__.py +53 -0
  204. unicode_logic_kit/prob/_bdd.py +225 -0
  205. unicode_logic_kit/prob/_column_gen.py +668 -0
  206. unicode_logic_kit/prob/distribution.py +686 -0
  207. unicode_logic_kit/prob/nilsson.py +470 -0
  208. unicode_logic_kit/py.typed +0 -0
  209. unicode_logic_kit/semantics/__init__.py +137 -0
  210. unicode_logic_kit/semantics/_modal_reject.py +156 -0
  211. unicode_logic_kit/semantics/action_models.py +466 -0
  212. unicode_logic_kit/semantics/asp_models.py +1200 -0
  213. unicode_logic_kit/semantics/conditional.py +580 -0
  214. unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
  215. unicode_logic_kit/semantics/free_logic.py +913 -0
  216. unicode_logic_kit/semantics/fuzzy.py +384 -0
  217. unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
  218. unicode_logic_kit/semantics/intuitionistic.py +581 -0
  219. unicode_logic_kit/semantics/kripke.py +1139 -0
  220. unicode_logic_kit/semantics/manyvalued.py +580 -0
  221. unicode_logic_kit/semantics/matrix.py +342 -0
  222. unicode_logic_kit/semantics/model_eval.py +1135 -0
  223. unicode_logic_kit/semantics/modelfinder.py +1036 -0
  224. unicode_logic_kit/semantics/nonmonotonic.py +372 -0
  225. unicode_logic_kit/semantics/relevant.py +331 -0
  226. unicode_logic_kit/semantics/secondorder.py +657 -0
  227. unicode_logic_kit/semantics/structures.py +352 -0
  228. unicode_logic_kit/semantics/tarski.py +975 -0
  229. unicode_logic_kit/semantics/team.py +315 -0
  230. unicode_logic_kit/semantics/team_translation.py +416 -0
  231. unicode_logic_kit/semantics/thirdorder.py +358 -0
  232. unicode_logic_kit/semantics/tnorm.py +85 -0
  233. unicode_logic_kit/semantics/truthtable.py +201 -0
  234. unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
  235. unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
  236. unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
  237. 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}