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,384 @@
1
+ """Łukasiewicz fuzzy evaluator: truth DEGREE in [0, 1] of an FL/MSFL formula.
2
+
3
+ The evaluator interprets the Łukasiewicz operators over the real interval
4
+ [0, 1] under a *valuation* — a mapping from ground atoms (keyed by the text they print as,
5
+ with every constant written by its name, e.g. ``'P(alice)'``, ``'P(a)'`` for the atom ``P``
6
+ of a constant named ``a``; the text of the atom as a formula, ``"P('a')"``, is read as the
7
+ same key) to degrees. A sorted constant
8
+ ``alice:Person`` is the constant ``alice``, so ``Tall(alice:Person)`` has the key
9
+ ``'Tall(alice)'`` -- the key the grounding of ``∀x:Person Tall(x)`` gives its instance at
10
+ ``alice`` -- and two different atoms that print alike (the numeral ``1`` and a constant named
11
+ ``1``) are refused by name.
12
+
13
+ Łukasiewicz semantics::
14
+
15
+ ¬φ = 1 − x
16
+ weak ∧ = min(x, y) (WeakConjunction)
17
+ weak ∨ = max(x, y) (WeakDisjunction)
18
+ strong ⊗ = max(0, x+y−1) (StrongConjunction, t-norm)
19
+ strong ⊕ = min(1, x+y) (StrongDisjunction, t-conorm)
20
+ → = min(1, 1−x+y) (LukImplication)
21
+ ↔ = 1 − |x−y| (LukEquivalence)
22
+
23
+ Quantifiers range over a domain of constant names: ``∀`` is the infimum (min
24
+ over the domain) and ``∃`` the supremum (max). The bound variable is grounded
25
+ by substituting ``Variable(v.name)`` with ``Constant(d)`` for each domain
26
+ element ``d`` before recursing.
27
+
28
+ Classical boolean nodes (And/Or/Not/Implies/Iff/Xor) are intentionally
29
+ rejected: a formula meant for fuzzy evaluation must be parsed in FL or MSFL
30
+ mode so its connectives carry unambiguous Łukasiewicz semantics. Lambda nodes,
31
+ numeric literals, and comparison atoms are likewise rejected.
32
+
33
+ Parse inputs with::
34
+
35
+ MSFLParser(fuzzy=True) # FL (unsorted)
36
+ MSFLParser(many_sorted=True, fuzzy=True) # MSFL (sorted)
37
+ """
38
+
39
+ from typing import Optional, Set, Dict
40
+
41
+ from ..fol.nodes import (
42
+ Node, Variable, Constant, Number, Function, Atom,
43
+ Not, And, Or, Xor, Implies, Iff, Quantifier,
44
+ SortedQuantifier, SortedConstant,
45
+ WeakConjunction, WeakDisjunction,
46
+ StrongConjunction, StrongDisjunction,
47
+ LukNegation, LukImplication, LukEquivalence,
48
+ LambdaVar, Lambda, Application,
49
+ )
50
+ from ..fol._atom_keys import AtomKeys, atom_key, find_own_key
51
+ from ..fol._truth_constants import truth_value as _truth_value
52
+ from .tnorm import get_tnorm
53
+
54
+ # Comparison predicates have no Łukasiewicz reading under a propositional
55
+ # valuation; they are rejected rather than silently treated as opaque atoms.
56
+ _COMPARISON_PREDS = frozenset({"=", "≠", "<", ">", "≤", "≥"})
57
+
58
+ # Classical connective classes that must not appear in fuzzy input.
59
+ _CLASSICAL_CONNECTIVES = (And, Or, Not, Implies, Iff, Xor)
60
+
61
+ # Lambda-calculus classes that have no truth degree.
62
+ _LAMBDA_NODES = (LambdaVar, Lambda, Application)
63
+
64
+
65
+ def _clamp(x: float) -> float:
66
+ """Defensively clamp a degree into the closed interval [0, 1]."""
67
+ if x < 0.0:
68
+ return 0.0
69
+ if x > 1.0:
70
+ return 1.0
71
+ return x
72
+
73
+
74
+ def _ground_term(node: Node, var_name: str, const_name: str) -> Node:
75
+ """Replace every free ``Variable(var_name)`` in a *term* with ``Constant(const_name)``.
76
+
77
+ Operates on term-position nodes (Variable, Constant, Number, Function,
78
+ SortedConstant). Returns a new node; the input is never mutated.
79
+ """
80
+ if isinstance(node, Variable):
81
+ return Constant(const_name) if node.name == var_name else node
82
+ if isinstance(node, Function):
83
+ return Function(node.name,
84
+ [_ground_term(a, var_name, const_name) for a in node.args])
85
+ # Constant, Number, SortedConstant and anything else carry no free Variable.
86
+ return node
87
+
88
+
89
+ def _ground(node: Node, var_name: str, const_name: str) -> Node:
90
+ """Substitute free ``Variable(var_name)`` with ``Constant(const_name)`` throughout.
91
+
92
+ Recurses through the full formula structure, stopping at an inner binder
93
+ that rebinds the same variable name (the inner binding shadows ours).
94
+ Returns a new node; the input is never mutated.
95
+ """
96
+ if isinstance(node, Atom):
97
+ return Atom(node.predicate,
98
+ [_ground_term(a, var_name, const_name) for a in node.args])
99
+ if isinstance(node, (Not, LukNegation)):
100
+ return type(node)(_ground(node.formula, var_name, const_name))
101
+ if isinstance(node, (And, Or, Xor, Implies, Iff,
102
+ WeakConjunction, WeakDisjunction,
103
+ StrongConjunction, StrongDisjunction,
104
+ LukImplication, LukEquivalence)):
105
+ return type(node)(_ground(node.left, var_name, const_name),
106
+ _ground(node.right, var_name, const_name))
107
+ if isinstance(node, Quantifier):
108
+ if node.variable.name == var_name:
109
+ return node # inner binder shadows the variable we are grounding
110
+ return Quantifier(node.type, node.variable,
111
+ _ground(node.formula, var_name, const_name))
112
+ if isinstance(node, SortedQuantifier):
113
+ if node.variable.name == var_name:
114
+ return node
115
+ return SortedQuantifier(node.type, node.variable, node.sort,
116
+ _ground(node.formula, var_name, const_name))
117
+ # Term-position nodes (rare at formula top level) and leaves pass through
118
+ # the term grounder so a bare quantified variable is still handled.
119
+ return _ground_term(node, var_name, const_name)
120
+
121
+
122
+ def _eval_quantifier(qtype: str, var_name: str, body: Node,
123
+ universe: Set[str], valuation: Dict[str, float],
124
+ domain: Optional[Set[str]],
125
+ sort_universes: Optional[Dict[str, Set[str]]],
126
+ descriptor: str, tnorm: str,
127
+ keys: Optional[AtomKeys] = None) -> float:
128
+ """Evaluate a quantifier by grounding ``var_name`` over ``universe``.
129
+
130
+ ``qtype`` is ``'∀'`` (infimum = min) or ``'∃'`` (supremum = max) — the lattice
131
+ inf/sup, independent of the t-norm. ``descriptor`` names the universe source for
132
+ error messages.
133
+ """
134
+ if not universe:
135
+ raise ValueError(
136
+ f"Cannot evaluate quantifier over an empty {descriptor}; "
137
+ "provide at least one element."
138
+ )
139
+ degrees = [
140
+ _evaluate(_ground(body, var_name, d), valuation, domain, sort_universes, tnorm, keys)
141
+ for d in universe
142
+ ]
143
+ if qtype in ("∀", "forall"):
144
+ return min(degrees)
145
+ if qtype in ("∃", "exists"):
146
+ return max(degrees)
147
+ raise ValueError(f"Unknown quantifier type: {qtype!r}")
148
+
149
+
150
+ def evaluate(node: Node,
151
+ valuation: Dict[str, float],
152
+ domain: Optional[Set[str]] = None,
153
+ sort_universes: Optional[Dict[str, Set[str]]] = None,
154
+ tnorm: str = "lukasiewicz") -> float:
155
+ """Compute the fuzzy truth degree in [0, 1] of an FL/MSFL formula.
156
+
157
+ Args:
158
+ node: an FL or MSFL formula node. Build it with
159
+ ``MSFLParser(fuzzy=True)`` (unsorted FL) or
160
+ ``MSFLParser(many_sorted=True, fuzzy=True)`` (sorted MSFL).
161
+ valuation: maps a ground atom's key — the text it prints as, with every
162
+ constant written by its name (``'P(alice)'``, and ``'P(a)'`` for a constant
163
+ named ``a``, which the text of the formula writes ``P('a')``) — to a degree in
164
+ [0, 1]. A key written as the text of the atom as a formula (``atom.to_unicode_str()``,
165
+ ``"P('a')"``) is read as the same key. A missing key raises ``KeyError`` with a
166
+ helpful message. A sorted constant ``alice:Person`` is the constant ``alice``:
167
+ ``Tall(alice:Person)`` has the key ``'Tall(alice)'``, the key the grounding of
168
+ ``∀x:Person Tall(x)`` gives its instance at ``alice``.
169
+ domain: a set of constant-name strings over which unsorted quantifiers
170
+ range. Required whenever a ``Quantifier`` is evaluated.
171
+ sort_universes: maps each sort name to its set of constant-name strings;
172
+ ``SortedQuantifier`` ranges over the universe of its sort.
173
+ tnorm: which continuous t-norm fixes the **strong** connectives ⊗ ⊕ → ¬ ↔
174
+ — ``"lukasiewicz"`` (default), ``"godel"`` or ``"product"``. The weak
175
+ ∧ / ∨ are always min / max, and ∀ / ∃ always inf / sup, regardless.
176
+
177
+ Returns:
178
+ The truth degree as a float clamped to [0, 1].
179
+
180
+ Raises:
181
+ KeyError: a ground atom's key is absent from the valuation.
182
+ ValueError: a quantifier lacks its domain / sort universe, or one is empty,
183
+ or ``tnorm`` is unknown, or the valuation holds both spellings of one atom
184
+ (its key and its text as a formula) with different degrees.
185
+ TypeError: the node carries a classical connective, lambda construct,
186
+ numeric literal, comparison atom, or otherwise unsupported type.
187
+ NotImplementedError: two different atoms print alike (the numeral ``1`` and a
188
+ constant named ``1``, a free variable ``x`` and a constant named ``x``), which
189
+ one key of the valuation could not tell apart.
190
+ ValueError: also for a sorted constant ``c:S`` whose name ``sort_universes[S]``
191
+ does not hold (see :func:`check_sorted_constants`).
192
+ """
193
+ check_sorted_constants(node, sort_universes, "evaluate")
194
+ return _evaluate(node, valuation, domain, sort_universes, tnorm, AtomKeys("evaluate"))
195
+
196
+
197
+ def check_sorted_constants(node: Node, sort_universes: Optional[Dict[str, Set[str]]],
198
+ where: str) -> None:
199
+ """Refuse a sorted constant that the universe given for its sort does not hold.
200
+
201
+ ``c:S`` denotes an element of ``S``, and here an element of a universe is named by its
202
+ constant. A universe for ``S`` without ``c`` therefore contradicts the formula, and
203
+ deciding it anyway reads ``c`` as something outside ``S``: over ``Person = {carol}``
204
+ the valid ``(∀x:Person Tall(x)) → Tall(alice:Person)`` would come out not valid
205
+ (``Tall(carol) = 1``, ``Tall(alice) = 0``). A sort with no universe given puts no
206
+ condition on its constants.
207
+
208
+ Raises:
209
+ ValueError: ``node`` holds a sorted constant ``c:S``, ``sort_universes`` has an
210
+ entry for ``S``, and ``c`` is not in it.
211
+ """
212
+ if not sort_universes:
213
+ return
214
+ for term in node.walk():
215
+ if (isinstance(term, SortedConstant) and term.sort in sort_universes
216
+ and term.name not in sort_universes[term.sort]):
217
+ raise ValueError(
218
+ f"{where}: the sorted constant {term.name}:{term.sort} names an element that "
219
+ f"sort_universes[{term.sort!r}] = {sorted(sort_universes[term.sort])} does not "
220
+ f"hold. A sorted constant is an element of its sort, so the universe and the "
221
+ f"formula contradict each other: add {term.name!r} to that universe, or write "
222
+ f"the constant without the sort.")
223
+
224
+
225
+ def _reject_comparison_atom(node: Node, route: str = "the fuzzy evaluator") -> None:
226
+ """Refuse a comparison atom (``=``, ``≠``, ``<``, ``>``, ``≤``, ``≥``) by name.
227
+
228
+ A comparison has no Łukasiewicz truth degree under a propositional valuation, and a
229
+ route that read one as a letter of its own would decide ``a = a`` as it decides ``P``
230
+ (not valid). The evaluator and the Z3 deciders of the fuzzy route share this refusal.
231
+
232
+ Raises:
233
+ TypeError: ``node`` is an atom whose predicate is a comparison symbol.
234
+ """
235
+ if isinstance(node, Atom) and node.predicate in _COMPARISON_PREDS:
236
+ raise TypeError(
237
+ f"Comparison atom {node.to_unicode_str()!r} has no Łukasiewicz "
238
+ f"truth degree; {route} only handles propositional predicate atoms."
239
+ )
240
+
241
+
242
+ def _evaluate(node: Node, valuation: Dict[str, float], domain: Optional[Set[str]],
243
+ sort_universes: Optional[Dict[str, Set[str]]], tnorm: str,
244
+ keys: Optional[AtomKeys]) -> float:
245
+ """The body of :func:`evaluate`; ``keys`` records and checks the key of every atom reached."""
246
+ t = get_tnorm(tnorm)
247
+ # --- Atoms (the base case) --------------------------------------------
248
+ if isinstance(node, Atom):
249
+ constant = _truth_value(node)
250
+ if constant is not None:
251
+ # `$true` / `$false` are the top and the bottom degree under every valuation.
252
+ return 1.0 if constant else 0.0
253
+ _reject_comparison_atom(node)
254
+ found = (find_own_key(valuation, node) if keys is None
255
+ else keys.find(valuation, node))
256
+ if found is None:
257
+ key = atom_key(node)
258
+ raise KeyError(
259
+ f"No degree for ground atom {key!r} in the valuation. "
260
+ "Provide valuation[{!r}] as a number in [0, 1].".format(key)
261
+ )
262
+ return _clamp(float(valuation[found]))
263
+
264
+ # --- strong negation (t-norm residual negation; involutive for Łukasiewicz) -
265
+ if isinstance(node, LukNegation):
266
+ x = _evaluate(node.formula, valuation, domain, sort_universes, tnorm, keys)
267
+ return _clamp(t.neg(x))
268
+
269
+ # --- binary connectives (weak ∧/∨ are min/max; strong ⊗⊕→↔ are the t-norm's) -
270
+ if isinstance(node, (WeakConjunction, WeakDisjunction,
271
+ StrongConjunction, StrongDisjunction,
272
+ LukImplication, LukEquivalence)):
273
+ x = _evaluate(node.left, valuation, domain, sort_universes, tnorm, keys)
274
+ y = _evaluate(node.right, valuation, domain, sort_universes, tnorm, keys)
275
+ if isinstance(node, WeakConjunction):
276
+ return _clamp(min(x, y))
277
+ if isinstance(node, WeakDisjunction):
278
+ return _clamp(max(x, y))
279
+ if isinstance(node, StrongConjunction):
280
+ return _clamp(t.conj(x, y))
281
+ if isinstance(node, StrongDisjunction):
282
+ return _clamp(t.disj(x, y))
283
+ if isinstance(node, LukImplication):
284
+ return _clamp(t.impl(x, y))
285
+ # LukEquivalence
286
+ return _clamp(t.equiv(x, y))
287
+
288
+ # --- Quantifiers -------------------------------------------------------
289
+ if isinstance(node, Quantifier):
290
+ if domain is None:
291
+ raise ValueError(
292
+ "Evaluating an unsorted Quantifier requires a 'domain' "
293
+ "(a set of constant-name strings)."
294
+ )
295
+ return _eval_quantifier(node.type, node.variable.name, node.formula,
296
+ set(domain), valuation, domain, sort_universes,
297
+ descriptor="domain", tnorm=tnorm, keys=keys)
298
+
299
+ if isinstance(node, SortedQuantifier):
300
+ if sort_universes is None or node.sort not in sort_universes:
301
+ raise ValueError(
302
+ f"Evaluating a SortedQuantifier over sort {node.sort!r} requires "
303
+ f"'sort_universes[{node.sort!r}]' (a set of constant-name strings)."
304
+ )
305
+ return _eval_quantifier(node.type, node.variable.name, node.formula,
306
+ set(sort_universes[node.sort]), valuation,
307
+ domain, sort_universes,
308
+ descriptor=f"sort universe {node.sort!r}", tnorm=tnorm,
309
+ keys=keys)
310
+
311
+ # --- Rejected node classes (informative errors) -----------------------
312
+ if isinstance(node, _CLASSICAL_CONNECTIVES):
313
+ raise TypeError(
314
+ f"Classical connective {type(node).__name__} is not valid in fuzzy "
315
+ "input; parse the formula in FL/MSFL mode "
316
+ "(MSFLParser(fuzzy=True) or MSFLParser(many_sorted=True, fuzzy=True)) "
317
+ "so its connectives carry Łukasiewicz semantics."
318
+ )
319
+ if isinstance(node, _LAMBDA_NODES):
320
+ raise TypeError(
321
+ f"{type(node).__name__} (a lambda-calculus construct) has no truth "
322
+ "degree; beta-reduce and eliminate lambdas before fuzzy evaluation."
323
+ )
324
+ if isinstance(node, Number):
325
+ raise TypeError(
326
+ "A bare Number has no truth degree; the fuzzy evaluator expects a "
327
+ "propositional FL/MSFL formula, not an arithmetic term."
328
+ )
329
+ if isinstance(node, (Variable, Constant, SortedConstant, Function)):
330
+ raise TypeError(
331
+ f"{type(node).__name__} is a term, not a formula; the fuzzy "
332
+ "evaluator can only score a formula's truth degree."
333
+ )
334
+
335
+ raise TypeError(f"evaluate: unsupported node type {type(node).__name__}")
336
+
337
+
338
+ def ground_quantifiers(node: Node,
339
+ domain: Optional[Set[str]] = None,
340
+ sort_universes: Optional[Dict[str, Set[str]]] = None) -> Node:
341
+ """Return a quantifier-free copy of ``node`` by grounding ∀/∃ over their universe.
342
+
343
+ ``∀x φ`` becomes the **weak**-conjunction fold (min = infimum) of ``φ[x:=d]`` over
344
+ the domain, ``∃x φ`` the weak-disjunction fold (max = supremum) — the finite-domain
345
+ reading of the fuzzy quantifiers. This lets the Z3 decider (:mod:`atp.z3_fuzzy`)
346
+ handle quantified fuzzy formulas: ground first, then decide the propositional result.
347
+
348
+ ``domain`` supplies the universe for unsorted ``∀x``/``∃x``; ``sort_universes`` maps
349
+ each sort to its universe for ``SortedQuantifier``. Raises ValueError if a needed
350
+ universe is missing or empty.
351
+ """
352
+ if isinstance(node, Quantifier):
353
+ if not domain:
354
+ raise ValueError("Grounding an unsorted Quantifier requires a non-empty 'domain'.")
355
+ insts = [ground_quantifiers(_ground(node.formula, node.variable.name, d),
356
+ domain, sort_universes) for d in sorted(domain)]
357
+ return _fold_quantifier(node.type, insts)
358
+ if isinstance(node, SortedQuantifier):
359
+ if sort_universes is None or not sort_universes.get(node.sort):
360
+ raise ValueError(
361
+ f"Grounding a SortedQuantifier over sort {node.sort!r} requires a "
362
+ f"non-empty 'sort_universes[{node.sort!r}]'.")
363
+ insts = [ground_quantifiers(_ground(node.formula, node.variable.name, d),
364
+ domain, sort_universes)
365
+ for d in sorted(sort_universes[node.sort])]
366
+ return _fold_quantifier(node.type, insts)
367
+ if isinstance(node, LukNegation):
368
+ return LukNegation(ground_quantifiers(node.formula, domain, sort_universes))
369
+ if isinstance(node, (WeakConjunction, WeakDisjunction,
370
+ StrongConjunction, StrongDisjunction,
371
+ LukImplication, LukEquivalence)):
372
+ return type(node)(ground_quantifiers(node.left, domain, sort_universes),
373
+ ground_quantifiers(node.right, domain, sort_universes))
374
+ # Atoms and terms have no quantifier to ground.
375
+ return node
376
+
377
+
378
+ def _fold_quantifier(qtype: str, insts) -> Node:
379
+ """Fold instances with weak ∧ (∀ = inf) or weak ∨ (∃ = sup)."""
380
+ op = WeakConjunction if qtype in ("∀", "forall") else WeakDisjunction
381
+ acc = insts[0]
382
+ for nxt in insts[1:]:
383
+ acc = op(acc, nxt)
384
+ return acc