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,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
|
+
]
|