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.
Files changed (237) hide show
  1. unicode_logic_kit/__init__.py +385 -0
  2. unicode_logic_kit/__main__.py +520 -0
  3. unicode_logic_kit/_deadline.py +219 -0
  4. unicode_logic_kit/ace/__init__.py +126 -0
  5. unicode_logic_kit/ace/_align.py +135 -0
  6. unicode_logic_kit/ace/chem_lexicon.py +128 -0
  7. unicode_logic_kit/ace/drs_reader.py +570 -0
  8. unicode_logic_kit/ace/mapping.py +666 -0
  9. unicode_logic_kit/ace/reverse_modal.py +138 -0
  10. unicode_logic_kit/ace/runner.py +551 -0
  11. unicode_logic_kit/ace/translate.py +452 -0
  12. unicode_logic_kit/ace/verbalize.py +1070 -0
  13. unicode_logic_kit/api.py +1284 -0
  14. unicode_logic_kit/atp/__init__.py +177 -0
  15. unicode_logic_kit/atp/_ascii_names.py +113 -0
  16. unicode_logic_kit/atp/_html.py +72 -0
  17. unicode_logic_kit/atp/_substructural_input.py +228 -0
  18. unicode_logic_kit/atp/_tff_problem.py +715 -0
  19. unicode_logic_kit/atp/_tptp_problem.py +1111 -0
  20. unicode_logic_kit/atp/_writer_support.py +289 -0
  21. unicode_logic_kit/atp/clingo_backend.py +1180 -0
  22. unicode_logic_kit/atp/cvc5_backend.py +1385 -0
  23. unicode_logic_kit/atp/eprover_backend.py +732 -0
  24. unicode_logic_kit/atp/finite_domain.py +1055 -0
  25. unicode_logic_kit/atp/fitch.py +1547 -0
  26. unicode_logic_kit/atp/fitch_search.py +551 -0
  27. unicode_logic_kit/atp/hets_backend.py +339 -0
  28. unicode_logic_kit/atp/hybrid_down.py +120 -0
  29. unicode_logic_kit/atp/incremental.py +250 -0
  30. unicode_logic_kit/atp/kripke_enum.py +741 -0
  31. unicode_logic_kit/atp/lambek.py +436 -0
  32. unicode_logic_kit/atp/leo3_backend.py +332 -0
  33. unicode_logic_kit/atp/linear.py +738 -0
  34. unicode_logic_kit/atp/lj.py +705 -0
  35. unicode_logic_kit/atp/logic_backends.py +566 -0
  36. unicode_logic_kit/atp/ltl_tableau.py +1084 -0
  37. unicode_logic_kit/atp/minizinc_backend.py +1402 -0
  38. unicode_logic_kit/atp/modal_tableau.py +1382 -0
  39. unicode_logic_kit/atp/nanocop_backend.py +410 -0
  40. unicode_logic_kit/atp/portfolio.py +489 -0
  41. unicode_logic_kit/atp/protocol.py +1803 -0
  42. unicode_logic_kit/atp/prover9_entailment.py +1153 -0
  43. unicode_logic_kit/atp/resolution.py +1376 -0
  44. unicode_logic_kit/atp/resolution_check.py +1114 -0
  45. unicode_logic_kit/atp/sequent.py +1050 -0
  46. unicode_logic_kit/atp/tableau.py +921 -0
  47. unicode_logic_kit/atp/tableau_check.py +543 -0
  48. unicode_logic_kit/atp/tptp_ncl.py +811 -0
  49. unicode_logic_kit/atp/tptp_tff.py +1546 -0
  50. unicode_logic_kit/atp/tstp.py +1333 -0
  51. unicode_logic_kit/atp/tstp_check.py +1096 -0
  52. unicode_logic_kit/atp/twee_backend.py +236 -0
  53. unicode_logic_kit/atp/twee_check.py +711 -0
  54. unicode_logic_kit/atp/twee_entailment.py +953 -0
  55. unicode_logic_kit/atp/vampire_entailment.py +540 -0
  56. unicode_logic_kit/atp/z3_arith.py +470 -0
  57. unicode_logic_kit/atp/z3_equivalence.py +36 -0
  58. unicode_logic_kit/atp/z3_fuzzy.py +362 -0
  59. unicode_logic_kit/atp/z3_input.py +500 -0
  60. unicode_logic_kit/atp/z3_models.py +208 -0
  61. unicode_logic_kit/chem/__init__.py +88 -0
  62. unicode_logic_kit/chem/_naming.py +284 -0
  63. unicode_logic_kit/chem/cache.py +185 -0
  64. unicode_logic_kit/chem/interop.py +244 -0
  65. unicode_logic_kit/chem/mol.py +525 -0
  66. unicode_logic_kit/chem/signature.py +112 -0
  67. unicode_logic_kit/comorphism.py +497 -0
  68. unicode_logic_kit/dl/__init__.py +384 -0
  69. unicode_logic_kit/dl/classification.py +227 -0
  70. unicode_logic_kit/dl/concepts.py +632 -0
  71. unicode_logic_kit/dl/datatypes.py +818 -0
  72. unicode_logic_kit/dl/owl_functional.py +2433 -0
  73. unicode_logic_kit/dl/owl_manchester.py +1637 -0
  74. unicode_logic_kit/dl/owl_reasoner.py +790 -0
  75. unicode_logic_kit/dl/parser.py +391 -0
  76. unicode_logic_kit/dl/tableau.py +4048 -0
  77. unicode_logic_kit/dl/translate.py +2704 -0
  78. unicode_logic_kit/drt/__init__.py +94 -0
  79. unicode_logic_kit/drt/export.py +179 -0
  80. unicode_logic_kit/drt/nodes.py +506 -0
  81. unicode_logic_kit/drt/parser.py +965 -0
  82. unicode_logic_kit/drt/resolve.py +195 -0
  83. unicode_logic_kit/drt/reverse.py +175 -0
  84. unicode_logic_kit/eval/__init__.py +106 -0
  85. unicode_logic_kit/eval/batch.py +382 -0
  86. unicode_logic_kit/eval/canonical.py +663 -0
  87. unicode_logic_kit/eval/chem_batch.py +606 -0
  88. unicode_logic_kit/eval/converses.py +200 -0
  89. unicode_logic_kit/eval/datasets/__init__.py +136 -0
  90. unicode_logic_kit/eval/datasets/_base.py +263 -0
  91. unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
  92. unicode_logic_kit/eval/datasets/c3po.py +678 -0
  93. unicode_logic_kit/eval/datasets/folio.py +158 -0
  94. unicode_logic_kit/eval/datasets/fracas.py +418 -0
  95. unicode_logic_kit/eval/datasets/groves.py +191 -0
  96. unicode_logic_kit/eval/datasets/logicbench.py +467 -0
  97. unicode_logic_kit/eval/datasets/logicnli.py +303 -0
  98. unicode_logic_kit/eval/datasets/malls.py +133 -0
  99. unicode_logic_kit/eval/datasets/pfolio.py +594 -0
  100. unicode_logic_kit/eval/datasets/pmb.py +242 -0
  101. unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
  102. unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
  103. unicode_logic_kit/eval/datasets/proverqa.py +674 -0
  104. unicode_logic_kit/eval/datasets/willow.py +478 -0
  105. unicode_logic_kit/eval/equivalence.py +466 -0
  106. unicode_logic_kit/eval/exercise_gen.py +533 -0
  107. unicode_logic_kit/eval/explain.py +791 -0
  108. unicode_logic_kit/eval/generality.py +750 -0
  109. unicode_logic_kit/eval/metric_hf.py +458 -0
  110. unicode_logic_kit/eval/predicate_match.py +343 -0
  111. unicode_logic_kit/eval/theory_check.py +1170 -0
  112. unicode_logic_kit/eval/validate.py +306 -0
  113. unicode_logic_kit/fol/__init__.py +177 -0
  114. unicode_logic_kit/fol/_atom_keys.py +510 -0
  115. unicode_logic_kit/fol/_fol_nodes.py +3586 -0
  116. unicode_logic_kit/fol/_free_parameters.py +105 -0
  117. unicode_logic_kit/fol/_ho_nodes.py +448 -0
  118. unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
  119. unicode_logic_kit/fol/_identifiers.py +1091 -0
  120. unicode_logic_kit/fol/_lambek_nodes.py +112 -0
  121. unicode_logic_kit/fol/_linear_nodes.py +352 -0
  122. unicode_logic_kit/fol/_modal_nodes.py +1467 -0
  123. unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
  124. unicode_logic_kit/fol/_numeral_symbols.py +231 -0
  125. unicode_logic_kit/fol/_so_nodes.py +200 -0
  126. unicode_logic_kit/fol/_symbol_names.py +81 -0
  127. unicode_logic_kit/fol/_team_nodes.py +181 -0
  128. unicode_logic_kit/fol/_tptp_symbols.py +551 -0
  129. unicode_logic_kit/fol/_truth_constants.py +117 -0
  130. unicode_logic_kit/fol/casl_export.py +1135 -0
  131. unicode_logic_kit/fol/casl_import.py +929 -0
  132. unicode_logic_kit/fol/derivation.py +367 -0
  133. unicode_logic_kit/fol/dialect_detect.py +70 -0
  134. unicode_logic_kit/fol/dialect_repair.py +537 -0
  135. unicode_logic_kit/fol/frames.py +637 -0
  136. unicode_logic_kit/fol/grammars/terminals.lark +31 -0
  137. unicode_logic_kit/fol/lambda_tools.py +297 -0
  138. unicode_logic_kit/fol/latex_input.py +429 -0
  139. unicode_logic_kit/fol/modal_translation.py +944 -0
  140. unicode_logic_kit/fol/msflparser.py +1033 -0
  141. unicode_logic_kit/fol/naming.py +422 -0
  142. unicode_logic_kit/fol/nodes.py +241 -0
  143. unicode_logic_kit/fol/normalforms.py +492 -0
  144. unicode_logic_kit/fol/pal.py +287 -0
  145. unicode_logic_kit/fol/prolog_export.py +566 -0
  146. unicode_logic_kit/fol/prolog_input.py +505 -0
  147. unicode_logic_kit/fol/prover9_input.py +1325 -0
  148. unicode_logic_kit/fol/qml.py +1760 -0
  149. unicode_logic_kit/fol/qmltp_input.py +525 -0
  150. unicode_logic_kit/fol/sanitize.py +221 -0
  151. unicode_logic_kit/fol/serialize.py +79 -0
  152. unicode_logic_kit/fol/signature.py +1290 -0
  153. unicode_logic_kit/fol/simplify_check.py +544 -0
  154. unicode_logic_kit/fol/spans.py +594 -0
  155. unicode_logic_kit/fol/tptp_input.py +1503 -0
  156. unicode_logic_kit/fol/tptp_repair.py +941 -0
  157. unicode_logic_kit/fol/unification.py +157 -0
  158. unicode_logic_kit/fol/verbalize.py +263 -0
  159. unicode_logic_kit/hets/__init__.py +163 -0
  160. unicode_logic_kit/hets/bridge.py +142 -0
  161. unicode_logic_kit/hets/client.py +748 -0
  162. unicode_logic_kit/hets/docker.py +420 -0
  163. unicode_logic_kit/hets/dol.py +712 -0
  164. unicode_logic_kit/hets/haskell_json.py +355 -0
  165. unicode_logic_kit/hets/owl_backend.py +794 -0
  166. unicode_logic_kit/hets/owl_cli.py +598 -0
  167. unicode_logic_kit/hets/symbols.py +512 -0
  168. unicode_logic_kit/hol/__init__.py +140 -0
  169. unicode_logic_kit/hol/_ho_common.py +323 -0
  170. unicode_logic_kit/hol/_isabelle_binders.py +125 -0
  171. unicode_logic_kit/hol/classical.py +812 -0
  172. unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
  173. unicode_logic_kit/hol/deepshallow/_common.py +177 -0
  174. unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
  175. unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
  176. unicode_logic_kit/hol/deepshallow/modal.py +217 -0
  177. unicode_logic_kit/hol/deepshallow/qml.py +406 -0
  178. unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
  179. unicode_logic_kit/hol/free.py +753 -0
  180. unicode_logic_kit/hol/goedel.py +336 -0
  181. unicode_logic_kit/hol/ho_modal.py +1743 -0
  182. unicode_logic_kit/hol/intuitionistic.py +403 -0
  183. unicode_logic_kit/hol/isabelle_conditional.py +593 -0
  184. unicode_logic_kit/hol/isabelle_modal.py +1908 -0
  185. unicode_logic_kit/hol/isabelle_relevant.py +412 -0
  186. unicode_logic_kit/hol/isabelle_runner.py +1147 -0
  187. unicode_logic_kit/hol/isabelle_substructural.py +884 -0
  188. unicode_logic_kit/hol/lean.py +1018 -0
  189. unicode_logic_kit/hol/manyvalued.py +921 -0
  190. unicode_logic_kit/hol/secondorder.py +687 -0
  191. unicode_logic_kit/hol/thf_modal.py +941 -0
  192. unicode_logic_kit/hol/thirdorder.py +397 -0
  193. unicode_logic_kit/ilp/__init__.py +89 -0
  194. unicode_logic_kit/ilp/readback.py +389 -0
  195. unicode_logic_kit/ilp/separation.py +153 -0
  196. unicode_logic_kit/ilp/task.py +730 -0
  197. unicode_logic_kit/logic.py +163 -0
  198. unicode_logic_kit/mcp/__init__.py +28 -0
  199. unicode_logic_kit/mcp/__main__.py +5 -0
  200. unicode_logic_kit/mcp/chem_tools.py +1031 -0
  201. unicode_logic_kit/mcp/server.py +2453 -0
  202. unicode_logic_kit/mcp/syntax_spec.py +681 -0
  203. unicode_logic_kit/prob/__init__.py +53 -0
  204. unicode_logic_kit/prob/_bdd.py +225 -0
  205. unicode_logic_kit/prob/_column_gen.py +668 -0
  206. unicode_logic_kit/prob/distribution.py +686 -0
  207. unicode_logic_kit/prob/nilsson.py +470 -0
  208. unicode_logic_kit/py.typed +0 -0
  209. unicode_logic_kit/semantics/__init__.py +137 -0
  210. unicode_logic_kit/semantics/_modal_reject.py +156 -0
  211. unicode_logic_kit/semantics/action_models.py +466 -0
  212. unicode_logic_kit/semantics/asp_models.py +1200 -0
  213. unicode_logic_kit/semantics/conditional.py +580 -0
  214. unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
  215. unicode_logic_kit/semantics/free_logic.py +913 -0
  216. unicode_logic_kit/semantics/fuzzy.py +384 -0
  217. unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
  218. unicode_logic_kit/semantics/intuitionistic.py +581 -0
  219. unicode_logic_kit/semantics/kripke.py +1139 -0
  220. unicode_logic_kit/semantics/manyvalued.py +580 -0
  221. unicode_logic_kit/semantics/matrix.py +342 -0
  222. unicode_logic_kit/semantics/model_eval.py +1135 -0
  223. unicode_logic_kit/semantics/modelfinder.py +1036 -0
  224. unicode_logic_kit/semantics/nonmonotonic.py +372 -0
  225. unicode_logic_kit/semantics/relevant.py +331 -0
  226. unicode_logic_kit/semantics/secondorder.py +657 -0
  227. unicode_logic_kit/semantics/structures.py +352 -0
  228. unicode_logic_kit/semantics/tarski.py +975 -0
  229. unicode_logic_kit/semantics/team.py +315 -0
  230. unicode_logic_kit/semantics/team_translation.py +416 -0
  231. unicode_logic_kit/semantics/thirdorder.py +358 -0
  232. unicode_logic_kit/semantics/tnorm.py +85 -0
  233. unicode_logic_kit/semantics/truthtable.py +201 -0
  234. unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
  235. unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
  236. unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
  237. unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,598 @@
1
+ r"""The HETS COMMAND-LINE route: ``owl_to_tptp``, the only way to the lossy translation.
2
+
3
+ Why a second route at all
4
+ -------------------------
5
+ :mod:`~unicode_logic_kit.hets.client` speaks HETS' REST API and never shells
6
+ out. That covers everything the server offers — except one switch. HETS
7
+ refuses a comorphism whose source sublogic does not cover the theory, and
8
+ ``GET /theory`` then answers HTTP 422 (see
9
+ :class:`~unicode_logic_kit.hets.client.HetsSublogicError`). ``hets-server``'s
10
+ ``-Y`` flag translates ANYWAY, dropping the axioms it cannot express, and
11
+ ``-Y`` exists ONLY on the command line: the REST API has no equivalent. For a
12
+ real ontology that one flag is the difference between a FOL image and nothing
13
+ at all — measured on OEO 2.13.0, two ``DataPropertyRange(d rdfs:Literal)``
14
+ axioms out of 4041 push the whole ontology out of ``OWL22CASL``'s sublogic.
15
+
16
+ So this module exists, and it is deliberately thin: it builds a command line,
17
+ runs it through ``docker exec``, copies the output back and reads the console. All the
18
+ interpretation lives in :mod:`~unicode_logic_kit.hets.symbols`, which is pure
19
+ and fully offline-testable — that split is what makes this specifiable on a
20
+ machine with no HETS.
21
+
22
+ ``lossy=True`` runs the NON-LOSSY translation FIRST, on purpose
23
+ ---------------------------------------------------------------
24
+ ``-Y`` is SILENT. Measured from HETS' own console output: the lossy run prints
25
+ only ``Translated using comorphism OWL22CASL;CASL2TPTP_FOF : OWL -> TPTP``,
26
+ while the sublogic warning appears only in the run WITHOUT ``-Y``::
27
+
28
+ ### Warning:
29
+ for 'OWL22CASL;CASL2TPTP_FOF' expected sublogic 'NP-sROIQux-D|-|'
30
+ but found sublogic 'NP-sROIQ-D|Literal|dateTime|decimal|integer|string|' ...
31
+ Keeping untranslated theory
32
+
33
+ A single ``-Y`` run would therefore hand the caller a quietly smaller theory
34
+ with nothing to indicate it. That is exactly the silent approximation this kit
35
+ refuses, so ``lossy=True`` runs the probe first and fills
36
+ :attr:`OwlTptpResult.sublogic_mismatch` from HETS' own warning text. The
37
+ mismatch is then reported even when no ``client=`` was passed and
38
+ :attr:`OwlTptpResult.omitted_axioms` is consequently ``None``.
39
+
40
+ The COST, stated rather than hidden: two HETS runs per call, measured at
41
+ 4.6 s + 4.4 s for OEO's 3.85 MB, so ~9 s instead of ~4.5 s. On a much larger
42
+ ontology that doubling is the dominant cost of the call, and the only way to
43
+ avoid it is to pass ``client=`` and let :func:`~unicode_logic_kit.hets.symbols.untranslated_axioms`
44
+ compute the loss from ``/dg`` against the TPTP instead — which is both exact
45
+ and free. ``lossy=False`` does one run and raises
46
+ :class:`~unicode_logic_kit.hets.client.HetsSublogicError`.
47
+
48
+ EXIT 0 is not success
49
+ ---------------------
50
+ Both non-lossy runs above exit 0 and write the UNTRANSLATED theory; with
51
+ ``-o tptp`` no ``.tptp`` file is written at all. The wrong separator exits 0
52
+ too (``### Warning: Cannot find logic comorphism OWL22CASL;CASL2TPTP_FOF``).
53
+ So success is the CONJUNCTION of: a ``.tptp`` file appeared in the output
54
+ directory, AND the console contains ``Translated using comorphism``. Anything
55
+ else is a named refusal.
56
+
57
+ What this module will NOT do: normalise the ontology
58
+ ----------------------------------------------------
59
+ HETS 0.108.0 rejects an ``AnnotationAssertion`` whose subject is not declared
60
+ (``Incorrect AnnotationAssertion axiom. Axiom subject is not declared:
61
+ 'obo:BFO_0000134'``), which OWL 2 does not require. Fixing that inside this
62
+ kit would need three things it must not do silently: (1) CHOOSE an entity kind
63
+ for an annotation-only IRI — the requesting project's own log reads "kind not
64
+ stated in the file, assumed ObjectProperty", i.e. a guess that puts a symbol
65
+ into the logical signature; (2) an RDF/XML WRITER, which this kit does not
66
+ have and should not grow for this; (3) DELETE two ``rdfs:isDefinedBy``
67
+ annotations, i.e. lose information. So :func:`owl_to_tptp` instead DETECTS the
68
+ pattern in HETS' console, collects every undeclared subject and raises
69
+ :class:`HetsOwlNormalizationError` listing them with both remedies and the
70
+ explicit statement that this kit will not choose the kind. That turns an
71
+ opaque HETS failure into a one-line instruction and leaves the semantic
72
+ choice with the ontology's owner.
73
+
74
+ Two routes
75
+ ----------
76
+ No ``unicode_logic_kit.dl`` file is touched and no construct reaches the
77
+ description-logic tableau. The two-route rule applies at the level the whole
78
+ OEO request is about: :func:`owl_to_tptp` produces the SECOND route's FOL
79
+ image of an ontology, to be compared against
80
+ :func:`unicode_logic_kit.dl.tbox_to_fol` / :func:`unicode_logic_kit.dl.kb_to_fol`
81
+ plus :func:`unicode_logic_kit.api.prove`. The helper therefore must never
82
+ present an incomplete image as complete — which is what
83
+ :attr:`~OwlTptpResult.sublogic_mismatch`, :attr:`~OwlTptpResult.omitted_axioms`,
84
+ :attr:`~unicode_logic_kit.hets.symbols.HetsSymbol.in_tptp` and
85
+ :attr:`~unicode_logic_kit.hets.symbols.HetsSymbolTable.unmapped_tptp` are for —
86
+ and HETS' own translation defects are listed in
87
+ :mod:`~unicode_logic_kit.hets.symbols`' docstring so a disagreement is
88
+ attributable rather than blamed on this kit.
89
+ """
90
+
91
+ from __future__ import annotations
92
+
93
+ import os
94
+ import posixpath
95
+ import re
96
+ import shutil
97
+ import subprocess
98
+ import uuid
99
+ from dataclasses import dataclass
100
+ from typing import List, Optional, Tuple, Union
101
+
102
+ from ..atp.protocol import BackendUnavailable
103
+ from .client import HetsClient, HetsSublogicError, _SUBLOGIC_RE
104
+ from .docker import HETS_IMAGE, HetsContainer
105
+ from .symbols import (
106
+ HetsSymbolTable,
107
+ UntranslatedAxiom,
108
+ hets_prefixes,
109
+ hets_symbol_table,
110
+ untranslated_axioms,
111
+ )
112
+
113
+ __all__ = [
114
+ "SublogicMismatch",
115
+ "OwlTptpResult",
116
+ "HetsOwlNormalizationError",
117
+ "owl_to_tptp",
118
+ "UFK_HETS_CONTAINER",
119
+ ]
120
+
121
+ #: The environment variable naming a running HETS container to ``docker
122
+ #: exec`` into. Deliberately NOT ``$UFK_HETS_URL``:
123
+ #: :func:`~unicode_logic_kit.hets.docker.discover_hets_url` may legitimately
124
+ #: return a URL that is not a local container at all (its own docstring says
125
+ #: so — a shared server, a CI sidecar), and ``docker exec`` into a URL is
126
+ #: impossible. So the command-line route gets its own discovery and its own
127
+ #: variable, mirroring ``discover_hets_url``'s "never guess, list every
128
+ #: option tried" contract rather than reusing it.
129
+ UFK_HETS_CONTAINER = "UFK_HETS_CONTAINER"
130
+
131
+ #: The console line that means the translation actually happened. HETS writes
132
+ #: the composition with ``;`` here even though the command line takes ``:``.
133
+ _TRANSLATED_RE = re.compile(
134
+ r"Translated using comorphism\s+(\S+)\s*:\s*(\S+)\s*->\s*(\S+)")
135
+
136
+ #: HETS could not resolve the comorphism name at all — what the ``;``
137
+ #: spelling produces on the command line, with EXIT 0 and no output file.
138
+ _NO_COMORPHISM_RE = re.compile(r"Cannot find logic comorphism\s+(\S+)")
139
+
140
+ #: ``Incorrect AnnotationAssertion axiom. Axiom subject is not declared:
141
+ #: 'obo:BFO_0000134'`` — HETS may report only the first, so the refusal says
142
+ #: the list may be incomplete.
143
+ _UNDECLARED_RE = re.compile(
144
+ r"Axiom subject is not declared:\s*'([^']*)'")
145
+
146
+
147
+ class HetsOwlNormalizationError(RuntimeError):
148
+ """HETS refused the ontology because an annotation subject is undeclared.
149
+
150
+ Attributes:
151
+ undeclared: every IRI HETS named, in the order it named them. HETS
152
+ may stop at the first, so this list can be incomplete — the
153
+ message says so.
154
+
155
+ A ``RuntimeError`` subclass, like the other HETS exceptions: the server
156
+ (here, the binary) is fine and the request was understood; the INPUT is
157
+ the problem. This kit will not repair it — see the module docstring for
158
+ the three things such a repair would have to guess.
159
+ """
160
+
161
+ def __init__(self, message: str, *, undeclared: Tuple[str, ...]):
162
+ super().__init__(message)
163
+ self.undeclared = undeclared
164
+
165
+
166
+ @dataclass(frozen=True)
167
+ class SublogicMismatch:
168
+ """HETS' sublogic complaint, parsed into its three parts.
169
+
170
+ Fields:
171
+
172
+ * ``comorphism`` — as HETS spells it, with ``;``
173
+ (``"OWL22CASL;CASL2TPTP_FOF"``).
174
+ * ``expected`` — the sublogic the comorphism covers.
175
+ * ``found`` — the sublogic the theory actually is.
176
+ """
177
+
178
+ comorphism: str
179
+ expected: str
180
+ found: str
181
+
182
+
183
+ @dataclass(frozen=True)
184
+ class OwlTptpResult:
185
+ """The outcome of one :func:`owl_to_tptp` call.
186
+
187
+ Fields:
188
+
189
+ * ``tptp`` — the ``.tptp`` text, exactly as HETS wrote it. Ready for
190
+ :func:`unicode_logic_kit.fol.tptp_input.parse_tptp` — unlike the
191
+ REST ``/theory`` rendering, the ``-o tptp`` file carries no
192
+ header.
193
+ * ``node`` — the development-graph node name, recovered from the output
194
+ file name (HETS percent-encodes the node IRI into it).
195
+ * ``comorphism`` — the comorphism HETS reports having used, in HETS'
196
+ ``;`` spelling.
197
+ * ``lossy`` — whether ``-Y`` was actually used for the run that produced
198
+ :attr:`tptp`.
199
+ * ``sublogic_mismatch`` — the loss REPORT. ``None`` means the probe run
200
+ succeeded without ``-Y``, so nothing was dropped for sublogic
201
+ reasons. Non-``None`` means ``-Y`` was needed, and names the
202
+ sublogic gap.
203
+ * ``console`` — HETS' stdout and stderr for the run that produced
204
+ :attr:`tptp`, verbatim.
205
+ * ``theory`` — the ``-o th`` text — the TRANSLATED theory.
206
+ * ``pretty`` — the ``-o pp.dol`` text — HETS' pretty-printed rendering of the
207
+ SOURCE ontology, and the only one of the three outputs that
208
+ carries the ``Prefix: p: <iri>`` lines (measured: the translated
209
+ ``.th`` of a TPTP chain has none). Feed it to
210
+ :func:`~unicode_logic_kit.hets.symbols.hets_prefixes`; that is what
211
+ :attr:`symbols` already does.
212
+ * ``symbols`` — the OWL-entity/TPTP-symbol join, or ``None`` when no
213
+ ``client=`` was given. ``None`` means NOT COMPUTED, never
214
+ "nothing found": the join needs ``/dg``, which needs the REST
215
+ client.
216
+ * ``omitted_axioms`` — the axioms with no formula in :attr:`tptp`, or
217
+ ``None`` when no ``client=`` was given. An empty tuple ``()``
218
+ means "computed, and the answer is none".
219
+ """
220
+
221
+ tptp: str
222
+ node: str
223
+ comorphism: str
224
+ lossy: bool
225
+ sublogic_mismatch: Optional[SublogicMismatch]
226
+ console: str
227
+ theory: str
228
+ pretty: str = ""
229
+ symbols: Optional[HetsSymbolTable] = None
230
+ omitted_axioms: Optional[Tuple[UntranslatedAxiom, ...]] = None
231
+
232
+
233
+ @dataclass(frozen=True)
234
+ class _Run:
235
+ """One ``hets-server`` invocation's raw outcome."""
236
+
237
+ argv: List[str]
238
+ returncode: int
239
+ console: str
240
+ out_dir: str
241
+ outputs: dict # basename -> text, for the files HETS wrote
242
+
243
+
244
+ def _docker_bin() -> str:
245
+ found = shutil.which("docker")
246
+ if found is None:
247
+ raise BackendUnavailable(
248
+ "hets: no `docker` binary found on PATH, and owl_to_tptp reaches "
249
+ "hets-server through `docker exec`. Install Docker Desktop "
250
+ "(https://www.docker.com/products/docker-desktop/) and ensure "
251
+ "`docker` is on PATH. A remote $UFK_HETS_URL cannot substitute: "
252
+ "the lossy switch (-Y) exists only on the command line and HETS' "
253
+ "REST route has no equivalent.")
254
+ return found
255
+
256
+
257
+ def _resolve_container(container, start_container: bool
258
+ ) -> Tuple[str, Optional[HetsContainer]]:
259
+ """Name the container to exec into; never guess, list every option tried.
260
+
261
+ Returns ``(name, owned)`` where ``owned`` is a container this call
262
+ started (and must stop) or ``None``.
263
+ """
264
+ if isinstance(container, HetsContainer):
265
+ return container.name, None
266
+ if isinstance(container, str) and container.strip():
267
+ return container.strip(), None
268
+ from_env = os.environ.get(UFK_HETS_CONTAINER, "").strip()
269
+ if from_env:
270
+ return from_env, None
271
+ if start_container:
272
+ started = HetsContainer()
273
+ started.start()
274
+ return started.name, started
275
+ raise BackendUnavailable(
276
+ "hets: owl_to_tptp needs a Hets CONTAINER to 'docker exec' into, not "
277
+ "just a server URL. Tried, in order:\n"
278
+ " 1. container= — not given\n"
279
+ f" 2. ${UFK_HETS_CONTAINER} — not set\n"
280
+ "Fix one of:\n"
281
+ " - set $" + UFK_HETS_CONTAINER + " to a running spechub2/hets "
282
+ "container name\n"
283
+ " - pass container=<name> or container=<HetsContainer>\n"
284
+ " - call owl_to_tptp(..., start_container=True) to have this kit "
285
+ "start and own one (needs Docker Desktop / a running daemon; image "
286
+ f"{HETS_IMAGE})\n"
287
+ "A remote $UFK_HETS_URL cannot be used instead: the lossy switch "
288
+ "(-Y) exists only on the command line and Hets' REST route has no "
289
+ "equivalent.")
290
+
291
+
292
+ def _exec(docker: str, container: str, command: str, timeout: float) -> Tuple[int, str]:
293
+ """Run one shell command inside the container; return (exit code, console)."""
294
+ argv = [docker, "exec", container, "sh", "-c", command]
295
+ proc = subprocess.run(argv, capture_output=True, text=True, timeout=timeout)
296
+ return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
297
+
298
+
299
+ def _hets_command(remote_in: str, remote_out: str, comorphism: str,
300
+ lossy: bool) -> str:
301
+ """The exact ``hets-server`` command line, as the evidence records it.
302
+
303
+ ``hets-server -v2 -a none [-Y] -t <comorphism> -o th,tptp,pp.dol -O <dir> <file>``
304
+ — ``-v2`` so the ``Translated using comorphism`` line is printed at all,
305
+ ``-a none`` so HETS does not try to PROVE anything, and ``-O`` into a
306
+ directory this call owns.
307
+
308
+ Three outputs, each for a measured reason. ``tptp`` is the problem itself.
309
+ ``th`` is the translated theory. ``pp.dol`` is the PRETTY-PRINTED SOURCE,
310
+ and it is the only one of the three that carries HETS' ``Prefix: p: <iri>``
311
+ lines: measured on the real ontology, the translated ``.th`` of a TPTP
312
+ chain has ZERO prefix lines (it is a TPTP theory, not an OWL one) while
313
+ ``pp.dol`` has all 15. Without it :attr:`OwlTptpResult.symbols` would
314
+ silently leave every CURIE unexpanded. All three come out of ONE
315
+ ``hets-server`` run — verified live; adding ``pp.dol`` costs no extra
316
+ invocation.
317
+ """
318
+ parts = ["hets-server", "-v2", "-a", "none"]
319
+ if lossy:
320
+ parts.append("-Y")
321
+ parts += ["-t", comorphism, "-o", "th,tptp,pp.dol",
322
+ "-O", remote_out, remote_in]
323
+ return " ".join(parts)
324
+
325
+
326
+ def _refuse_bad_comorphism(comorphism: str) -> None:
327
+ """Refuse the two comorphism spellings that fail silently or abort."""
328
+ if ";" in comorphism:
329
+ raise RuntimeError(
330
+ "hets: Hets could not find the comorphism "
331
+ f"{comorphism!r} (its console says \"Cannot find logic "
332
+ "comorphism\"). On the command line the composition separator is "
333
+ "':' — pass comorphism="
334
+ f"\"{comorphism.replace(';', ':')}\". Hets spells it with ';' in "
335
+ "its own messages; that spelling is not accepted as input.")
336
+ if "SoftFOL" in comorphism:
337
+ raise RuntimeError(
338
+ "hets: owl_to_tptp does not offer the SoftFOL/DFG route: Hets "
339
+ "0.108.0 aborts on CASL numerals "
340
+ "(\"SuleCFOL2SoftFOL.transOPSYMB: unknown op: Qual_op_name 1 "
341
+ "...\", exit 1), which any ontology with a DataHasValue numeral "
342
+ "or a DatatypeRestriction produces. Use "
343
+ "OWL22CASL:CASL2TPTP_FOF.")
344
+
345
+
346
+ def _check_console(run: _Run, comorphism: str) -> None:
347
+ """Turn HETS' known console failures into named refusals."""
348
+ undeclared = tuple(_UNDECLARED_RE.findall(run.console))
349
+ if undeclared:
350
+ listed = "\n".join(f" - {iri}" for iri in undeclared)
351
+ raise HetsOwlNormalizationError(
352
+ "hets: Hets refuses this ontology because an AnnotationAssertion "
353
+ "names a subject the file never declares. OWL 2 does not require "
354
+ "such a declaration, so this is a Hets restriction, not an error "
355
+ "in the ontology. Hets named:\n"
356
+ f"{listed}\n"
357
+ "(Hets may report only the FIRST one, so this list can be "
358
+ "incomplete — fix these and re-run.)\n"
359
+ "Two remedies, both yours to choose:\n"
360
+ " - add a Declaration( <Kind>( <iri> ) ) for each IRI above, or\n"
361
+ " - remove the annotation assertions that name them.\n"
362
+ "This kit will NOT choose the Kind for you: an annotation-only "
363
+ "IRI has no kind stated in the file, and guessing one would put a "
364
+ "symbol into the logical signature on a guess — which changes "
365
+ "what the translated theory means. Nor will it rewrite your "
366
+ "RDF/XML: this kit has no OWL writer and will not grow one to "
367
+ "edit your ontology behind your back.",
368
+ undeclared=undeclared)
369
+ missing = _NO_COMORPHISM_RE.search(run.console)
370
+ if missing:
371
+ raise RuntimeError(
372
+ f"hets: Hets could not find the comorphism {missing.group(1)!r} "
373
+ "(its console says \"Cannot find logic comorphism\"). On the "
374
+ "command line the composition separator is ':' — pass "
375
+ f"comorphism=\"{comorphism}\" spelled with ':'. Hets spells it "
376
+ "with ';' in its own messages; that spelling is not accepted as "
377
+ "input.")
378
+
379
+
380
+ def _sublogic_mismatch(console: str) -> Optional[SublogicMismatch]:
381
+ """HETS' sublogic warning, parsed, or ``None`` if it did not complain."""
382
+ match = _SUBLOGIC_RE.search(console)
383
+ if not match:
384
+ return None
385
+ comorphism, expected, found = match.groups()
386
+ return SublogicMismatch(comorphism=comorphism, expected=expected, found=found)
387
+
388
+
389
+ def _pick_output(run: _Run, suffix: str) -> Tuple[str, str]:
390
+ """The one file with this suffix; never a pick between two.
391
+
392
+ Returns ``(basename, text)``.
393
+ """
394
+ candidates = sorted(name for name in run.outputs if name.endswith(suffix))
395
+ if len(candidates) == 1:
396
+ return candidates[0], run.outputs[candidates[0]]
397
+ if not candidates:
398
+ raise RuntimeError(
399
+ f"hets: hets-server exited {run.returncode} and reported a "
400
+ f"translation, but wrote no {suffix} file into {run.out_dir} "
401
+ f"(it wrote {sorted(run.outputs) or 'nothing'}). Its console "
402
+ "was:\n" + run.console)
403
+ raise RuntimeError(
404
+ f"hets: hets-server wrote {len(candidates)} {suffix} files into "
405
+ f"{run.out_dir} ({', '.join(candidates)}) and this helper will not "
406
+ "pick between them — one ontology file should produce one translated "
407
+ "theory. Translate a single-ontology file, or inspect the directory "
408
+ "yourself.")
409
+
410
+
411
+ def _node_from_filename(basename: str, suffix: str) -> str:
412
+ """Recover the development-graph node IRI from HETS' output file name.
413
+
414
+ HETS writes ``<stem>_<percent-encoded node IRI><suffix>``, e.g.
415
+ ``oeo-hets_https%3A%2F%2Fopenenergyplatform.org%2Fontology%2Foeo%2F.tptp``.
416
+ """
417
+ import urllib.parse
418
+
419
+ stem = basename[:-len(suffix)] if basename.endswith(suffix) else basename
420
+ _, sep, encoded = stem.partition("_")
421
+ return urllib.parse.unquote(encoded) if sep else stem
422
+
423
+
424
+ def _run_hets(docker: str, container: str, remote_dir: str, remote_in: str,
425
+ comorphism: str, lossy: bool, timeout: float) -> _Run:
426
+ """One translation attempt, with its own output directory, read back."""
427
+ out_dir = posixpath.join(remote_dir, "out_lossy" if lossy else "out_probe")
428
+ command = _hets_command(remote_in, out_dir, comorphism, lossy)
429
+ argv = [docker, "exec", container, "sh", "-c",
430
+ f"mkdir -p {out_dir} && cd {remote_dir} && {command}"]
431
+ proc = subprocess.run(argv, capture_output=True, text=True, timeout=timeout)
432
+ console = (proc.stdout or "") + (proc.stderr or "")
433
+ _code, listing = _exec(docker, container,
434
+ f"ls -1 {out_dir} 2>/dev/null || true", timeout)
435
+ outputs = {}
436
+ for name in (line.strip() for line in listing.splitlines()):
437
+ if not name:
438
+ continue
439
+ code, text = _exec(docker, container,
440
+ f"cat {posixpath.join(out_dir, name)}", timeout)
441
+ if code == 0:
442
+ outputs[name] = text
443
+ return _Run(argv=argv, returncode=proc.returncode, console=console,
444
+ out_dir=out_dir, outputs=outputs)
445
+
446
+
447
+ def owl_to_tptp(path: str, *, lossy: bool = True,
448
+ comorphism: str = "OWL22CASL:CASL2TPTP_FOF",
449
+ container: Union[str, HetsContainer, None] = None,
450
+ start_container: bool = False,
451
+ client: Optional[HetsClient] = None,
452
+ timeout: float = 900.0) -> OwlTptpResult:
453
+ r"""Translate an OWL ontology file to TPTP through ``hets-server``.
454
+
455
+ The command line, exactly as the evidence records it working::
456
+
457
+ hets-server -v2 -a none [-Y] -t <comorphism> -o th,tptp -O <dir> <file>
458
+
459
+ Args:
460
+ path: a local OWL file (RDF/XML or functional syntax — whatever HETS
461
+ reads). It is copied into the container and removed again.
462
+ lossy: ``True`` (the default) runs the NON-LOSSY translation first as
463
+ a probe and only falls back to ``-Y`` when HETS complains about
464
+ the sublogic, so a loss is always REPORTED on
465
+ :attr:`OwlTptpResult.sublogic_mismatch` rather than being silent.
466
+ Costs a second HETS run; see the module docstring for the
467
+ measured price and the ``client=`` alternative. ``False`` does
468
+ one run and raises
469
+ :class:`~unicode_logic_kit.hets.client.HetsSublogicError` instead.
470
+ comorphism: a HETS comorphism composition, ``:``-separated. The
471
+ ``;`` spelling HETS uses in its own messages is refused by name
472
+ (it exits 0 and silently writes nothing), and any ``SoftFOL``
473
+ target is refused by name too (HETS 0.108.0 aborts on CASL
474
+ numerals).
475
+ container: a running container's name, or a
476
+ :class:`~unicode_logic_kit.hets.docker.HetsContainer`. Falls back
477
+ to ``$UFK_HETS_CONTAINER``.
478
+ start_container: start (and stop) a container owned by this call when
479
+ nothing else is available.
480
+ client: a :class:`~unicode_logic_kit.hets.client.HetsClient` for the
481
+ SAME HETS. Given one, the result also carries
482
+ :attr:`~OwlTptpResult.symbols` and
483
+ :attr:`~OwlTptpResult.omitted_axioms`, computed from ``/dg``.
484
+ Without one both are ``None`` — not empty.
485
+ timeout: seconds for each ``docker exec``.
486
+
487
+ Returns:
488
+ An :class:`OwlTptpResult`.
489
+
490
+ Raises:
491
+ ~unicode_logic_kit.atp.protocol.BackendUnavailable: no container to
492
+ exec into, or no ``docker`` on PATH.
493
+ ~unicode_logic_kit.hets.client.HetsSublogicError: ``lossy=False`` and
494
+ HETS refused the comorphism for this theory.
495
+ HetsOwlNormalizationError: HETS refused the ontology because an
496
+ annotation subject is undeclared.
497
+ RuntimeError: a bad comorphism spelling, or HETS exited without
498
+ writing a ``.tptp``.
499
+ """
500
+ _refuse_bad_comorphism(comorphism)
501
+ docker = _docker_bin()
502
+ container_name, owned = _resolve_container(container, start_container)
503
+ remote_dir = posixpath.join("/tmp", f"ufk_owl_to_tptp_{uuid.uuid4().hex[:12]}")
504
+ local_name = os.path.basename(path) or "ontology.owl"
505
+ remote_in = posixpath.join(remote_dir, local_name)
506
+ try:
507
+ _exec(docker, container_name, f"mkdir -p {remote_dir}", timeout)
508
+ copy = subprocess.run(
509
+ [docker, "cp", path, f"{container_name}:{remote_in}"],
510
+ capture_output=True, text=True, timeout=timeout)
511
+ if copy.returncode != 0:
512
+ raise RuntimeError(
513
+ f"hets: `docker cp {path} {container_name}:{remote_in}` failed "
514
+ f"(exit {copy.returncode}): "
515
+ f"{(copy.stderr or '').strip()!r}")
516
+
517
+ probe = _run_hets(docker, container_name, remote_dir, remote_in,
518
+ comorphism, lossy=False, timeout=timeout)
519
+ _check_console(probe, comorphism)
520
+ mismatch = _sublogic_mismatch(probe.console)
521
+ translated = _TRANSLATED_RE.search(probe.console)
522
+
523
+ if translated and not mismatch:
524
+ run, used_lossy = probe, False
525
+ elif not lossy:
526
+ if mismatch:
527
+ raise HetsSublogicError(
528
+ "hets: Hets refuses the comorphism "
529
+ f"{mismatch.comorphism!r} for this ontology — it covers "
530
+ f"sublogic {mismatch.expected!r} but the theory is "
531
+ f"{mismatch.found!r}, so hets-server kept the "
532
+ "UNTRANSLATED theory and wrote no .tptp. Pass lossy=True "
533
+ "to run hets-server -Y, which translates anyway and "
534
+ "reports the axioms it omitted on the result's "
535
+ ".omitted_axioms.",
536
+ comorphism=mismatch.comorphism,
537
+ expected=mismatch.expected, found=mismatch.found,
538
+ body=probe.console)
539
+ run, used_lossy = probe, False
540
+ else:
541
+ run = _run_hets(docker, container_name, remote_dir, remote_in,
542
+ comorphism, lossy=True, timeout=timeout)
543
+ _check_console(run, comorphism)
544
+ used_lossy = True
545
+ translated = _TRANSLATED_RE.search(run.console)
546
+
547
+ if not translated:
548
+ raise RuntimeError(
549
+ f"hets: hets-server exited {run.returncode} but wrote no "
550
+ ".tptp into the output directory and its console does not "
551
+ "contain \"Translated using comorphism\" — Hets keeps the "
552
+ "UNTRANSLATED theory and only warns. Its console was:\n"
553
+ + run.console)
554
+ tptp_name, tptp_text = _pick_output(run, ".tptp")
555
+ try:
556
+ _theory_name, theory_text = _pick_output(run, ".th")
557
+ except RuntimeError:
558
+ theory_text = ""
559
+ try:
560
+ _pretty_name, pretty_text = _pick_output(run, ".pp.dol")
561
+ except RuntimeError:
562
+ pretty_text = ""
563
+ node = _node_from_filename(tptp_name, ".tptp")
564
+
565
+ symbols = None
566
+ omitted = None
567
+ if client is not None:
568
+ with open(path, encoding="utf-8") as handle:
569
+ iri = client.upload(handle.read(), local_name)
570
+ dg = client.dg(iri)
571
+ # pp.dol first: it is the SOURCE rendering and the only output
572
+ # that carries the Prefix: lines. The translated .th is tried as
573
+ # a fallback for a comorphism whose target keeps them.
574
+ prefixes = hets_prefixes(pretty_text) or hets_prefixes(theory_text)
575
+ symbols = hets_symbol_table(dg, tptp_text, prefixes=prefixes)
576
+ omitted = untranslated_axioms(dg, tptp_text)
577
+
578
+ return OwlTptpResult(
579
+ tptp=tptp_text,
580
+ node=node,
581
+ comorphism=translated.group(1),
582
+ lossy=used_lossy,
583
+ sublogic_mismatch=mismatch,
584
+ console=run.console,
585
+ theory=theory_text,
586
+ pretty=pretty_text,
587
+ symbols=symbols,
588
+ omitted_axioms=omitted,
589
+ )
590
+ finally:
591
+ # Never leave anything behind in a container this kit does not own,
592
+ # and never write outside /tmp.
593
+ try:
594
+ _exec(docker, container_name, f"rm -rf {remote_dir}", timeout)
595
+ except Exception:
596
+ pass
597
+ if owned is not None:
598
+ owned.stop()