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,442 @@
|
|
|
1
|
+
"""Graded (fuzzy) Kripke semantics: a truth-DEGREE evaluator for propositional
|
|
2
|
+
modal logic over a t-norm, generalising :mod:`kripke`'s two-valued
|
|
3
|
+
satisfaction relation the way :mod:`fuzzy` generalises classical FOL.
|
|
4
|
+
|
|
5
|
+
A :class:`FuzzyKripkeModel` is the graded analogue of
|
|
6
|
+
:class:`~unicode_logic_kit.semantics.kripke.KripkeModel`: instead of a crisp
|
|
7
|
+
edge SET, each named accessibility relation is a ``Dict[(w, w'), float]`` of
|
|
8
|
+
edge WEIGHTS in ``[0, 1]`` (a missing pair reads as ``0.0``); instead of a
|
|
9
|
+
crisp atom-key SET, each world's valuation is a ``Dict[str, float]`` of
|
|
10
|
+
ground-atom DEGREES in ``[0, 1]`` (a missing key reads as ``0.0``). This is
|
|
11
|
+
exactly the "fuzzify every crisp component of the frame" move of Fitting's
|
|
12
|
+
many-valued modal logics (M. Fitting, "Many-valued modal logics",
|
|
13
|
+
*Fundamenta Informaticae* 15(3-4), 1991, 235-254, and its sequel "Many-valued
|
|
14
|
+
modal logics II", *Fundamenta Informaticae* 17(1-2), 1992, 55-73) and of the
|
|
15
|
+
residuated Kripke frames studied by Hájek (P. Hájek, *Metamathematics of
|
|
16
|
+
Fuzzy Logic*, Kluwer, 1998, ch. 8) and, for the specific residuated □ /
|
|
17
|
+
t-norm ◇ pair implemented here, by Bou, Esteva, Godo & Rodríguez ("On the
|
|
18
|
+
minimum many-valued modal logic over a finite residuated lattice", *Journal
|
|
19
|
+
of Logic and Computation* 21(5), 2011, 739-790).
|
|
20
|
+
|
|
21
|
+
:func:`satisfies_fuzzy_modal` returns a truth DEGREE in ``[0, 1]`` (mirroring
|
|
22
|
+
:func:`~unicode_logic_kit.semantics.fuzzy.evaluate`'s float-returning shape,
|
|
23
|
+
not :func:`~unicode_logic_kit.semantics.kripke.satisfies_modal`'s bool one):
|
|
24
|
+
|
|
25
|
+
- an ``Atom`` looks up its degree in the current world's valuation;
|
|
26
|
+
- the Łukasiewicz connectives (``LukNegation`` / ``WeakConjunction`` /
|
|
27
|
+
``WeakDisjunction`` / ``StrongConjunction`` / ``StrongDisjunction`` /
|
|
28
|
+
``LukImplication`` / ``LukEquivalence``) dispatch straight to
|
|
29
|
+
``model.tnorm``'s ``neg`` / weak min-max / ``conj`` / ``disj`` / ``impl`` /
|
|
30
|
+
``equiv`` — verbatim reuse of :mod:`tnorm`, no new connective semantics;
|
|
31
|
+
- ``Box φ`` is the RESIDUATED universal reading
|
|
32
|
+
``inf_{w'} tnorm.impl(R(w, w'), deg(φ, w'))`` — "every accessible world
|
|
33
|
+
forces φ AT LEAST as much as it is accessible" — and ``Diamond φ`` is the
|
|
34
|
+
T-NORM existential reading ``sup_{w'} tnorm.conj(R(w, w'), deg(φ, w'))`` —
|
|
35
|
+
"some accessible world witnesses φ, discounted by how accessible it is".
|
|
36
|
+
The aggregation domain at ``world`` is ``model.worlds`` UNIONED with every
|
|
37
|
+
actual edge target ``w'`` for which ``(world, w') `` has a positive weight
|
|
38
|
+
in the named relation — this matches
|
|
39
|
+
:meth:`~unicode_logic_kit.semantics.kripke.KripkeModel.successors` exactly,
|
|
40
|
+
which is never filtered by ``model.worlds`` either (a relation may name an
|
|
41
|
+
edge to a world outside the declared ``worlds=`` set, and that edge must
|
|
42
|
+
still be counted, not silently dropped). Because ``tnorm.impl(0, y) = 1``
|
|
43
|
+
and ``tnorm.conj(0, y) = 0`` for all three t-norms in :mod:`tnorm` (0 is
|
|
44
|
+
the residuum's right-identity and the t-norm's absorbing element), the
|
|
45
|
+
``model.worlds`` members that carry no edge to them contribute only the
|
|
46
|
+
aggregate's neutral element, so folding them into the domain alongside the
|
|
47
|
+
true successors is always safe. An empty aggregation domain (no worlds
|
|
48
|
+
declared AND no successor edge from ``world`` in this relation) is the one
|
|
49
|
+
place the aggregate set is genuinely empty; it is handled by the same
|
|
50
|
+
convention :mod:`~unicode_logic_kit.semantics.kripke` uses for a vacuous
|
|
51
|
+
crisp Box/Diamond: infimum over the empty set is ``1.0``, supremum is
|
|
52
|
+
``0.0``.
|
|
53
|
+
``Knows`` / ``Believes`` / ``Says`` / ``Wants`` get the same residuated
|
|
54
|
+
UNIVERSAL reading as ``Box`` (matching
|
|
55
|
+
:func:`~unicode_logic_kit.semantics.kripke.satisfies_modal`, where all four
|
|
56
|
+
are ``all(...)``-quantified exactly like Box) over their own
|
|
57
|
+
``"K:"``/``"B:"``/``"Say:"``/``"Want:"``-prefixed relation, for free, via
|
|
58
|
+
the same relation-name mechanism :mod:`kripke` documents.
|
|
59
|
+
|
|
60
|
+
**v1 scope** (deliberately narrow, matching
|
|
61
|
+
:mod:`~unicode_logic_kit.semantics.kripke`'s own "propositional / ground, v1"
|
|
62
|
+
discipline): exactly graded ``Box``/``Diamond``/``Knows``/``Believes``/
|
|
63
|
+
``Says``/``Wants``, single-step aggregation, no fixpoints, plus the
|
|
64
|
+
Łukasiewicz connectives. Every other node kind :mod:`kripke` interprets is
|
|
65
|
+
REJECTED here by name by design, not silently approximated:
|
|
66
|
+
|
|
67
|
+
- classical crisp connectives (``Not``/``And``/``Or``/``Implies``/``Iff``/
|
|
68
|
+
``Xor``) — a formula meant for graded evaluation must be built with fuzzy
|
|
69
|
+
connective nodes, the same discipline :mod:`fuzzy` already enforces for
|
|
70
|
+
non-modal formulas;
|
|
71
|
+
- ``Quantifier``/``SortedQuantifier`` and the lambda-calculus nodes, rejected
|
|
72
|
+
via :mod:`_modal_reject`'s shared ``reject_quantifier``/``reject_lambda``
|
|
73
|
+
(unchanged, so the message wording matches :mod:`kripke`'s own);
|
|
74
|
+
- temporal closure (``Next``/``Always``/``Eventually``/``Until``/
|
|
75
|
+
``Previous``/``Historically``/``Once``/``Since``), hybrid logic
|
|
76
|
+
(``Nominal``/``At``), public announcement logic (``Announce``/
|
|
77
|
+
``AnnounceDiamond``), group-epistemic operators (``EverybodyKnows``/
|
|
78
|
+
``DistributedKnowledge``/``CommonKnowledge``), and Standard-Deontic-style
|
|
79
|
+
serial-frame reasoning (``Obligatory``/``Permitted``) — a graded fixpoint,
|
|
80
|
+
model update, or group-relation combinator over a CONTINUOUS t-norm is an
|
|
81
|
+
open research question in its own right (what does "the least fixpoint of
|
|
82
|
+
a residuated implication" even mean over ``[0, 1]``? how does public
|
|
83
|
+
announcement restrict a model whose edges are weights, not a crisp
|
|
84
|
+
subset?) and must not be silently approximated by, say, reusing the crisp
|
|
85
|
+
fixpoint machinery over a 0/1-thresholded relation.
|
|
86
|
+
|
|
87
|
+
No parser change ships with this module: formulas are built via the Node API
|
|
88
|
+
directly (``Box(WeakConjunction(Atom("P", []), Atom("Q", [])))``), exactly
|
|
89
|
+
the precedent :mod:`~unicode_logic_kit.semantics.kripke` and
|
|
90
|
+
``docs/guide/quantified-modal.md`` already set for modal ASTs — "no parser
|
|
91
|
+
is involved" is a documented v1 limitation here too, not an oversight
|
|
92
|
+
(``MSFLParser`` still refuses ``modal=True, fuzzy=True`` together).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
from typing import Any, Dict, FrozenSet, Iterable, Mapping, NoReturn, Optional, Tuple
|
|
96
|
+
from types import MappingProxyType
|
|
97
|
+
|
|
98
|
+
from ..fol.nodes import (
|
|
99
|
+
Node,
|
|
100
|
+
Atom, Not, And, Or, Xor, Implies, Iff,
|
|
101
|
+
Quantifier, SortedQuantifier,
|
|
102
|
+
Box, Diamond, Knows, Believes, Says, Wants,
|
|
103
|
+
Always, Eventually, Next, Until,
|
|
104
|
+
Historically, Once, Previous, Since,
|
|
105
|
+
Obligatory, Permitted,
|
|
106
|
+
Nominal, At,
|
|
107
|
+
WeakConjunction, WeakDisjunction,
|
|
108
|
+
StrongConjunction, StrongDisjunction,
|
|
109
|
+
LukNegation, LukImplication, LukEquivalence,
|
|
110
|
+
LambdaVar, Lambda, Application,
|
|
111
|
+
)
|
|
112
|
+
from ..fol._modal_nodes import (
|
|
113
|
+
Announce, AnnounceDiamond,
|
|
114
|
+
EverybodyKnows, DistributedKnowledge, CommonKnowledge,
|
|
115
|
+
)
|
|
116
|
+
from ..fol._atom_keys import find_key
|
|
117
|
+
from ..fol._msfl_nodes import key_text
|
|
118
|
+
from ..fol._truth_constants import truth_value as _truth_value
|
|
119
|
+
from ._modal_reject import LAMBDA_TYPES, reject_quantifier, reject_lambda
|
|
120
|
+
from .tnorm import TNorm, LUKASIEWICZ
|
|
121
|
+
|
|
122
|
+
# Note on _modal_reject reuse: that module's FUZZY_TYPES / reject_fuzzy
|
|
123
|
+
# classify Łukasiewicz nodes as OUT OF SCOPE for the crisp Kripke evaluator —
|
|
124
|
+
# here they are exactly the nodes this module interprets, so only
|
|
125
|
+
# LAMBDA_TYPES / reject_lambda and reject_quantifier are reused from it.
|
|
126
|
+
|
|
127
|
+
# Relation-name prefixes, matching kripke.py's convention EXACTLY (the two
|
|
128
|
+
# modules must stay in sync on these strings so the same relations= dict
|
|
129
|
+
# shape reads the same way under both evaluators).
|
|
130
|
+
_ALETHIC = "alethic"
|
|
131
|
+
_KNOWS_PREFIX = "K:"
|
|
132
|
+
_BELIEVES_PREFIX = "B:"
|
|
133
|
+
_SAYS_PREFIX = "Say:"
|
|
134
|
+
_WANTS_PREFIX = "Want:"
|
|
135
|
+
|
|
136
|
+
World = Any
|
|
137
|
+
Edge = Tuple[World, World]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _agent_key(agent: Node) -> str:
|
|
141
|
+
"""Relation-key suffix for an epistemic/doxastic/assertive/bouletic agent term.
|
|
142
|
+
|
|
143
|
+
Mirrors :func:`unicode_logic_kit.semantics.kripke._agent_key` exactly (kept
|
|
144
|
+
as a small local copy rather than an import of that module's private
|
|
145
|
+
helper, so this module stays self-contained the way its docstring
|
|
146
|
+
promises), so a ``relations=`` dict built for one evaluator names its
|
|
147
|
+
agent keys identically for the other.
|
|
148
|
+
"""
|
|
149
|
+
return getattr(agent, "name", None) or key_text(agent)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _clamp(x: float) -> float:
|
|
153
|
+
"""Defensively clamp a degree into the closed interval [0, 1]."""
|
|
154
|
+
if x < 0.0:
|
|
155
|
+
return 0.0
|
|
156
|
+
if x > 1.0:
|
|
157
|
+
return 1.0
|
|
158
|
+
return x
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class FuzzyKripkeModel:
|
|
162
|
+
"""A graded Kripke model: worlds, named WEIGHTED accessibility relations, and
|
|
163
|
+
a graded valuation.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
worlds: an iterable of worlds (any hashable values). Stored as a
|
|
167
|
+
frozen set; duplicates collapse.
|
|
168
|
+
relations: maps a relation NAME (str) to a ``Dict[(w, w'), float]`` of
|
|
169
|
+
edge weights in ``[0, 1]`` — the graded generalisation of
|
|
170
|
+
:class:`~unicode_logic_kit.semantics.kripke.KripkeModel`'s crisp
|
|
171
|
+
edge SETS. Recognised names follow the same convention:
|
|
172
|
+
``"alethic"`` (Box/Diamond), ``"K:"+agent`` (Knows),
|
|
173
|
+
``"B:"+agent`` (Believes), ``"Say:"+agent`` (Says),
|
|
174
|
+
``"Want:"+agent`` (Wants). A missing name is the everywhere-zero
|
|
175
|
+
relation; within a named relation, a missing ``(w, w')`` pair
|
|
176
|
+
reads as weight ``0.0``. Each weight is clamped into ``[0, 1]``.
|
|
177
|
+
valuation: maps a world to a ``Dict[str, float]`` of GROUND-ATOM-KEY
|
|
178
|
+
(the text of the atom with every constant written by its name, ``P(a)``
|
|
179
|
+
for the atom ``P`` of the constant ``a``, and a sorted constant ``c:S``
|
|
180
|
+
read as the constant ``c``, the same convention as
|
|
181
|
+
:mod:`~unicode_logic_kit.semantics.fuzzy`'s valuation and
|
|
182
|
+
:class:`~unicode_logic_kit.semantics.kripke.KripkeModel`'s atom
|
|
183
|
+
keys; the text of the atom as a formula, ``P('a')``, is read as
|
|
184
|
+
the same key) to a degree in ``[0, 1]``. A missing world, or a
|
|
185
|
+
missing key within a world, reads as degree ``0.0``. A world that
|
|
186
|
+
holds both spellings of one atom with different degrees is refused
|
|
187
|
+
when the atom is read (``ValueError``). Each degree is
|
|
188
|
+
clamped into ``[0, 1]``.
|
|
189
|
+
tnorm: a :class:`~unicode_logic_kit.semantics.tnorm.TNorm` instance
|
|
190
|
+
(one of :data:`~unicode_logic_kit.semantics.tnorm.LUKASIEWICZ`,
|
|
191
|
+
:data:`~unicode_logic_kit.semantics.tnorm.GODEL`,
|
|
192
|
+
:data:`~unicode_logic_kit.semantics.tnorm.PRODUCT`, or a custom
|
|
193
|
+
one) fixing the strong connectives AND the Box/Diamond residuum —
|
|
194
|
+
reused UNMODIFIED from :mod:`tnorm`, never re-implemented here.
|
|
195
|
+
Defaults to :data:`~unicode_logic_kit.semantics.tnorm.LUKASIEWICZ`.
|
|
196
|
+
|
|
197
|
+
All mappings default to empty, so ``FuzzyKripkeModel({0, 1})`` is a valid
|
|
198
|
+
(atom-free, relation-free, everywhere-0.0) frame. The constructor copies
|
|
199
|
+
every container into an immutable ``MappingProxyType`` view, so later
|
|
200
|
+
edits to the caller's structures never leak in.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
worlds: Iterable[World],
|
|
206
|
+
relations: Optional[Mapping[str, Mapping[Edge, float]]] = None,
|
|
207
|
+
valuation: Optional[Mapping[World, Mapping[str, float]]] = None,
|
|
208
|
+
tnorm: TNorm = LUKASIEWICZ,
|
|
209
|
+
):
|
|
210
|
+
if not isinstance(tnorm, TNorm):
|
|
211
|
+
raise TypeError(
|
|
212
|
+
f"FuzzyKripkeModel: tnorm must be a TNorm instance (e.g. "
|
|
213
|
+
f"tnorm.LUKASIEWICZ / GODEL / PRODUCT), got {type(tnorm).__name__}."
|
|
214
|
+
)
|
|
215
|
+
self.worlds: FrozenSet[World] = frozenset(worlds)
|
|
216
|
+
self.relations: Dict[str, Mapping[Edge, float]] = {
|
|
217
|
+
name: MappingProxyType({edge: _clamp(float(w)) for edge, w in edges.items()})
|
|
218
|
+
for name, edges in (relations or {}).items()
|
|
219
|
+
}
|
|
220
|
+
self.valuation: Dict[World, Mapping[str, float]] = {
|
|
221
|
+
world: MappingProxyType({key: _clamp(float(d)) for key, d in degrees.items()})
|
|
222
|
+
for world, degrees in (valuation or {}).items()
|
|
223
|
+
}
|
|
224
|
+
self.tnorm: TNorm = tnorm
|
|
225
|
+
|
|
226
|
+
def __repr__(self) -> str:
|
|
227
|
+
"""Show world count and the relation / valuation tables for inspection."""
|
|
228
|
+
return (
|
|
229
|
+
f"FuzzyKripkeModel(worlds={set(self.worlds)!r}, "
|
|
230
|
+
f"relations={ {k: dict(v) for k, v in self.relations.items()} !r}, "
|
|
231
|
+
f"valuation={ {k: dict(v) for k, v in self.valuation.items()} !r}, "
|
|
232
|
+
f"tnorm={self.tnorm.name!r})"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def relation_weight(self, name: str, w1: World, w2: World) -> float:
|
|
236
|
+
"""Return the ``(w1, w2)`` edge weight of the named relation (0.0 if absent)."""
|
|
237
|
+
return self.relations.get(name, MappingProxyType({})).get((w1, w2), 0.0)
|
|
238
|
+
|
|
239
|
+
def atom_degree(self, world: World, key: str) -> float:
|
|
240
|
+
"""Return the degree of ground-atom key ``key`` at ``world`` (0.0 if absent)."""
|
|
241
|
+
return self.valuation.get(world, MappingProxyType({})).get(key, 0.0)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _aggregation_domain(rel_name: str, model: FuzzyKripkeModel, world: World) -> FrozenSet[World]:
|
|
245
|
+
"""The set of ``w'`` a Box/Diamond aggregate at ``world`` must range over.
|
|
246
|
+
|
|
247
|
+
``model.worlds`` UNIONED with every actual edge target of the named
|
|
248
|
+
relation from ``world`` (even one outside ``model.worlds``) — this
|
|
249
|
+
matches :meth:`~unicode_logic_kit.semantics.kripke.KripkeModel.successors`
|
|
250
|
+
exactly, which is never filtered by ``model.worlds`` either. A
|
|
251
|
+
``model.worlds`` member with no edge to it still appears here, but only
|
|
252
|
+
ever contributes the aggregate's neutral element (see the module
|
|
253
|
+
docstring), so including it is always safe.
|
|
254
|
+
"""
|
|
255
|
+
edges = model.relations.get(rel_name, MappingProxyType({}))
|
|
256
|
+
successors = {w2 for (w1, w2) in edges if w1 == world}
|
|
257
|
+
return model.worlds | successors
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _residuated_box(rel_name: str, formula: Node, model: FuzzyKripkeModel, world: World) -> float:
|
|
261
|
+
"""``inf_{w'} tnorm.impl(R(rel_name)(world, w'), deg(formula, w'))`` over the
|
|
262
|
+
aggregation domain (see :func:`_aggregation_domain`).
|
|
263
|
+
|
|
264
|
+
The shared aggregator behind ``Box``/``Knows``/``Believes``/``Says``/
|
|
265
|
+
``Wants``, all of which read universally in the crisp evaluator.
|
|
266
|
+
"""
|
|
267
|
+
domain = _aggregation_domain(rel_name, model, world)
|
|
268
|
+
if not domain:
|
|
269
|
+
return 1.0
|
|
270
|
+
t = model.tnorm
|
|
271
|
+
return min(
|
|
272
|
+
t.impl(model.relation_weight(rel_name, world, w2),
|
|
273
|
+
satisfies_fuzzy_modal(formula, model, w2))
|
|
274
|
+
for w2 in domain
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _tnorm_diamond(rel_name: str, formula: Node, model: FuzzyKripkeModel, world: World) -> float:
|
|
279
|
+
"""``sup_{w'} tnorm.conj(R(rel_name)(world, w'), deg(formula, w'))`` over the
|
|
280
|
+
aggregation domain (see :func:`_aggregation_domain`).
|
|
281
|
+
|
|
282
|
+
The existential counterpart of :func:`_residuated_box`; only ``Diamond``
|
|
283
|
+
uses it (Knows/Believes/Says/Wants are all universal, per the module
|
|
284
|
+
docstring).
|
|
285
|
+
"""
|
|
286
|
+
domain = _aggregation_domain(rel_name, model, world)
|
|
287
|
+
if not domain:
|
|
288
|
+
return 0.0
|
|
289
|
+
t = model.tnorm
|
|
290
|
+
return max(
|
|
291
|
+
t.conj(model.relation_weight(rel_name, world, w2),
|
|
292
|
+
satisfies_fuzzy_modal(formula, model, w2))
|
|
293
|
+
for w2 in domain
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# Classical connective classes that must not appear in fuzzy modal input —
|
|
298
|
+
# same discipline (and message shape) as fuzzy.py's _CLASSICAL_CONNECTIVES.
|
|
299
|
+
_CLASSICAL_CONNECTIVES = (Not, And, Or, Xor, Implies, Iff)
|
|
300
|
+
|
|
301
|
+
# Out-of-scope modal node families, rejected BY NAME with a category label
|
|
302
|
+
# (see the module docstring's "v1 scope" section for why each is excluded).
|
|
303
|
+
_TEMPORAL_TYPES = (Next, Always, Eventually, Until, Previous, Historically, Once, Since)
|
|
304
|
+
_HYBRID_TYPES = (Nominal, At)
|
|
305
|
+
_PAL_TYPES = (Announce, AnnounceDiamond)
|
|
306
|
+
_DEONTIC_TYPES = (Obligatory, Permitted)
|
|
307
|
+
_GROUP_EPISTEMIC_TYPES = (EverybodyKnows, DistributedKnowledge, CommonKnowledge)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _reject_out_of_scope(formula: Node, caller: str, category: str) -> NoReturn:
|
|
311
|
+
"""Reject a modal node family the graded evaluator does not interpret in v1.
|
|
312
|
+
|
|
313
|
+
Names both the offending node TYPE and the CATEGORY it belongs to, and
|
|
314
|
+
states the reason (a graded fixpoint/model-update/group-combinator over a
|
|
315
|
+
continuous t-norm is open research, not a silently-approximable detail) —
|
|
316
|
+
see the module docstring's "v1 scope" section.
|
|
317
|
+
"""
|
|
318
|
+
raise NotImplementedError(
|
|
319
|
+
f"{caller}: {type(formula).__name__} ({category}) is not supported by "
|
|
320
|
+
"the graded (fuzzy) Kripke evaluator — v1 covers exactly graded "
|
|
321
|
+
"Box/Diamond/Knows/Believes/Says/Wants with single-step aggregation, "
|
|
322
|
+
"no fixpoints; graded temporal closure, hybrid logic, public "
|
|
323
|
+
"announcement model updates, group-epistemic combinators, and "
|
|
324
|
+
"deontic serial-frame reasoning over a continuous t-norm are open "
|
|
325
|
+
"research questions in their own right and must not be silently "
|
|
326
|
+
"approximated. This is future work, tracked the same way "
|
|
327
|
+
"kripke.py/_modal_reject.py track quantified/lambda modal logic."
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def satisfies_fuzzy_modal(formula: Node, model: FuzzyKripkeModel, world: World) -> float:
|
|
332
|
+
"""Compute the truth DEGREE in ``[0, 1]`` of ``formula`` at ``world`` in ``model``.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
formula: a propositional/ground modal formula built from the Node API
|
|
336
|
+
directly (no parser is involved — see the module docstring),
|
|
337
|
+
using Łukasiewicz connective nodes (never classical And/Or/Not/
|
|
338
|
+
Implies/Iff/Xor) under Box/Diamond/Knows/Believes/Says/Wants.
|
|
339
|
+
model: the :class:`FuzzyKripkeModel` to evaluate against.
|
|
340
|
+
world: the world to evaluate at (any hashable value; need not be a
|
|
341
|
+
member of ``model.worlds``, exactly like
|
|
342
|
+
:func:`~unicode_logic_kit.semantics.kripke.satisfies_modal`).
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The truth degree as a float clamped to ``[0, 1]``.
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
NotImplementedError: on a Quantifier/SortedQuantifier, a lambda node,
|
|
349
|
+
temporal/hybrid/PAL/group-epistemic/deontic node (all future
|
|
350
|
+
work — see the module docstring), or an otherwise unsupported
|
|
351
|
+
node type.
|
|
352
|
+
TypeError: on a classical crisp connective (build the formula with
|
|
353
|
+
fuzzy connective nodes instead).
|
|
354
|
+
ValueError: the valuation of a world holds both spellings of an atom (its key and
|
|
355
|
+
its text as a formula) with different degrees.
|
|
356
|
+
"""
|
|
357
|
+
t = model.tnorm
|
|
358
|
+
|
|
359
|
+
# --- atomic ---
|
|
360
|
+
if isinstance(formula, Atom):
|
|
361
|
+
constant = _truth_value(formula)
|
|
362
|
+
if constant is not None:
|
|
363
|
+
return 1.0 if constant else 0.0 # the top / bottom degree at every world
|
|
364
|
+
# The key first, then the text of the atom as a formula: a valuation keyed either
|
|
365
|
+
# way is read, and a key found in neither spelling reads as degree 0.0.
|
|
366
|
+
degrees = model.valuation.get(world, MappingProxyType({}))
|
|
367
|
+
found = find_key(degrees, formula)
|
|
368
|
+
return 0.0 if found is None else _clamp(model.atom_degree(world, found))
|
|
369
|
+
|
|
370
|
+
# --- Łukasiewicz connectives: verbatim dispatch into model.tnorm ---
|
|
371
|
+
if isinstance(formula, LukNegation):
|
|
372
|
+
return _clamp(t.neg(satisfies_fuzzy_modal(formula.formula, model, world)))
|
|
373
|
+
if isinstance(formula, (WeakConjunction, WeakDisjunction,
|
|
374
|
+
StrongConjunction, StrongDisjunction,
|
|
375
|
+
LukImplication, LukEquivalence)):
|
|
376
|
+
x = satisfies_fuzzy_modal(formula.left, model, world)
|
|
377
|
+
y = satisfies_fuzzy_modal(formula.right, model, world)
|
|
378
|
+
if isinstance(formula, WeakConjunction):
|
|
379
|
+
return _clamp(min(x, y))
|
|
380
|
+
if isinstance(formula, WeakDisjunction):
|
|
381
|
+
return _clamp(max(x, y))
|
|
382
|
+
if isinstance(formula, StrongConjunction):
|
|
383
|
+
return _clamp(t.conj(x, y))
|
|
384
|
+
if isinstance(formula, StrongDisjunction):
|
|
385
|
+
return _clamp(t.disj(x, y))
|
|
386
|
+
if isinstance(formula, LukImplication):
|
|
387
|
+
return _clamp(t.impl(x, y))
|
|
388
|
+
return _clamp(t.equiv(x, y)) # LukEquivalence
|
|
389
|
+
|
|
390
|
+
# --- alethic: residuated universal Box, t-norm existential Diamond ---
|
|
391
|
+
if isinstance(formula, Box):
|
|
392
|
+
return _clamp(_residuated_box(_ALETHIC, formula.formula, model, world))
|
|
393
|
+
if isinstance(formula, Diamond):
|
|
394
|
+
return _clamp(_tnorm_diamond(_ALETHIC, formula.formula, model, world))
|
|
395
|
+
|
|
396
|
+
# --- epistemic / doxastic / assertive / bouletic (all four residuated-universal,
|
|
397
|
+
# matching kripke.py's satisfies_modal where all four are all(...)-quantified) ---
|
|
398
|
+
if isinstance(formula, Knows):
|
|
399
|
+
rel = _KNOWS_PREFIX + _agent_key(formula.agent)
|
|
400
|
+
return _clamp(_residuated_box(rel, formula.formula, model, world))
|
|
401
|
+
if isinstance(formula, Believes):
|
|
402
|
+
rel = _BELIEVES_PREFIX + _agent_key(formula.agent)
|
|
403
|
+
return _clamp(_residuated_box(rel, formula.formula, model, world))
|
|
404
|
+
if isinstance(formula, Says):
|
|
405
|
+
rel = _SAYS_PREFIX + _agent_key(formula.agent)
|
|
406
|
+
return _clamp(_residuated_box(rel, formula.formula, model, world))
|
|
407
|
+
if isinstance(formula, Wants):
|
|
408
|
+
rel = _WANTS_PREFIX + _agent_key(formula.agent)
|
|
409
|
+
return _clamp(_residuated_box(rel, formula.formula, model, world))
|
|
410
|
+
|
|
411
|
+
# --- rejected: classical crisp connectives ---
|
|
412
|
+
if isinstance(formula, _CLASSICAL_CONNECTIVES):
|
|
413
|
+
raise TypeError(
|
|
414
|
+
f"Classical connective {type(formula).__name__} is not valid in graded "
|
|
415
|
+
"modal input; build the formula with Łukasiewicz connective nodes "
|
|
416
|
+
"(LukNegation/WeakConjunction/WeakDisjunction/StrongConjunction/"
|
|
417
|
+
"StrongDisjunction/LukImplication/LukEquivalence) instead — the same "
|
|
418
|
+
"discipline unicode_logic_kit.semantics.fuzzy already enforces for "
|
|
419
|
+
"non-modal formulas."
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# --- rejected: quantifiers / lambda (shared with kripke.py's v1 discipline) ---
|
|
423
|
+
if isinstance(formula, (Quantifier, SortedQuantifier)):
|
|
424
|
+
reject_quantifier(formula, "satisfies_fuzzy_modal")
|
|
425
|
+
if isinstance(formula, LAMBDA_TYPES):
|
|
426
|
+
reject_lambda(formula, "satisfies_fuzzy_modal")
|
|
427
|
+
|
|
428
|
+
# --- rejected: out-of-scope modal node families (see module docstring) ---
|
|
429
|
+
if isinstance(formula, _TEMPORAL_TYPES):
|
|
430
|
+
_reject_out_of_scope(formula, "satisfies_fuzzy_modal", "temporal")
|
|
431
|
+
if isinstance(formula, _HYBRID_TYPES):
|
|
432
|
+
_reject_out_of_scope(formula, "satisfies_fuzzy_modal", "hybrid")
|
|
433
|
+
if isinstance(formula, _PAL_TYPES):
|
|
434
|
+
_reject_out_of_scope(formula, "satisfies_fuzzy_modal", "public announcement logic")
|
|
435
|
+
if isinstance(formula, _GROUP_EPISTEMIC_TYPES):
|
|
436
|
+
_reject_out_of_scope(formula, "satisfies_fuzzy_modal", "group-epistemic")
|
|
437
|
+
if isinstance(formula, _DEONTIC_TYPES):
|
|
438
|
+
_reject_out_of_scope(formula, "satisfies_fuzzy_modal", "deontic")
|
|
439
|
+
|
|
440
|
+
raise NotImplementedError(
|
|
441
|
+
f"satisfies_fuzzy_modal: unsupported node type {type(formula).__name__}."
|
|
442
|
+
)
|