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,416 @@
1
+ """Dependence logic -> existential second-order logic (Skolem/Henkin translation).
2
+
3
+ Väänänen's team semantics (:mod:`unicode_logic_kit.semantics.team`) evaluates a
4
+ dependence-logic SENTENCE by brute-force search over teams; this module instead
5
+ *translates* a sentence into an ordinary classical formula headed by existential
6
+ second-order (ESO) quantifiers, whose truth in a structure — checked by
7
+ :func:`unicode_logic_kit.semantics.secondorder.satisfies_so` / :func:`holds` — is
8
+ PROVABLY EQUIVALENT to the team-semantic truth of the original sentence, for the
9
+ fragment described below. This realises half of Väänänen's headline theorem that
10
+ dependence logic captures exactly ESO (the other half, ESO -> D, is not needed
11
+ here): every sentence of the *SKOLEM-NORMAL fragment* is equivalent to the
12
+ existence of a family of SKOLEM FUNCTIONS obeying the dependence/independence
13
+ constraints written into the sentence.
14
+
15
+ THE TRANSLATION (Skolemisation with restricted dependency)
16
+ ------------------------------------------------------------
17
+ Each existential — plain ``∃u``, an independence-friendly ``∃u/{V}``, or a
18
+ plain ``∃u`` immediately guarded by a dependence atom ``=(t̄,u)`` — is replaced
19
+ by a fresh GRAPH PREDICATE ``F_u`` of arity ``|args| + 1`` (the last argument is
20
+ ``u``'s value), existentially quantified at the very front of the formula,
21
+ together with two first-order axioms making ``F_u`` TOTAL and FUNCTIONAL in its
22
+ first ``|args|`` positions (so it really is the graph of a function
23
+ ``args -> u``):
24
+
25
+ - **unguarded** ``∃u φ`` (no dependence atom, not slashed): ``args`` = every
26
+ ``∀``-bound variable enclosing this ``∃`` (in order) — the ordinary
27
+ first-order Skolem function of the full universal prefix. (Any INTERVENING
28
+ ``∃``-bound variable is safely omittable here: it is itself already a
29
+ function of those same ``∀``'s, so it carries no information an unrestricted
30
+ witness could not already reconstruct.)
31
+ - **slashed** ``∃u/{V} φ``: ``args`` = every variable BOUND SO FAR — ``∀`` or
32
+ ``∃`` alike, in binding order — EXCEPT those named in the slash set ``V``.
33
+ Note this is *every enclosing binder*, not just the ``∀``'s: independence is
34
+ a constraint on the witness function's visible ARGUMENT NAMES, and a
35
+ variable not literally in ``V`` stays visible even if it happens to be a
36
+ deterministic function of one that is — which is exactly the SIGNALLING
37
+ phenomenon (Väänänen 2007 §4): ``∀x∃z(z=x ∧ ∃y/{x}(y=x))`` lets ``y``
38
+ recover ``x`` via ``z``, and IS true everywhere, unlike ``∀x∃y/{x}(y=x)``
39
+ with no such intervening ``z``.
40
+ - **dependence-atom-guarded** ``∃u(=(t̄,u) ∧ φ)`` (the dependence atom a
41
+ top-level conjunct in the IMMEDIATE body of the ``∃`` binding its dependent
42
+ variable ``u``, the canonical Väänänen normal form): ``args`` = exactly
43
+ ``t̄`` — the atom's own determinant terms override the default.
44
+
45
+ Every occurrence is then rewritten in place, innermost, as the classical
46
+ ``∃u(F_u(args, u) ∧ φ')`` (``φ'`` = the (recursively translated) rest of the
47
+ guard's body, or bare ``F_u(args,u)`` if the guard was the whole body) — an
48
+ ORDINARY first-order ``∃u``, not a team-semantic one. Every ``F_u`` and its two
49
+ axioms then float to the front: ``(χ ∧ ∃F ρ) ≡ ∃F(χ ∧ ρ)`` for ``F`` fresh is
50
+ an unconditional second-order equivalence, so nesting position never matters.
51
+ ``∀`` is passed through unchanged; literals (atoms, negated atoms) are passed
52
+ through unchanged (they are already classical).
53
+
54
+ FAITHFULNESS — exactly what is (and is not) covered
55
+ ------------------------------------------------------
56
+ 1. **Plain FO ∃, in any position.** ``⊨ ∀x̄∃u ψ(x̄,u) ↔ ∃F(∀x̄ ψ(x̄,F(x̄)))`` is a
57
+ basic, unconditional second-order validity (given a witness for every
58
+ ``x̄``, PICK one — a purely combinatorial choice on a finite domain, no
59
+ axiom of choice needed) — the ordinary Skolemisation identity. Dependence
60
+ logic makes this exact: Väänänen 2007, Prop. 3.9 (flatness), shows every
61
+ formula with NO dependence atom and NO slash is FLAT — team truth equals
62
+ ordinary classical truth pointwise — so classical Skolemisation of a
63
+ dependence/slash-FREE sentence, in ANY connective shape (``∧``/``∨`` mixed
64
+ with quantifiers arbitrarily — not just prenex), is faithful to its team
65
+ truth. This is why a *plain FOL* sentence translates to something classically
66
+ equivalent to itself (mod the vacuous Skolem layer if it has any ``∃`` at
67
+ all).
68
+ 2. **A dependence-atom-guarded or slashed ∃, PROVIDED no ``∨`` is anywhere in
69
+ the sentence.** This is exactly the classical Hintikka–Sandu Skolem/game
70
+ semantics of independence-friendly logic, which Väänänen 2007 (Thm. 4.10 and
71
+ surrounding discussion) shows AGREES with the compositional team semantics
72
+ for a quantifier-and-conjunction prefix over a quantifier-free (dependence
73
+ atoms aside) matrix — the shape this module accepts (arbitrary ``∀``/``∃``/
74
+ ``∃/{…}`` nesting through ``∧``, since ``∧`` commutes freely with an
75
+ existential's Skolem function exactly as in case 1).
76
+ 3. **``∨`` (the split disjunction) is REJECTED outright whenever the sentence
77
+ contains ANY dependence atom or slashed existential**, even in a part of the
78
+ tree that looks "far away" from the ``∨``. Team-semantic ``∨`` lets the team
79
+ split into ANY partition, and once a non-flat construct (dependence atom,
80
+ slash) is in play this split is exactly the source of dependence logic's
81
+ extra expressive power (Väänänen 2007 §4) — establishing a Skolem-normal-form
82
+ equivalence for ``∨`` mixed with non-flat constructs needs a case analysis
83
+ this module does not attempt. This is the CONSERVATIVE choice the module
84
+ commits to: soundness over coverage. (``∨`` remains fully supported, in any
85
+ position, for a dependence/slash-FREE sentence — case 1 above.)
86
+ 4. **A "loose" dependence atom** — one that is not a top-level conjunct of the
87
+ IMMEDIATE body of the ``∃`` binding its own dependent variable (e.g. it
88
+ guards a ``∀``-bound variable, sits several ``∧``-levels away from its own
89
+ binder, or names a variable other than the enclosing ``∃``'s) — is rejected:
90
+ the guard extraction only ever looks at an ``∃``'s own immediate (flattened)
91
+ conjunct list, so any dependence atom not consumed there surfaces, unchanged,
92
+ to the generic recursion, which raises.
93
+ 5. **Shadowing** (the same variable name bound twice along one ancestor chain,
94
+ e.g. ``∀x∃x…``) is rejected: the translation reuses each bound name as-is
95
+ (no fresh-renaming pass), which is sound only when names are not reused
96
+ along a single scope chain. Reuse across SIBLING branches (``∃x φ ∧ ∃x ψ``)
97
+ is unaffected — it is not shadowing.
98
+ 6. **``¬``** is accepted only in front of an atom (mirroring
99
+ :mod:`unicode_logic_kit.semantics.team`'s own restriction); ``→``/``↔`` are
100
+ not part of the team fragment at all and are rejected immediately (as any
101
+ node outside the recognised shapes is).
102
+
103
+ The INPUT must already be a SENTENCE (no free object variables) — the same
104
+ assumption :func:`unicode_logic_kit.semantics.team.team_models` makes for
105
+ starting from the singleton empty-assignment team.
106
+
107
+ Every rejection raises :class:`NotImplementedError` naming the offending shape
108
+ and pointing back at ``team_satisfies`` / ``team_models`` as the fallback
109
+ (brute-force, but total over the whole team fragment).
110
+
111
+ Public API: :func:`dependence_to_eso`, :func:`dependence_holds_eso`.
112
+ """
113
+
114
+ from typing import FrozenSet, List, Optional, Tuple
115
+
116
+ from ..fol.nodes import (
117
+ Node, Variable, Atom, Not, And, Or, Implies, Quantifier,
118
+ Dependence, SlashedExists, SecondOrderQuantifier,
119
+ )
120
+ from .tarski import Structure
121
+
122
+ _FORALL = ("∀", "forall")
123
+ _EXISTS = ("∃", "exists")
124
+
125
+
126
+ # ===========================================================================
127
+ # And-tree flattening (to locate a guard adjacent to its own ∃, regardless of
128
+ # the parser's left-associative nesting) and rebuilding.
129
+ # ===========================================================================
130
+
131
+ def _flatten_and(node: Node) -> List[Node]:
132
+ """Return the list of top-level conjuncts of an (arbitrarily nested) And-tree."""
133
+ if isinstance(node, And):
134
+ return _flatten_and(node.left) + _flatten_and(node.right)
135
+ return [node]
136
+
137
+
138
+ def _rebuild_and(conjuncts: List[Node]) -> Node:
139
+ """Left-fold a non-empty conjunct list back into a binary And-tree."""
140
+ result = conjuncts[0]
141
+ for c in conjuncts[1:]:
142
+ result = And(result, c)
143
+ return result
144
+
145
+
146
+ def _extract_guard(body: Node, var: str) -> Tuple[Optional[Dependence], Optional[Node]]:
147
+ """Find a dependence atom ``=(t̄,var)`` among ``body``'s top-level conjuncts.
148
+
149
+ Returns ``(guard, rest)`` where ``rest`` is the And of the remaining
150
+ conjuncts (``None`` if the guard was the ONLY conjunct), or ``(None, None)``
151
+ if no conjunct is a dependence atom whose dependent (last) argument is
152
+ exactly the variable ``var``. Only the FIRST matching conjunct is used.
153
+ """
154
+ conjuncts = _flatten_and(body)
155
+ for i, c in enumerate(conjuncts):
156
+ if (isinstance(c, Dependence) and isinstance(c.args[-1], Variable)
157
+ and c.args[-1].name == var):
158
+ rest_conjuncts = conjuncts[:i] + conjuncts[i + 1:]
159
+ rest = _rebuild_and(rest_conjuncts) if rest_conjuncts else None
160
+ return c, rest
161
+ return None, None
162
+
163
+
164
+ # ===========================================================================
165
+ # Totality / functionality axiom for a Skolem graph predicate.
166
+ # ===========================================================================
167
+
168
+ def _forall_chain(vs: List[Variable], body: Node) -> Node:
169
+ """Fold ``∀v1 ∀v2 … body`` over ``vs`` (``body`` unchanged if ``vs`` is empty)."""
170
+ result = body
171
+ for v in reversed(vs):
172
+ result = Quantifier("∀", v, result)
173
+ return result
174
+
175
+
176
+ def _wf_axiom(fname: str, arity: int) -> Node:
177
+ """``F`` is TOTAL and FUNCTIONAL in its first ``arity`` arguments.
178
+
179
+ Totality: ``∀x̄ ∃y F(x̄,y)`` (a bare ``∃y F(y)`` when ``arity == 0``, i.e.
180
+ ``F`` is non-empty). Functionality: ``∀x̄∀y1∀y2((F(x̄,y1)∧F(x̄,y2))→y1=y2)``.
181
+ Together these make ``F``'s graph exactly the graph of a total function
182
+ ``dom^arity -> dom`` (a single constant when ``arity == 0``).
183
+ """
184
+ xs = [Variable(f"_{fname}_x{i}") for i in range(arity)]
185
+ y0, y1, y2 = Variable(f"_{fname}_y0"), Variable(f"_{fname}_y1"), Variable(f"_{fname}_y2")
186
+ totality = _forall_chain(xs, Quantifier("∃", y0, Atom(fname, tuple(xs) + (y0,))))
187
+ functionality = _forall_chain(
188
+ xs + [y1, y2],
189
+ Implies(
190
+ And(Atom(fname, tuple(xs) + (y1,)), Atom(fname, tuple(xs) + (y2,))),
191
+ Atom("=", (y1, y2)),
192
+ ),
193
+ )
194
+ return And(totality, functionality)
195
+
196
+
197
+ # ===========================================================================
198
+ # The translation
199
+ # ===========================================================================
200
+
201
+ def _contains_any(node: Node, classes) -> bool:
202
+ """Whether any descendant of ``node`` (``node`` included) is an instance of ``classes``."""
203
+ return any(isinstance(n, classes) for n in node.walk())
204
+
205
+
206
+ def _fragment_error(node: Node) -> NotImplementedError:
207
+ """The generic rejection for a node/shape outside the covered fragment."""
208
+ return NotImplementedError(
209
+ f"dependence_to_eso: {type(node).__name__} is outside the fragment this "
210
+ "translation is proven faithful for (literals, ∧, split ∨ in dependence/"
211
+ "slash-FREE sub-formulas, ∀/∃, a dependence atom that is a top-level "
212
+ "conjunct in the immediate body of the ∃ binding its own dependent "
213
+ "variable, and slashed ∃x/{…}); evaluate with "
214
+ "unicode_logic_kit.semantics.team.team_satisfies / team_models instead."
215
+ )
216
+
217
+
218
+ def dependence_to_eso(sentence: Node) -> Node:
219
+ """Translate a dependence-logic SENTENCE to an equivalent ESO formula.
220
+
221
+ Returns a classical :class:`~unicode_logic_kit.fol.nodes.Node`, headed by one
222
+ ``∃``-:class:`SecondOrderQuantifier` per Skolemised existential in
223
+ ``sentence`` (zero if ``sentence`` has no existential at all — then the
224
+ result is just the classically-translated formula, unwrapped). The result
225
+ is meant for :func:`unicode_logic_kit.semantics.secondorder.satisfies_so` /
226
+ :func:`holds`, or for :func:`unicode_logic_kit.hol.secondorder.to_isabelle_so`
227
+ / :func:`to_thf_so`. See the module docstring for the exact supported
228
+ grammar and the faithfulness argument.
229
+
230
+ Raises:
231
+ NotImplementedError: for any node/shape outside the covered fragment —
232
+ ``∨`` combined (anywhere) with a dependence atom or slashed
233
+ existential, a dependence atom not guarding its own ``∃``, ``¬`` of
234
+ a non-atom, a shadowed (re-bound) variable name, or any node type
235
+ the team fragment itself does not recognise (``→``, ``↔``, …).
236
+ """
237
+ if _contains_any(sentence, (Or,)) and _contains_any(sentence, (Dependence, SlashedExists)):
238
+ raise NotImplementedError(
239
+ "dependence_to_eso: the split disjunction ∨ is combined with a "
240
+ "dependence atom or slashed existential somewhere in this sentence. "
241
+ "∨ lets a team split into ANY partition, and once a non-flat "
242
+ "construct (dependence atom / slash) is present that split is "
243
+ "exactly the source of dependence logic's extra expressive power "
244
+ "(Väänänen 2007 §4) — no Skolem-normal-form equivalence is "
245
+ "established here for that combination, so it is rejected rather "
246
+ "than silently mistranslated. ∨ IS supported anywhere in a "
247
+ "dependence/slash-FREE (plain first-order) sentence. Evaluate the "
248
+ "full sentence with team_satisfies / team_models instead."
249
+ )
250
+
251
+ used_preds = {n.predicate for n in sentence.walk() if isinstance(n, Atom)}
252
+ skolems: List[Tuple[str, int]] = []
253
+ wf_axioms: List[Node] = []
254
+
255
+ def fresh_pred(base: str) -> str:
256
+ name, candidate, i = f"F_{base}", f"F_{base}", 0
257
+ while candidate in used_preds:
258
+ candidate = f"{name}_{i}"
259
+ i += 1
260
+ used_preds.add(candidate)
261
+ return candidate
262
+
263
+ def introduce_skolem(var: Variable, args: Tuple[Node, ...]) -> Atom:
264
+ """Register a fresh Skolem predicate for ``var`` over ``args`` and return its atom."""
265
+ fname = fresh_pred(var.name)
266
+ arity = len(args)
267
+ skolems.append((fname, arity + 1))
268
+ wf_axioms.append(_wf_axiom(fname, arity))
269
+ return Atom(fname, args + (var,))
270
+
271
+ def translate(node: Node, foralls: Tuple[str, ...], scope: Tuple[str, ...],
272
+ bound: FrozenSet[str]) -> Node:
273
+ if isinstance(node, Atom):
274
+ return node
275
+
276
+ if isinstance(node, Not):
277
+ if not isinstance(node.formula, Atom):
278
+ raise NotImplementedError(
279
+ f"dependence_to_eso: ¬ applies only to atoms in the team "
280
+ f"fragment (found ¬ of a {type(node.formula).__name__}); "
281
+ "evaluate with team_satisfies / team_models instead."
282
+ )
283
+ return node
284
+
285
+ if isinstance(node, Dependence):
286
+ raise NotImplementedError(
287
+ f"dependence_to_eso: the dependence atom {node.to_unicode_str()!r} "
288
+ "does not guard its own existential. The supported canonical "
289
+ "pattern is ∃u(=(t̄,u) ∧ …), where the dependence atom is a "
290
+ "TOP-LEVEL conjunct in the IMMEDIATE body of the ∃ binding its "
291
+ "own dependent variable u; evaluate with team_satisfies / "
292
+ "team_models instead."
293
+ )
294
+
295
+ if isinstance(node, And):
296
+ return And(translate(node.left, foralls, scope, bound),
297
+ translate(node.right, foralls, scope, bound))
298
+
299
+ if isinstance(node, Or):
300
+ # Reachable only for a dependence/slash-FREE sentence (checked up
301
+ # front), where ∨ is fully flat and safe in any position.
302
+ return Or(translate(node.left, foralls, scope, bound),
303
+ translate(node.right, foralls, scope, bound))
304
+
305
+ if isinstance(node, Quantifier):
306
+ name = node.variable.name
307
+ if name in bound:
308
+ raise NotImplementedError(
309
+ f"dependence_to_eso: variable {name!r} is bound twice along "
310
+ "one scope chain (shadowing); the translation reuses each "
311
+ "bound name as-is, which requires distinct names along any "
312
+ "single nesting path. Rename the inner binder."
313
+ )
314
+ new_bound = bound | {name}
315
+ new_scope = scope + (name,)
316
+
317
+ if node.type in _FORALL:
318
+ return Quantifier("∀", node.variable,
319
+ translate(node.formula, foralls + (name,), new_scope, new_bound))
320
+
321
+ if node.type not in _EXISTS:
322
+ raise ValueError(f"dependence_to_eso: unknown quantifier type {node.type!r}.")
323
+
324
+ guard, rest = _extract_guard(node.formula, name)
325
+ if guard is not None:
326
+ args = tuple(guard.args[:-1])
327
+ else:
328
+ args = tuple(Variable(n) for n in foralls)
329
+ rest = node.formula
330
+ graph_atom = introduce_skolem(node.variable, args)
331
+ body = (graph_atom if rest is None
332
+ else And(graph_atom, translate(rest, foralls, new_scope, new_bound)))
333
+ return Quantifier("∃", node.variable, body)
334
+
335
+ if isinstance(node, SlashedExists):
336
+ name = node.variable.name
337
+ if name in bound:
338
+ raise NotImplementedError(
339
+ f"dependence_to_eso: variable {name!r} is bound twice along "
340
+ "one scope chain (shadowing); the translation reuses each "
341
+ "bound name as-is, which requires distinct names along any "
342
+ "single nesting path. Rename the inner binder."
343
+ )
344
+ new_bound = bound | {name}
345
+ new_scope = scope + (name,)
346
+ # Uniformity ("independent of V") is a constraint on the WITNESS
347
+ # FUNCTION'S visible ARGUMENTS, checked by NAME against every
348
+ # variable bound so far (∀ or ∃) — not just the enclosing ∀'s. An
349
+ # intervening ∃ that happens to be a deterministic function of a
350
+ # slashed ∀ (e.g. ∃z(z=x ∧ ∃y/{x} …)) is NOT itself named in the
351
+ # slash set, so it stays visible and the "independent" witness can
352
+ # still depend on it — the signalling phenomenon (Väänänen 2007
353
+ # §4; Hodges' compositional semantics for IF logic). Using `scope`
354
+ # (every bound name so far) rather than `foralls` here is what
355
+ # makes this case translate faithfully.
356
+ args = tuple(Variable(n) for n in scope if n not in node.slashed)
357
+ graph_atom = introduce_skolem(node.variable, args)
358
+ body = And(graph_atom, translate(node.formula, foralls, new_scope, new_bound))
359
+ return Quantifier("∃", node.variable, body)
360
+
361
+ raise _fragment_error(node)
362
+
363
+ core = translate(sentence, foralls=(), scope=(), bound=frozenset())
364
+ combined = _rebuild_and(list(wf_axioms) + [core])
365
+ result = combined
366
+ for fname, arity in skolems:
367
+ result = SecondOrderQuantifier("∃", fname, arity, result)
368
+ return result
369
+
370
+
371
+ def dependence_holds_eso(sentence: Node, structure: Structure, fast: bool = False) -> bool:
372
+ """Translate ``sentence`` via :func:`dependence_to_eso` and check it in ``structure``.
373
+
374
+ The "checking side" this module's own faithfulness argument (and its test
375
+ suite, ``holds(dependence_to_eso(sentence), structure)``) always pairs the
376
+ translation with: a convenience for that exact two-step pattern, plus an
377
+ opt-in fast path (roadmap C24).
378
+
379
+ ``dependence_to_eso``'s output is always a SINGLE, leading, all-``∃``
380
+ block of ``SecondOrderQuantifier`` nodes wrapping a classical core (see
381
+ the module docstring: one fresh ``∃F_u`` per Skolemised existential,
382
+ nested front-to-back, never alternating with a ``∀``) — always within
383
+ :func:`~unicode_logic_kit.semantics.asp_models.asp_holds_so`'s single-block
384
+ fragment, so ``fast=True`` here never raises for that reason (it can
385
+ still raise for a symbol :func:`~unicode_logic_kit.semantics.asp_models.asp_holds_so`
386
+ needs that ``structure`` does not interpret, or a missing ``clingo``).
387
+
388
+ Args:
389
+ sentence: a dependence-logic sentence in :func:`dependence_to_eso`'s
390
+ supported fragment.
391
+ structure: the structure to check the translation against.
392
+ fast: opt-in (default ``False``, matching this module's own tests'
393
+ existing ``holds(dependence_to_eso(sentence), structure)`` call
394
+ exactly); ``True`` checks via
395
+ :func:`~unicode_logic_kit.semantics.asp_models.asp_holds_so`
396
+ instead of the brute-force
397
+ :func:`~unicode_logic_kit.semantics.secondorder.holds`.
398
+
399
+ Returns:
400
+ Whether ``structure`` satisfies the ESO translation of ``sentence``
401
+ (provably equivalent to ``sentence``'s own team-semantic truth, for
402
+ the fragment :func:`dependence_to_eso` covers — see the module
403
+ docstring's "FAITHFULNESS" section).
404
+
405
+ Raises:
406
+ NotImplementedError: from :func:`dependence_to_eso`, for ``sentence``
407
+ outside its fragment.
408
+ ImportError: ``fast=True`` and ``clingo`` (the optional ``asp``
409
+ extra) is not installed.
410
+ """
411
+ translated = dependence_to_eso(sentence)
412
+ if fast:
413
+ from .asp_models import asp_holds_so
414
+ return asp_holds_so(translated, structure)
415
+ from .secondorder import holds
416
+ return holds(translated, structure)