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,157 @@
1
+ """Robinson first-order term unification (with occurs-check).
2
+
3
+ Computes a most-general unifier (MGU) of two terms, or of two atoms, over the
4
+ classical term language: Variable, Constant, Number, Function (and Atom for the
5
+ literal case). A substitution is a plain dict mapping a variable NAME (str) to a
6
+ term Node. The algorithm is functional: inputs are never mutated, and the input
7
+ substitution is copied before any binding is added.
8
+
9
+ Unification rules:
10
+ - A Variable unifies with any term t, binding its name to t, provided the
11
+ occurs-check passes (the variable does not occur in t under the current
12
+ substitution).
13
+ - Constant and Number unify iff they are equal (same value).
14
+ - Function unify iff they share name and arity and their arguments unify
15
+ pairwise.
16
+ - Atom unify iff they share predicate and arity and their arguments unify
17
+ pairwise.
18
+ """
19
+
20
+ from typing import Dict, Optional
21
+
22
+ from .nodes import Variable, Constant, Number, Function, Atom, Node, Measure
23
+
24
+
25
+ def apply_subst(node: Node, subst: Dict[str, Node]) -> Node:
26
+ """Apply substitution subst to node, returning a new node.
27
+
28
+ Recurses into Function/Atom arguments. A Variable is replaced by its bound
29
+ term when its name is a key of subst (the replacement is itself substituted
30
+ into, so chained bindings resolve fully); otherwise it is returned
31
+ unchanged. Constants and Numbers are returned unchanged. The input node is
32
+ never mutated.
33
+ """
34
+ if isinstance(node, Variable):
35
+ if node.name in subst:
36
+ return apply_subst(subst[node.name], subst)
37
+ return node
38
+ if isinstance(node, (Constant, Number)):
39
+ return node
40
+ if isinstance(node, Function):
41
+ return Function(node.name, [apply_subst(a, subst) for a in node.args])
42
+ if isinstance(node, Measure):
43
+ # μ(entity, dimension) is an ordinary binary term (lowered to the
44
+ # function measure/2 by the exports): substitute into both slots.
45
+ return Measure(apply_subst(node.entity, subst),
46
+ apply_subst(node.dimension, subst))
47
+ if isinstance(node, Atom):
48
+ return Atom(node.predicate, [apply_subst(a, subst) for a in node.args])
49
+ raise TypeError(f"apply_subst: unsupported node type {type(node).__name__}")
50
+
51
+
52
+ def _occurs(name: str, term: Node, subst: Dict[str, Node]) -> bool:
53
+ """Return True if the variable name occurs in term after applying subst.
54
+
55
+ Used by the occurs-check to reject bindings such as x ↦ f(x) that would
56
+ create an infinite (cyclic) term.
57
+ """
58
+ term = apply_subst(term, subst)
59
+ if isinstance(term, Variable):
60
+ return term.name == name
61
+ if isinstance(term, (Function, Atom)):
62
+ return any(_occurs(name, a, subst) for a in term.args)
63
+ if isinstance(term, Measure):
64
+ return (_occurs(name, term.entity, subst)
65
+ or _occurs(name, term.dimension, subst))
66
+ return False
67
+
68
+
69
+ def _bind(name: str, term: Node, subst: Dict[str, Node]) -> Optional[Dict[str, Node]]:
70
+ """Extend subst with the binding name ↦ term, with occurs-check.
71
+
72
+ Returns a new substitution dict (the input is copied, never mutated), or
73
+ None if the occurs-check fails. Binding a variable to itself is a no-op that
74
+ leaves the substitution unchanged.
75
+ """
76
+ if isinstance(term, Variable) and term.name == name:
77
+ return subst
78
+ if _occurs(name, term, subst):
79
+ return None
80
+ new_subst = dict(subst)
81
+ new_subst[name] = apply_subst(term, subst)
82
+ return new_subst
83
+
84
+
85
+ def unify(t1: Node, t2: Node, subst: Optional[Dict[str, Node]] = None) -> Optional[Dict[str, Node]]:
86
+ """Most-general unifier of t1 and t2, or None if they do not unify.
87
+
88
+ Robinson's algorithm with occurs-check. The result is a dict mapping
89
+ variable NAME -> term such that applying it to t1 and t2 (via apply_subst)
90
+ yields structurally equal nodes. An empty dict means the terms are already
91
+ equal / trivially unifiable.
92
+
93
+ Accepts Variable, Constant, Number, Function term nodes, or two Atoms (which
94
+ unify iff they share predicate and arity and their arguments unify
95
+ pairwise). The optional subst argument is treated as immutable: it is copied
96
+ before extension, never modified in place.
97
+ """
98
+ if subst is None:
99
+ subst = {}
100
+
101
+ t1 = apply_subst(t1, subst)
102
+ t2 = apply_subst(t2, subst)
103
+
104
+ # Variable on either side: bind it (occurs-check inside _bind).
105
+ if isinstance(t1, Variable):
106
+ return _bind(t1.name, t2, subst)
107
+ if isinstance(t2, Variable):
108
+ return _bind(t2.name, t1, subst)
109
+
110
+ # Constants / Numbers: unify iff equal in value.
111
+ if isinstance(t1, (Constant, Number)) or isinstance(t2, (Constant, Number)):
112
+ if type(t1) is type(t2) and _leaf_value(t1) == _leaf_value(t2):
113
+ return subst
114
+ return None
115
+
116
+ # Function applications: same name + arity, then unify arguments pairwise.
117
+ if isinstance(t1, Function) and isinstance(t2, Function):
118
+ if t1.name != t2.name or len(t1.args) != len(t2.args):
119
+ return None
120
+ return _unify_args(t1.args, t2.args, subst)
121
+
122
+ # Measure terms: a fixed binary function symbol — unify slot-wise. Purely
123
+ # syntactic: a Measure never unifies with a Function (even one literally
124
+ # named "measure"); the measure/2 spelling is an EXPORT convention, not an
125
+ # AST identity.
126
+ if isinstance(t1, Measure) and isinstance(t2, Measure):
127
+ return _unify_args((t1.entity, t1.dimension),
128
+ (t2.entity, t2.dimension), subst)
129
+
130
+ # Atoms (literal-level unification): same predicate + arity, then args.
131
+ if isinstance(t1, Atom) and isinstance(t2, Atom):
132
+ if t1.predicate != t2.predicate or len(t1.args) != len(t2.args):
133
+ return None
134
+ return _unify_args(t1.args, t2.args, subst)
135
+
136
+ return None
137
+
138
+
139
+ def _leaf_value(node: Node):
140
+ """Return the comparable payload of a leaf node (Constant name / Number value)."""
141
+ if isinstance(node, Constant):
142
+ return node.name
143
+ if isinstance(node, Number):
144
+ return node.value
145
+ return None
146
+
147
+
148
+ def _unify_args(args1, args2, subst: Dict[str, Node]) -> Optional[Dict[str, Node]]:
149
+ """Unify two equal-length argument lists left-to-right, threading the substitution.
150
+
151
+ Returns the accumulated substitution, or None as soon as any pair fails.
152
+ """
153
+ for a, b in zip(args1, args2):
154
+ subst = unify(a, b, subst)
155
+ if subst is None:
156
+ return None
157
+ return subst
@@ -0,0 +1,263 @@
1
+ """Verbalize a formula as an English sentence (a readability aid, not a parse inverse).
2
+
3
+ ``to_english(node)`` renders a formula with structural English templates: ``∀x φ`` →
4
+ "for every x, …", ``A → B`` → "if A, then B", a unary atom ``Human(x)`` → "x is
5
+ human", an equality → "a is equal to b", and so on. It is deliberately predictable
6
+ rather than fluent, and — unlike ``to_unicode_str``/``parse`` — it is **not** an exact
7
+ round-trip: the English phrasing of nested connectives can be ambiguous. Use it to
8
+ explain a formula, not to re-parse one.
9
+
10
+ The two truth constants (the nullary atoms ``$true`` / ``⊤`` and ``$false`` / ``⊥``) are
11
+ not propositional letters and are not printed as one: ``⊤`` is read "truth" and ``⊥``
12
+ "falsity", so ``P → ⊥`` is "if P, then falsity" and ``¬⊥`` is "it is not the case that
13
+ falsity". A nullary atom of any other name is read as its name.
14
+
15
+ Public API: :func:`to_english`.
16
+ """
17
+
18
+ from ..fol.nodes import (
19
+ Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
20
+ Variable, Constant, Number, Function,
21
+ SortedQuantifier, SortedConstant,
22
+ Count, Measure, Cardinality, Contrast,
23
+ SortedCount, SortedCardinality,
24
+ Nominal, At,
25
+ Dependence, SlashedExists,
26
+ Tensor, With, OPlus, LinearImplies, OfCourse, One,
27
+ Product, Under, Over,
28
+ Box, Diamond, Knows, Believes, Says, Wants, Obligatory, Permitted,
29
+ Always, Eventually, Next, Until,
30
+ Historically, Once, Previous, Since,
31
+ Would, Might,
32
+ WeakConjunction, WeakDisjunction, StrongConjunction, StrongDisjunction,
33
+ LukNegation, LukImplication, LukEquivalence,
34
+ )
35
+ from ._so_nodes import SecondOrderQuantifier
36
+ from ._modal_nodes import Announce, AnnounceDiamond
37
+ from ._truth_constants import truth_value
38
+
39
+
40
+ # Counting-quantifier op codes → English determiner phrases.
41
+ _COUNT_WORDS = {"ge": "at least", "le": "at most", "eq": "exactly"}
42
+
43
+
44
+ # Comparison / equality predicates rendered with an English copula.
45
+ _COPULA = {
46
+ "=": ("is equal to", "is not equal to"),
47
+ "≠": ("is not equal to", "is equal to"),
48
+ "<": ("is less than", "is not less than"),
49
+ ">": ("is greater than", "is not greater than"),
50
+ "≤": ("is at most", "is greater than"),
51
+ "≥": ("is at least", "is less than"),
52
+ }
53
+
54
+
55
+ def _term(node: Node) -> str:
56
+ """Render a term in a readable form."""
57
+ if isinstance(node, (Variable, Constant, SortedConstant)):
58
+ return node.name
59
+ if isinstance(node, Number):
60
+ return str(node.value)
61
+ if isinstance(node, Function):
62
+ return f"{node.name}({', '.join(_term(a) for a in node.args)})"
63
+ if isinstance(node, Measure):
64
+ return f"the {_term(node.dimension)} of {_term(node.entity)}"
65
+ if isinstance(node, Cardinality):
66
+ return f"the number of {node.variable.name} such that {to_english(node.formula)}"
67
+ if isinstance(node, SortedCardinality):
68
+ return (f"the number of {node.variable.name} of sort {node.sort} such that "
69
+ f"{to_english(node.formula)}")
70
+ return node.to_unicode_str()
71
+
72
+
73
+ def _atom(node: Atom, negated: bool = False) -> str:
74
+ """Render an atom (optionally negated) in English."""
75
+ pred, args = node.predicate, node.args
76
+ if pred in _COPULA and len(args) == 2:
77
+ phrase = _COPULA[pred][1 if negated else 0]
78
+ return f"{_term(args[0])} {phrase} {_term(args[1])}"
79
+ if len(args) == 0:
80
+ constant = truth_value(node)
81
+ if constant is None:
82
+ body = pred
83
+ else:
84
+ body = "truth" if constant else "falsity" # the truth constants, not letters
85
+ return f"it is not the case that {body}" if negated else body
86
+ if len(args) == 1:
87
+ # "x is human" / "x is not human"
88
+ prop = pred.lower()
89
+ return f"{_term(args[0])} is {'not ' if negated else ''}{prop}"
90
+ call = f"{pred}({', '.join(_term(a) for a in args)})"
91
+ return f"it is not the case that {call}" if negated else call
92
+
93
+
94
+ def _needs_clause(node: Node) -> bool:
95
+ """True if a sub-formula should be wrapped as a parenthetical clause for clarity."""
96
+ return isinstance(node, (And, Or, Xor, Implies, Iff))
97
+
98
+
99
+ def _sub(node: Node) -> str:
100
+ """Render a sub-formula, parenthesising a nested binary connective."""
101
+ text = to_english(node)
102
+ return f"({text})" if _needs_clause(node) else text
103
+
104
+
105
+ def to_english(node: Node) -> str:
106
+ """Return an English paraphrase of ``node`` (best-effort, not a parse inverse)."""
107
+ if isinstance(node, Atom):
108
+ return _atom(node)
109
+
110
+ if isinstance(node, Not):
111
+ inner = node.formula
112
+ if isinstance(inner, Atom):
113
+ return _atom(inner, negated=True)
114
+ return f"it is not the case that {_sub(inner)}"
115
+
116
+ if isinstance(node, And):
117
+ return f"{_sub(node.left)} and {_sub(node.right)}"
118
+
119
+ if isinstance(node, Or):
120
+ return f"{_sub(node.left)} or {_sub(node.right)}"
121
+
122
+ if isinstance(node, Xor):
123
+ return f"either {_sub(node.left)} or {_sub(node.right)}, but not both"
124
+
125
+ if isinstance(node, Contrast):
126
+ return f"{_sub(node.left)}, whereas {_sub(node.right)}"
127
+
128
+ if isinstance(node, Implies):
129
+ return f"if {_sub(node.left)}, then {_sub(node.right)}"
130
+
131
+ if isinstance(node, Iff):
132
+ return f"{_sub(node.left)} if and only if {_sub(node.right)}"
133
+
134
+ if isinstance(node, SortedQuantifier):
135
+ kind = "every" if node.type in ("∀", "forall") else "some"
136
+ return f"for {kind} {node.variable.name} of sort {node.sort}, {to_english(node.formula)}"
137
+
138
+ if isinstance(node, Quantifier):
139
+ if node.type in ("∀", "forall"):
140
+ return f"for every {node.variable.name}, {to_english(node.formula)}"
141
+ return f"for some {node.variable.name}, {to_english(node.formula)}"
142
+
143
+ if isinstance(node, Count):
144
+ return (f"there are {_COUNT_WORDS[node.op]} {node.n.value} {node.variable.name} "
145
+ f"such that {to_english(node.formula)}")
146
+
147
+ if isinstance(node, SortedCount):
148
+ return (f"there are {_COUNT_WORDS[node.op]} {node.n.value} {node.variable.name} "
149
+ f"of sort {node.sort} such that {to_english(node.formula)}")
150
+
151
+ if isinstance(node, SecondOrderQuantifier):
152
+ kind = "every" if node.type in ("∀", "forall") else "some"
153
+ return f"for {kind} {node.arity}-ary predicate {node.predicate}, {to_english(node.formula)}"
154
+
155
+ # --- modal / temporal / epistemic / doxastic / deontic ---
156
+ if isinstance(node, Box):
157
+ return f"necessarily, {_sub(node.formula)}"
158
+ if isinstance(node, Diamond):
159
+ return f"possibly, {_sub(node.formula)}"
160
+ if isinstance(node, Knows):
161
+ return f"agent {_term(node.agent)} knows that {_sub(node.formula)}"
162
+ if isinstance(node, Believes):
163
+ return f"agent {_term(node.agent)} believes that {_sub(node.formula)}"
164
+ if isinstance(node, Says):
165
+ return f"agent {_term(node.agent)} says that {_sub(node.formula)}"
166
+ if isinstance(node, Wants):
167
+ return f"agent {_term(node.agent)} wants it to be that {_sub(node.formula)}"
168
+ if isinstance(node, Obligatory):
169
+ return f"it is obligatory that {_sub(node.formula)}"
170
+ if isinstance(node, Permitted):
171
+ return f"it is permitted that {_sub(node.formula)}"
172
+ if isinstance(node, Always):
173
+ return f"it will always be the case that {_sub(node.formula)}"
174
+ if isinstance(node, Eventually):
175
+ return f"it will eventually be the case that {_sub(node.formula)}"
176
+ if isinstance(node, Next):
177
+ return f"at the next moment, {_sub(node.formula)}"
178
+ if isinstance(node, Until):
179
+ return f"{_sub(node.left)} until {_sub(node.right)}"
180
+ if isinstance(node, Historically):
181
+ return f"it has always been the case that {_sub(node.formula)}"
182
+ if isinstance(node, Once):
183
+ return f"it was once the case that {_sub(node.formula)}"
184
+ if isinstance(node, Previous):
185
+ return f"at the previous moment, {_sub(node.formula)}"
186
+ if isinstance(node, Since):
187
+ return f"{_sub(node.left)} since {_sub(node.right)}"
188
+
189
+ # --- public announcement logic (PAL) ---
190
+ if isinstance(node, Announce):
191
+ return (f"after it is truthfully announced that {to_english(node.announcement)}, "
192
+ f"{_sub(node.formula)}")
193
+ if isinstance(node, AnnounceDiamond):
194
+ return (f"{to_english(node.announcement)}, and after it is truthfully "
195
+ f"announced, {_sub(node.formula)}")
196
+
197
+ # --- counterfactual conditionals (subjunctive, not material) ---
198
+ if isinstance(node, Would):
199
+ return f"if {_sub(node.left)} were the case, {_sub(node.right)} would be"
200
+ if isinstance(node, Might):
201
+ return f"if {_sub(node.left)} were the case, {_sub(node.right)} might be"
202
+
203
+ # --- hybrid (nominals and @) ---
204
+ if isinstance(node, Nominal):
205
+ return f"this is world {node.name}"
206
+ if isinstance(node, At):
207
+ return f"at world {node.nominal.name}, {_sub(node.formula)}"
208
+
209
+ # --- dependence / IF (team semantics) ---
210
+ if isinstance(node, Dependence):
211
+ if len(node.args) == 1:
212
+ return f"the value of {_term(node.args[0])} is constant"
213
+ deps = ", ".join(_term(a) for a in node.args[:-1])
214
+ return (f"the value of {_term(node.args[-1])} is functionally "
215
+ f"determined by {deps}")
216
+ if isinstance(node, SlashedExists):
217
+ indep = ", ".join(node.slashed)
218
+ return (f"for some {node.variable.name} chosen independently of "
219
+ f"{indep}, {to_english(node.formula)}")
220
+
221
+ # --- linear logic (resource reading) ---
222
+ if isinstance(node, Tensor):
223
+ return f"{_sub(node.left)} together with {_sub(node.right)}"
224
+ if isinstance(node, With):
225
+ return f"a free choice between {_sub(node.left)} and {_sub(node.right)}"
226
+ if isinstance(node, OPlus):
227
+ return f"{_sub(node.left)} or else {_sub(node.right)}"
228
+ if isinstance(node, LinearImplies):
229
+ return f"consuming {_sub(node.left)} yields {_sub(node.right)}"
230
+ if isinstance(node, OfCourse):
231
+ return f"an unlimited supply of {_sub(node.formula)}"
232
+ if isinstance(node, One):
233
+ return "the empty resource"
234
+
235
+ # --- Lambek calculus (categorial reading) ---
236
+ if isinstance(node, Product):
237
+ return f"{_sub(node.left)} followed by {_sub(node.right)}"
238
+ if isinstance(node, Under):
239
+ return (f"something that combines with {_sub(node.left)} on its left "
240
+ f"to give {_sub(node.right)}")
241
+ if isinstance(node, Over):
242
+ return (f"something that combines with {_sub(node.right)} on its right "
243
+ f"to give {_sub(node.left)}")
244
+
245
+ # --- fuzzy (Łukasiewicz): name the strong/weak/Łukasiewicz operators so they do
246
+ # NOT read as their classical look-alikes ---
247
+ if isinstance(node, LukNegation):
248
+ return f"it is fuzzily not the case that {_sub(node.formula)}"
249
+ if isinstance(node, WeakConjunction):
250
+ return f"{_sub(node.left)} and weakly {_sub(node.right)}"
251
+ if isinstance(node, WeakDisjunction):
252
+ return f"{_sub(node.left)} or weakly {_sub(node.right)}"
253
+ if isinstance(node, StrongConjunction):
254
+ return f"{_sub(node.left)} and strongly {_sub(node.right)}"
255
+ if isinstance(node, StrongDisjunction):
256
+ return f"{_sub(node.left)} or strongly {_sub(node.right)}"
257
+ if isinstance(node, LukImplication):
258
+ return f"if {_sub(node.left)} then fuzzily {_sub(node.right)}"
259
+ if isinstance(node, LukEquivalence):
260
+ return f"{_sub(node.left)} is fuzzily equivalent to {_sub(node.right)}"
261
+
262
+ # Anything else (e.g. lambda) falls back to its glyph form.
263
+ return node.to_unicode_str()
@@ -0,0 +1,163 @@
1
+ r"""REST binding to a Dockerized HETS (Heterogeneous Tool Set) server.
2
+
3
+ `HETS <https://github.com/spechub/Hets>`_ is the reference heterogeneous
4
+ proof-management system for CASL and its extensions (and, via comorphisms,
5
+ many other logics): a development-graph manager over a network of theories
6
+ that dispatches proof obligations to external provers (SPASS, darwin,
7
+ Vampire, E, ...) through translations/comorphisms it selects. This subpackage
8
+ does not reimplement any of that — it is a thin client that lets the rest of
9
+ the kit drive a real HETS instance and read back its verdicts.
10
+
11
+ Architecture: process/network separation, not in-process linking
12
+ --------------------------------------------------------------------
13
+ HETS is GPL-licensed Haskell with a build realistically only reproducible via
14
+ its official Docker image. Two integration routes exist:
15
+
16
+ * **In-process bindings** — the ``hets``/``spechub`` Python API some
17
+ installs expose links this process directly against the GPL Haskell
18
+ library via its GHC-built shared objects, and is only buildable on Linux
19
+ with a working GHC toolchain. That is wrong for this kit on two independent
20
+ axes: it would pull a GPL dependency into an MIT-licensed process, and it
21
+ is not available on this kit's primary development platform (Windows) at
22
+ all. Documented here as the alternative that was considered and rejected,
23
+ not silently omitted.
24
+ * **REST against Docker** — the route this subpackage takes.
25
+ :mod:`~unicode_logic_kit.hets.docker` starts (or discovers) a
26
+ ``spechub2/hets:latest`` container running HETS's own bundled HTTP server;
27
+ :mod:`~unicode_logic_kit.hets.client` speaks plain HTTP/JSON to it via
28
+ stdlib ``urllib`` (no ``requests`` dependency, matching this kit's other
29
+ external-tool adapters). This process and the HETS process share nothing
30
+ but a TCP socket — no linking, no shared address space, no GPL code ever
31
+ imported into this Python process. The boundary is enforced structurally,
32
+ not by a license header: :class:`~unicode_logic_kit.hets.client.HetsClient`
33
+ cannot reach HETS internals any way other than the documented REST API.
34
+
35
+ This mirrors the kit-wide convention for external provers that are
36
+ subprocess-spawned rather than linked (``atp.prover9_entailment``,
37
+ ``atp.vampire_entailment``, ``hol.isabelle_runner``) — HETS just uses a
38
+ network boundary instead of a process boundary, because that is how its
39
+ official distribution ships (a server, not a CLI batch tool).
40
+
41
+ Modules
42
+ -------
43
+ * :mod:`~unicode_logic_kit.hets.docker` — :class:`~unicode_logic_kit.hets.docker.HetsContainer`
44
+ (start/stop/health-poll one container) and
45
+ :func:`~unicode_logic_kit.hets.docker.discover_hets_url` (the precedence
46
+ ``$UFK_HETS_URL`` -> localhost -> optionally auto-start, never a silent
47
+ guess — raises :class:`~unicode_logic_kit.atp.protocol.BackendUnavailable`
48
+ with an actionable message otherwise). Also documents which reasoners in
49
+ the shipped image actually work.
50
+ * :mod:`~unicode_logic_kit.hets.client` — :class:`~unicode_logic_kit.hets.client.HetsClient`,
51
+ the REST binding itself (upload, development-graph inspection, prover /
52
+ translation listing, theory rendering/translation, prove,
53
+ consistency-check).
54
+ * :mod:`~unicode_logic_kit.hets.bridge` — the server's CASL comorphisms as
55
+ dynamic ``hets:<Name>`` edges in the kit's translation registry
56
+ (:func:`~unicode_logic_kit.hets.bridge.register_hets_comorphisms`).
57
+ * :mod:`~unicode_logic_kit.hets.dol` — DOL library EMISSION
58
+ (:func:`~unicode_logic_kit.hets.dol.to_dol_library`): multiple named CASL
59
+ specs, ``then``-extension structure and ``%implied`` goals in one
60
+ ``library`` document a HETS server turns into a multi-node development
61
+ graph (no DOL *parsing* — same emission-only cut as the rest of the
62
+ CASL route; the importer for single basic specs is
63
+ :func:`unicode_logic_kit.fol.casl_import.parse_casl_spec`).
64
+ * :mod:`~unicode_logic_kit.hets.owl_backend` — a second, independent external
65
+ OWL 2 DL reasoner (FaCT++ via the server's ``Fact`` prover identifier),
66
+ over the ALCHQ + I + O fragment; mirrors
67
+ :mod:`~unicode_logic_kit.dl.owl_reasoner`'s function-per-namesake shape,
68
+ opt-in and never in a default chain.
69
+ * :mod:`~unicode_logic_kit.hets.haskell_json` —
70
+ :func:`~unicode_logic_kit.hets.haskell_json.repair_haskell_json`, which
71
+ recovers the Haskell ``show`` escapes HETS 0.108.0 leaks into its JSON
72
+ (``"Verdi\226\128\153s Requiem"`` for ``"Verdi’s Requiem"``). Without it
73
+ ``GET /dg`` is unreadable for any library with one non-ASCII annotation,
74
+ and with it the real 8.3 MB OEO graph loads. Applied by
75
+ :meth:`~unicode_logic_kit.hets.client.HetsClient.dg` only on the failure
76
+ path, so a body ``json`` already accepts is never touched.
77
+ * :mod:`~unicode_logic_kit.hets.symbols` — the join between HETS' mangled TPTP
78
+ symbols and the OWL entities they came from
79
+ (:func:`~unicode_logic_kit.hets.symbols.hets_symbol_table`), plus the axioms
80
+ a translation silently dropped
81
+ (:func:`~unicode_logic_kit.hets.symbols.untranslated_axioms`). Pure
82
+ functions over a ``/dg`` dict and a TPTP string: no network, no Docker.
83
+ * :mod:`~unicode_logic_kit.hets.owl_cli` —
84
+ :func:`~unicode_logic_kit.hets.owl_cli.owl_to_tptp`, the COMMAND-LINE route
85
+ (``docker exec`` into a running container). It exists for exactly one
86
+ reason: ``hets-server``'s lossy ``-Y`` switch, which translates an
87
+ ontology whose sublogic a comorphism does not cover and which has no REST
88
+ equivalent. It runs the non-lossy translation first so the loss is always
89
+ reported, never silent.
90
+
91
+ The TPTP a HETS ``GET /theory`` returns is NOT a TPTP problem — HETS
92
+ prefixes it with a DOL ``logic TPTP.FOF`` line and a CASL ``%{ ... }%``
93
+ signature block. :func:`~unicode_logic_kit.hets.client.strip_hets_theory_header`
94
+ splits the two (and
95
+ :meth:`~unicode_logic_kit.hets.client.HetsClient.theory_tptp` does it for you);
96
+ :func:`unicode_logic_kit.fol.tptp_input.parse_tptp` refuses the unstripped text
97
+ BY NAME and points here, rather than learning a comment syntax that is not
98
+ TPTP's.
99
+
100
+ Kit formulas reach HETS through the Verdict layer:
101
+ :class:`unicode_logic_kit.atp.hets_backend.HetsBackend` (registry name
102
+ ``"hets"``, never in a default chain) exports FOL/MSFOL problems to CASL
103
+ via :mod:`unicode_logic_kit.fol.casl_export` and maps the per-goal results
104
+ back onto the kit's :class:`~unicode_logic_kit.atp.protocol.Verdict`.
105
+ """
106
+
107
+ from .bridge import HETS_EDGE_PREFIX, register_hets_comorphisms
108
+ from .client import (
109
+ HetsClient,
110
+ HetsNoTranslationsError,
111
+ HetsSublogicError,
112
+ strip_hets_theory_header,
113
+ )
114
+ from .docker import HETS_IMAGE, HetsContainer, discover_hets_url, hets_available
115
+ from .haskell_json import (
116
+ HaskellJsonRepair,
117
+ HaskellJsonRepairError,
118
+ repair_haskell_json,
119
+ )
120
+ from .owl_cli import (
121
+ HetsOwlNormalizationError,
122
+ OwlTptpResult,
123
+ SublogicMismatch,
124
+ owl_to_tptp,
125
+ )
126
+ from .symbols import (
127
+ HetsSymbol,
128
+ HetsSymbolCollisionError,
129
+ HetsSymbolTable,
130
+ UntranslatedAxiom,
131
+ hets_prefixes,
132
+ hets_symbol_table,
133
+ untranslated_axioms,
134
+ )
135
+ from .dol import DolSpec, to_dol_library, to_dol_library_from_modal, sanitize_modal_identifiers
136
+ from .owl_backend import (
137
+ hets_owl_available,
138
+ external_concept_satisfiable, external_concept_unsatisfiable,
139
+ external_subsumes, external_equivalent,
140
+ external_abox_consistent, external_instance_check, external_instance_retrieval,
141
+ external_realize, external_realize_all,
142
+ HetsOwlError,
143
+ )
144
+
145
+ __all__ = [
146
+ "HetsClient",
147
+ "HetsNoTranslationsError", "HetsSublogicError", "strip_hets_theory_header",
148
+ "HaskellJsonRepair", "HaskellJsonRepairError", "repair_haskell_json",
149
+ "owl_to_tptp", "OwlTptpResult", "SublogicMismatch",
150
+ "HetsOwlNormalizationError",
151
+ "hets_symbol_table", "HetsSymbolTable", "HetsSymbol",
152
+ "HetsSymbolCollisionError",
153
+ "untranslated_axioms", "UntranslatedAxiom", "hets_prefixes",
154
+ "HETS_IMAGE", "HetsContainer", "discover_hets_url", "hets_available",
155
+ "HETS_EDGE_PREFIX", "register_hets_comorphisms",
156
+ "DolSpec", "to_dol_library", "to_dol_library_from_modal", "sanitize_modal_identifiers",
157
+ "hets_owl_available",
158
+ "external_concept_satisfiable", "external_concept_unsatisfiable",
159
+ "external_subsumes", "external_equivalent",
160
+ "external_abox_consistent", "external_instance_check", "external_instance_retrieval",
161
+ "external_realize", "external_realize_all",
162
+ "HetsOwlError",
163
+ ]