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