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,470 @@
1
+ """Nilsson probabilistic-entailment bounds — exact, propositional, no sampling.
2
+
3
+ Nilsson (*Probabilistic Logic*, Artificial Intelligence 28, 1986) attaches
4
+ probability INTERVALS, not point values, to formulas: a set of constraints
5
+ ``lower ≤ P(φ) ≤ upper`` need not pin down a unique probability distribution
6
+ over the possible worlds, only a convex polytope of them. The tightest bounds
7
+ on a conclusion's probability are then the minimum and maximum of ``P(ψ)``
8
+ over every distribution in that polytope — a linear program, decided here
9
+ EXACTLY (rational arithmetic throughout, never a float) via :mod:`z3`'s
10
+ ``Optimize`` over ``Real`` variables fed rational literals.
11
+
12
+ **Semantics, precisely.** Collect every distinct atom (by key — the text of the
13
+ atom with every constant written by its name, the kit-wide convention for "what
14
+ counts as one propositional variable"; see
15
+ :mod:`unicode_logic_kit.semantics.truthtable`) occurring in the constraints and
16
+ the conclusion; a quantified formula is refused (see below). The two truth
17
+ constants (``⊤`` / ``$true`` and ``⊥`` / ``$false``) are not atoms: every
18
+ world gives ``⊤`` the value true and ``⊥`` the value false
19
+ (:func:`~unicode_logic_kit.fol._truth_constants.truth_value`), so ``P(⊤) = 1``,
20
+ ``P(⊥) = 0``, a constraint ``P(⊥) = 1`` is probabilistically inconsistent, and
21
+ a constant adds no world bit. With ``n``
22
+ distinct atoms there are ``2^n`` possible worlds (truth-value assignments);
23
+ one nonnegative real variable ``p_w`` per world, constrained to sum to 1, is a
24
+ full parametrisation of every probability distribution over those worlds. An
25
+ UNconditional constraint ``lower ≤ P(φ) ≤ upper`` becomes
26
+ ``lower ≤ Σ_{w ⊨ φ} p_w ≤ upper`` — linear in the ``p_w``. A CONDITIONAL
27
+ constraint (``given=φ``, ``formula=ψ``, read ``P(ψ | φ)``) is put in Nilsson's
28
+ standard LINEAR form ``lower·P(φ) ≤ P(ψ∧φ) ≤ upper·P(φ)`` rather than the
29
+ division ``P(ψ∧φ)/P(φ)`` that would leave the LP domain (products of a
30
+ constant rational with a linear expression stay linear). **When ``P(φ) = 0``
31
+ this constraint is VACUOUS** — both sides collapse to ``0 ≤ P(ψ∧φ) ≤ 0``,
32
+ which is already implied by ``P(ψ∧φ) ≤ P(φ) = 0``, so it adds no information.
33
+ This is the usual Nilsson/probability-theory treatment of a conditional whose
34
+ condition has probability zero (the conditional is undefined pointwise, but
35
+ the linear encoding degrades gracefully to "no constraint" rather than raising
36
+ or dividing by zero).
37
+
38
+ Given that polytope, :func:`entailment_bounds` returns the minimum and maximum
39
+ of ``Σ_{w ⊨ conclusion} p_w`` — the tightest probability bounds the
40
+ constraints entail for the conclusion, via two SEPARATE ``Optimize`` calls
41
+ (minimize, then maximize; both must be satisfiable together with the
42
+ constraints, or the constraint set is probabilistically inconsistent and
43
+ :func:`entailment_bounds` raises ``ValueError``). A classical special case
44
+ falls out for free: if every premise is pinned to probability 1 (via
45
+ :meth:`ProbConstraint.exact`) and the conclusion is classically entailed, both
46
+ bounds collapse to exactly ``(1, 1)`` — Nilsson's bounds subsume classical
47
+ entailment as the ``{0, 1}``-only corner of the polytope (cross-checked
48
+ against :func:`unicode_logic_kit.api.prove` in the test suite).
49
+
50
+ **Scope.** Propositional only, and DELIBERATELY so: a quantified formula
51
+ anywhere in a constraint or the conclusion raises ``ValueError`` rather than
52
+ being silently skolemised or ground over some implicit domain — the kit's
53
+ standing rule (see :mod:`unicode_logic_kit.semantics.truthtable`) is that a
54
+ formula outside a module's decidable fragment is refused loudly, never
55
+ approximated quietly. The world count is ``2^n`` in the number of distinct
56
+ atoms, so ``max_atoms`` (default 12, i.e. up to 4096 worlds) is a deliberate,
57
+ overridable brake on that exponential — raise it explicitly if a larger
58
+ problem is intended.
59
+
60
+ **A second, algorithm-only route.** :func:`entailment_bounds` takes a
61
+ ``strategy`` keyword: ``"direct"`` (above, the default, unchanged) or
62
+ ``"column_generation"`` (:mod:`unicode_logic_kit.prob._column_gen`), which
63
+ never materialises a ``2^n``-sized world array — it grows a small subset of
64
+ worlds on demand, using a Z3 Boolean-SAT search (over the ``n`` atoms
65
+ directly, never ``2^n`` enumerated) as the pricing subproblem that decides
66
+ which world to add next, so it can answer problems with far more than
67
+ ``max_atoms`` distinct atoms. Both strategies solve the EXACT SAME linear
68
+ program and are held to agreeing exactly (never a tolerance) wherever both
69
+ can answer — see :func:`entailment_bounds`'s own docstring and
70
+ :mod:`unicode_logic_kit.prob._column_gen`'s module docstring for the algorithm and its
71
+ termination/optimality proof.
72
+
73
+ Public API: :class:`ProbConstraint`, :class:`ProbBounds`,
74
+ :func:`entailment_bounds`.
75
+ """
76
+
77
+ from dataclasses import dataclass
78
+ from fractions import Fraction
79
+ from itertools import product
80
+ from typing import Literal, Optional, Sequence
81
+
82
+ import z3
83
+
84
+ from ..fol.nodes import Node, Atom, Not, And, Or, Xor, Implies, Iff
85
+ from ..fol._atom_keys import AtomKeys, atom_key
86
+ from ..fol._truth_constants import truth_value
87
+
88
+ __all__ = ["ProbConstraint", "ProbBounds", "entailment_bounds"]
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Exact-rational helpers
93
+ # ---------------------------------------------------------------------------
94
+
95
+ def _as_exact_prob(value, label: str) -> Fraction:
96
+ """Coerce ``value`` to an exact :class:`~fractions.Fraction` (range checked by the caller).
97
+
98
+ Accepts ``Fraction`` and ``int`` (both lossless); rejects ``float``
99
+ outright rather than silently rounding it into a Fraction — this module
100
+ performs EXACT probabilistic logic, and ``Fraction(0.7)`` is not ``7/10``
101
+ (it is the nearest IEEE-754 double, a long ugly binary fraction), so
102
+ accepting a float would quietly smuggle sampling-grade imprecision into a
103
+ supposedly exact bound.
104
+ """
105
+ if isinstance(value, bool) or not isinstance(value, (Fraction, int)):
106
+ raise TypeError(
107
+ f"ProbConstraint: {label} must be an exact Fraction (or int, coerced "
108
+ f"losslessly) — got {type(value).__name__} {value!r}. Pass "
109
+ f"Fraction({label}) explicitly (e.g. Fraction(7, 10)); a float is refused "
110
+ "because this module never approximates a probability."
111
+ )
112
+ return Fraction(value)
113
+
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Constraints and bounds
117
+ # ---------------------------------------------------------------------------
118
+
119
+ @dataclass(frozen=True)
120
+ class ProbConstraint:
121
+ """One Nilsson probability constraint: ``lower ≤ P(formula [| given]) ≤ upper``.
122
+
123
+ ``given=None`` (the default) is an UNconditional constraint on
124
+ ``P(formula)``; a non-``None`` ``given`` makes it the CONDITIONAL
125
+ ``P(formula | given)`` (see the module docstring for how each is encoded
126
+ as a linear constraint, and the ``P(given) = 0`` vacuity case). ``lower``
127
+ and ``upper`` are coerced to exact ``Fraction`` (int accepted, float
128
+ rejected — see :func:`_as_exact_prob`) and validated as
129
+ ``0 ≤ lower ≤ upper ≤ 1``.
130
+ """
131
+
132
+ formula: Node
133
+ lower: Fraction
134
+ upper: Fraction
135
+ given: Optional[Node] = None
136
+
137
+ def __post_init__(self):
138
+ lower = _as_exact_prob(self.lower, "lower")
139
+ upper = _as_exact_prob(self.upper, "upper")
140
+ object.__setattr__(self, "lower", lower)
141
+ object.__setattr__(self, "upper", upper)
142
+ if not (Fraction(0) <= lower <= upper <= Fraction(1)):
143
+ raise ValueError(
144
+ "ProbConstraint: bounds must satisfy 0 <= lower <= upper <= 1; "
145
+ f"got lower={lower}, upper={upper}."
146
+ )
147
+
148
+ @classmethod
149
+ def exact(cls, formula: Node, value, given: Optional[Node] = None) -> "ProbConstraint":
150
+ """Convenience constructor pinning ``P(formula [| given])`` to a single exact value."""
151
+ p = _as_exact_prob(value, "value")
152
+ return cls(formula, p, p, given)
153
+
154
+ def _describe(self) -> str:
155
+ """Render as ``P(φ) ∈ [lo, hi]`` / ``P(φ | ψ) ∈ [lo, hi]`` for error messages."""
156
+ target = self.formula.to_unicode_str()
157
+ if self.given is not None:
158
+ target = f"{target} | {self.given.to_unicode_str()}"
159
+ return f"P({target}) ∈ [{self.lower}, {self.upper}]"
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class ProbBounds:
164
+ """The tightest ``[lower, upper]`` probability bounds a constraint set entails.
165
+
166
+ ``n_worlds`` is ``2**n`` for the ``n`` distinct atoms collected from the
167
+ constraints and the conclusion — the size of the world enumeration the
168
+ bounds were computed over.
169
+ """
170
+
171
+ lower: Fraction
172
+ upper: Fraction
173
+ n_worlds: int
174
+
175
+ def to_dict(self) -> dict:
176
+ """Serialise to a JSON-compatible dict; bounds as exact ``"num/den"`` strings."""
177
+ return {"lower": str(self.lower), "upper": str(self.upper), "n_worlds": self.n_worlds}
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Own propositional evaluator / atom collector (classical connectives only).
182
+ # ---------------------------------------------------------------------------
183
+
184
+ _UNSUPPORTED = (
185
+ "nilsson: unsupported node type {cls} in a probability formula. Only the "
186
+ "classical propositional connectives (¬ ∧ ∨ → ↔ ⊕) over Atom leaves are "
187
+ "supported — in particular a QUANTIFIED formula has no finite space of "
188
+ "possible worlds and is refused rather than silently approximated (e.g. by "
189
+ "skolemising or grounding over an implicit domain)."
190
+ )
191
+
192
+
193
+ def _collect_atoms(formula: Node, atoms: set, keys: Optional[AtomKeys] = None) -> None:
194
+ """Add the key of every distinct atom in ``formula`` to ``atoms``.
195
+
196
+ The two truth constants (``⊤`` / ``$true``, ``⊥`` / ``$false``) are not
197
+ atoms: a distribution gives the true constant probability 1 and the false
198
+ constant 0 in every world, so neither has a world bit of its own and
199
+ neither is added.
200
+
201
+ ``keys`` records the atom behind every key of one problem: two different atoms that print
202
+ alike (the numeral ``1`` and a constant named ``1``) would be one world bit and the
203
+ probabilities another problem, and a sorted constant has no probability of membership to
204
+ go with it, so both are refused with ``ValueError``.
205
+
206
+ Raises ``ValueError`` on anything other than Atom/¬/∧/∨/→/↔/⊕ — in
207
+ particular on any quantifier, loudly, per the module's scope contract.
208
+ """
209
+ if isinstance(formula, Atom):
210
+ if truth_value(formula) is None:
211
+ atoms.add(atom_key(formula) if keys is None else keys.key(formula))
212
+ return
213
+ if isinstance(formula, Not):
214
+ _collect_atoms(formula.formula, atoms, keys)
215
+ return
216
+ if isinstance(formula, (And, Or, Xor, Implies, Iff)):
217
+ _collect_atoms(formula.left, atoms, keys)
218
+ _collect_atoms(formula.right, atoms, keys)
219
+ return
220
+ raise ValueError(_UNSUPPORTED.format(cls=type(formula).__name__))
221
+
222
+
223
+ def _eval(formula: Node, valuation: dict) -> bool:
224
+ """Evaluate ``formula`` (¬ ∧ ∨ → ↔ ⊕ over Atom leaves) under ``valuation``.
225
+
226
+ ``valuation`` maps each atom's key to a bool; a truth
227
+ constant is read as the truth value it is and is never looked up. Raises
228
+ ``ValueError`` on an unsupported node type (defensive: :func:`_collect_atoms`
229
+ already rejects these upstream of every call site in this module).
230
+ """
231
+ if isinstance(formula, Atom):
232
+ constant = truth_value(formula)
233
+ if constant is not None:
234
+ return constant
235
+ return valuation[atom_key(formula)]
236
+ if isinstance(formula, Not):
237
+ return not _eval(formula.formula, valuation)
238
+ if isinstance(formula, And):
239
+ return _eval(formula.left, valuation) and _eval(formula.right, valuation)
240
+ if isinstance(formula, Or):
241
+ return _eval(formula.left, valuation) or _eval(formula.right, valuation)
242
+ if isinstance(formula, Xor):
243
+ return _eval(formula.left, valuation) != _eval(formula.right, valuation)
244
+ if isinstance(formula, Implies):
245
+ return (not _eval(formula.left, valuation)) or _eval(formula.right, valuation)
246
+ if isinstance(formula, Iff):
247
+ return _eval(formula.left, valuation) == _eval(formula.right, valuation)
248
+ raise ValueError(_UNSUPPORTED.format(cls=type(formula).__name__))
249
+
250
+
251
+ def _to_z3_bool(formula: Node, atom_vars: dict):
252
+ """Translate ``formula`` (¬ ∧ ∨ → ↔ ⊕ over Atom leaves) into a Z3 Bool expression.
253
+
254
+ The exact structural mirror of :func:`_eval`, one recursive case for
255
+ one recursive case, with ``atom_vars`` (mapping each atom's
256
+ key to its own :func:`z3.Bool` variable) standing in
257
+ for the Python ``valuation`` dict — so a world is no longer a fixed
258
+ Python bool assignment to evaluate against, but a Z3 model Z3 itself
259
+ gets to choose, which is exactly what :mod:`unicode_logic_kit.prob._column_gen`'s pricing
260
+ subproblem needs (searching over all ``2^n`` worlds via SAT rather than
261
+ enumerating them). Used only by ``strategy="column_generation"``; the
262
+ default ``strategy="direct"`` path never calls this (it evaluates
263
+ formulas against concrete Python valuations via :func:`_eval` alone).
264
+ Raises the same ``ValueError`` as :func:`_eval` on an unsupported node
265
+ (defensive: :func:`_collect_atoms` already rejects these upstream). A
266
+ truth constant is the Boolean constant it is, not a variable of ``atom_vars``.
267
+ """
268
+ if isinstance(formula, Atom):
269
+ constant = truth_value(formula)
270
+ if constant is not None:
271
+ return z3.BoolVal(constant)
272
+ return atom_vars[atom_key(formula)]
273
+ if isinstance(formula, Not):
274
+ return z3.Not(_to_z3_bool(formula.formula, atom_vars))
275
+ if isinstance(formula, And):
276
+ return z3.And(_to_z3_bool(formula.left, atom_vars), _to_z3_bool(formula.right, atom_vars))
277
+ if isinstance(formula, Or):
278
+ return z3.Or(_to_z3_bool(formula.left, atom_vars), _to_z3_bool(formula.right, atom_vars))
279
+ if isinstance(formula, Xor):
280
+ return z3.Xor(_to_z3_bool(formula.left, atom_vars), _to_z3_bool(formula.right, atom_vars))
281
+ if isinstance(formula, Implies):
282
+ return z3.Implies(_to_z3_bool(formula.left, atom_vars), _to_z3_bool(formula.right, atom_vars))
283
+ if isinstance(formula, Iff):
284
+ return _to_z3_bool(formula.left, atom_vars) == _to_z3_bool(formula.right, atom_vars)
285
+ raise ValueError(_UNSUPPORTED.format(cls=type(formula).__name__))
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # The linear program
290
+ # ---------------------------------------------------------------------------
291
+
292
+ def _z3_to_fraction(value) -> Fraction:
293
+ """Read a Z3 numeral back as an exact Python Fraction.
294
+
295
+ A Real-valued optimum that happens to be a whole number comes back from
296
+ Z3 as an ``IntNumRef`` (no ``as_fraction()``) rather than a ``RatNumRef``
297
+ — both are exact, so this dispatches on whichever accessor is present
298
+ instead of assuming one numeral type.
299
+ """
300
+ if hasattr(value, "as_fraction"):
301
+ return value.as_fraction()
302
+ return Fraction(value.as_long())
303
+
304
+
305
+ def _infeasible_error(constraints: Sequence["ProbConstraint"]) -> ValueError:
306
+ """The ``ValueError`` raised when no distribution satisfies ``constraints`` at once.
307
+
308
+ Shared by both strategies (see :mod:`unicode_logic_kit.prob._column_gen`'s phase-1 termination)
309
+ so an inconsistent constraint set is refused with the identical message
310
+ regardless of which algorithm found the inconsistency.
311
+ """
312
+ detail = "; ".join(c._describe() for c in constraints)
313
+ return ValueError(
314
+ "entailment_bounds: probabilistically inconsistent — no probability "
315
+ f"distribution over the possible worlds satisfies all of: {detail}"
316
+ )
317
+
318
+
319
+ def _solve(base_constraints, objective, minimize: bool, constraints: Sequence[ProbConstraint]) -> Fraction:
320
+ """Minimize or maximize ``objective`` subject to ``base_constraints`` (a fresh Optimize)."""
321
+ opt = z3.Optimize()
322
+ for c in base_constraints:
323
+ opt.add(c)
324
+ handle = opt.minimize(objective) if minimize else opt.maximize(objective)
325
+ if opt.check() != z3.sat:
326
+ raise _infeasible_error(constraints)
327
+ value = opt.lower(handle) if minimize else opt.upper(handle)
328
+ return _z3_to_fraction(value)
329
+
330
+
331
+ def entailment_bounds(constraints: Sequence[ProbConstraint], conclusion: Node,
332
+ *, max_atoms: int = 12,
333
+ strategy: Literal["direct", "column_generation"] = "direct",
334
+ max_columns: int = 500) -> ProbBounds:
335
+ """Return the tightest ``[lower, upper]`` bounds :func:`ProbConstraint`\\ s entail for ``conclusion``.
336
+
337
+ ``strategy`` selects the solving ALGORITHM, never the semantics — both
338
+ solve the exact same LP (one nonnegative real per possible world, summing
339
+ to 1, subject to the constraints; see the module docstring) and are held
340
+ to agreeing EXACTLY (``Fraction`` equality, never a tolerance) on every
341
+ problem either can answer, which is what this module's own differential
342
+ test suite (``tests/test_nilsson_colgen.py``) checks directly.
343
+
344
+ ``"direct"`` (the default, UNCHANGED from before ``strategy`` existed)
345
+ builds one nonnegative real variable per possible world (``2^n`` for the
346
+ ``n`` distinct atoms across every constraint and ``conclusion``), then
347
+ minimizes and maximizes ``P(conclusion)`` over that polytope with two
348
+ separate :class:`z3.Optimize` calls. Every probability in the result is
349
+ an EXACT :class:`~fractions.Fraction` read back from Z3's rational model
350
+ — never routed through ``float``. ``max_atoms`` (12 by default) is an
351
+ explicit, overridable brake on the ``2^n`` blow-up.
352
+
353
+ ``"column_generation"`` (:mod:`unicode_logic_kit.prob._column_gen`) never
354
+ materialises a world: it grows a small ``columns`` subset of worlds on
355
+ demand, pricing a candidate world in via one small exact LP plus one
356
+ satisfiability search over the ``n`` atoms per pricing step, which
357
+ enumerates no worlds, so it can answer problems with far more than
358
+ ``max_atoms`` distinct atoms (``max_atoms`` is not enforced under this
359
+ strategy; the only brake is ``max_columns``, 500 by default, raising
360
+ ``ValueError`` rather than ever returning an unproven bound). See
361
+ :func:`unicode_logic_kit.prob._column_gen.solve` for the two-phase
362
+ primal/dual column-generation algorithm and its termination/optimality
363
+ proof.
364
+
365
+ Raises:
366
+ ValueError: if ``conclusion`` or any constraint's ``formula``/``given``
367
+ is quantified or otherwise outside ¬/∧/∨/→/↔/⊕-over-Atom; under
368
+ ``strategy="direct"``, if the distinct-atom count exceeds
369
+ ``max_atoms`` (the world count is ``2^n`` — an explicit,
370
+ overridable brake on that exponential); under
371
+ ``strategy="column_generation"``, if column generation does not
372
+ certify optimality within ``max_columns`` generated columns, or
373
+ cannot certify its answer at all (a pricing step whose result is
374
+ neither an exact evaluation nor a refutation: no bound is returned
375
+ that was not certified); an
376
+ unrecognised ``strategy``; or (either strategy, identical message)
377
+ if the constraint set is probabilistically inconsistent (no
378
+ distribution satisfies every constraint at once); or if two
379
+ different atoms of the problem print alike (the numeral ``1`` and a
380
+ constant named ``1``, which would be ONE world bit and so another
381
+ problem), or an atom holds a sorted constant.
382
+ """
383
+ if strategy not in ("direct", "column_generation"):
384
+ raise ValueError(
385
+ f"entailment_bounds: unknown strategy {strategy!r}; expected "
386
+ "'direct' or 'column_generation'."
387
+ )
388
+
389
+ atoms: set = set()
390
+ atom_keys = AtomKeys("entailment_bounds", "refuse", ValueError)
391
+ for c in constraints:
392
+ _collect_atoms(c.formula, atoms, atom_keys)
393
+ if c.given is not None:
394
+ _collect_atoms(c.given, atoms, atom_keys)
395
+ _collect_atoms(conclusion, atoms, atom_keys)
396
+
397
+ atom_list = sorted(atoms)
398
+ n = len(atom_list)
399
+
400
+ if strategy == "column_generation":
401
+ return _entailment_bounds_column_generation(constraints, conclusion, atom_list, max_columns)
402
+
403
+ if n > max_atoms:
404
+ raise ValueError(
405
+ f"entailment_bounds: {n} distinct atoms exceeds max_atoms={max_atoms}. "
406
+ f"The world enumeration is O(2^n) — {n} atoms means 2**{n} = {2 ** n} "
407
+ "probability variables. Reduce the formulas, or pass a larger max_atoms "
408
+ "explicitly if that blow-up is intended."
409
+ )
410
+
411
+ worlds = list(product((False, True), repeat=n))
412
+ valuations = [dict(zip(atom_list, w)) for w in worlds]
413
+
414
+ p = [z3.Real(f"nilsson!p_{i}") for i in range(len(worlds))]
415
+ base = [pi >= z3.RealVal(0) for pi in p]
416
+ base.append(z3.Sum(p) == z3.RealVal(1))
417
+
418
+ def indicator_sum(formula: Node):
419
+ idxs = [i for i, val in enumerate(valuations) if _eval(formula, val)]
420
+ if not idxs:
421
+ return z3.RealVal(0)
422
+ return z3.Sum([p[i] for i in idxs])
423
+
424
+ for c in constraints:
425
+ if c.given is None:
426
+ p_phi = indicator_sum(c.formula)
427
+ base.append(p_phi >= z3.RealVal(c.lower))
428
+ base.append(p_phi <= z3.RealVal(c.upper))
429
+ else:
430
+ idxs_given = [i for i, val in enumerate(valuations) if _eval(c.given, val)]
431
+ idxs_both = [i for i in idxs_given if _eval(c.formula, valuations[i])]
432
+ p_given = z3.Sum([p[i] for i in idxs_given]) if idxs_given else z3.RealVal(0)
433
+ p_both = z3.Sum([p[i] for i in idxs_both]) if idxs_both else z3.RealVal(0)
434
+ base.append(p_both >= z3.RealVal(c.lower) * p_given)
435
+ base.append(p_both <= z3.RealVal(c.upper) * p_given)
436
+
437
+ p_concl = indicator_sum(conclusion)
438
+
439
+ lower = _solve(base, p_concl, True, constraints)
440
+ upper = _solve(base, p_concl, False, constraints)
441
+ return ProbBounds(lower, upper, len(worlds))
442
+
443
+
444
+ # ---------------------------------------------------------------------------
445
+ # strategy="column_generation" dispatch
446
+ # ---------------------------------------------------------------------------
447
+
448
+ def _entailment_bounds_column_generation(constraints: Sequence[ProbConstraint], conclusion: Node,
449
+ atom_list, max_columns: int) -> ProbBounds:
450
+ """``entailment_bounds(..., strategy="column_generation")``'s dispatch target.
451
+
452
+ A lazy, function-local import of :mod:`unicode_logic_kit.prob._column_gen` (not a module-level
453
+ one): that module imports several names FROM this one (:class:`ProbConstraint`,
454
+ :func:`_eval`, :func:`_to_z3_bool`, :func:`_z3_to_fraction`,
455
+ :func:`_infeasible_error`) to build its rows and pricing subproblem, so a
456
+ module-level import here in the other direction would be circular — the
457
+ same lazy-import shape :func:`~unicode_logic_kit.atp.z3_input.to_smtlib`
458
+ already uses for an analogous reason (see its own module docstring).
459
+
460
+ ``n_worlds`` on the returned :class:`ProbBounds` is still ``2**n`` — the
461
+ SAME nominal meaning it has under ``strategy="direct"`` (the polytope's
462
+ world count), even though column generation never materialises anywhere
463
+ near that many worlds — so a :class:`ProbBounds` from either strategy
464
+ means the same thing and the differential tests' ``.lower``/``.upper``
465
+ equality checks compare like with like.
466
+ """
467
+ from . import _column_gen
468
+
469
+ result = _column_gen.solve(atom_list, constraints, conclusion, max_columns)
470
+ return ProbBounds(result.lower, result.upper, 2 ** len(atom_list))
File without changes
@@ -0,0 +1,137 @@
1
+ """Semantic evaluation of unicode-logic-kit formulas.
2
+
3
+ Two complementary evaluators:
4
+
5
+ - Classical **Tarskian** model theory for FOL/MSFOL: define a :class:`Structure`
6
+ (a "world" with a domain of individuals and interpretations of the symbols)
7
+ and compute a formula's two-valued truth value with :func:`satisfies`.
8
+ - **Second-order** finite-model semantics :func:`satisfies_so` (and :func:`holds`
9
+ for sentences): the same Tarskian satisfaction extended with ∀P / ∃P over
10
+ predicate variables, by brute-force enumeration of relations on a small finite
11
+ domain.
12
+ - **Third-order** finite-model semantics :func:`satisfies_to` (and
13
+ :func:`holds_to`): the level where an ARGUMENT can be a property —
14
+ ``Positive(G)``, ``Positive(λx. ¬G(x))`` — and a quantifier can range over
15
+ predicates OF properties. It enumerates over each bound symbol's argument
16
+ SIGNATURE rather than its arity, since at this level arity no longer says what
17
+ a predicate is.
18
+ - The **Łukasiewicz fuzzy** evaluator :func:`evaluate`, which computes the truth
19
+ degree in [0, 1] of an FL/MSFL formula under a valuation.
20
+ - The **graded (fuzzy) Kripke** evaluator :func:`satisfies_fuzzy_modal`, which
21
+ computes the truth degree in [0, 1] of a propositional modal formula at a
22
+ world of a :class:`FuzzyKripkeModel` (weighted accessibility relations,
23
+ graded valuations) under a chosen t-norm.
24
+ """
25
+
26
+ from .tarski import (
27
+ Structure, term_value, satisfies, models,
28
+ IllegalStructureError, check_structure, structure_violations,
29
+ )
30
+ from .secondorder import (
31
+ satisfies_so, holds,
32
+ so_find_model, so_find_countermodel, so_is_satisfiable_finite, so_is_valid_finite, CandidateBoundExceeded,
33
+ )
34
+ from .thirdorder import (
35
+ satisfies_to, holds_to, slot_values, all_interpretations, interpretation_count,
36
+ )
37
+ from .fuzzy import evaluate, ground_quantifiers
38
+ from .tnorm import TNorm, get_tnorm, TNORMS, LUKASIEWICZ, GODEL, PRODUCT
39
+ from .kripke import (
40
+ KripkeModel, satisfies_modal, models_at, reflexive_transitive_closure,
41
+ ctl_ex, ctl_af, ctl_eg, ctl_au,
42
+ )
43
+ from .fuzzy_kripke import FuzzyKripkeModel, satisfies_fuzzy_modal
44
+ from .manyvalued import (
45
+ kleene_value, is_valid, is_satisfiable, entails, DESIGNATED,
46
+ )
47
+ from .truthtable import (
48
+ truth_table, TruthTable, is_tautology, is_contradiction, is_satisfiable_tt,
49
+ )
50
+ from .modelfinder import (
51
+ find_model, find_countermodel, is_satisfiable_finite, is_valid_finite,
52
+ is_size_exhaustive,
53
+ )
54
+ from .intuitionistic import IntKripkeModel, int_valid, int_countermodel
55
+ from .matrix import (
56
+ TruthMatrix, matrix_value, matrix_is_valid, matrix_is_satisfiable, matrix_entails,
57
+ K3_MATRIX, LP_MATRIX, FDE_MATRIX, MATRICES,
58
+ )
59
+ from .free_logic import (
60
+ FreeModel, free_satisfies, free_holds, NONDENOTING,
61
+ free_find_model, free_countermodel, free_is_valid, free_entails,
62
+ )
63
+ from .dynamic_epistemic import announce, box_announce, diamond_announce
64
+ from .action_models import (
65
+ ActionModel, product_update, public_announcement_action,
66
+ common_knowledge_holds, everybody_knows, distributed_knowledge_holds,
67
+ )
68
+ from .conditional import (
69
+ CounterfactualModel, cf_satisfies, cf_countermodel, cf_valid, would, might,
70
+ CENTERING_LEVELS, DEFAULT_MAX_WORLDS,
71
+ )
72
+ from .nonmonotonic import (
73
+ minimal_models, minimal_entails,
74
+ circumscription_formula, circumscription_entails_so,
75
+ )
76
+ from .relevant import RelevantModel, rel_satisfies, rel_countermodel, rel_valid
77
+ from .team import team_satisfies, team_models, MAX_TEAM_SEARCH
78
+ from .team_translation import dependence_to_eso, dependence_holds_eso
79
+ from .structures import FiniteStructure, structure_from_dict, graph_to_structure
80
+ # The structure evaluator's own `evaluate` is re-exported under the explicit
81
+ # name `evaluate_in_structure`: this package's bare `evaluate` has meant the
82
+ # Łukasiewicz fuzzy evaluator since long before, and silently rebinding an
83
+ # established public name would break callers in the quietest possible way.
84
+ # `evaluate_detailed` carries no such history and keeps its name.
85
+ from .model_eval import (
86
+ evaluate as evaluate_in_structure,
87
+ evaluate_detailed,
88
+ EvalResult, BudgetExhausted, UninterpretedSymbol, UnsupportedNode,
89
+ )
90
+
91
+ __all__ = [
92
+ "Structure", "term_value", "satisfies", "models",
93
+ # Is a given structure a structure of the many-sorted definition?
94
+ "IllegalStructureError", "check_structure", "structure_violations",
95
+ # Finite structures + the direct structure evaluator (model CHECKING:
96
+ # is this sentence true in THIS given interpretation?).
97
+ "FiniteStructure", "structure_from_dict", "graph_to_structure",
98
+ "evaluate_in_structure", "evaluate_detailed", "EvalResult",
99
+ "BudgetExhausted", "UninterpretedSymbol", "UnsupportedNode",
100
+ # Second-order finite-model semantics (∀P / ∃P over predicate variables).
101
+ "satisfies_so", "holds",
102
+ "satisfies_to", "holds_to",
103
+ "slot_values", "all_interpretations", "interpretation_count",
104
+ "so_find_model", "so_find_countermodel",
105
+ "so_is_satisfiable_finite", "so_is_valid_finite", "CandidateBoundExceeded",
106
+ "evaluate", "ground_quantifiers",
107
+ "TNorm", "get_tnorm", "TNORMS", "LUKASIEWICZ", "GODEL", "PRODUCT",
108
+ "KripkeModel", "satisfies_modal", "models_at", "reflexive_transitive_closure",
109
+ "ctl_ex", "ctl_af", "ctl_eg", "ctl_au",
110
+ "FuzzyKripkeModel", "satisfies_fuzzy_modal",
111
+ # Many-valued (Kleene K3 / Priest LP). Note: is_valid/is_satisfiable/entails here are
112
+ # the three-valued versions — distinct from the Z3-based ones at the package top level.
113
+ "kleene_value", "is_valid", "is_satisfiable", "entails", "DESIGNATED",
114
+ # Truth tables (classical / K3 / LP).
115
+ "truth_table", "TruthTable", "is_tautology", "is_contradiction", "is_satisfiable_tt",
116
+ # Finite model finder + countermodels.
117
+ "find_model", "find_countermodel", "is_satisfiable_finite", "is_valid_finite",
118
+ "is_size_exhaustive",
119
+ # Intuitionistic propositional logic (Kripke semantics).
120
+ "IntKripkeModel", "int_valid", "int_countermodel",
121
+ # Finite-valued logical matrices (K3 / LP re-expressed; Belnap–Dunn FDE).
122
+ "TruthMatrix", "matrix_value", "matrix_is_valid", "matrix_is_satisfiable",
123
+ "matrix_entails", "K3_MATRIX", "LP_MATRIX", "FDE_MATRIX", "MATRICES",
124
+ # Free logic, dynamic epistemic (PAL), counterfactuals, circumscription.
125
+ "FreeModel", "free_satisfies", "free_holds", "NONDENOTING",
126
+ "free_find_model", "free_countermodel", "free_is_valid", "free_entails",
127
+ "announce", "box_announce", "diamond_announce",
128
+ "ActionModel", "product_update", "public_announcement_action",
129
+ "common_knowledge_holds", "everybody_knows", "distributed_knowledge_holds",
130
+ "CounterfactualModel", "cf_satisfies", "cf_countermodel", "cf_valid",
131
+ "would", "might", "CENTERING_LEVELS", "DEFAULT_MAX_WORLDS",
132
+ "minimal_models", "minimal_entails",
133
+ "circumscription_formula", "circumscription_entails_so",
134
+ "RelevantModel", "rel_satisfies", "rel_countermodel", "rel_valid",
135
+ "team_satisfies", "team_models", "MAX_TEAM_SEARCH",
136
+ "dependence_to_eso", "dependence_holds_eso",
137
+ ]