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,794 @@
|
|
|
1
|
+
"""A second, independent external OWL 2 DL reasoner route — over Hets/Docker,
|
|
2
|
+
for the ALCHQ + I + O fragment (inverse roles, nominals) — via the ``Fact``
|
|
3
|
+
prover the ``spechub2/hets:latest`` image bundles.
|
|
4
|
+
|
|
5
|
+
This is C43 ("OWL2-DL reasoner fallback via Hets") as actually built, after a
|
|
6
|
+
live Phase-0 capability spike (see "Phase 0 spike findings" below) and after
|
|
7
|
+
re-checking the roadmap item's own claims against the code that shipped
|
|
8
|
+
since it was written (see "Deviations from the roadmap spec" below). The
|
|
9
|
+
short version: :mod:`unicode_logic_kit.dl.owl_reasoner` (added by C43's own
|
|
10
|
+
sibling item, N5) already gives this kit a full ALCHQ+I+O reasoner
|
|
11
|
+
(HermiT via ``owlready2``) and a full-ontology OWL 2 Functional-Style Syntax
|
|
12
|
+
writer (:mod:`unicode_logic_kit.dl.owl_functional`, ALCHQ only) — so the ONLY
|
|
13
|
+
thing left for a Hets route to add is a SECOND, INDEPENDENT oracle: a
|
|
14
|
+
different reasoner (FaCT++, not HermiT), reached a different way (a Docker
|
|
15
|
+
container's REST API, not an in-process JVM binding), so an agreement
|
|
16
|
+
between the two is not just one implementation checking itself twice.
|
|
17
|
+
|
|
18
|
+
Phase 0 spike findings (live, 2026-09-18, against the exact digest
|
|
19
|
+
``hets/docker.py`` already pins — see that module's own "Container image
|
|
20
|
+
quirks" section, which this module's findings were folded back into)
|
|
21
|
+
------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
1. The image DOES parse OWL 2: uploading a ``.omn`` (Manchester) or ``.ofn``
|
|
24
|
+
(Functional-Style) file makes ``GET /dg/<iri>?format=json`` report
|
|
25
|
+
``"logic": "OWL"`` for the resulting development-graph node, alongside
|
|
26
|
+
CASL nodes.
|
|
27
|
+
2. ``GET /provers/<iri>?format=json`` for an OWL-typed file lists
|
|
28
|
+
``Fact, eprover, darwin, darwin-non-fd, Vampire, MathServeBroker, SPASS,
|
|
29
|
+
EProver, Darwin`` — **no** ``Pellet``, **no** ``HermiT``. Only ``Fact``
|
|
30
|
+
(FaCT++, confirmed live inside the image: ``/usr/lib/hets/hets-owl-tools/
|
|
31
|
+
lib/uk.ac.manchester.cs.owl.factplusplus-P5.0-v1.6.3.1.jar`` plus its
|
|
32
|
+
native ``libFaCTPlusPlusJNI.so``, invoked as its own ``java`` subprocess by
|
|
33
|
+
the Hets server, not by this Python process) is a genuine DL reasoner; the
|
|
34
|
+
rest are the FOL provers :mod:`unicode_logic_kit.hets.docker`'s docstring
|
|
35
|
+
already catalogues (some broken there too), reachable for OWL input only
|
|
36
|
+
through a translation chain (``OWL22CASL:...``) that this module does NOT
|
|
37
|
+
use — see point 3.
|
|
38
|
+
3. **Leaving ``reasoner`` unset (Hets's own default) is broken for OWL
|
|
39
|
+
input**: ``POST /consistency-check`` with no explicit reasoner picks an
|
|
40
|
+
``OWL22CASL:CASL2SoftFOL``-family translation and then crashes outright —
|
|
41
|
+
live-observed: ``*** Error: SuleCFOL2SoftFOL.transPREDSYMB: unknown pred:
|
|
42
|
+
Qual_pred_name owl_uNothing ...`` — on an ontology as simple as one class
|
|
43
|
+
asserted a subclass of ``owl:Nothing``. This module therefore ALWAYS
|
|
44
|
+
passes ``reasoner="Fact"`` explicitly (see :data:`_REASONER`) and never
|
|
45
|
+
lets Hets choose, unlike :mod:`unicode_logic_kit.atp.hets_backend`'s FOL
|
|
46
|
+
route (where an unset reasoner is fine and used).
|
|
47
|
+
4. With ``reasoner="Fact"`` explicit, both directions verified live and
|
|
48
|
+
correctly: an ontology whose ABox is genuinely inconsistent (an
|
|
49
|
+
individual in two disjoint classes) answers ``"Inconsistent"`` with FaCT++
|
|
50
|
+
provenance in ``prover_output``; a merely-unsatisfiable-but-unused named
|
|
51
|
+
class (``C ⊑ owl:Nothing`` with no individual ever asserted into ``C``)
|
|
52
|
+
correctly still answers ``"Consistent"`` (an empty class does not make the
|
|
53
|
+
KB inconsistent) — i.e. this is not a reasoner that rubber-stamps
|
|
54
|
+
"Consistent" the way the broken FOL provers rubber-stamp ``"Open"``
|
|
55
|
+
(see :mod:`unicode_logic_kit.hets.docker`'s docstring for that precedent).
|
|
56
|
+
A third result, ``"Timeout"``, is also live-confirmed (forced with
|
|
57
|
+
``timeLimit: 0``) — treated exactly like an unrecognised result: this
|
|
58
|
+
module raises rather than guessing (see :class:`HetsOwlError`).
|
|
59
|
+
5. **No AGPL callout is needed.** The roadmap spec's Phase-0 instruction was
|
|
60
|
+
conditional: add one "if a Pellet-backed route is what's actually live".
|
|
61
|
+
It is not — the only live DL reasoner is FaCT++, whose kernel is
|
|
62
|
+
**LGPL-2.1** (confirmed: FaCT++'s own project page,
|
|
63
|
+
http://owl.cs.manchester.ac.uk/tools/fact/, and the FSF Free Software
|
|
64
|
+
Directory both state LGPL for the reasoner kernel), and this kit reaches
|
|
65
|
+
it over the same REST/Docker network boundary
|
|
66
|
+
:mod:`unicode_logic_kit.hets` already draws for the whole subpackage (see
|
|
67
|
+
its module docstring's "Architecture" section) — no different in kind
|
|
68
|
+
from the SPASS/darwin route :mod:`unicode_logic_kit.atp.hets_backend`
|
|
69
|
+
already ships uncommented-on. This is a genuine, recorded deviation from
|
|
70
|
+
the roadmap spec's ``description``/``dependencies`` fields, which framed
|
|
71
|
+
the AGPL callout as likely needed; it turned out not to apply.
|
|
72
|
+
6. A differential battery against BOTH existing routes — :mod:`unicode_logic_kit.dl.tableau`
|
|
73
|
+
on ALCHQ (satisfiability, subsumption, the RBox, qualified number
|
|
74
|
+
restrictions) and :mod:`unicode_logic_kit.dl.owl_reasoner` on the I/O
|
|
75
|
+
fragment it alone decides — passed 21/21 live during this spike, reusing
|
|
76
|
+
the SAME hand-checked fixtures ``tests/test_owl_reasoner.py`` already
|
|
77
|
+
uses (see ``tests/test_hets_owl.py``, this module's test file, for the
|
|
78
|
+
shipped, pytest-integrated version of that spike).
|
|
79
|
+
|
|
80
|
+
Deviations from the roadmap spec
|
|
81
|
+
----------------------------------
|
|
82
|
+
* **No ``dl/owl_export.py``.** The spec's Phase 1 asked for one; it does not
|
|
83
|
+
exist because N5 already shipped the equivalent capability twice over —
|
|
84
|
+
:func:`unicode_logic_kit.dl.owl_functional.to_owl_functional` (ALCHQ
|
|
85
|
+
TBox+ABox -> OWL 2 Functional-Style Syntax text) and
|
|
86
|
+
:mod:`unicode_logic_kit.dl.owl_reasoner` (the full ALCHQ+I+O reasoner
|
|
87
|
+
itself). This module does not even reuse ``to_owl_functional`` for its own
|
|
88
|
+
rendering, though — see "Why this module renders its own OWL text" below.
|
|
89
|
+
* **No ``DLHetsBackend`` class / ``atp.protocol.Verdict`` mapping.** The
|
|
90
|
+
spec's Phase 2 sketch mirrored :class:`unicode_logic_kit.atp.hets_backend.HetsBackend`'s
|
|
91
|
+
``ProverBackend`` shape. This module instead mirrors
|
|
92
|
+
:mod:`unicode_logic_kit.dl.owl_reasoner`'s shape (free functions returning
|
|
93
|
+
plain ``bool``/``Set``/``List``/``Dict``, one per :mod:`unicode_logic_kit.dl.tableau`
|
|
94
|
+
namesake, raising rather than returning an error-carrying envelope) — DL
|
|
95
|
+
reasoning is not part of the ``atp`` FOL-prover registry (:mod:`unicode_logic_kit.dl`
|
|
96
|
+
already draws that line: even ``owl_reasoner`` does not implement
|
|
97
|
+
``ProverBackend``), and the differential-testing story this task's own
|
|
98
|
+
``test_oracle`` asks for ("agree exactly with dl.tableau ... and with
|
|
99
|
+
dl.owl_reasoner") is far more direct when all three routes share one
|
|
100
|
+
calling convention.
|
|
101
|
+
* **No general ALCHQ FOL fallback via Hets.** The roadmap's own
|
|
102
|
+
``existing_coverage`` field already notes this exists in a different
|
|
103
|
+
shape: :func:`unicode_logic_kit.dl.translate.concept_to_fol` feeds ALCHQ
|
|
104
|
+
into the WHOLE ``atp`` backend stable, including
|
|
105
|
+
:class:`unicode_logic_kit.atp.hets_backend.HetsBackend` itself (SPASS via
|
|
106
|
+
CASL). This module adds nothing there; its only reason to exist is the
|
|
107
|
+
I/O fragment neither the in-house tableau nor that FOL route can decide,
|
|
108
|
+
PLUS a second, differently-implemented DL reasoner for the ALCHQ fragment
|
|
109
|
+
as a cross-check.
|
|
110
|
+
|
|
111
|
+
Why this module renders its own OWL text (not ``dl.owl_functional``'s)
|
|
112
|
+
--------------------------------------------------------------------------
|
|
113
|
+
:mod:`unicode_logic_kit.dl.owl_functional` is deliberately ALCHQ-only — its
|
|
114
|
+
``to_owl_functional``/``to_owl_functional_class_expression`` RAISE
|
|
115
|
+
``TypeError`` on an :class:`~unicode_logic_kit.dl.concepts.InverseRole`-valued
|
|
116
|
+
role or a :class:`~unicode_logic_kit.dl.concepts.Nominal` (see that module's
|
|
117
|
+
own docstring), because ALCHQ-only round-tripping is its whole contract.
|
|
118
|
+
This module needs the FULL ALCHQ+I+O fragment in TEXT form (Hets's REST API
|
|
119
|
+
only takes uploaded file text, unlike ``owlready2``'s Python object API,
|
|
120
|
+
which is why :mod:`unicode_logic_kit.dl.owl_reasoner` could sidestep this
|
|
121
|
+
exact problem by never going through text at all — see that module's own
|
|
122
|
+
"Translation route" section). So :func:`_render_document` is a second,
|
|
123
|
+
small, ALCHQ+I+O-complete OWL 2 Functional-Style Syntax renderer, written
|
|
124
|
+
fresh rather than extending ``owl_functional.py`` (out of this task's file
|
|
125
|
+
ownership, and extending its ALCHQ-only contract would be the wrong change
|
|
126
|
+
for a module whose entire point is "round-trips exactly ALCHQ, nothing
|
|
127
|
+
more"). It reuses that module's naming-and-precedence-free INSIGHT
|
|
128
|
+
(``Keyword(arg arg ...)`` needs no precedence table — see
|
|
129
|
+
``owl_functional.py``'s "No ambiguity, no precedence" section) but not its
|
|
130
|
+
code, and it takes a different, simpler approach to names than either
|
|
131
|
+
sibling module:
|
|
132
|
+
|
|
133
|
+
Names: fresh synthetic local names, never round-tripped, exactly like
|
|
134
|
+
``dl.owl_reasoner``'s ``_Ctx``
|
|
135
|
+
-----------------------------------------------------------------------
|
|
136
|
+
OWL 2 Functional-Style Syntax has no "bare word" name token — every name is
|
|
137
|
+
either a full ``<IRI>`` or a *prefixed* name (``PNAME_LN``, e.g. ``:Doctor``
|
|
138
|
+
or ``ex:Doctor``); a bare ``Doctor`` with no prefix and no angle brackets is
|
|
139
|
+
a PARSE ERROR (live-confirmed: the OWL API's functional-syntax parser
|
|
140
|
+
reports ``Encountered " <PN_LOCAL> "Doctor "" ... Was expecting ... <PNAME_LN>``).
|
|
141
|
+
This kit's own :class:`~unicode_logic_kit.dl.concepts.Atomic`/role/individual
|
|
142
|
+
names are arbitrary opaque strings (may contain the kit's own Unicode DL
|
|
143
|
+
glyphs, whitespace, parentheses, ...) with no such restriction, and — just
|
|
144
|
+
as in ``dl.owl_reasoner``'s identical situation — every question this
|
|
145
|
+
module answers is a yes/no (or a `Set`/`List` of the CALLER's OWN concept
|
|
146
|
+
objects, for realize/retrieval — never "what is this entity called back"),
|
|
147
|
+
so there is nothing to lose by never sending the original name through
|
|
148
|
+
Hets's OWL/IRI machinery at all. :class:`_NameMap` allocates one fresh
|
|
149
|
+
``:C<n>``/``:R<n>``/``:I<n>`` PNAME_LN token per distinct kit name on first
|
|
150
|
+
use (a bare ``:`` DEFAULT-prefixed local name — declared once, via
|
|
151
|
+
``Prefix(:=<...>)``, per uploaded document — is a valid PNAME_LN and needs
|
|
152
|
+
no per-name IRI-safety reasoning at all, unlike ``owl_functional.py``'s
|
|
153
|
+
``to_owl_functional``, which instead renders a kit name back VERBATIM,
|
|
154
|
+
bracketed as a full IRI when it contains ``"://"`` — that module's contract
|
|
155
|
+
is "faithful round-trip of THIS name", this module's is "a yes/no verdict
|
|
156
|
+
Hets can compute", and the synthetic-name approach is simpler because it
|
|
157
|
+
does not need to be).
|
|
158
|
+
|
|
159
|
+
Wire fact this module relies on: the development-graph node name Hets
|
|
160
|
+
assigns an uploaded OWL file is exactly the ``Ontology(<iri> ...)`` IRI
|
|
161
|
+
declared inside it (live-confirmed against every probe file used in this
|
|
162
|
+
spike) — so :func:`_render_document` always declares the SAME fixed
|
|
163
|
+
:data:`_ONTOLOGY_IRI`, and every :meth:`~unicode_logic_kit.hets.client.HetsClient.consistency_check`
|
|
164
|
+
call in this module passes that same constant as ``node``, with no per-call
|
|
165
|
+
``GET /dg`` round trip needed to discover it (contrast
|
|
166
|
+
:mod:`unicode_logic_kit.hets.bridge`, which DOES need that round trip, because
|
|
167
|
+
its CASL input's node name is caller-chosen, not a constant this module
|
|
168
|
+
controls).
|
|
169
|
+
|
|
170
|
+
The data layer
|
|
171
|
+
----------------
|
|
172
|
+
FaCT++ is an SROIQ(D) reasoner, so unlike ``dl.owl_reasoner`` (whose HermiT
|
|
173
|
+
bridge refuses the data layer by name) THIS oracle can be asked about it:
|
|
174
|
+
:func:`_render_document` writes the data box and the data assertions, with
|
|
175
|
+
OWL 2's own datatype and facet names verbatim (``xsd:integer`` means the same
|
|
176
|
+
to FaCT++ as to us) and the same fresh synthetic names for data properties
|
|
177
|
+
(``:P<n>``) and user-defined datatypes (``:T<n>``) it gives classes and roles --
|
|
178
|
+
a LITERAL's datatype included: ``"5"^^:T1``, never the kit's own datatype name,
|
|
179
|
+
which the document does not declare.
|
|
180
|
+
The ``xsd``/``rdfs``/``rdf`` prefixes are declared only for a knowledge base
|
|
181
|
+
that mentions a datatype, so every data-free document is unchanged. This is an
|
|
182
|
+
independent check on the kit's own two-sorted FOL image, not a replacement for
|
|
183
|
+
it; no test here starts the container.
|
|
184
|
+
|
|
185
|
+
The one primitive: :func:`_kb_consistent`
|
|
186
|
+
--------------------------------------------
|
|
187
|
+
Every public ``external_*`` function reduces to it, exactly the way
|
|
188
|
+
``dl.owl_reasoner``'s whole public API reduces to its own ``_kb_consistent``,
|
|
189
|
+
and ``dl.tableau``'s to ``_solve`` — see "What each function decides" in
|
|
190
|
+
``dl.owl_reasoner``'s module docstring, which this module's reduction chain
|
|
191
|
+
mirrors call-for-call (``external_subsumes`` reduces to
|
|
192
|
+
``external_concept_satisfiable`` exactly the way ``subsumes`` reduces to
|
|
193
|
+
``concept_satisfiable``, and so on).
|
|
194
|
+
|
|
195
|
+
Opt-in, never in a default chain
|
|
196
|
+
-----------------------------------
|
|
197
|
+
Exactly :mod:`unicode_logic_kit.atp.hets_backend`'s cost-model discipline: a
|
|
198
|
+
Docker container start is minutes-expensive, so this module NEVER starts one
|
|
199
|
+
(:func:`hets_owl_available` and every ``external_*`` function only ever call
|
|
200
|
+
``discover_hets_url(start_container=False)``) and is never wired into any
|
|
201
|
+
automatic reasoning chain — a caller opts in by importing this module and
|
|
202
|
+
calling it directly, the same way ``dl.owl_reasoner`` is opt-in.
|
|
203
|
+
|
|
204
|
+
Public API: :func:`hets_owl_available`, :func:`external_concept_satisfiable`,
|
|
205
|
+
:func:`external_concept_unsatisfiable`, :func:`external_subsumes`,
|
|
206
|
+
:func:`external_equivalent`, :func:`external_abox_consistent`,
|
|
207
|
+
:func:`external_instance_check`, :func:`external_instance_retrieval`,
|
|
208
|
+
:func:`external_realize`, :func:`external_realize_all`, :class:`HetsOwlError`.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
from typing import Dict, List, Optional, Set
|
|
212
|
+
|
|
213
|
+
from ..dl.concepts import (
|
|
214
|
+
Concept, Top, Bottom, Atomic, Not, And, Or, Exists, ForAll, AtLeast, AtMost,
|
|
215
|
+
InverseRole, Nominal, HasValue, DataExists, DataForAll, DataHasValue,
|
|
216
|
+
DataAtLeast, DataAtMost,
|
|
217
|
+
)
|
|
218
|
+
from ..dl.datatypes import (
|
|
219
|
+
DataRange, Literal, is_builtin_datatype, render_datarange_fs, render_literal_fs,
|
|
220
|
+
)
|
|
221
|
+
from ..dl.tableau import (
|
|
222
|
+
TBox, ABox, _abox_individual_names, _reject_abox_roles,
|
|
223
|
+
_reject_concept_roles_deep, _tbox_class_expressions, _validate_data_box,
|
|
224
|
+
_validate_role_box,
|
|
225
|
+
)
|
|
226
|
+
from ..dl.translate import _check_name_punning, _collect_vocabulary
|
|
227
|
+
from .client import HetsClient
|
|
228
|
+
from .docker import discover_hets_url, hets_available
|
|
229
|
+
|
|
230
|
+
__all__ = [
|
|
231
|
+
"hets_owl_available",
|
|
232
|
+
"external_concept_satisfiable", "external_concept_unsatisfiable",
|
|
233
|
+
"external_subsumes", "external_equivalent",
|
|
234
|
+
"external_abox_consistent", "external_instance_check", "external_instance_retrieval",
|
|
235
|
+
"external_realize", "external_realize_all",
|
|
236
|
+
"HetsOwlError",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
#: The only prover identifier this module ever passes — see the module
|
|
240
|
+
#: docstring's "Phase 0 spike findings" point 3 for why an unset reasoner
|
|
241
|
+
#: (Hets's own default choice) is broken for OWL input, and point 2 for why
|
|
242
|
+
#: no other listed identifier is a genuine DL reasoner in this image.
|
|
243
|
+
_REASONER = "Fact"
|
|
244
|
+
|
|
245
|
+
#: Fixed ontology IRI every uploaded probe document declares — also the
|
|
246
|
+
#: development-graph node name passed to ``consistency_check`` (see the
|
|
247
|
+
#: module docstring's "Wire fact this module relies on").
|
|
248
|
+
_ONTOLOGY_IRI = "http://unicode-logic-kit.invalid/hets-owl-probe"
|
|
249
|
+
|
|
250
|
+
#: The default-prefix IRI backing every synthetic ``:C<n>``/``:R<n>``/``:I<n>``
|
|
251
|
+
#: PNAME_LN token (see the module docstring's "Names" section). Never
|
|
252
|
+
#: resolved or dereferenced — it exists only to make the ``Prefix(:=<...>)``
|
|
253
|
+
#: declaration well-formed.
|
|
254
|
+
_PREFIX_IRI = "http://unicode-logic-kit.invalid/owl#"
|
|
255
|
+
|
|
256
|
+
_FILENAME = "kit_owl_probe.ofn"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class HetsOwlError(RuntimeError):
|
|
260
|
+
"""A reachable Hets server could not decide a knowledge base: FaCT++
|
|
261
|
+
reported ``"Timeout"`` (see the module docstring's point 4), an
|
|
262
|
+
unrecognised result string, or a goal count other than one (this module
|
|
263
|
+
always builds a single-goal ``consistency-check`` request, so anything
|
|
264
|
+
else means the server answered a different question than asked — the
|
|
265
|
+
same "infra" discipline :meth:`unicode_logic_kit.atp.hets_backend.HetsBackend.decide`
|
|
266
|
+
already applies to its own single-conjecture case). Deliberately a plain
|
|
267
|
+
``RuntimeError`` (not :class:`~unicode_logic_kit.atp.protocol.BackendUnavailable`,
|
|
268
|
+
which every ``external_*`` function here still raises UNCHANGED, via
|
|
269
|
+
:func:`~unicode_logic_kit.hets.docker.discover_hets_url`, when no server is
|
|
270
|
+
reachable at all): the server answering *something* unusable is a
|
|
271
|
+
different failure mode than there being no server to ask, and callers
|
|
272
|
+
that already special-case ``BackendUnavailable`` for "no Hets right now"
|
|
273
|
+
must not have that same handling silently swallow "Hets is up but this
|
|
274
|
+
query broke it".
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def hets_owl_available() -> bool:
|
|
279
|
+
"""True iff a Hets server answers right now — the DL/OWL route's twin of
|
|
280
|
+
:func:`unicode_logic_kit.hets.docker.hets_available`.
|
|
281
|
+
|
|
282
|
+
Literally delegates to it (same discovery, same never-start-a-container
|
|
283
|
+
discipline): the "Fact" reasoner this module always requests is a fixed
|
|
284
|
+
property of the digest-pinned image every part of :mod:`unicode_logic_kit.hets`
|
|
285
|
+
already assumes (see :data:`unicode_logic_kit.hets.docker.HETS_IMAGE`), not
|
|
286
|
+
something that needs its own per-call ``GET /provers`` re-verification —
|
|
287
|
+
mirrors how :mod:`unicode_logic_kit.hets.docker`'s own docstring documents
|
|
288
|
+
which FOL provers work in this image as a static fact, not a live probe.
|
|
289
|
+
"""
|
|
290
|
+
return hets_available()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# --------------------------------------------------------------------------- #
|
|
294
|
+
# Rendering: Concept/TBox/ABox -> OWL 2 Functional-Style Syntax text, over the
|
|
295
|
+
# FULL ALCHQ + I + O fragment (see the module docstring's "Why this module
|
|
296
|
+
# renders its own OWL text" and "Names" sections).
|
|
297
|
+
# --------------------------------------------------------------------------- #
|
|
298
|
+
|
|
299
|
+
class _NameMap:
|
|
300
|
+
"""Per-call allocator of fresh, synthetic PNAME_LN tokens (``:C1``, ``:R1``,
|
|
301
|
+
``:I1``, ...) for this kit's class/role/individual names — never the kit's
|
|
302
|
+
own name string verbatim (see the module docstring's "Names" section).
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
def __init__(self):
|
|
306
|
+
self._classes: Dict[str, str] = {}
|
|
307
|
+
self._roles: Dict[str, str] = {}
|
|
308
|
+
self._individuals: Dict[str, str] = {}
|
|
309
|
+
self._data_properties: Dict[str, str] = {}
|
|
310
|
+
self._datatypes: Dict[str, str] = {}
|
|
311
|
+
|
|
312
|
+
def cls(self, name: str) -> str:
|
|
313
|
+
if name not in self._classes:
|
|
314
|
+
self._classes[name] = f":C{len(self._classes) + 1}"
|
|
315
|
+
return self._classes[name]
|
|
316
|
+
|
|
317
|
+
def role(self, name: str) -> str:
|
|
318
|
+
if name not in self._roles:
|
|
319
|
+
self._roles[name] = f":R{len(self._roles) + 1}"
|
|
320
|
+
return self._roles[name]
|
|
321
|
+
|
|
322
|
+
def ind(self, name: str) -> str:
|
|
323
|
+
if name not in self._individuals:
|
|
324
|
+
self._individuals[name] = f":I{len(self._individuals) + 1}"
|
|
325
|
+
return self._individuals[name]
|
|
326
|
+
|
|
327
|
+
def dprop(self, name: str) -> str:
|
|
328
|
+
"""A fresh ``:P<n>`` token for a DATA property name."""
|
|
329
|
+
if name not in self._data_properties:
|
|
330
|
+
self._data_properties[name] = f":P{len(self._data_properties) + 1}"
|
|
331
|
+
return self._data_properties[name]
|
|
332
|
+
|
|
333
|
+
def dtype(self, name: str) -> str:
|
|
334
|
+
"""A datatype name as OWL 2 text: a BUILT-IN datatype keeps its own
|
|
335
|
+
name (``xsd:integer`` means the same to Hets as to us), a user-defined
|
|
336
|
+
one gets a fresh ``:T<n>`` token."""
|
|
337
|
+
if is_builtin_datatype(name):
|
|
338
|
+
return name
|
|
339
|
+
if name not in self._datatypes:
|
|
340
|
+
self._datatypes[name] = f":T{len(self._datatypes) + 1}"
|
|
341
|
+
return self._datatypes[name]
|
|
342
|
+
|
|
343
|
+
def class_tokens(self) -> List[str]:
|
|
344
|
+
return list(self._classes.values())
|
|
345
|
+
|
|
346
|
+
def role_tokens(self) -> List[str]:
|
|
347
|
+
return list(self._roles.values())
|
|
348
|
+
|
|
349
|
+
def individual_tokens(self) -> List[str]:
|
|
350
|
+
return list(self._individuals.values())
|
|
351
|
+
|
|
352
|
+
def data_property_tokens(self) -> List[str]:
|
|
353
|
+
return list(self._data_properties.values())
|
|
354
|
+
|
|
355
|
+
def datatype_tokens(self) -> List[str]:
|
|
356
|
+
return list(self._datatypes.values())
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _render_datarange(datarange: DataRange, names: _NameMap) -> str:
|
|
360
|
+
"""A data range as OWL 2 Functional-Style text, with user-defined datatypes
|
|
361
|
+
under their synthetic names."""
|
|
362
|
+
return render_datarange_fs(datarange, names.dtype)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _render_literal(literal: Literal, names: _NameMap) -> str:
|
|
366
|
+
"""A literal as OWL 2 Functional-Style text, its datatype under the SAME
|
|
367
|
+
name :func:`_render_datarange` gives it: a user-defined datatype is declared
|
|
368
|
+
as ``:T<n>``, so a literal written with the kit's own name would use a name
|
|
369
|
+
the document never declared."""
|
|
370
|
+
return render_literal_fs(literal, names.dtype)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _render_role_expr(role_field, names: _NameMap) -> str:
|
|
374
|
+
"""Render a ``role`` field (a plain role name, or an :class:`InverseRole`)
|
|
375
|
+
as an OWL 2 ``ObjectPropertyExpression`` — the one place this renderer
|
|
376
|
+
covers a construct ``dl.owl_functional`` refuses outright (see the
|
|
377
|
+
module docstring).
|
|
378
|
+
"""
|
|
379
|
+
if isinstance(role_field, InverseRole):
|
|
380
|
+
return f"ObjectInverseOf({names.role(role_field.role)})"
|
|
381
|
+
return names.role(role_field)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _render_ce(c: Concept, names: _NameMap) -> str:
|
|
385
|
+
"""Render ``c`` as an OWL 2 Functional-Style ``ClassExpression``, over the
|
|
386
|
+
full ALCHQ + I + O fragment (see :mod:`unicode_logic_kit.dl.concepts` for
|
|
387
|
+
the AST this mirrors, and the module docstring for why this duplicates a
|
|
388
|
+
small amount of ``dl.owl_functional._render_ce``-shaped logic rather than
|
|
389
|
+
reusing it).
|
|
390
|
+
"""
|
|
391
|
+
if isinstance(c, Top):
|
|
392
|
+
return "owl:Thing"
|
|
393
|
+
if isinstance(c, Bottom):
|
|
394
|
+
return "owl:Nothing"
|
|
395
|
+
if isinstance(c, Atomic):
|
|
396
|
+
return names.cls(c.name)
|
|
397
|
+
if isinstance(c, Nominal):
|
|
398
|
+
return f"ObjectOneOf({names.ind(c.individual)})"
|
|
399
|
+
if isinstance(c, HasValue):
|
|
400
|
+
return (f"ObjectHasValue({_render_role_expr(c.role, names)} "
|
|
401
|
+
f"{names.ind(c.individual)})")
|
|
402
|
+
if isinstance(c, Not):
|
|
403
|
+
return f"ObjectComplementOf({_render_ce(c.concept, names)})"
|
|
404
|
+
if isinstance(c, And):
|
|
405
|
+
return f"ObjectIntersectionOf({_render_ce(c.left, names)} {_render_ce(c.right, names)})"
|
|
406
|
+
if isinstance(c, Or):
|
|
407
|
+
return f"ObjectUnionOf({_render_ce(c.left, names)} {_render_ce(c.right, names)})"
|
|
408
|
+
if isinstance(c, Exists):
|
|
409
|
+
return f"ObjectSomeValuesFrom({_render_role_expr(c.role, names)} {_render_ce(c.concept, names)})"
|
|
410
|
+
if isinstance(c, ForAll):
|
|
411
|
+
return f"ObjectAllValuesFrom({_render_role_expr(c.role, names)} {_render_ce(c.concept, names)})"
|
|
412
|
+
if isinstance(c, AtLeast):
|
|
413
|
+
return f"ObjectMinCardinality({c.n} {_render_role_expr(c.role, names)} {_render_ce(c.concept, names)})"
|
|
414
|
+
if isinstance(c, AtMost):
|
|
415
|
+
return f"ObjectMaxCardinality({c.n} {_render_role_expr(c.role, names)} {_render_ce(c.concept, names)})"
|
|
416
|
+
if isinstance(c, DataExists):
|
|
417
|
+
return f"DataSomeValuesFrom({names.dprop(c.prop)} {_render_datarange(c.datarange, names)})"
|
|
418
|
+
if isinstance(c, DataForAll):
|
|
419
|
+
return f"DataAllValuesFrom({names.dprop(c.prop)} {_render_datarange(c.datarange, names)})"
|
|
420
|
+
if isinstance(c, DataHasValue):
|
|
421
|
+
return f"DataHasValue({names.dprop(c.prop)} {_render_literal(c.value, names)})"
|
|
422
|
+
if isinstance(c, DataAtLeast):
|
|
423
|
+
return (f"DataMinCardinality({c.n} {names.dprop(c.prop)} "
|
|
424
|
+
f"{_render_datarange(c.datarange, names)})")
|
|
425
|
+
if isinstance(c, DataAtMost):
|
|
426
|
+
return (f"DataMaxCardinality({c.n} {names.dprop(c.prop)} "
|
|
427
|
+
f"{_render_datarange(c.datarange, names)})")
|
|
428
|
+
raise TypeError(f"hets.owl_backend: unsupported concept {type(c).__name__}")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
#: ``(TBox field, OWL 2 keyword)`` for the seven one-role characteristic
|
|
432
|
+
#: axioms, in the order ``dl.tableau._AXIOM_KINDS`` lists them. A table rather
|
|
433
|
+
#: than seven loops, for the same reason ``dl.owl_functional`` uses one: they
|
|
434
|
+
#: differ only in the keyword, and a FaCT++ oracle that silently dropped one
|
|
435
|
+
#: would agree with the in-house tableau for the wrong reason.
|
|
436
|
+
#: ``transitive_roles`` is NOT here: a transitivity declaration is rendered
|
|
437
|
+
#: with the role inclusions, ahead of everything else, which is the ordering
|
|
438
|
+
#: this module has had since the RBox landed and which several pinned
|
|
439
|
+
#: expected-document strings depend on.
|
|
440
|
+
_CHARACTERISTIC_AXIOMS = (
|
|
441
|
+
("asymmetric_roles", "AsymmetricObjectProperty"),
|
|
442
|
+
("irreflexive_roles", "IrreflexiveObjectProperty"),
|
|
443
|
+
("functional_roles", "FunctionalObjectProperty"),
|
|
444
|
+
("symmetric_roles", "SymmetricObjectProperty"),
|
|
445
|
+
("reflexive_roles", "ReflexiveObjectProperty"),
|
|
446
|
+
("inverse_functional_roles", "InverseFunctionalObjectProperty"),
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _guard_document(tbox: TBox, abox: ABox, concepts=()) -> None:
|
|
451
|
+
"""Refuse, by name, what no route may answer, BEFORE a word of the document
|
|
452
|
+
is written.
|
|
453
|
+
|
|
454
|
+
A renderer gives every name it meets a synthetic one of its own (``:R1``), so
|
|
455
|
+
an OWL 2 built-in property name (``owl:topObjectProperty`` and the other
|
|
456
|
+
three, and ``=`` / ``≠``) as the role of a class expression came out as an
|
|
457
|
+
ordinary role, and FaCT++ was asked about a different restriction — while
|
|
458
|
+
every other route refuses the same concept by name. These are the validations
|
|
459
|
+
:func:`unicode_logic_kit.dl.owl_reasoner._guard_inputs` runs for the HermiT
|
|
460
|
+
route, CALLED here and not copied, so one expression is refused with one
|
|
461
|
+
message wherever it is asked: the stored role box and data box are validated
|
|
462
|
+
before anything is unpacked (a hand-built wrong-arity pair is a
|
|
463
|
+
:class:`~unicode_logic_kit.dl.tableau.RoleExpressionError`, not a bare
|
|
464
|
+
``ValueError``), no ABox assertion carries a built-in property name, and no
|
|
465
|
+
class expression — in a TBox inclusion, in a domain or range filler, in a
|
|
466
|
+
DATA property domain filler (a class expression this route renders; the
|
|
467
|
+
HermiT route refuses the whole data box instead), in an ABox concept
|
|
468
|
+
assertion (which is where every QUERY concept ends up) — uses one as a role,
|
|
469
|
+
at any depth. A NAME used as both an object property and a data property, or
|
|
470
|
+
as both a class and a datatype, is refused too
|
|
471
|
+
(:func:`~unicode_logic_kit.dl.translate._check_name_punning`, the one the FOL
|
|
472
|
+
image runs): this renderer would write the two kinds under separate tokens
|
|
473
|
+
(``:R1`` / ``:P1``), so the oracle would answer a knowledge base the kit's own
|
|
474
|
+
route refuses. Not run here: the data-layer refusal (FaCT++ is an SROIQ(D)
|
|
475
|
+
reasoner and IS asked about the data layer) and the simple-role check.
|
|
476
|
+
|
|
477
|
+
``concepts`` are query concepts that are NOT yet in the ABox: an entry point
|
|
478
|
+
that may make no Hets call at all (``external_realize`` over an empty
|
|
479
|
+
vocabulary, ``external_realize_all`` and ``external_instance_retrieval`` over
|
|
480
|
+
an empty ABox) runs the guard itself, as ``dl.owl_reasoner`` does, so that it
|
|
481
|
+
does not return a quiet ``[]`` for a knowledge base every other route refuses.
|
|
482
|
+
"""
|
|
483
|
+
where = "hets.owl_backend"
|
|
484
|
+
_validate_role_box(tbox, where=where)
|
|
485
|
+
_validate_data_box(tbox, where=where)
|
|
486
|
+
_reject_abox_roles(abox, where=where)
|
|
487
|
+
for concept in _tbox_class_expressions(tbox):
|
|
488
|
+
_reject_concept_roles_deep(concept, where=where)
|
|
489
|
+
for _prop, filler in tbox.data_property_domains:
|
|
490
|
+
_reject_concept_roles_deep(filler, where=where)
|
|
491
|
+
for _individual, concept in abox.concept_assertions:
|
|
492
|
+
_reject_concept_roles_deep(concept, where=where)
|
|
493
|
+
for concept in concepts:
|
|
494
|
+
_reject_concept_roles_deep(concept, where=where)
|
|
495
|
+
# Over the whole knowledge base, which includes every query concept (they
|
|
496
|
+
# reach this renderer as ABox assertions).
|
|
497
|
+
_check_name_punning(_collect_vocabulary(tbox, abox), where)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _render_document(tbox: TBox, abox: ABox) -> str:
|
|
501
|
+
"""Render ``(tbox, abox)`` as one OWL 2 Functional-Style ``Ontology(...)``
|
|
502
|
+
document under the fixed :data:`_ONTOLOGY_IRI`, with a ``Declaration(...)``
|
|
503
|
+
for every synthetic name used (mirrors ``dl.owl_functional.to_owl_functional``'s
|
|
504
|
+
own declaration-block convention, for the same reason: cheap, harmless,
|
|
505
|
+
and matches what a hand-written OWL ontology looks like) — the whole role
|
|
506
|
+
box first, then the TBox's inclusions, then the ABox's assertions, matching
|
|
507
|
+
that sibling module's own axiom ordering.
|
|
508
|
+
|
|
509
|
+
EVERY role-box field is rendered, including the ones
|
|
510
|
+
``dl.owl_functional`` cannot read back (an ``ObjectInverseOf`` on either
|
|
511
|
+
side of a sub-property axiom): this module exists to be a SECOND,
|
|
512
|
+
INDEPENDENT oracle, and an oracle that drops the axiom under test agrees
|
|
513
|
+
with everything. What no route answers is refused first
|
|
514
|
+
(:func:`_guard_document`).
|
|
515
|
+
"""
|
|
516
|
+
_guard_document(tbox, abox)
|
|
517
|
+
names = _NameMap()
|
|
518
|
+
axiom_lines: List[str] = []
|
|
519
|
+
for sub_role, super_role in tbox.role_inclusions:
|
|
520
|
+
# _render_role_expr, not names.role: an InverseRole must come out as
|
|
521
|
+
# ObjectInverseOf(...), and using it as a _NameMap KEY (what this did
|
|
522
|
+
# until 0.30.0) invents a brand-new atomic property instead -- so an
|
|
523
|
+
# inverse-role inclusion and a chain rendered CHARACTER-FOR-CHARACTER
|
|
524
|
+
# identically, and FaCT++ was asked about neither of them.
|
|
525
|
+
axiom_lines.append(f"SubObjectPropertyOf({_render_role_expr(sub_role, names)} "
|
|
526
|
+
f"{_render_role_expr(super_role, names)})")
|
|
527
|
+
for role in sorted(tbox.transitive_roles):
|
|
528
|
+
axiom_lines.append(f"TransitiveObjectProperty({names.role(role)})")
|
|
529
|
+
# The remaining role-box fields, in the order dl.tableau._AXIOM_KINDS lists
|
|
530
|
+
# them -- the same order dl.owl_functional._render_role_box uses, so the
|
|
531
|
+
# two renderers' documents line up axiom for axiom.
|
|
532
|
+
for left, right in tbox.disjoint_role_pairs:
|
|
533
|
+
axiom_lines.append(
|
|
534
|
+
f"DisjointObjectProperties({names.role(left)} {names.role(right)})")
|
|
535
|
+
for field_name, keyword in _CHARACTERISTIC_AXIOMS:
|
|
536
|
+
for role in sorted(getattr(tbox, field_name)):
|
|
537
|
+
axiom_lines.append(f"{keyword}({names.role(role)})")
|
|
538
|
+
for p, q in tbox.inverse_role_pairs:
|
|
539
|
+
axiom_lines.append(
|
|
540
|
+
f"InverseObjectProperties({names.role(p)} {names.role(q)})")
|
|
541
|
+
for chain, super_role in tbox.role_chains:
|
|
542
|
+
body = " ".join(_render_role_expr(role, names) for role in chain)
|
|
543
|
+
axiom_lines.append(f"SubObjectPropertyOf(ObjectPropertyChain({body}) "
|
|
544
|
+
f"{names.role(super_role)})")
|
|
545
|
+
for role, filler in tbox.role_domains:
|
|
546
|
+
axiom_lines.append(f"ObjectPropertyDomain({names.role(role)} "
|
|
547
|
+
f"{_render_ce(filler, names)})")
|
|
548
|
+
for role, filler in tbox.role_ranges:
|
|
549
|
+
axiom_lines.append(f"ObjectPropertyRange({names.role(role)} "
|
|
550
|
+
f"{_render_ce(filler, names)})")
|
|
551
|
+
# The DATA box: FaCT++ is an SROIQ(D) reasoner, so unlike the HermiT route
|
|
552
|
+
# this oracle CAN be asked about the data layer. Datatype and facet names
|
|
553
|
+
# are OWL 2's own and stay verbatim; a user-defined datatype and every data
|
|
554
|
+
# property get fresh synthetic names, like classes and roles.
|
|
555
|
+
for sub_prop, super_prop in tbox.data_property_inclusions:
|
|
556
|
+
axiom_lines.append(f"SubDataPropertyOf({names.dprop(sub_prop)} {names.dprop(super_prop)})")
|
|
557
|
+
for left, right in tbox.disjoint_data_property_pairs:
|
|
558
|
+
axiom_lines.append(f"DisjointDataProperties({names.dprop(left)} {names.dprop(right)})")
|
|
559
|
+
for prop in sorted(tbox.functional_data_properties):
|
|
560
|
+
axiom_lines.append(f"FunctionalDataProperty({names.dprop(prop)})")
|
|
561
|
+
for prop, filler in tbox.data_property_domains:
|
|
562
|
+
axiom_lines.append(f"DataPropertyDomain({names.dprop(prop)} {_render_ce(filler, names)})")
|
|
563
|
+
for prop, datarange in tbox.data_property_ranges:
|
|
564
|
+
axiom_lines.append(f"DataPropertyRange({names.dprop(prop)} "
|
|
565
|
+
f"{_render_datarange(datarange, names)})")
|
|
566
|
+
for name, datarange in tbox.datatype_definitions:
|
|
567
|
+
axiom_lines.append(f"DatatypeDefinition({names.dtype(name)} "
|
|
568
|
+
f"{_render_datarange(datarange, names)})")
|
|
569
|
+
for sub, sup in tbox.inclusions:
|
|
570
|
+
axiom_lines.append(f"SubClassOf({_render_ce(sub, names)} {_render_ce(sup, names)})")
|
|
571
|
+
for individual, concept in abox.concept_assertions:
|
|
572
|
+
axiom_lines.append(f"ClassAssertion({_render_ce(concept, names)} {names.ind(individual)})")
|
|
573
|
+
for a, b, role in abox.role_assertions:
|
|
574
|
+
axiom_lines.append(
|
|
575
|
+
f"ObjectPropertyAssertion({names.role(role)} {names.ind(a)} {names.ind(b)})")
|
|
576
|
+
for a, b in abox.distinct_assertions:
|
|
577
|
+
axiom_lines.append(f"DifferentIndividuals({names.ind(a)} {names.ind(b)})")
|
|
578
|
+
for a, b in abox.same_assertions:
|
|
579
|
+
axiom_lines.append(f"SameIndividual({names.ind(a)} {names.ind(b)})")
|
|
580
|
+
for a, b, role in abox.negative_role_assertions:
|
|
581
|
+
axiom_lines.append(f"NegativeObjectPropertyAssertion({names.role(role)} "
|
|
582
|
+
f"{names.ind(a)} {names.ind(b)})")
|
|
583
|
+
for individual, prop, value in abox.data_assertions:
|
|
584
|
+
axiom_lines.append(f"DataPropertyAssertion({names.dprop(prop)} "
|
|
585
|
+
f"{names.ind(individual)} {_render_literal(value, names)})")
|
|
586
|
+
for individual, prop, value in abox.negative_data_assertions:
|
|
587
|
+
axiom_lines.append(f"NegativeDataPropertyAssertion({names.dprop(prop)} "
|
|
588
|
+
f"{names.ind(individual)} {_render_literal(value, names)})")
|
|
589
|
+
|
|
590
|
+
decl_lines = (
|
|
591
|
+
[f"Declaration(Class({t}))" for t in names.class_tokens()]
|
|
592
|
+
+ [f"Declaration(ObjectProperty({t}))" for t in names.role_tokens()]
|
|
593
|
+
+ [f"Declaration(DataProperty({t}))" for t in names.data_property_tokens()]
|
|
594
|
+
+ [f"Declaration(Datatype({t}))" for t in names.datatype_tokens()]
|
|
595
|
+
+ [f"Declaration(NamedIndividual({t}))" for t in names.individual_tokens()]
|
|
596
|
+
)
|
|
597
|
+
body_lines = decl_lines + axiom_lines
|
|
598
|
+
header = (f"Prefix(:=<{_PREFIX_IRI}>)\n"
|
|
599
|
+
f"Prefix(owl:=<http://www.w3.org/2002/07/owl#>)\n")
|
|
600
|
+
if _collect_vocabulary(tbox, abox).has_data():
|
|
601
|
+
# The datatypes' own prefixes, declared so the document does not lean on
|
|
602
|
+
# the implicit ones -- only for a knowledge base that mentions a
|
|
603
|
+
# datatype at all, which leaves every data-free document as it was.
|
|
604
|
+
header += ("Prefix(xsd:=<http://www.w3.org/2001/XMLSchema#>)\n"
|
|
605
|
+
"Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>)\n"
|
|
606
|
+
"Prefix(rdf:=<http://www.w3.org/1999/02/22-rdf-syntax-ns#>)\n")
|
|
607
|
+
header += f"Ontology(<{_ONTOLOGY_IRI}>"
|
|
608
|
+
if not body_lines:
|
|
609
|
+
return header + ")"
|
|
610
|
+
body = "\n".join(f" {line}" for line in body_lines)
|
|
611
|
+
return f"{header}\n{body}\n)"
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
# --------------------------------------------------------------------------- #
|
|
615
|
+
# The one HETS-invoking primitive.
|
|
616
|
+
# --------------------------------------------------------------------------- #
|
|
617
|
+
|
|
618
|
+
def _kb_consistent(tbox: Optional[TBox], abox: ABox, *,
|
|
619
|
+
time_limit: int, url: Optional[str]) -> bool:
|
|
620
|
+
"""Render ``(tbox, abox)``, upload it, and ask ``POST /consistency-check``
|
|
621
|
+
with ``reasoner="Fact"``. The ONE Hets-invoking primitive every other
|
|
622
|
+
function in this module reduces to (see the module docstring's "The one
|
|
623
|
+
primitive" section).
|
|
624
|
+
|
|
625
|
+
Raises:
|
|
626
|
+
BackendUnavailable: no Hets server is reachable (from
|
|
627
|
+
:func:`~unicode_logic_kit.hets.docker.discover_hets_url`, when
|
|
628
|
+
``url`` is not given).
|
|
629
|
+
RuntimeError: the upload or the consistency-check call itself failed
|
|
630
|
+
(:class:`~unicode_logic_kit.hets.client.HetsClient`'s own contract
|
|
631
|
+
— an HTTP error, an HETS ``"*** Error"`` body, malformed JSON).
|
|
632
|
+
HetsOwlError: the server answered, but not with a genuine
|
|
633
|
+
``"Consistent"``/``"Inconsistent"`` verdict (a ``"Timeout"``, an
|
|
634
|
+
unrecognised result string, or other than exactly one goal
|
|
635
|
+
result) — see :class:`HetsOwlError`.
|
|
636
|
+
"""
|
|
637
|
+
tbox = tbox if tbox is not None else TBox()
|
|
638
|
+
# Rendered BEFORE a server is looked for: a question that is refused by name
|
|
639
|
+
# (see _guard_document) must not need a reachable Hets to be refused.
|
|
640
|
+
text = _render_document(tbox, abox)
|
|
641
|
+
if url is None:
|
|
642
|
+
url, _container = discover_hets_url(start_container=False)
|
|
643
|
+
client = HetsClient(url, timeout=float(time_limit + 30))
|
|
644
|
+
iri = client.upload(text, _FILENAME)
|
|
645
|
+
goals = client.consistency_check(iri, _ONTOLOGY_IRI, reasoner=_REASONER,
|
|
646
|
+
time_limit=time_limit)
|
|
647
|
+
if len(goals) != 1:
|
|
648
|
+
raise HetsOwlError(
|
|
649
|
+
f"hets.owl_backend: expected exactly 1 goal result from "
|
|
650
|
+
f"consistency-check, got {len(goals)}: {goals!r}")
|
|
651
|
+
result = (goals[0].get("result") or "").strip()
|
|
652
|
+
if result == "Consistent":
|
|
653
|
+
return True
|
|
654
|
+
if result == "Inconsistent":
|
|
655
|
+
return False
|
|
656
|
+
raise HetsOwlError(
|
|
657
|
+
f"hets.owl_backend: consistency-check returned an unusable result "
|
|
658
|
+
f"{result!r} (never guessed as Consistent/Inconsistent — see "
|
|
659
|
+
f"HetsOwlError's docstring): {goals[0]!r}")
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
# --------------------------------------------------------------------------- #
|
|
663
|
+
# Public API — mirrors dl.tableau's / dl.owl_reasoner's exactly, over the
|
|
664
|
+
# ALCHQ + I + O fragment (see the module docstring's "The one primitive").
|
|
665
|
+
# --------------------------------------------------------------------------- #
|
|
666
|
+
|
|
667
|
+
def external_concept_satisfiable(concept: Concept, tbox: Optional[TBox] = None, *,
|
|
668
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
669
|
+
"""Return True iff ``concept`` is satisfiable with respect to ``tbox`` —
|
|
670
|
+
this module's twin of :func:`unicode_logic_kit.dl.tableau.concept_satisfiable`
|
|
671
|
+
/ :func:`unicode_logic_kit.dl.owl_reasoner.external_concept_satisfiable`,
|
|
672
|
+
over the full ALCHQ + I + O fragment. Reduced to :func:`_kb_consistent`
|
|
673
|
+
via a single fresh probe individual, exactly the way
|
|
674
|
+
``dl.owl_reasoner``'s own function does.
|
|
675
|
+
"""
|
|
676
|
+
probe_abox = ABox().assert_concept("_probe", concept)
|
|
677
|
+
return _kb_consistent(tbox, probe_abox, time_limit=time_limit, url=url)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def external_concept_unsatisfiable(concept: Concept, tbox: Optional[TBox] = None, *,
|
|
681
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
682
|
+
"""Return True iff ``concept`` is unsatisfiable with respect to ``tbox``."""
|
|
683
|
+
return not external_concept_satisfiable(concept, tbox, time_limit=time_limit, url=url)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def external_subsumes(sub: Concept, sup: Concept, tbox: Optional[TBox] = None, *,
|
|
687
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
688
|
+
"""Return True iff ``tbox`` entails ``sub ⊑ sup`` — the same ``sub ⊓ ¬sup``
|
|
689
|
+
unsatisfiability reduction :func:`unicode_logic_kit.dl.tableau.subsumes` uses.
|
|
690
|
+
"""
|
|
691
|
+
return not external_concept_satisfiable(And(sub, Not(sup)), tbox,
|
|
692
|
+
time_limit=time_limit, url=url)
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def external_equivalent(c: Concept, d: Concept, tbox: Optional[TBox] = None, *,
|
|
696
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
697
|
+
"""Return True iff ``tbox`` entails ``c ≡ d`` (mutual subsumption)."""
|
|
698
|
+
return (external_subsumes(c, d, tbox, time_limit=time_limit, url=url)
|
|
699
|
+
and external_subsumes(d, c, tbox, time_limit=time_limit, url=url))
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def external_abox_consistent(abox: ABox, tbox: Optional[TBox] = None, *,
|
|
703
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
704
|
+
"""Return True iff the knowledge base ``(tbox, abox)`` is consistent."""
|
|
705
|
+
return _kb_consistent(tbox, abox, time_limit=time_limit, url=url)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _abox_with(abox: ABox, individual: str, concept: Concept) -> ABox:
|
|
709
|
+
"""A copy of ``abox`` with one extra concept assertion ``individual : concept``
|
|
710
|
+
(used by :func:`external_instance_check`'s entailment reduction — mirrors
|
|
711
|
+
:func:`unicode_logic_kit.dl.owl_reasoner._abox_with` exactly).
|
|
712
|
+
|
|
713
|
+
:meth:`~unicode_logic_kit.dl.tableau.ABox.copy`, not a field-by-field
|
|
714
|
+
reconstruction: this module is one of the kit's INDEPENDENT oracles, and a
|
|
715
|
+
probe that silently drops an assertion kind the caller supplied asks FaCT++
|
|
716
|
+
about a strictly WEAKER knowledge base than the one the caller has, so the
|
|
717
|
+
two oracles would agree for the wrong reason. (Field-by-field is what this
|
|
718
|
+
did until 0.30.0, and it dropped ``same_assertions`` and
|
|
719
|
+
``negative_role_assertions`` — and would have dropped the data assertions —
|
|
720
|
+
the moment they existed.)
|
|
721
|
+
"""
|
|
722
|
+
probe = abox.copy()
|
|
723
|
+
probe.assert_concept(individual, concept)
|
|
724
|
+
return probe
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def external_instance_check(abox: ABox, individual: str, concept: Concept,
|
|
728
|
+
tbox: Optional[TBox] = None, *,
|
|
729
|
+
time_limit: int = 15, url: Optional[str] = None) -> bool:
|
|
730
|
+
"""Return True iff ``(tbox, abox)`` entails ``individual : concept`` — the
|
|
731
|
+
same "does asserting the complement make it inconsistent" reduction
|
|
732
|
+
:func:`unicode_logic_kit.dl.tableau.instance_check` uses. Open-world,
|
|
733
|
+
exactly like that function: False means NOT entailed, not "entailed false".
|
|
734
|
+
"""
|
|
735
|
+
return not external_abox_consistent(
|
|
736
|
+
_abox_with(abox, individual, Not(concept)), tbox, time_limit=time_limit, url=url)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _all_individuals(abox: ABox) -> Set[str]:
|
|
740
|
+
"""Every individual name mentioned in ``abox`` (ANY assertion list), and NO
|
|
741
|
+
anonymous fallback: an ABox that names nobody has nobody to retrieve or
|
|
742
|
+
realize.
|
|
743
|
+
|
|
744
|
+
It IS :func:`unicode_logic_kit.dl.tableau._abox_individual_names`, the scan the
|
|
745
|
+
tableau's two sweeps (``instance_retrieval``, ``realize_all``), the HermiT
|
|
746
|
+
route and ``kb_to_fol(...).individuals`` all read, driven by ``dl.tableau.
|
|
747
|
+
_AXIOM_KINDS``' ``individual_positions``: an assertion kind added to the
|
|
748
|
+
table is enumerated by every route that sweeps individuals from its row
|
|
749
|
+
alone. NOT ``dl.tableau._individuals``, which adds the anonymous ``"a"`` for
|
|
750
|
+
an empty ABox because ``abox_consistent`` needs SOME node for the TBox to
|
|
751
|
+
run on — that node is not an individual of the knowledge base, and sweeping
|
|
752
|
+
it asked FaCT++ about a phantom and reported it as a member
|
|
753
|
+
(``TBox().add(⊤, A)`` over an empty ABox retrieved ``{"a"}`` here and
|
|
754
|
+
``set()`` from the tableau and from HermiT). A local re-implementation of
|
|
755
|
+
the scan is what this was until 0.30.0, and it omitted ``same_assertions``
|
|
756
|
+
and ``negative_role_assertions``.
|
|
757
|
+
"""
|
|
758
|
+
return set(_abox_individual_names(abox))
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def external_instance_retrieval(abox: ABox, concept: Concept, tbox: Optional[TBox] = None, *,
|
|
762
|
+
time_limit: int = 15, url: Optional[str] = None) -> Set[str]:
|
|
763
|
+
"""Return every individual of ``abox`` that ``(tbox, abox)`` entails is a
|
|
764
|
+
``concept`` — sweeps :func:`external_instance_check` exactly like
|
|
765
|
+
:func:`unicode_logic_kit.dl.tableau.instance_retrieval` does.
|
|
766
|
+
"""
|
|
767
|
+
_guard_document(tbox if tbox is not None else TBox(), abox, [concept])
|
|
768
|
+
return {ind for ind in _all_individuals(abox)
|
|
769
|
+
if external_instance_check(abox, ind, concept, tbox, time_limit=time_limit, url=url)}
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def external_realize(abox: ABox, individual: str, vocabulary: List[Concept],
|
|
773
|
+
tbox: Optional[TBox] = None, *,
|
|
774
|
+
time_limit: int = 15, url: Optional[str] = None) -> List[Concept]:
|
|
775
|
+
"""Return ``individual``'s most-specific concepts from ``vocabulary`` — the
|
|
776
|
+
same filter-then-drop-non-minimal reduction
|
|
777
|
+
:func:`unicode_logic_kit.dl.tableau.realize` uses.
|
|
778
|
+
"""
|
|
779
|
+
_guard_document(tbox if tbox is not None else TBox(), abox, vocabulary)
|
|
780
|
+
candidates = [c for c in vocabulary
|
|
781
|
+
if external_instance_check(abox, individual, c, tbox,
|
|
782
|
+
time_limit=time_limit, url=url)]
|
|
783
|
+
return [c for c in candidates
|
|
784
|
+
if not any(external_subsumes(d, c, tbox, time_limit=time_limit, url=url)
|
|
785
|
+
and not external_subsumes(c, d, tbox, time_limit=time_limit, url=url)
|
|
786
|
+
for d in candidates)]
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def external_realize_all(abox: ABox, vocabulary: List[Concept], tbox: Optional[TBox] = None, *,
|
|
790
|
+
time_limit: int = 15, url: Optional[str] = None) -> Dict[str, List[Concept]]:
|
|
791
|
+
"""Return :func:`external_realize` for every individual named in ``abox``."""
|
|
792
|
+
_guard_document(tbox if tbox is not None else TBox(), abox, vocabulary)
|
|
793
|
+
return {ind: external_realize(abox, ind, vocabulary, tbox, time_limit=time_limit, url=url)
|
|
794
|
+
for ind in sorted(_all_individuals(abox))}
|