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,580 @@
|
|
|
1
|
+
"""Three-valued (Kleene K3 / Priest LP) evaluation of classical FOL formulas.
|
|
2
|
+
|
|
3
|
+
The strong-Kleene tables interpret the classical connectives over the chain
|
|
4
|
+
``{0.0, 0.5, 1.0}`` — read as *false*, *undefined/both*, *true*::
|
|
5
|
+
|
|
6
|
+
¬x = 1 − x
|
|
7
|
+
a ∧ b = min(a, b)
|
|
8
|
+
a ∨ b = max(a, b)
|
|
9
|
+
a → b = max(1 − a, b) (material implication)
|
|
10
|
+
a ↔ b = min(a → b, b → a)
|
|
11
|
+
a ⊕ b = min(max(a, b), 1 − min(a, b)) (a ∨ b) ∧ ¬(a ∧ b)
|
|
12
|
+
|
|
13
|
+
These are the *same* truth functions for K3 and LP; the two logics differ only
|
|
14
|
+
in which values count as **designated** (truth-preserved):
|
|
15
|
+
|
|
16
|
+
K3 designates {1.0} — only plain truth
|
|
17
|
+
LP designates {0.5, 1.0} — "true" or "both" (Priest's paraconsistent LP)
|
|
18
|
+
|
|
19
|
+
That single choice produces the headline contrasts: the law of excluded middle
|
|
20
|
+
``P ∨ ¬P`` is K3-invalid but LP-valid, and LP is paraconsistent — ``P, ¬P`` does
|
|
21
|
+
*not* entail an arbitrary ``Q`` — while K3 fails non-contradiction validity.
|
|
22
|
+
|
|
23
|
+
This module operates on **classical** AST nodes (Atom, Not, And, Or, Xor,
|
|
24
|
+
Implies, Iff, Quantifier) parsed by ``MSFLParser()`` — no new grammar. Truth
|
|
25
|
+
values are the floats ``0.0``, ``0.5`` and ``1.0``. A *valuation* maps a ground
|
|
26
|
+
atom's key (the text it prints as, with every constant written by its name, e.g.
|
|
27
|
+
``'P'`` or ``'P(a)'``, also for a constant named ``a`` that the text of the formula
|
|
28
|
+
writes in quotes, ``P('a')``, which is read as the same key) to one of those three values.
|
|
29
|
+
The nullary atoms ``$true`` and
|
|
30
|
+
``$false`` (``⊤`` and ``⊥``) are not letters of the formula but the constants truth and falsity: they have the value
|
|
31
|
+
``1.0`` and ``0.0`` under every valuation, and an enumeration does not vary them.
|
|
32
|
+
|
|
33
|
+
Quantifiers are read substitutionally over a finite ``domain`` of constant
|
|
34
|
+
names, exactly as in the fuzzy evaluator: ``∀`` is the minimum and ``∃`` the
|
|
35
|
+
maximum of the body's value over the domain (∀ = "all", ∃ = "some"). A domain
|
|
36
|
+
is required whenever a quantifier is present.
|
|
37
|
+
|
|
38
|
+
A variable that is free in a formula that is decided together with a quantifier (the
|
|
39
|
+
deciders :func:`is_valid`, :func:`is_satisfiable` and :func:`entails`, and their
|
|
40
|
+
counterparts in :mod:`~unicode_logic_kit.semantics.matrix`) is a PARAMETER: one unknown
|
|
41
|
+
element of the domain, the same in every formula of the problem. The decider tries each
|
|
42
|
+
assignment of the domain's elements to the problem's free variables: a formula is valid
|
|
43
|
+
(a consequence) when it is so under every assignment and satisfiable when it is so
|
|
44
|
+
under some. ``∀y P(y) → P(x)`` is therefore LP-valid over ``{a, b}`` (the parameter ``x``
|
|
45
|
+
is ``a`` or ``b``), ``P(x) ⊢ ∃y P(y)`` is a consequence (whichever element ``x`` is, ``P``
|
|
46
|
+
of it is one of the disjuncts), and ``P(x), Q(y) ⊢ ∀z (P(z) ∧ Q(z))`` is not. Where no
|
|
47
|
+
quantifier is present the domain says nothing about a variable, and the atom ``P(x)`` is a
|
|
48
|
+
letter of its own, exactly as ``P(a)`` is. The evaluator (:func:`kleene_value`) takes the
|
|
49
|
+
valuation as it stands: the atom ``P(x)`` of a free variable is one more key of it.
|
|
50
|
+
|
|
51
|
+
An atom is a letter named by the text it prints as. Two different atoms that print alike
|
|
52
|
+
(the numeral ``1`` and a constant named ``1``, a free variable ``x`` and a constant named
|
|
53
|
+
``x``) would be read as one letter, so a problem that has such a pair is refused by name.
|
|
54
|
+
|
|
55
|
+
Łukasiewicz (fuzzy), sorted, lambda, and modal nodes carry no classical
|
|
56
|
+
three-valued reading here and raise ``NotImplementedError``. A sorted constant
|
|
57
|
+
(``alice:Human``) is refused the same way wherever it stands, inside an atom as well:
|
|
58
|
+
that ``alice`` lies in ``Human`` is a fact about the sort which the three truth
|
|
59
|
+
values have no statement of, and reading ``Human(alice)`` as unrelated to it would
|
|
60
|
+
answer another question.
|
|
61
|
+
|
|
62
|
+
Parse inputs with ``MSFLParser()`` (classical propositional / FOL).
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
from itertools import product
|
|
66
|
+
from typing import Collection, Dict, Iterator, List, Optional, Sequence, Set, Tuple
|
|
67
|
+
|
|
68
|
+
from ..fol._atom_keys import AtomKeys, atom_key, find_own_key
|
|
69
|
+
from ..fol._free_parameters import free_parameter_names
|
|
70
|
+
from ..fol.nodes import (
|
|
71
|
+
Node, Variable, Constant, Number, Function, Atom,
|
|
72
|
+
Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
73
|
+
SortedQuantifier, SortedConstant,
|
|
74
|
+
LukNegation, WeakConjunction, WeakDisjunction,
|
|
75
|
+
StrongConjunction, StrongDisjunction, LukImplication, LukEquivalence,
|
|
76
|
+
Lambda, LambdaVar, Application,
|
|
77
|
+
Box, Diamond, Always, Eventually, Next, Until,
|
|
78
|
+
Historically, Once, Previous, Since, Knows, Believes,
|
|
79
|
+
)
|
|
80
|
+
from ..fol._truth_constants import truth_value as _truth_value
|
|
81
|
+
|
|
82
|
+
# The three truth values, smallest first: false < undefined/both < true.
|
|
83
|
+
FALSE = 0.0
|
|
84
|
+
UNDEFINED = 0.5
|
|
85
|
+
TRUE = 1.0
|
|
86
|
+
TRUTH_VALUES = (FALSE, UNDEFINED, TRUE)
|
|
87
|
+
|
|
88
|
+
# Designated (truth-preserved) value sets keyed by logic name.
|
|
89
|
+
# K3 — only plain truth is designated.
|
|
90
|
+
# LP — Priest's Logic of Paradox: both "true" and "both" are designated.
|
|
91
|
+
#: Designated (truth-preserved) value sets, keyed by logic name: ``"K3"``
|
|
92
|
+
#: designates plain truth alone, ``"LP"`` (Priest's Logic of Paradox)
|
|
93
|
+
#: designates both "true" and "both".
|
|
94
|
+
DESIGNATED: Dict[str, frozenset] = {
|
|
95
|
+
"K3": frozenset({TRUE}),
|
|
96
|
+
"LP": frozenset({UNDEFINED, TRUE}),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Safety cap for the validity/satisfiability/entailment enumeration: those
|
|
100
|
+
# routines visit 3 ** (#distinct ground atoms) assignments. Past this many the
|
|
101
|
+
# enumeration is hopeless in practice, so it raises a clear error instead of
|
|
102
|
+
# hanging. Raise this module attribute if you genuinely need a larger run.
|
|
103
|
+
MAX_MODELS = 3_000_000
|
|
104
|
+
|
|
105
|
+
# Łukasiewicz / sorted / lambda / modal node groups, rejected with a clear
|
|
106
|
+
# NotImplementedError: none has a strong-Kleene three-valued reading here.
|
|
107
|
+
_FUZZY_NODES = (
|
|
108
|
+
LukNegation, WeakConjunction, WeakDisjunction,
|
|
109
|
+
StrongConjunction, StrongDisjunction, LukImplication, LukEquivalence,
|
|
110
|
+
)
|
|
111
|
+
_SORTED_NODES = (SortedQuantifier, SortedConstant)
|
|
112
|
+
_LAMBDA_NODES = (Lambda, LambdaVar, Application)
|
|
113
|
+
_MODAL_NODES = (Box, Diamond, Always, Eventually, Next, Until,
|
|
114
|
+
Historically, Once, Previous, Since, Knows, Believes)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _snap(x: float) -> float:
|
|
118
|
+
"""Snap a raw number to the nearest of {0.0, 0.5, 1.0}, or reject it.
|
|
119
|
+
|
|
120
|
+
Tiny floating drift around the three legal values is tolerated and snapped;
|
|
121
|
+
anything genuinely outside ``[0, 1]`` or far from a legal value raises
|
|
122
|
+
``ValueError`` so a malformed valuation is caught early rather than silently
|
|
123
|
+
distorting a result.
|
|
124
|
+
"""
|
|
125
|
+
for v in TRUTH_VALUES:
|
|
126
|
+
if abs(x - v) < 1e-9:
|
|
127
|
+
return v
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Three-valued truth value must be one of {{0.0, 0.5, 1.0}}; got {x!r}."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _ground_term(node: Node, var_name: str, const_name: str) -> Node:
|
|
134
|
+
"""Replace every free ``Variable(var_name)`` in a *term* with ``Constant(const_name)``.
|
|
135
|
+
|
|
136
|
+
Operates on term-position nodes (Variable, Constant, Number, Function).
|
|
137
|
+
Returns a new node; the input is never mutated.
|
|
138
|
+
"""
|
|
139
|
+
if isinstance(node, Variable):
|
|
140
|
+
return Constant(const_name) if node.name == var_name else node
|
|
141
|
+
if isinstance(node, Function):
|
|
142
|
+
return Function(node.name,
|
|
143
|
+
[_ground_term(a, var_name, const_name) for a in node.args])
|
|
144
|
+
# Constant, Number and anything else carry no free Variable.
|
|
145
|
+
return node
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _ground(node: Node, var_name: str, const_name: str) -> Node:
|
|
149
|
+
"""Substitute free ``Variable(var_name)`` with ``Constant(const_name)`` throughout.
|
|
150
|
+
|
|
151
|
+
Recurses the classical formula structure, stopping at an inner quantifier
|
|
152
|
+
that rebinds the same variable name (the inner binding shadows ours).
|
|
153
|
+
Returns a new node; the input is never mutated.
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(node, Atom):
|
|
156
|
+
return Atom(node.predicate,
|
|
157
|
+
[_ground_term(a, var_name, const_name) for a in node.args])
|
|
158
|
+
if isinstance(node, Not):
|
|
159
|
+
return Not(_ground(node.formula, var_name, const_name))
|
|
160
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff)):
|
|
161
|
+
return type(node)(_ground(node.left, var_name, const_name),
|
|
162
|
+
_ground(node.right, var_name, const_name))
|
|
163
|
+
if isinstance(node, Quantifier):
|
|
164
|
+
if node.variable.name == var_name:
|
|
165
|
+
return node # inner binder shadows the variable we are grounding
|
|
166
|
+
return Quantifier(node.type, node.variable,
|
|
167
|
+
_ground(node.formula, var_name, const_name))
|
|
168
|
+
# Term-position leaves pass through the term grounder.
|
|
169
|
+
return _ground_term(node, var_name, const_name)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _eval_quantifier(qtype: str, var_name: str, body: Node,
|
|
173
|
+
valuation: Dict[str, float], domain: Set[str],
|
|
174
|
+
keys: Optional[AtomKeys] = None) -> float:
|
|
175
|
+
"""Evaluate a quantifier substitutionally: ∀ = min, ∃ = max over the domain."""
|
|
176
|
+
if not domain:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
"Cannot evaluate a quantifier over an empty domain; provide at "
|
|
179
|
+
"least one constant name."
|
|
180
|
+
)
|
|
181
|
+
values = [
|
|
182
|
+
_kleene_value(_ground(body, var_name, d), valuation, domain, keys)
|
|
183
|
+
for d in domain
|
|
184
|
+
]
|
|
185
|
+
if qtype in ("∀", "forall"):
|
|
186
|
+
return min(values)
|
|
187
|
+
if qtype in ("∃", "exists"):
|
|
188
|
+
return max(values)
|
|
189
|
+
raise ValueError(f"Unknown quantifier type: {qtype!r}")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def kleene_value(formula: Node,
|
|
193
|
+
valuation: Dict[str, float],
|
|
194
|
+
domain: Optional[Set[str]] = None) -> float:
|
|
195
|
+
"""Strong-Kleene three-valued truth value of a classical formula.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
formula: a classical FOL formula node (Atom, Not, And, Or, Xor, Implies,
|
|
199
|
+
Iff, Quantifier). Build it with ``MSFLParser()``.
|
|
200
|
+
valuation: maps a ground atom's key — the text it prints as, with every
|
|
201
|
+
constant written by its name, e.g. ``'P'`` or ``'P(a)'`` — to a
|
|
202
|
+
value in ``{0.0, 0.5, 1.0}``. A key written as the text of the atom as a
|
|
203
|
+
formula (``atom.to_unicode_str()``, ``"P('a')"``) is read as the same key. A
|
|
204
|
+
missing key raises ``KeyError``. The atom of a free variable (``'P(x)'``) is one
|
|
205
|
+
more key.
|
|
206
|
+
domain: a set of constant-name strings over which quantifiers range
|
|
207
|
+
(∀ = min, ∃ = max). Required whenever a ``Quantifier`` is present.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The truth value as one of ``0.0``, ``0.5`` or ``1.0``.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
KeyError: a ground atom's key is absent from the valuation.
|
|
214
|
+
ValueError: a quantifier lacks a domain (or the domain is empty), or a
|
|
215
|
+
valuation entry is not snappable to ``{0.0, 0.5, 1.0}``, or the valuation holds
|
|
216
|
+
both spellings of one atom (its key and its text as a formula) with different
|
|
217
|
+
values.
|
|
218
|
+
NotImplementedError: the node is a Łukasiewicz, sorted, lambda or modal
|
|
219
|
+
construct, which has no strong-Kleene three-valued reading here; a
|
|
220
|
+
sorted constant inside an atom is refused the same way; so is a pair of
|
|
221
|
+
different atoms that print alike (the numeral ``1`` and a constant named
|
|
222
|
+
``1``, a free variable ``x`` and a constant named ``x``), which one key of the
|
|
223
|
+
valuation could not tell apart.
|
|
224
|
+
TypeError: the node is a bare term, not a formula, or otherwise
|
|
225
|
+
unsupported.
|
|
226
|
+
"""
|
|
227
|
+
return _kleene_value(formula, valuation, domain, AtomKeys("manyvalued.kleene_value", "refuse"))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _kleene_value(formula: Node, valuation: Dict[str, float],
|
|
231
|
+
domain: Optional[Set[str]], keys: Optional[AtomKeys]) -> float:
|
|
232
|
+
"""The body of :func:`kleene_value`; ``keys`` records and checks the key of every atom
|
|
233
|
+
reached, and is ``None`` where the atoms were checked already."""
|
|
234
|
+
# --- Atoms (the base case) --------------------------------------------
|
|
235
|
+
if isinstance(formula, Atom):
|
|
236
|
+
# The truth constants ``$true`` / ``$false`` are the top and the bottom value
|
|
237
|
+
# (1.0 and 0.0) whatever the valuation says; they are not letters.
|
|
238
|
+
constant = _truth_value(formula)
|
|
239
|
+
if constant is not None:
|
|
240
|
+
return TRUE if constant else FALSE
|
|
241
|
+
found = (find_own_key(valuation, formula) if keys is None
|
|
242
|
+
else keys.find(valuation, formula))
|
|
243
|
+
if found is None:
|
|
244
|
+
key = atom_key(formula)
|
|
245
|
+
raise KeyError(
|
|
246
|
+
f"No truth value for ground atom {key!r} in the valuation. "
|
|
247
|
+
f"Provide valuation[{key!r}] as one of 0.0, 0.5 or 1.0."
|
|
248
|
+
)
|
|
249
|
+
return _snap(float(valuation[found]))
|
|
250
|
+
|
|
251
|
+
# --- Negation ----------------------------------------------------------
|
|
252
|
+
if isinstance(formula, Not):
|
|
253
|
+
x = _kleene_value(formula.formula, valuation, domain, keys)
|
|
254
|
+
return 1.0 - x
|
|
255
|
+
|
|
256
|
+
# --- Binary classical connectives -------------------------------------
|
|
257
|
+
if isinstance(formula, (And, Or, Xor, Implies, Iff)):
|
|
258
|
+
a = _kleene_value(formula.left, valuation, domain, keys)
|
|
259
|
+
b = _kleene_value(formula.right, valuation, domain, keys)
|
|
260
|
+
if isinstance(formula, And):
|
|
261
|
+
return min(a, b)
|
|
262
|
+
if isinstance(formula, Or):
|
|
263
|
+
return max(a, b)
|
|
264
|
+
if isinstance(formula, Implies):
|
|
265
|
+
return max(1.0 - a, b) # material: ¬a ∨ b
|
|
266
|
+
if isinstance(formula, Iff):
|
|
267
|
+
a_to_b = max(1.0 - a, b)
|
|
268
|
+
b_to_a = max(1.0 - b, a)
|
|
269
|
+
return min(a_to_b, b_to_a)
|
|
270
|
+
# Xor: (a ∨ b) ∧ ¬(a ∧ b) = min(max(a, b), 1 − min(a, b))
|
|
271
|
+
return min(max(a, b), 1.0 - min(a, b))
|
|
272
|
+
|
|
273
|
+
# --- Quantifiers -------------------------------------------------------
|
|
274
|
+
if isinstance(formula, Quantifier):
|
|
275
|
+
if domain is None:
|
|
276
|
+
raise ValueError(
|
|
277
|
+
"Evaluating a Quantifier requires a 'domain' (a set of "
|
|
278
|
+
"constant-name strings)."
|
|
279
|
+
)
|
|
280
|
+
return _eval_quantifier(formula.type, formula.variable.name,
|
|
281
|
+
formula.formula, valuation, set(domain), keys)
|
|
282
|
+
|
|
283
|
+
# --- Rejected node classes (informative errors) -----------------------
|
|
284
|
+
_reject_if_unsupported(formula)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _reject_if_unsupported(formula: Node) -> None:
|
|
288
|
+
"""Raise the error kleene_value uses for a node with no three-valued reading.
|
|
289
|
+
|
|
290
|
+
Shared by :func:`kleene_value` and the compiled enumerator (:func:`_compile`)
|
|
291
|
+
so both reject the same node classes with identical messages. The supported
|
|
292
|
+
nodes (Atom, Not, And, Or, Xor, Implies, Iff, Quantifier) are dispatched by
|
|
293
|
+
the caller before this is reached, so reaching here is always an error.
|
|
294
|
+
"""
|
|
295
|
+
if isinstance(formula, _FUZZY_NODES):
|
|
296
|
+
raise NotImplementedError(
|
|
297
|
+
f"{type(formula).__name__} (a Łukasiewicz operator) has no "
|
|
298
|
+
"strong-Kleene three-valued reading; parse classical input with "
|
|
299
|
+
"MSFLParser() so connectives carry classical semantics."
|
|
300
|
+
)
|
|
301
|
+
if isinstance(formula, _SORTED_NODES):
|
|
302
|
+
raise NotImplementedError(
|
|
303
|
+
f"{type(formula).__name__} (a sorted construct) is not supported by "
|
|
304
|
+
"the three-valued evaluator; the K3/LP evaluator is unsorted."
|
|
305
|
+
)
|
|
306
|
+
if isinstance(formula, _LAMBDA_NODES):
|
|
307
|
+
raise NotImplementedError(
|
|
308
|
+
f"{type(formula).__name__} (a lambda-calculus construct) has no "
|
|
309
|
+
"truth value; beta-reduce and eliminate lambdas first."
|
|
310
|
+
)
|
|
311
|
+
if isinstance(formula, _MODAL_NODES):
|
|
312
|
+
raise NotImplementedError(
|
|
313
|
+
f"{type(formula).__name__} (a modal/temporal/epistemic operator) is "
|
|
314
|
+
"not supported by the three-valued evaluator; it is non-modal."
|
|
315
|
+
)
|
|
316
|
+
if isinstance(formula, (Variable, Constant, Number, Function)):
|
|
317
|
+
raise TypeError(
|
|
318
|
+
f"{type(formula).__name__} is a term, not a formula; the "
|
|
319
|
+
"three-valued evaluator can only score a formula's truth value."
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
raise TypeError(
|
|
323
|
+
f"kleene_value: unsupported node type {type(formula).__name__}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _designated_set(logic: str) -> frozenset:
|
|
328
|
+
"""Look up the designated-value set for a logic name, with a clear error."""
|
|
329
|
+
try:
|
|
330
|
+
return DESIGNATED[logic]
|
|
331
|
+
except KeyError:
|
|
332
|
+
raise ValueError(
|
|
333
|
+
f"Unknown logic {logic!r}; choose one of {sorted(DESIGNATED)}."
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _atom_keys(*formulas: Node, route: str = "manyvalued") -> List[str]:
|
|
338
|
+
"""Distinct ground-atom keys across the formulas, in first-seen order.
|
|
339
|
+
|
|
340
|
+
The key is the text of each atom with every constant written by its name — these are the
|
|
341
|
+
independent variables enumerated over ``{0.0, 0.5, 1.0}``. A sorted constant, and
|
|
342
|
+
two different atoms that print alike, are refused by name (``route`` names the
|
|
343
|
+
caller in the message).
|
|
344
|
+
"""
|
|
345
|
+
return AtomKeys(route, "refuse").letters(formulas)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _instantiate(node: Node, domain: Set[str]) -> Node:
|
|
349
|
+
"""Replace every quantifier by the ∧/∨ of its domain instances.
|
|
350
|
+
|
|
351
|
+
``∀x φ`` becomes the conjunction and ``∃x φ`` the disjunction of ``φ`` with
|
|
352
|
+
the bound variable grounded to each domain element. The result is
|
|
353
|
+
quantifier-free, so its atom set is exactly the ground atoms an assignment
|
|
354
|
+
must fix. Used only to discover assignment variables — the actual value is
|
|
355
|
+
computed by ``kleene_value`` so the two stay in lock-step.
|
|
356
|
+
"""
|
|
357
|
+
if isinstance(node, Atom):
|
|
358
|
+
return node
|
|
359
|
+
if isinstance(node, Not):
|
|
360
|
+
return Not(_instantiate(node.formula, domain))
|
|
361
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff)):
|
|
362
|
+
return type(node)(_instantiate(node.left, domain),
|
|
363
|
+
_instantiate(node.right, domain))
|
|
364
|
+
if isinstance(node, Quantifier):
|
|
365
|
+
instances = [
|
|
366
|
+
_instantiate(_ground(node.formula, node.variable.name, d), domain)
|
|
367
|
+
for d in domain
|
|
368
|
+
]
|
|
369
|
+
combine = And if node.type in ("∀", "forall") else Or
|
|
370
|
+
result = instances[0]
|
|
371
|
+
for inst in instances[1:]:
|
|
372
|
+
result = combine(result, inst)
|
|
373
|
+
return result
|
|
374
|
+
return node
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _compile(node: Node, index: Dict[str, int]):
|
|
378
|
+
"""Compile a quantifier-free classical formula into ``fn(values) -> float``.
|
|
379
|
+
|
|
380
|
+
``values`` is a tuple of truth values positionally aligned with ``index``
|
|
381
|
+
(atom key → tuple position). The returned closure computes the strong-Kleene
|
|
382
|
+
value with no per-assignment AST walk and no per-atom key rendering — those
|
|
383
|
+
happen once, here, at compile time. It is the enumeration
|
|
384
|
+
fast path; it is exhaustively cross-checked against :func:`kleene_value` in the
|
|
385
|
+
test suite (they must agree on every assignment), and uses the SAME strong-
|
|
386
|
+
Kleene truth functions and the SAME rejection (:func:`_reject_if_unsupported`).
|
|
387
|
+
"""
|
|
388
|
+
if isinstance(node, Atom):
|
|
389
|
+
constant = _truth_value(node)
|
|
390
|
+
if constant is not None:
|
|
391
|
+
value = TRUE if constant else FALSE
|
|
392
|
+
return lambda values: value
|
|
393
|
+
i = index[atom_key(node)]
|
|
394
|
+
return lambda values: values[i]
|
|
395
|
+
if isinstance(node, Not):
|
|
396
|
+
inner = _compile(node.formula, index)
|
|
397
|
+
return lambda values: 1.0 - inner(values)
|
|
398
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff)):
|
|
399
|
+
fa = _compile(node.left, index)
|
|
400
|
+
fb = _compile(node.right, index)
|
|
401
|
+
if isinstance(node, And):
|
|
402
|
+
return lambda values: min(fa(values), fb(values))
|
|
403
|
+
if isinstance(node, Or):
|
|
404
|
+
return lambda values: max(fa(values), fb(values))
|
|
405
|
+
if isinstance(node, Implies):
|
|
406
|
+
return lambda values: max(1.0 - fa(values), fb(values))
|
|
407
|
+
if isinstance(node, Iff):
|
|
408
|
+
return lambda values: min(max(1.0 - fa(values), fb(values)),
|
|
409
|
+
max(1.0 - fb(values), fa(values)))
|
|
410
|
+
# Xor: (a ∨ b) ∧ ¬(a ∧ b) = min(max(a, b), 1 − min(a, b))
|
|
411
|
+
return lambda values: min(max(fa(values), fb(values)),
|
|
412
|
+
1.0 - min(fa(values), fb(values)))
|
|
413
|
+
# A Quantifier never reaches here (callers instantiate it away); any other
|
|
414
|
+
# node is rejected exactly as kleene_value would reject it.
|
|
415
|
+
_reject_if_unsupported(node)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _parameter_instances(formulas: Sequence[Node],
|
|
419
|
+
domain: Optional[Collection[str]]) -> Iterator[List[Node]]:
|
|
420
|
+
"""The formulas of a problem under each assignment of its free variables to the domain.
|
|
421
|
+
|
|
422
|
+
A variable that is free in some formula of a problem that also has a quantifier is a
|
|
423
|
+
parameter of the problem: ONE element of the ``domain`` the quantifiers range over, the
|
|
424
|
+
same in every formula. Each assignment of the domain's elements to the free variables
|
|
425
|
+
gives the formulas with those variables replaced by the constants of the elements
|
|
426
|
+
(a quantifier that binds the name keeps it: only free occurrences are replaced), and
|
|
427
|
+
the problem is decided under every assignment (valid, a consequence) or under some
|
|
428
|
+
(satisfiable). Where nothing makes the domain the range of a variable (no quantifier,
|
|
429
|
+
no domain, no free variable) the formulas come out once, unchanged.
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
ValueError: there are more assignments than :data:`MAX_MODELS`.
|
|
433
|
+
"""
|
|
434
|
+
formulas = list(formulas)
|
|
435
|
+
names: Tuple[str, ...] = ()
|
|
436
|
+
if domain and any(formula.count(Quantifier) for formula in formulas):
|
|
437
|
+
names = free_parameter_names(formulas)
|
|
438
|
+
if not names or not domain:
|
|
439
|
+
yield formulas
|
|
440
|
+
return
|
|
441
|
+
elements = sorted(set(domain), key=str)
|
|
442
|
+
total = len(elements) ** len(names)
|
|
443
|
+
if total > MAX_MODELS:
|
|
444
|
+
raise ValueError(
|
|
445
|
+
f"The {len(names)} free variable(s) {list(names)} are parameters over a domain of "
|
|
446
|
+
f"{len(elements)} elements: {len(elements)}**{len(names)} = {total} assignments, "
|
|
447
|
+
f"above MAX_MODELS = {MAX_MODELS}. Shrink the domain, bind the variables with "
|
|
448
|
+
"quantifiers, or replace them by constants.")
|
|
449
|
+
for chosen in product(elements, repeat=len(names)):
|
|
450
|
+
instance = formulas
|
|
451
|
+
for name, element in zip(names, chosen):
|
|
452
|
+
instance = [_ground(formula, name, element) for formula in instance]
|
|
453
|
+
yield instance
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _prepare_enumeration(formulas: Sequence[Node], domain: Optional[Set[str]],
|
|
457
|
+
route: str = "manyvalued"):
|
|
458
|
+
"""Ground quantifiers, collect ground-atom keys, and compile each formula.
|
|
459
|
+
|
|
460
|
+
Returns ``(keys, compiled)`` with ``compiled[i](values) == kleene_value(
|
|
461
|
+
formulas[i], dict(zip(keys, values)), domain=domain)`` for every assignment.
|
|
462
|
+
Raises ValueError if a quantified formula is given without a domain, or if the
|
|
463
|
+
``3 ** len(keys)`` enumeration would exceed :data:`MAX_MODELS`.
|
|
464
|
+
"""
|
|
465
|
+
grounded: List[Node] = []
|
|
466
|
+
for formula in formulas:
|
|
467
|
+
if formula.count(Quantifier):
|
|
468
|
+
if not domain:
|
|
469
|
+
raise ValueError(
|
|
470
|
+
"Enumerating a quantified formula requires a non-empty 'domain'."
|
|
471
|
+
)
|
|
472
|
+
grounded.append(_instantiate(formula, set(domain)))
|
|
473
|
+
else:
|
|
474
|
+
grounded.append(formula)
|
|
475
|
+
keys = _atom_keys(*grounded, route=route)
|
|
476
|
+
total = 3 ** len(keys)
|
|
477
|
+
if total > MAX_MODELS:
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"Three-valued enumeration would visit 3**{len(keys)} = {total} "
|
|
480
|
+
f"assignments, above MAX_MODELS = {MAX_MODELS}. Reduce the number of "
|
|
481
|
+
"distinct ground atoms, or raise manyvalued.MAX_MODELS if you really "
|
|
482
|
+
"want to wait."
|
|
483
|
+
)
|
|
484
|
+
index = {key: i for i, key in enumerate(keys)}
|
|
485
|
+
compiled = [_compile(g, index) for g in grounded]
|
|
486
|
+
return keys, compiled
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def is_valid(formula: Node, logic: str = "K3",
|
|
490
|
+
domain: Optional[Set[str]] = None) -> bool:
|
|
491
|
+
"""True iff the formula is designated under *every* three-valued assignment.
|
|
492
|
+
|
|
493
|
+
Enumerates all ``3**n`` assignments of the formula's ``n`` distinct ground
|
|
494
|
+
atoms to ``{0.0, 0.5, 1.0}`` (after grounding quantifiers over ``domain``)
|
|
495
|
+
and checks the value is designated for ``logic`` in each. ``n`` is the number
|
|
496
|
+
of ground atoms; cost is exponential in ``n``, so keep formulas (and the
|
|
497
|
+
domain) small. Each assignment is scored by a compiled evaluator (built once
|
|
498
|
+
from the formula); above :data:`MAX_MODELS` assignments a ValueError is raised
|
|
499
|
+
rather than hanging.
|
|
500
|
+
|
|
501
|
+
A variable that is free in a quantified formula is a parameter: one element of ``domain``,
|
|
502
|
+
and the formula is valid when it is designated under every assignment of the domain's
|
|
503
|
+
elements to its free variables as well (see the module docstring).
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
formula: a classical FOL formula node (built with ``MSFLParser()``).
|
|
507
|
+
logic: ``"K3"`` (designate {1.0}) or ``"LP"`` (designate {0.5, 1.0}).
|
|
508
|
+
domain: constant names for any quantifiers; required if quantified.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
NotImplementedError: a sorted construct, or two different atoms that print alike
|
|
512
|
+
(see the module docstring).
|
|
513
|
+
"""
|
|
514
|
+
designated = _designated_set(logic)
|
|
515
|
+
for instance in _parameter_instances([formula], domain):
|
|
516
|
+
keys, (evaluate,) = _prepare_enumeration(instance, domain, "manyvalued.is_valid")
|
|
517
|
+
if not all(evaluate(values) in designated
|
|
518
|
+
for values in product(TRUTH_VALUES, repeat=len(keys))):
|
|
519
|
+
return False
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def is_satisfiable(formula: Node, logic: str = "K3",
|
|
524
|
+
domain: Optional[Set[str]] = None) -> bool:
|
|
525
|
+
"""True iff *some* three-valued assignment designates the formula.
|
|
526
|
+
|
|
527
|
+
Enumerates the same ``3**n`` assignments as :func:`is_valid` and returns
|
|
528
|
+
True as soon as one yields a value designated for ``logic``. A variable that is free in a
|
|
529
|
+
quantified formula is a parameter over ``domain`` (see :func:`is_valid`): the formula is
|
|
530
|
+
satisfiable when it is under some assignment of the domain's elements to its free
|
|
531
|
+
variables.
|
|
532
|
+
"""
|
|
533
|
+
designated = _designated_set(logic)
|
|
534
|
+
for instance in _parameter_instances([formula], domain):
|
|
535
|
+
keys, (evaluate,) = _prepare_enumeration(instance, domain, "manyvalued.is_satisfiable")
|
|
536
|
+
if any(evaluate(values) in designated
|
|
537
|
+
for values in product(TRUTH_VALUES, repeat=len(keys))):
|
|
538
|
+
return True
|
|
539
|
+
return False
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def entails(premises: Sequence[Node], conclusion: Node, logic: str = "K3",
|
|
543
|
+
domain: Optional[Set[str]] = None) -> bool:
|
|
544
|
+
"""Designation-preserving entailment: every model of the premises models the conclusion.
|
|
545
|
+
|
|
546
|
+
True iff for every three-valued assignment that designates *all* premises,
|
|
547
|
+
the conclusion is also designated (for ``logic``). The enumeration ranges
|
|
548
|
+
over the ground atoms of the premises *and* the conclusion together.
|
|
549
|
+
|
|
550
|
+
This is where K3 and LP diverge sharply: under LP the explosion inference
|
|
551
|
+
``entails([P, ¬P], Q, "LP")`` is False (at P=0.5, Q=0.0 both premises are
|
|
552
|
+
designated yet Q is not), so LP is paraconsistent; under K3 it holds
|
|
553
|
+
vacuously because ``P`` and ``¬P`` are never both designated.
|
|
554
|
+
|
|
555
|
+
A variable that is free in some formula of a problem that has a quantifier is a parameter:
|
|
556
|
+
ONE element of ``domain``, the same in the premises and the conclusion, and the
|
|
557
|
+
conclusion must follow under every assignment of the domain's elements to the free
|
|
558
|
+
variables. ``∀y P(y) ⊢ P(x)`` holds in LP over ``{a, b}``, ``P(x) ⊢ ∃y P(y)`` holds, and
|
|
559
|
+
``P(x), Q(y) ⊢ ∀z (P(z) ∧ Q(z))`` does not.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
premises: a sequence of classical formula nodes.
|
|
563
|
+
conclusion: a classical formula node.
|
|
564
|
+
logic: ``"K3"`` or ``"LP"``.
|
|
565
|
+
domain: constant names for any quantifiers; required if quantified.
|
|
566
|
+
|
|
567
|
+
Raises:
|
|
568
|
+
NotImplementedError: a sorted construct, or two different atoms that print alike
|
|
569
|
+
(see the module docstring).
|
|
570
|
+
"""
|
|
571
|
+
designated = _designated_set(logic)
|
|
572
|
+
premises = list(premises)
|
|
573
|
+
for instance in _parameter_instances([*premises, conclusion], domain):
|
|
574
|
+
keys, compiled = _prepare_enumeration(instance, domain, "manyvalued.entails")
|
|
575
|
+
*premise_fns, conclusion_fn = compiled
|
|
576
|
+
for values in product(TRUTH_VALUES, repeat=len(keys)):
|
|
577
|
+
if all(f(values) in designated for f in premise_fns):
|
|
578
|
+
if conclusion_fn(values) not in designated:
|
|
579
|
+
return False
|
|
580
|
+
return True
|