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