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,372 @@
|
|
|
1
|
+
"""Non-monotonic reasoning — minimal-model entailment (predicate circumscription).
|
|
2
|
+
|
|
3
|
+
Classical entailment is *monotonic*: adding premises never retracts a conclusion.
|
|
4
|
+
Common-sense reasoning is not — "Tweety is a bird" lets you conclude "Tweety flies"
|
|
5
|
+
until you learn "Tweety is a penguin". **Circumscription** (McCarthy) captures this by
|
|
6
|
+
believing only what holds in the **minimal** models of a theory: those that make the
|
|
7
|
+
*circumscribed* predicates as small (false of as many things) as possible.
|
|
8
|
+
|
|
9
|
+
``Γ ⊨_circ φ`` (circumscribing the predicates in ``circumscribed``) holds iff ``φ`` is
|
|
10
|
+
true in every model of ``Γ`` that is ≤-minimal — where ``M ≤ M'`` means ``M`` and ``M'``
|
|
11
|
+
share a domain, constants, functions and the *non-circumscribed* (fixed) predicates,
|
|
12
|
+
and every circumscribed predicate's extension in ``M`` is a subset of its extension in
|
|
13
|
+
``M'``. With ``circumscribed=None`` *all* predicates are minimised (closed-world-style
|
|
14
|
+
minimal-model semantics), so e.g. ``∅ ⊨_circ ¬P(a)``.
|
|
15
|
+
|
|
16
|
+
The search reuses the finite model finder, so it is **bounded** (domains up to
|
|
17
|
+
``max_size``): ``True`` is minimal-model entailment over models within the bound, not a
|
|
18
|
+
proof over all (possibly infinite) models. The relation is genuinely non-monotonic —
|
|
19
|
+
adding premises can flip a ``True`` to ``False``.
|
|
20
|
+
|
|
21
|
+
**A free variable is a parameter.** It names ONE unknown element, the same in every premise
|
|
22
|
+
and in the conclusion; it is replaced, in all the formulas of a call together, by a constant
|
|
23
|
+
of its own name (:func:`~unicode_logic_kit.fol._free_parameters.parameterize`), and a constant
|
|
24
|
+
is part of the fixed part two models must share to be compared. A premise is never closed
|
|
25
|
+
universally: ``P(x) ⊨_circ P(alpha)`` fails (the minimal models make ``P`` true of ``x``
|
|
26
|
+
alone, and ``alpha`` may be another element), while ``∀x P(x) ⊨_circ P(alpha)`` holds. A
|
|
27
|
+
free variable that has the spelling of a constant of the problem is refused
|
|
28
|
+
(``NotImplementedError``): a structure holds one entry per name.
|
|
29
|
+
|
|
30
|
+
**Second-order circumscription axiom.** :func:`circumscription_formula` builds McCarthy's
|
|
31
|
+
textbook SO sentence for the same ``Γ ⊨_circ φ`` relation — ``T(P) ∧ ∀P'((T(P') ∧ P'⊆P) →
|
|
32
|
+
P⊆P')`` — so it can be checked (or exported to Isabelle/THF via
|
|
33
|
+
:mod:`unicode_logic_kit.hol.secondorder`) with the finite second-order evaluator
|
|
34
|
+
:mod:`unicode_logic_kit.semantics.secondorder` instead of the bounded model search above; the
|
|
35
|
+
two are cross-checked against each other in the test suite.
|
|
36
|
+
|
|
37
|
+
Public API: :func:`minimal_models`, :func:`minimal_entails`, :func:`circumscription_formula`,
|
|
38
|
+
:func:`circumscription_entails_so`.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
|
42
|
+
|
|
43
|
+
from ..fol._free_parameters import parameterize
|
|
44
|
+
from ..fol.nodes import (
|
|
45
|
+
Node, Atom, And, Implies, Quantifier, Variable, SecondOrderQuantifier,
|
|
46
|
+
)
|
|
47
|
+
from .tarski import Structure, models, _refuse_cardinality_as_individual
|
|
48
|
+
from .modelfinder import (
|
|
49
|
+
_Signature, _interpretations, _candidate_count, MAX_CANDIDATES,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
#: A circumscribed-predicate spec: either a bare name (arity inferred from the
|
|
53
|
+
#: premises) or an explicit ``(name, arity)`` pair (needed when the predicate is
|
|
54
|
+
#: never applied in the premises, e.g. with empty premises).
|
|
55
|
+
CircSpec = Union[str, Tuple[str, int]]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _func_key(functions) -> tuple:
|
|
59
|
+
"""A hashable key for a function interpretation."""
|
|
60
|
+
return tuple(sorted((sig, tuple(sorted(table.items())))
|
|
61
|
+
for sig, table in functions.items()))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _fixed_key(constants, functions, predicates, pred_sig, circ) -> tuple:
|
|
65
|
+
"""The 'fixed part' (everything not minimised) two comparable models must share."""
|
|
66
|
+
fixed_preds = tuple(
|
|
67
|
+
(name, ar, frozenset(predicates.get((name, ar), frozenset())))
|
|
68
|
+
for (name, ar) in pred_sig if name not in circ)
|
|
69
|
+
return (tuple(sorted(constants.items())), _func_key(functions), fixed_preds)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _circ_profile(predicates, pred_sig, circ) -> dict:
|
|
73
|
+
"""The extensions of the circumscribed predicates (what minimality compares)."""
|
|
74
|
+
return {(name, ar): frozenset(predicates.get((name, ar), frozenset()))
|
|
75
|
+
for (name, ar) in pred_sig if name in circ}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _strictly_below(prof_a: dict, prof_b: dict) -> bool:
|
|
79
|
+
"""True iff ``prof_a`` ≤ ``prof_b`` componentwise with at least one strict ⊂."""
|
|
80
|
+
if any(not (prof_a[k] <= prof_b[k]) for k in prof_a):
|
|
81
|
+
return False
|
|
82
|
+
return any(prof_a[k] < prof_b[k] for k in prof_a)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def minimal_models(premises, circumscribed: Optional[set] = None,
|
|
86
|
+
max_size: int = 4,
|
|
87
|
+
max_candidates: int = MAX_CANDIDATES,
|
|
88
|
+
extra_signature=()) -> List[Structure]:
|
|
89
|
+
"""Return the ≤-minimal finite models of ``premises`` up to ``max_size`` (bounded).
|
|
90
|
+
|
|
91
|
+
``circumscribed`` names the predicates to minimise (default: every predicate).
|
|
92
|
+
Models are compared only within a shared domain and fixed part, so the result is
|
|
93
|
+
the union of the minimal models found at each domain size. ``extra_signature`` adds
|
|
94
|
+
formulas whose symbols the models must interpret without being constrained by them
|
|
95
|
+
(so a goal's constants/predicates are total in every enumerated model).
|
|
96
|
+
|
|
97
|
+
A free variable of ``premises`` and ``extra_signature`` is a parameter shared by all of
|
|
98
|
+
them (see the module docstring): a constant of its own name that every model
|
|
99
|
+
interprets, and that two models must interpret alike to be compared.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
NotImplementedError: a free variable has the spelling of a constant of the
|
|
103
|
+
formulas, or a cardinality term ``|{v : φ}|`` is not an operand of a comparison
|
|
104
|
+
with a number (see :func:`~unicode_logic_kit.semantics.tarski.satisfies`).
|
|
105
|
+
"""
|
|
106
|
+
premises = list(premises)
|
|
107
|
+
closed, _ = parameterize(premises + list(extra_signature), after_variables=True)
|
|
108
|
+
_refuse_cardinality_as_individual(closed, "semantics.nonmonotonic")
|
|
109
|
+
sentences = closed[:len(premises)]
|
|
110
|
+
sig = _Signature()
|
|
111
|
+
for s in closed:
|
|
112
|
+
sig.scan(s)
|
|
113
|
+
pred_sig = sorted(sig.predicates)
|
|
114
|
+
circ = set(circumscribed) if circumscribed is not None else {n for n, _ in pred_sig}
|
|
115
|
+
|
|
116
|
+
result: List[Structure] = []
|
|
117
|
+
for k in range(1, max_size + 1):
|
|
118
|
+
if _candidate_count(sig, k) > max_candidates:
|
|
119
|
+
continue
|
|
120
|
+
domain = tuple(range(k))
|
|
121
|
+
found = []
|
|
122
|
+
for constants, functions, predicates in _interpretations(sig, domain):
|
|
123
|
+
structure = Structure(domain, constants=constants,
|
|
124
|
+
functions=functions, predicates=predicates)
|
|
125
|
+
if all(models(s, structure) for s in sentences):
|
|
126
|
+
found.append((constants, functions, predicates, structure))
|
|
127
|
+
# Group by fixed part; keep the circ-minimal models of each group.
|
|
128
|
+
groups = {}
|
|
129
|
+
for entry in found:
|
|
130
|
+
constants, functions, predicates, _ = entry
|
|
131
|
+
key = _fixed_key(constants, functions, predicates, pred_sig, circ)
|
|
132
|
+
profile = _circ_profile(predicates, pred_sig, circ)
|
|
133
|
+
groups.setdefault(key, []).append((entry, profile))
|
|
134
|
+
for grp in groups.values():
|
|
135
|
+
for (entry, profile) in grp:
|
|
136
|
+
if not any(_strictly_below(other, profile)
|
|
137
|
+
for (e2, other) in grp if e2 is not entry):
|
|
138
|
+
result.append(entry[3])
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def minimal_entails(premises, conclusion: Node, circumscribed: Optional[set] = None,
|
|
143
|
+
max_size: int = 4, max_candidates: int = MAX_CANDIDATES) -> bool:
|
|
144
|
+
"""Return whether ``premises`` circumscriptively entail ``conclusion`` (bounded).
|
|
145
|
+
|
|
146
|
+
``True`` iff ``conclusion`` holds in *every* minimal model of ``premises`` found up to
|
|
147
|
+
``max_size`` (see :func:`minimal_models`). Non-monotonic: strengthening ``premises`` can
|
|
148
|
+
turn a ``True`` into a ``False``. A free variable is a parameter shared by the premises
|
|
149
|
+
and the conclusion (see the module docstring): the conclusion is evaluated with each
|
|
150
|
+
model's own element for it, so ``P(x)`` circumscribing ``P`` does not entail ``P(alpha)``.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
NotImplementedError: a free variable has the spelling of a constant of the
|
|
154
|
+
formulas.
|
|
155
|
+
"""
|
|
156
|
+
premises = list(premises)
|
|
157
|
+
closed, _ = parameterize(premises + [conclusion], after_variables=True)
|
|
158
|
+
goal = closed[-1]
|
|
159
|
+
for structure in minimal_models(closed[:-1], circumscribed, max_size, max_candidates,
|
|
160
|
+
extra_signature=[goal]):
|
|
161
|
+
if not models(goal, structure):
|
|
162
|
+
return False
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Second-order circumscription axiom (McCarthy's textbook SO formula).
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
#
|
|
170
|
+
# T(P) ∧ ∀P'((T(P') ∧ P'⊆P) → P⊆P'): T holds of P, and no relation P' that is a
|
|
171
|
+
# PROPER subset of P (in a model that otherwise agrees on the domain, constants,
|
|
172
|
+
# functions, and every FIXED — non-circumscribed — predicate, since those occur
|
|
173
|
+
# as the SAME free symbol in both T(P) and T(P') and so are literally the same
|
|
174
|
+
# extension in any one structure the SO evaluator checks) also satisfies T. This
|
|
175
|
+
# is exactly what minimal_models compares within one ``_fixed_key`` group at one
|
|
176
|
+
# domain size, which is why the two are cross-checked against each other below.
|
|
177
|
+
#
|
|
178
|
+
# Predicate substitution T(P) -> T(P') is just a global RENAME of the predicate
|
|
179
|
+
# symbol P to a fresh P' (same arity, so no comprehension/lambda machinery is
|
|
180
|
+
# needed) — reused, lazily, from the sequent calculus's predicate-substitution
|
|
181
|
+
# helpers (atp.sequent) to avoid re-implementing capture-avoiding renaming.
|
|
182
|
+
|
|
183
|
+
def _pred_arity(sentences: List[Node], name: str) -> Optional[int]:
|
|
184
|
+
"""Infer ``name``'s arity from its applications in ``sentences`` (or ``None``).
|
|
185
|
+
|
|
186
|
+
Raises ValueError if ``name`` is applied at more than one arity (an
|
|
187
|
+
ill-formed signature for a single circumscribed predicate).
|
|
188
|
+
"""
|
|
189
|
+
sig = _Signature()
|
|
190
|
+
for s in sentences:
|
|
191
|
+
sig.scan(s)
|
|
192
|
+
arities = {ar for (nm, ar) in sig.predicates if nm == name}
|
|
193
|
+
if len(arities) > 1:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"circumscription_formula: predicate {name!r} occurs at conflicting "
|
|
196
|
+
f"arities {sorted(arities)} in the premises."
|
|
197
|
+
)
|
|
198
|
+
return next(iter(arities), None)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _subset_formula(sub_name: str, sup_name: str, arity: int) -> Node:
|
|
202
|
+
"""Build ``sub ⊆ sup``: ``∀x̄ (sub(x̄) → sup(x̄))``, or a bare Implies at arity 0."""
|
|
203
|
+
if arity == 0:
|
|
204
|
+
return Implies(Atom(sub_name, ()), Atom(sup_name, ()))
|
|
205
|
+
xs = tuple(Variable(f"_circ_x{i}") for i in range(arity))
|
|
206
|
+
body = Implies(Atom(sub_name, xs), Atom(sup_name, xs))
|
|
207
|
+
return _forall_chain_circ(xs, body)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _forall_chain_circ(xs, body: Node) -> Node:
|
|
211
|
+
"""Fold ``∀x1 ∀x2 … body`` over ``xs`` (local helper, mirrors modelfinder's style)."""
|
|
212
|
+
result = body
|
|
213
|
+
for x in reversed(xs):
|
|
214
|
+
result = Quantifier("∀", x, result)
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _conjoin_all(sentences: List[Node]) -> Optional[Node]:
|
|
219
|
+
"""Left-fold ``sentences`` into a binary And-tree, or ``None`` if the list is empty."""
|
|
220
|
+
if not sentences:
|
|
221
|
+
return None
|
|
222
|
+
result = sentences[0]
|
|
223
|
+
for s in sentences[1:]:
|
|
224
|
+
result = And(result, s)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def circumscription_formula(premises, circumscribed: Iterable[CircSpec], varied: tuple = ()) -> Node:
|
|
229
|
+
"""Build the second-order circumscription axiom for ``premises`` w.r.t. ``circumscribed``.
|
|
230
|
+
|
|
231
|
+
``T(P̄) ∧ ∀P̄'((T(P̄') ∧ P̄'⊆P̄) → P̄⊆P̄')`` — ``T`` is the conjunction of the
|
|
232
|
+
``premises``, in which a free variable is a PARAMETER (a constant of its own name,
|
|
233
|
+
the same in every premise; the premises are never closed universally);
|
|
234
|
+
``P̄`` are the ``circumscribed`` predicates
|
|
235
|
+
(one or more), each replaced by a FRESH predicate ``P'`` inside a second copy
|
|
236
|
+
of ``T``, with that ``P'`` bound by a second-order quantifier — **universal**
|
|
237
|
+
(``∀P'``), since the axiom says NO strictly-smaller ``P'`` also satisfies
|
|
238
|
+
``T``. Every OTHER
|
|
239
|
+
predicate is FIXED automatically: it occurs as the same free symbol in both
|
|
240
|
+
copies of ``T``, so it denotes the same extension in any one structure a
|
|
241
|
+
second-order evaluator checks — no extra bookkeeping is needed for the
|
|
242
|
+
"everything else fixed" reading.
|
|
243
|
+
|
|
244
|
+
``circumscribed`` is a non-empty iterable of predicate specs: a bare name
|
|
245
|
+
(its arity is inferred from an application in ``premises``) or an explicit
|
|
246
|
+
``(name, arity)`` pair (needed when the predicate is never applied in
|
|
247
|
+
``premises`` — e.g. empty premises; see :func:`circumscription_entails_so`,
|
|
248
|
+
which also scans the conclusion for the arity).
|
|
249
|
+
|
|
250
|
+
Only ``varied=()`` (the default) is implemented: every non-circumscribed
|
|
251
|
+
predicate is FIXED. Passing a non-empty ``varied`` (letting some predicates
|
|
252
|
+
float freely — neither minimised nor held fixed) raises ``NotImplementedError``;
|
|
253
|
+
that reading needs a second family of second-order-quantified predicates kept
|
|
254
|
+
OUTSIDE the ``⊆`` comparison, which this builder does not construct.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
NotImplementedError: for a non-empty ``varied``.
|
|
258
|
+
ValueError: if ``circumscribed`` is empty, or a bare name's arity cannot
|
|
259
|
+
be inferred (never applied in ``premises``), or a predicate is
|
|
260
|
+
applied at conflicting arities in ``premises``.
|
|
261
|
+
NotImplementedError: also when a free variable has the spelling of a constant
|
|
262
|
+
of ``premises`` (a structure holds one entry per name).
|
|
263
|
+
"""
|
|
264
|
+
if tuple(varied):
|
|
265
|
+
raise NotImplementedError(
|
|
266
|
+
"circumscription_formula: only the simple reading — every predicate "
|
|
267
|
+
"NOT circumscribed is FIXED — is implemented; a non-empty `varied` "
|
|
268
|
+
"(letting some predicates float freely, neither minimised nor held "
|
|
269
|
+
"fixed) needs a second family of second-order-quantified predicates "
|
|
270
|
+
"kept OUTSIDE the P'⊆P / P⊆P' comparison, which this builder does "
|
|
271
|
+
"not construct. Pass varied=() (the default)."
|
|
272
|
+
)
|
|
273
|
+
from ..atp.sequent import _rename_pred, _all_pred_names, _fresh_pred_name
|
|
274
|
+
|
|
275
|
+
sentences, _ = parameterize(list(premises), after_variables=True)
|
|
276
|
+
|
|
277
|
+
circ_list = sorted(circumscribed, key=lambda e: e[0] if isinstance(e, tuple) else e)
|
|
278
|
+
if not circ_list:
|
|
279
|
+
raise ValueError("circumscription_formula: `circumscribed` must name at least one predicate.")
|
|
280
|
+
|
|
281
|
+
resolved: List[Tuple[str, int]] = []
|
|
282
|
+
for entry in circ_list:
|
|
283
|
+
if isinstance(entry, tuple):
|
|
284
|
+
name, arity = entry
|
|
285
|
+
else:
|
|
286
|
+
name = entry
|
|
287
|
+
arity = _pred_arity(sentences, name)
|
|
288
|
+
if arity is None:
|
|
289
|
+
raise ValueError(
|
|
290
|
+
f"circumscription_formula: cannot infer the arity of "
|
|
291
|
+
f"circumscribed predicate {name!r} — it is never applied in "
|
|
292
|
+
"`premises`. Pass an explicit (name, arity) pair in "
|
|
293
|
+
"`circumscribed`, or build the axiom via "
|
|
294
|
+
"circumscription_entails_so(premises, circumscribed, "
|
|
295
|
+
"conclusion), which also scans the conclusion for the arity."
|
|
296
|
+
)
|
|
297
|
+
resolved.append((name, arity))
|
|
298
|
+
|
|
299
|
+
avoid = {name for name, _ in resolved}
|
|
300
|
+
for s in sentences:
|
|
301
|
+
avoid |= _all_pred_names(s)
|
|
302
|
+
|
|
303
|
+
primed: Dict[str, str] = {}
|
|
304
|
+
for name, _ in resolved:
|
|
305
|
+
p2 = _fresh_pred_name(name, avoid)
|
|
306
|
+
avoid.add(p2)
|
|
307
|
+
primed[name] = p2
|
|
308
|
+
|
|
309
|
+
conj = _conjoin_all(sentences)
|
|
310
|
+
conj_primed = conj
|
|
311
|
+
if conj_primed is not None:
|
|
312
|
+
for name, _ in resolved:
|
|
313
|
+
conj_primed = _rename_pred(conj_primed, name, primed[name])
|
|
314
|
+
|
|
315
|
+
le_primed_orig = _conjoin_all([_subset_formula(primed[n], n, ar) for n, ar in resolved])
|
|
316
|
+
le_orig_primed = _conjoin_all([_subset_formula(n, primed[n], ar) for n, ar in resolved])
|
|
317
|
+
|
|
318
|
+
antecedent = le_primed_orig if conj_primed is None else And(conj_primed, le_primed_orig)
|
|
319
|
+
minimality = Implies(antecedent, le_orig_primed)
|
|
320
|
+
|
|
321
|
+
quantified = minimality
|
|
322
|
+
for name, arity in resolved:
|
|
323
|
+
quantified = SecondOrderQuantifier("∀", primed[name], arity, quantified)
|
|
324
|
+
|
|
325
|
+
return quantified if conj is None else And(conj, quantified)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def circumscription_entails_so(premises, circumscribed: Iterable[CircSpec], conclusion: Node) -> Node:
|
|
329
|
+
"""Build ``circumscription_formula(premises, circumscribed) → conclusion`` (a closed sentence).
|
|
330
|
+
|
|
331
|
+
The caller decides validity — e.g. with
|
|
332
|
+
:func:`unicode_logic_kit.semantics.secondorder.so_is_valid_finite` (bounded, but
|
|
333
|
+
the natural finite oracle for a second-order sentence) — or exports the
|
|
334
|
+
result with :mod:`unicode_logic_kit.hol.secondorder`. This function only BUILDS
|
|
335
|
+
the implication node; it never evaluates it.
|
|
336
|
+
|
|
337
|
+
Unlike :func:`circumscription_formula`, a bare predicate name in
|
|
338
|
+
``circumscribed`` has its arity inferred from BOTH ``premises`` and
|
|
339
|
+
``conclusion`` — so e.g. ``circumscription_entails_so([], {"P"}, ¬P(b))``
|
|
340
|
+
(empty premises, ``P``'s arity known only from the conclusion) works.
|
|
341
|
+
|
|
342
|
+
A free variable is a PARAMETER shared by the premises and the conclusion: it is
|
|
343
|
+
replaced, in all of them together, by a constant of its own name before the axiom
|
|
344
|
+
is built, so the sentence is closed and says what ``minimal_entails`` decides
|
|
345
|
+
(``P(x)`` circumscribing ``P`` does not entail ``P(alpha)``).
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
ValueError: a bare predicate name is never applied in the premises or the
|
|
349
|
+
conclusion.
|
|
350
|
+
NotImplementedError: a free variable has the spelling of a constant of the
|
|
351
|
+
formulas.
|
|
352
|
+
"""
|
|
353
|
+
premises = list(premises)
|
|
354
|
+
parameterized, _ = parameterize(premises + [conclusion], after_variables=True)
|
|
355
|
+
sentences, closed_conclusion = parameterized[:-1], parameterized[-1]
|
|
356
|
+
|
|
357
|
+
resolved_circumscribed: List[Tuple[str, int]] = []
|
|
358
|
+
for entry in circumscribed:
|
|
359
|
+
if isinstance(entry, tuple):
|
|
360
|
+
resolved_circumscribed.append(entry)
|
|
361
|
+
continue
|
|
362
|
+
arity = _pred_arity(sentences + [closed_conclusion], entry)
|
|
363
|
+
if arity is None:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"circumscription_entails_so: predicate {entry!r} is never "
|
|
366
|
+
"applied in the premises or the conclusion, so its arity "
|
|
367
|
+
"cannot be inferred."
|
|
368
|
+
)
|
|
369
|
+
resolved_circumscribed.append((entry, arity))
|
|
370
|
+
|
|
371
|
+
axiom = circumscription_formula(sentences, resolved_circumscribed)
|
|
372
|
+
return Implies(axiom, closed_conclusion)
|