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,497 @@
|
|
|
1
|
+
"""A composable registry of the kit's logic-to-logic translations.
|
|
2
|
+
|
|
3
|
+
The kit has long owned a family of translation functions — the modal standard
|
|
4
|
+
translation, ALC→FOL / ALC→modal-K, dependence→ESO — each living in its home
|
|
5
|
+
module as a bare function. This module catalogues them as first-class
|
|
6
|
+
:class:`Comorphism` edges in a graph keyed by logic labels, with BFS path
|
|
7
|
+
composition: ``translate(term, "alc", "fol")`` works whether the registry has
|
|
8
|
+
a direct edge or has to compose ``alc→modal→fol``. (The name and the idea —
|
|
9
|
+
logic translations as first-class objects — are HETS'; the machinery here is
|
|
10
|
+
deliberately lightweight and native Python.)
|
|
11
|
+
|
|
12
|
+
Logic labels used by the default registry: ``"fol"`` (classical FOL),
|
|
13
|
+
``"msfol"`` (many-sorted FOL, whose sorts are non-empty), ``"modal"`` (the
|
|
14
|
+
propositional modal / temporal / deontic / hybrid family), ``"qml"``
|
|
15
|
+
(quantified modal logic with a domain regime), ``"alc"`` (description-logic
|
|
16
|
+
concepts), ``"drs"`` (discourse representation structures), ``"team"``
|
|
17
|
+
(dependence / team-semantic sentences), ``"eso"`` (existential second-order
|
|
18
|
+
formulas) and ``"fuzzy"`` (Łukasiewicz many-valued logic, whose single edge is
|
|
19
|
+
lossy). :mod:`unicode_logic_kit.logic` is the typed surface over these labels:
|
|
20
|
+
``FOL(MODAL(f))`` converts and keeps the side axioms with the term.
|
|
21
|
+
Third-party edges register with
|
|
22
|
+
:func:`register_comorphism` and become reachable from
|
|
23
|
+
:func:`unicode_logic_kit.api.translate` immediately.
|
|
24
|
+
|
|
25
|
+
A comorphism's ``apply`` maps a SOURCE-logic term to a TARGET-logic term; the
|
|
26
|
+
term type is whatever the source logic's AST is (``Node`` for the formula
|
|
27
|
+
families, ``Concept`` for ALC). Fragment restrictions of the underlying
|
|
28
|
+
functions (e.g. ``concept_to_modal`` accepts only single-role concepts)
|
|
29
|
+
surface as their own exceptions, unchanged.
|
|
30
|
+
|
|
31
|
+
STABILITY POLICY (:data:`DEFAULT_REGISTRY`): within a minor release line
|
|
32
|
+
(0.N.x) no edge — identified by its ``name`` — is removed, renamed, or has
|
|
33
|
+
its ``source``/``target``/``lossy`` changed; only new edges are added.
|
|
34
|
+
This mirrors :mod:`unicode_logic_kit.api`'s own STABILITY POLICY (see that
|
|
35
|
+
module's docstring) applied to the registry surface rather than the
|
|
36
|
+
result dataclasses, and it is what lets a caller compose
|
|
37
|
+
``translate(term, a, b)`` across a minor line without re-checking that the
|
|
38
|
+
path it found last time still exists. ``tests/test_mcp_stability.py``
|
|
39
|
+
pins ``(name, source, target, lossy)`` for the current edges as a
|
|
40
|
+
baseline-subset check.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from collections import deque
|
|
44
|
+
from dataclasses import dataclass
|
|
45
|
+
from inspect import Parameter, signature
|
|
46
|
+
from typing import Any, Callable, Dict, FrozenSet, List, Optional, Tuple
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"Comorphism", "TranslationResult", "ComorphismRegistry",
|
|
50
|
+
"DEFAULT_REGISTRY", "GUARANTEES", "register_comorphism", "weakest_guarantee",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# What a translation preserves, strongest first. An edge DECLARES one; a path
|
|
54
|
+
# gets the weakest on it (:func:`weakest_guarantee`).
|
|
55
|
+
#
|
|
56
|
+
# EVERY level is a statement about the PAIR (image, ``axioms``), never about the
|
|
57
|
+
# bare image: a translation with side conditions is faithful to its source only
|
|
58
|
+
# in the models that satisfy them. ``to_fol`` is the plain case — the unsorted
|
|
59
|
+
# image of ``(∀x:Human M(x)) → ∃x:Human M(x)`` has a model the sorted formula
|
|
60
|
+
# has none of (``Human`` empty), and the non-emptiness axiom is exactly what
|
|
61
|
+
# rules it out. So "with the axioms as separate premises" is the premise of all
|
|
62
|
+
# four readings below, and what distinguishes them is WHICH questions transfer:
|
|
63
|
+
#
|
|
64
|
+
# "faithful" the model classes correspond in both directions on the
|
|
65
|
+
# edge's own fragment (pointwise, for an edge with a free
|
|
66
|
+
# anchor), so every question transfers.
|
|
67
|
+
# "validity" a validity / entailment question transfers, but the model
|
|
68
|
+
# classes do not correspond (e.g. the image is a closed
|
|
69
|
+
# sentence that only ENCODES validity of the source).
|
|
70
|
+
# "satisfiability" only satisfiability transfers; a validity answer does NOT
|
|
71
|
+
# (e.g. a free anchor that has to be closed existentially).
|
|
72
|
+
# "lossy" none of the above; ``note`` must say what is dropped.
|
|
73
|
+
#
|
|
74
|
+
# ``None`` means the edge has not declared one — then a path's guarantee is
|
|
75
|
+
# None as well. It is NOT a synonym for "faithful": an undeclared edge makes no
|
|
76
|
+
# promise, and a caller that needs one must look at the edge.
|
|
77
|
+
#: The guarantee vocabulary, strongest first (see the comment above).
|
|
78
|
+
GUARANTEES: Tuple[str, ...] = ("faithful", "validity", "satisfiability", "lossy")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def weakest_guarantee(values) -> Optional[str]:
|
|
82
|
+
"""The weakest guarantee among ``values`` (``None`` if any is undeclared).
|
|
83
|
+
|
|
84
|
+
The order is :data:`GUARANTEES`. This is what a composed path can promise:
|
|
85
|
+
a faithful edge after a satisfiability-only one does not repair the latter.
|
|
86
|
+
"""
|
|
87
|
+
values = list(values)
|
|
88
|
+
if not values or any(v is None for v in values):
|
|
89
|
+
return None
|
|
90
|
+
return max(values, key=GUARANTEES.index)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class Comorphism:
|
|
95
|
+
"""One directed translation edge between two logic labels.
|
|
96
|
+
|
|
97
|
+
``lossy`` marks edges that do not preserve the full source semantics; it
|
|
98
|
+
must agree with ``guarantee == "lossy"`` (checked in ``__post_init__``).
|
|
99
|
+
The one lossy default edge is ``to_msfol``, the two-valued projection of
|
|
100
|
+
Łukasiewicz logic. ``note`` documents conventions a consumer must know
|
|
101
|
+
(free anchors, fragment limits), and ``axioms`` / ``options`` are the
|
|
102
|
+
side-condition producer and the option names this edge reads — see
|
|
103
|
+
:meth:`side_axioms`.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
name: str
|
|
107
|
+
source: str
|
|
108
|
+
target: str
|
|
109
|
+
apply: Callable[[Any], Any]
|
|
110
|
+
lossy: bool = False
|
|
111
|
+
note: str = ""
|
|
112
|
+
guarantee: Optional[str] = None
|
|
113
|
+
axioms: Optional[Callable[..., Tuple[Any, ...]]] = None
|
|
114
|
+
options: FrozenSet[str] = frozenset()
|
|
115
|
+
|
|
116
|
+
def __post_init__(self):
|
|
117
|
+
if self.guarantee is not None and self.guarantee not in GUARANTEES:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Comorphism {self.name!r}: guarantee {self.guarantee!r} is not "
|
|
120
|
+
f"one of {GUARANTEES}")
|
|
121
|
+
# The flag and the declaration must agree, or a consumer that reads only
|
|
122
|
+
# one of them gets a different answer than one reading the other.
|
|
123
|
+
if self.lossy and self.guarantee not in (None, "lossy"):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"Comorphism {self.name!r}: lossy=True but guarantee="
|
|
126
|
+
f"{self.guarantee!r} — a lossy edge preserves nothing")
|
|
127
|
+
if self.guarantee == "lossy" and not self.lossy:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Comorphism {self.name!r}: guarantee='lossy' but lossy=False")
|
|
130
|
+
object.__setattr__(self, "options", frozenset(self.options))
|
|
131
|
+
|
|
132
|
+
def _for(self, fn: Callable, options: Dict[str, Any]) -> Dict[str, Any]:
|
|
133
|
+
"""The subset of ``options`` that ``fn`` itself accepts.
|
|
134
|
+
|
|
135
|
+
An edge declares its option NAMES once in :attr:`options`; which of the
|
|
136
|
+
two callables reads them is a detail of the edge (a frame system, say,
|
|
137
|
+
changes the side axioms but not the translation itself). The subset is
|
|
138
|
+
read off the signature — ``**kwargs`` takes everything — so neither
|
|
139
|
+
callable is ever handed a keyword it cannot name.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
params = signature(fn).parameters
|
|
143
|
+
except (TypeError, ValueError): # builtins without signatures
|
|
144
|
+
return {}
|
|
145
|
+
if any(p.kind is Parameter.VAR_KEYWORD for p in params.values()):
|
|
146
|
+
return dict(options)
|
|
147
|
+
return {k: v for k, v in options.items() if k in params}
|
|
148
|
+
|
|
149
|
+
def side_axioms(self, term: Any, **options) -> Tuple[Any, ...]:
|
|
150
|
+
"""The axioms a caller must add for this edge, computed from ``term``.
|
|
151
|
+
|
|
152
|
+
Empty when the edge declares none. The axioms are TARGET-logic terms and
|
|
153
|
+
are never folded into the translated term: folding them in breaks every
|
|
154
|
+
validity question through the edge (0.28.0 shipped exactly that bug for
|
|
155
|
+
subsort axioms — ``api.prove(to_fol(f, signature=sig))`` then had to
|
|
156
|
+
prove the axiom itself and valid formulas came back REFUTED).
|
|
157
|
+
"""
|
|
158
|
+
if self.axioms is None:
|
|
159
|
+
return ()
|
|
160
|
+
return tuple(self.axioms(term, **self._for(self.axioms, options)))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass(frozen=True)
|
|
164
|
+
class TranslationResult:
|
|
165
|
+
"""Outcome of a (possibly composed) translation.
|
|
166
|
+
|
|
167
|
+
``path`` names the comorphisms applied, in order; ``lossy`` is True iff
|
|
168
|
+
any edge on the path is. ``note`` concatenates the edge notes so the
|
|
169
|
+
conventions travel with the result.
|
|
170
|
+
|
|
171
|
+
``axioms`` are the side conditions of the whole path, already expressed in
|
|
172
|
+
the TARGET logic (an upstream edge's axioms are translated by the remaining
|
|
173
|
+
edges, because they are terms of the intermediate logic). They are SEPARATE
|
|
174
|
+
PREMISES, never conjoined onto ``result``::
|
|
175
|
+
|
|
176
|
+
t = api.translate(f, "msfol", "fol")
|
|
177
|
+
api.prove(t.result, [*premises, *t.axioms])
|
|
178
|
+
|
|
179
|
+
``guarantee`` is the weakest one on the path (see :data:`GUARANTEES`), or
|
|
180
|
+
``None`` when some edge on it declares none.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
result: Any
|
|
184
|
+
source: str
|
|
185
|
+
target: str
|
|
186
|
+
path: Tuple[str, ...]
|
|
187
|
+
lossy: bool
|
|
188
|
+
note: str = ""
|
|
189
|
+
axioms: Tuple[Any, ...] = ()
|
|
190
|
+
guarantee: Optional[str] = None
|
|
191
|
+
|
|
192
|
+
def to_dict(self) -> dict:
|
|
193
|
+
"""JSON-compatible dict; the result serialises via its own ``to_dict``
|
|
194
|
+
when it has one, else via ``repr``."""
|
|
195
|
+
if hasattr(self.result, "to_dict"):
|
|
196
|
+
rendered = self.result.to_dict()
|
|
197
|
+
else:
|
|
198
|
+
rendered = repr(self.result)
|
|
199
|
+
return {
|
|
200
|
+
"result": rendered,
|
|
201
|
+
"source": self.source,
|
|
202
|
+
"target": self.target,
|
|
203
|
+
"path": list(self.path),
|
|
204
|
+
"lossy": self.lossy,
|
|
205
|
+
"note": self.note,
|
|
206
|
+
"axioms": [a.to_dict() if hasattr(a, "to_dict") else repr(a)
|
|
207
|
+
for a in self.axioms],
|
|
208
|
+
"guarantee": self.guarantee,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ComorphismRegistry:
|
|
213
|
+
"""A graph of :class:`Comorphism` edges with BFS path composition."""
|
|
214
|
+
|
|
215
|
+
def __init__(self):
|
|
216
|
+
self._edges: Dict[Tuple[str, str], Comorphism] = {}
|
|
217
|
+
|
|
218
|
+
def register(self, comorphism: Comorphism, replace: bool = False) -> None:
|
|
219
|
+
"""Add an edge; refuses to silently overwrite an existing one.
|
|
220
|
+
|
|
221
|
+
One edge per (source, target) pair keeps ``translate`` deterministic —
|
|
222
|
+
pass ``replace=True`` to intentionally swap an edge out.
|
|
223
|
+
"""
|
|
224
|
+
key = (comorphism.source, comorphism.target)
|
|
225
|
+
if key in self._edges and not replace:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"register: an edge {key[0]}→{key[1]} is already registered "
|
|
228
|
+
f"({self._edges[key].name!r}); pass replace=True to swap it.")
|
|
229
|
+
self._edges[key] = comorphism
|
|
230
|
+
|
|
231
|
+
def unregister(self, name: str) -> bool:
|
|
232
|
+
"""Remove the edge registered under ``name``; True iff one existed.
|
|
233
|
+
|
|
234
|
+
The inverse of :meth:`register`, added for DYNAMIC edge providers
|
|
235
|
+
(the Hets bridge re-binds its ``hets:<Name>`` edges on every
|
|
236
|
+
re-registration and must be able to drop edges the new server no
|
|
237
|
+
longer offers — leaving them would keep stale closures pointing at
|
|
238
|
+
a dead URL). Removing an unknown name is a no-op returning False,
|
|
239
|
+
not an error: refresh code calls this speculatively.
|
|
240
|
+
"""
|
|
241
|
+
for key, edge in list(self._edges.items()):
|
|
242
|
+
if edge.name == name:
|
|
243
|
+
del self._edges[key]
|
|
244
|
+
return True
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
def edges(self) -> Tuple[Comorphism, ...]:
|
|
248
|
+
"""All registered edges, sorted by (source, target) for determinism."""
|
|
249
|
+
return tuple(self._edges[k] for k in sorted(self._edges))
|
|
250
|
+
|
|
251
|
+
def find_path(self, source: str, target: str) -> List[Comorphism]:
|
|
252
|
+
"""Shortest edge sequence from ``source`` to ``target`` (BFS).
|
|
253
|
+
|
|
254
|
+
Deterministic: neighbours are explored in sorted label order, so equal
|
|
255
|
+
length paths always resolve the same way. Raises ``ValueError`` when no
|
|
256
|
+
path exists — with the known labels in the message, because a typo in a
|
|
257
|
+
logic label should fail loudly.
|
|
258
|
+
"""
|
|
259
|
+
if source == target:
|
|
260
|
+
return []
|
|
261
|
+
labels = sorted({l for k in self._edges for l in k})
|
|
262
|
+
if source not in labels or target not in labels:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"find_path: unknown logic label in {source!r}→{target!r} "
|
|
265
|
+
f"(known: {labels})")
|
|
266
|
+
# BFS over labels; predecessors remember the edge that reached a label.
|
|
267
|
+
seen = {source}
|
|
268
|
+
queue = deque([source])
|
|
269
|
+
via: Dict[str, Comorphism] = {}
|
|
270
|
+
while queue:
|
|
271
|
+
here = queue.popleft()
|
|
272
|
+
for (s, t), edge in sorted(self._edges.items()):
|
|
273
|
+
if s != here or t in seen:
|
|
274
|
+
continue
|
|
275
|
+
via[t] = edge
|
|
276
|
+
if t == target:
|
|
277
|
+
path = [edge]
|
|
278
|
+
while path[0].source != source:
|
|
279
|
+
path.insert(0, via[path[0].source])
|
|
280
|
+
return path
|
|
281
|
+
seen.add(t)
|
|
282
|
+
queue.append(t)
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f"find_path: no comorphism path {source!r}→{target!r} "
|
|
285
|
+
f"(edges: {[f'{c.source}→{c.target}' for c in self.edges()]})")
|
|
286
|
+
|
|
287
|
+
def carry(self, term: Any, source: str, target: str, **options) -> Any:
|
|
288
|
+
"""Translate ``term`` along the path WITHOUT collecting side axioms.
|
|
289
|
+
|
|
290
|
+
For a term that IS a side axiom: it has to be expressed in the target
|
|
291
|
+
logic like any other term, but the edges' own axioms were already
|
|
292
|
+
collected once by the :meth:`translate` call that produced it, and
|
|
293
|
+
collecting them again per axiom would multiply them.
|
|
294
|
+
"""
|
|
295
|
+
path = self.find_path(source, target)
|
|
296
|
+
accepted = {name for edge in path for name in edge.options}
|
|
297
|
+
unknown = sorted(set(options) - accepted)
|
|
298
|
+
if unknown:
|
|
299
|
+
raise ValueError(
|
|
300
|
+
f"carry: option(s) {unknown} are accepted by no edge on "
|
|
301
|
+
f"{source!r}→{target!r} (path {[e.name for e in path]}, "
|
|
302
|
+
f"accepted: {sorted(accepted)})")
|
|
303
|
+
for edge in path:
|
|
304
|
+
mine = {k: v for k, v in options.items() if k in edge.options}
|
|
305
|
+
term = edge.apply(term, **edge._for(edge.apply, mine))
|
|
306
|
+
return term
|
|
307
|
+
|
|
308
|
+
def translate(self, term: Any, source: str, target: str,
|
|
309
|
+
**options) -> TranslationResult:
|
|
310
|
+
"""Apply the (composed) translation ``source``→``target`` to ``term``.
|
|
311
|
+
|
|
312
|
+
``options`` are forwarded to the edges on the path that DECLARE them in
|
|
313
|
+
:attr:`Comorphism.options` (e.g. a frame system for a modal edge). An
|
|
314
|
+
option no edge on the path declares raises ``ValueError`` naming the
|
|
315
|
+
path and what it does accept — a silently ignored option would answer a
|
|
316
|
+
different question than the caller asked.
|
|
317
|
+
|
|
318
|
+
Side axioms accumulate along the path: each edge contributes its own
|
|
319
|
+
(computed from the term it is handed), and the axioms collected so far
|
|
320
|
+
are carried through the edge as well, since they are terms of its source
|
|
321
|
+
logic. An edge that cannot carry an axiom raises; the path is then not
|
|
322
|
+
usable for that term, rather than quietly dropping the condition.
|
|
323
|
+
"""
|
|
324
|
+
path = self.find_path(source, target)
|
|
325
|
+
accepted = {name for edge in path for name in edge.options}
|
|
326
|
+
unknown = sorted(set(options) - accepted)
|
|
327
|
+
if unknown:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
f"translate: option(s) {unknown} are accepted by no edge on "
|
|
330
|
+
f"{source!r}→{target!r} (path "
|
|
331
|
+
f"{[e.name for e in path]}, accepted: {sorted(accepted)})")
|
|
332
|
+
result = term
|
|
333
|
+
axioms: Tuple[Any, ...] = ()
|
|
334
|
+
for edge in path:
|
|
335
|
+
mine = {k: v for k, v in options.items() if k in edge.options}
|
|
336
|
+
fresh = edge.side_axioms(result, **mine)
|
|
337
|
+
for_apply = edge._for(edge.apply, mine)
|
|
338
|
+
try:
|
|
339
|
+
carried = tuple(edge.apply(a, **for_apply) for a in axioms)
|
|
340
|
+
except Exception as exc: # pragma: no cover - guard
|
|
341
|
+
raise ValueError(
|
|
342
|
+
f"translate: edge {edge.name!r} cannot carry a side axiom "
|
|
343
|
+
f"of an earlier edge on {source!r}→{target!r}; this path "
|
|
344
|
+
f"cannot answer the question for this term") from exc
|
|
345
|
+
result = edge.apply(result, **for_apply)
|
|
346
|
+
axioms = carried + fresh
|
|
347
|
+
notes = "; ".join(e.note for e in path if e.note)
|
|
348
|
+
return TranslationResult(
|
|
349
|
+
result=result, source=source, target=target,
|
|
350
|
+
path=tuple(e.name for e in path),
|
|
351
|
+
lossy=any(e.lossy for e in path), note=notes,
|
|
352
|
+
axioms=axioms,
|
|
353
|
+
# The empty path (source == target) is the identity translation.
|
|
354
|
+
guarantee=("faithful" if not path
|
|
355
|
+
else weakest_guarantee([e.guarantee for e in path])))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _build_default_registry() -> ComorphismRegistry:
|
|
359
|
+
"""The default edges: exactly the kit's existing translation functions.
|
|
360
|
+
|
|
361
|
+
Note what is deliberately NOT an edge: ``circumscription_entails_so`` is an
|
|
362
|
+
entailment decision rather than a term-to-term translation; the Isabelle /
|
|
363
|
+
THF / TPTP / CASL writers are serialisers into text, not into another
|
|
364
|
+
logic's AST, so a path never runs through them; and ``eliminate_lambdas``
|
|
365
|
+
and the ChemLog renamings stay inside one logic. ``qml_translate`` has its
|
|
366
|
+
own ``qml → fol`` edge (the propositional standard translation keeps
|
|
367
|
+
``modal → fol``), because the two differ in more than the source language:
|
|
368
|
+
the quantified one sorts the domain into worlds and objects and needs the
|
|
369
|
+
sort discipline among its side axioms.
|
|
370
|
+
"""
|
|
371
|
+
from .fol.modal_translation import frame_axioms, standard_translation
|
|
372
|
+
from .fol.qml import qml_axioms, qml_translate
|
|
373
|
+
from .fol._msfl_nodes import sort_axioms, subsort_axioms, to_fol
|
|
374
|
+
from .dl.translate import concept_to_fol, concept_to_modal, _reject_data_concept
|
|
375
|
+
from .drt.export import drs_to_fol
|
|
376
|
+
from .drt.reverse import fol_to_drs
|
|
377
|
+
from .semantics.team_translation import dependence_to_eso
|
|
378
|
+
|
|
379
|
+
def alc_to_fol(concept):
|
|
380
|
+
# Faithful with no side axioms is true of a concept WITHOUT a data
|
|
381
|
+
# restriction; with one the sorts and the datatype lattice are axioms
|
|
382
|
+
# of a knowledge base (dl.kb_to_fol(..., query=[concept])), so the edge
|
|
383
|
+
# refuses it by name instead of answering for a weaker theory.
|
|
384
|
+
_reject_data_concept(concept, "alc→fol (dl.concept_to_fol)")
|
|
385
|
+
return concept_to_fol(concept)
|
|
386
|
+
|
|
387
|
+
registry = ComorphismRegistry()
|
|
388
|
+
registry.register(Comorphism(
|
|
389
|
+
name="standard_translation", source="modal", target="fol",
|
|
390
|
+
apply=standard_translation,
|
|
391
|
+
guarantee="faithful",
|
|
392
|
+
# frame_axioms returns a list; an edge's axioms producer returns a
|
|
393
|
+
# tuple, because TranslationResult.axioms accumulates along a path.
|
|
394
|
+
axioms=lambda term, **options: tuple(frame_axioms(term, **options)),
|
|
395
|
+
options=frozenset({"frame", "systems", "temporal_closure"}),
|
|
396
|
+
note="anchored at the FREE world variable 'w' — universally close it "
|
|
397
|
+
"(∀w …) for validity questions, existentially for satisfiability; "
|
|
398
|
+
"the frame conditions of every relation the image mentions are in "
|
|
399
|
+
".axioms (pass frame=/systems= to choose the systems), and so is "
|
|
400
|
+
"the membership of every sorted constant c:S of the formula in S "
|
|
401
|
+
"at every world, ∀v0 S(c, v0) — a constant is a rigid designator, "
|
|
402
|
+
"and without that axiom the image has a countermodel in which c "
|
|
403
|
+
"is no S. Take them as SEPARATE premises, never conjoined onto "
|
|
404
|
+
"the image",
|
|
405
|
+
))
|
|
406
|
+
registry.register(Comorphism(
|
|
407
|
+
name="qml_translate", source="qml", target="fol",
|
|
408
|
+
apply=qml_translate,
|
|
409
|
+
guarantee="faithful",
|
|
410
|
+
# qml_axioms takes the formula as a KEYWORD and gates its output on the
|
|
411
|
+
# relations and sorts occurring in it, so the adapter passes it there.
|
|
412
|
+
axioms=lambda term, **options: tuple(qml_axioms(formula=term, **options)),
|
|
413
|
+
options=frozenset({"mode", "frame", "systems", "bridges",
|
|
414
|
+
"temporal_closure"}),
|
|
415
|
+
note="quantified modal logic: sorted into World/Object with the "
|
|
416
|
+
"existence predicate E; anchored at the free world variable 'w'. "
|
|
417
|
+
".axioms carries the sort discipline, the domain regime of mode= "
|
|
418
|
+
"and the frame conditions — the image answers nothing without "
|
|
419
|
+
"them (fol.qml.qml_validity_formula bundles all of it into one "
|
|
420
|
+
"closed sentence instead)",
|
|
421
|
+
))
|
|
422
|
+
registry.register(Comorphism(
|
|
423
|
+
name="to_fol", source="msfol", target="fol",
|
|
424
|
+
apply=to_fol,
|
|
425
|
+
guarantee="faithful",
|
|
426
|
+
# Sort non-emptiness and the membership of every sorted constant in its
|
|
427
|
+
# sort are read off the sentence (to_fol drops both: ``c:S`` becomes the
|
|
428
|
+
# plain ``c``); a SUBSORT hierarchy is not in the term at all, so it has
|
|
429
|
+
# to come from the signature.
|
|
430
|
+
axioms=lambda term, signature=None: (
|
|
431
|
+
tuple(sort_axioms(term))
|
|
432
|
+
+ (tuple(subsort_axioms(signature)) if signature is not None else ())),
|
|
433
|
+
options=frozenset({"signature"}),
|
|
434
|
+
note="many-sorted → unsorted: sorts become unary guards and a sorted "
|
|
435
|
+
"constant c:S becomes the plain c. .axioms has one non-emptiness "
|
|
436
|
+
"sentence per sort (the kit's MSFOL convention), one membership "
|
|
437
|
+
"atom S(c) per sorted constant (without it the image forgets that "
|
|
438
|
+
"c is in S) and, with signature=, one implication per declared "
|
|
439
|
+
"subsort edge; all of them are SEPARATE premises — conjoining "
|
|
440
|
+
"them onto the image makes a prover prove the axiom and valid "
|
|
441
|
+
"formulas come back REFUTED. Take .axioms of the premises AND of "
|
|
442
|
+
"the conclusion",
|
|
443
|
+
))
|
|
444
|
+
registry.register(Comorphism(
|
|
445
|
+
name="to_msfol", source="fuzzy", target="msfol",
|
|
446
|
+
apply=lambda term: term.to_msfol(),
|
|
447
|
+
lossy=True, guarantee="lossy",
|
|
448
|
+
note="Łukasiewicz → classical is a TWO-VALUED PROJECTION: weak and "
|
|
449
|
+
"strong conjunction get the same image, so it is sound only on "
|
|
450
|
+
"crisp {0,1} valuations and is not a validity-preserving route "
|
|
451
|
+
"for fuzzy logic (use semantics.fuzzy / the fuzzy Kripke route)",
|
|
452
|
+
))
|
|
453
|
+
registry.register(Comorphism(
|
|
454
|
+
name="drs_to_fol", source="drs", target="fol",
|
|
455
|
+
apply=drs_to_fol,
|
|
456
|
+
guarantee="faithful",
|
|
457
|
+
note="a DRS is a closed box: the image is a sentence, no anchor",
|
|
458
|
+
))
|
|
459
|
+
registry.register(Comorphism(
|
|
460
|
+
name="fol_to_drs", source="fol", target="drs",
|
|
461
|
+
apply=fol_to_drs,
|
|
462
|
+
guarantee="faithful",
|
|
463
|
+
note="the inverse of drs_to_fol ON ITS IMAGE; a formula outside that "
|
|
464
|
+
"image is refused by name (FolToDrsError), never approximated",
|
|
465
|
+
))
|
|
466
|
+
registry.register(Comorphism(
|
|
467
|
+
name="concept_to_modal", source="alc", target="modal",
|
|
468
|
+
apply=concept_to_modal,
|
|
469
|
+
guarantee="faithful",
|
|
470
|
+
note="single-role concepts only (ALC with one role = modal K); "
|
|
471
|
+
"multi-role concepts raise",
|
|
472
|
+
))
|
|
473
|
+
registry.register(Comorphism(
|
|
474
|
+
name="concept_to_fol", source="alc", target="fol",
|
|
475
|
+
apply=alc_to_fol,
|
|
476
|
+
guarantee="faithful",
|
|
477
|
+
note="one free individual variable 'x' — close existentially for "
|
|
478
|
+
"satisfiability questions; read against the EMPTY knowledge base. "
|
|
479
|
+
"A concept with a data restriction is refused by name "
|
|
480
|
+
"(UnsupportedDatatypeError): its sort and datatype axioms belong "
|
|
481
|
+
"to a knowledge base — ask dl.kb_to_fol(..., query=[concept])",
|
|
482
|
+
))
|
|
483
|
+
registry.register(Comorphism(
|
|
484
|
+
name="dependence_to_eso", source="team", target="eso",
|
|
485
|
+
apply=dependence_to_eso,
|
|
486
|
+
guarantee="faithful",
|
|
487
|
+
note="sentences only (no free variables)",
|
|
488
|
+
))
|
|
489
|
+
return registry
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
DEFAULT_REGISTRY = _build_default_registry()
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def register_comorphism(comorphism: Comorphism, replace: bool = False) -> None:
|
|
496
|
+
"""Register an edge in the DEFAULT registry (see :class:`ComorphismRegistry`)."""
|
|
497
|
+
DEFAULT_REGISTRY.register(comorphism, replace=replace)
|