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,506 @@
1
+ """The Discourse Representation Structure (DRS) data model — the classical Kamp/Reyle core.
2
+
3
+ Discourse Representation Theory (Kamp & Reyle, *From Discourse to Logic*, 1993) represents
4
+ a discourse as a **box**: a set of *discourse referents* (the individuals under discussion)
5
+ plus a set of *conditions* asserted about them. A box can itself appear as an operand of
6
+ another condition (negation, a conditional's antecedent/consequent, a disjunct), which is
7
+ how DRT gets recursive structure and — crucially — a notion of which referents are visible
8
+ where. This module implements exactly the propositional core of that theory: no tense,
9
+ no plurals, no presupposition, no generalized quantifiers beyond the donkey-sentence
10
+ universal reading built into the conditional (see ``unicode_logic_kit.drt.export`` for why
11
+ that reading is forced rather than optional). Those omissions are deliberate scope limits
12
+ of this kit subpackage, not oversights — see ``unicode_logic_kit.drt``'s package docstring.
13
+
14
+ Conditions (five classical variants matching Kamp & Reyle's own inventory, plus
15
+ the two plural-DRT extensions of chapter 4 added in 0.24.0)::
16
+
17
+ Pred(name, args) # an atomic condition, e.g. Pred("Farmer", ("x",))
18
+ Eq(a, b) # a = b
19
+ Neg(drs) # ¬[drs] (negation)
20
+ Impl(antecedent, consequent) # [antecedent] => [consequent] (a "duplex condition")
21
+ Or(left, right) # [left] or [right]
22
+ Card(ref, op, n) # |ref| op n (group cardinality)
23
+ Part(member, group) # member ∈ group (renders/exports as Part_of)
24
+
25
+ The plural pair is deliberately MINIMAL — measured against the ACE corpus, not
26
+ designed in the abstract: ``Card`` + ``Part`` are what collective readings
27
+ ("John and Mary lift a table") and counted plurals ("at least 3 men wait")
28
+ actually need, while DISTRIBUTION needs no operator of its own because it is
29
+ the duplex condition applied to membership (``[x | Part_of(x, g)] => [ |
30
+ ...]`` — exactly what ACE's "each of" compiles to). What is still absent, and
31
+ still deliberate: no maximality/abstraction (Kamp & Reyle's Σ — "exactly n"
32
+ as a counting quantifier lives at the FORMULA level via
33
+ ``unicode_logic_kit.fol.nodes.Count``, see ``unicode_logic_kit.ace.translate``),
34
+ no tense, no presupposition.
35
+
36
+ ``DRS(referents, conditions)`` is the box itself: a frozen dataclass pairing a tuple of
37
+ referent names with a tuple of conditions.
38
+
39
+ Referent vs. constant: a single lexical convention, not two vocabularies
40
+ --------------------------------------------------------------------------
41
+ Every string that appears as a ``Pred``/``Eq`` argument is either a *discourse referent*
42
+ (bound by some box's own existential/universal quantifier once translated to FOL) or a
43
+ *constant* (a rigid designator, e.g. a proper name). Rather than tagging each argument with
44
+ its role explicitly, this module reuses the kit's OWN lexical convention for telling a FOL
45
+ Variable from a Constant — see ``unicode_logic_kit/fol/grammars/terminals.lark``::
46
+
47
+ VARIABLE.1: /[a-z][0-9]*/ # single lowercase letter + digits
48
+ NAME.2: /[a-z][a-zA-Z0-9_]*[a-zA-Z][a-zA-Z0-9_]*/ # lowercase-initial, >= 2 LETTERS,
49
+ # `_` in continuation (0.23.0/0.23.1)
50
+ CONSTANT.3: /c_[a-zA-Z0-9]+/ | ... # explicit c_-prefixed form
51
+
52
+ so here: a single lowercase letter optionally followed by digits (``x``, ``y``, ``z1``,
53
+ ``e12``, ...) is a REFERENT; anything else that is lowercase-initial and at least two
54
+ characters (``john``, ``daisy``, ...) or carries the explicit ``c_...`` form is a CONSTANT.
55
+ This is a deliberate, load-bearing restriction (not merely descriptive): a single-letter
56
+ name is ALWAYS read as a referent, so a one-character constant is not directly expressible
57
+ in this notation — the parser's quoted-string / SBN's quoted-constant path sanitizes such
58
+ cases into a legal (necessarily >= 2 character) constant name instead of silently colliding
59
+ with the referent namespace (see ``unicode_logic_kit.drt.parser``). ``is_referent`` /
60
+ ``is_predicate_name`` / ``is_constant_name`` below are the single source of truth for this
61
+ classification; every other module in this subpackage (and ``unicode_logic_kit.drt.export``'s
62
+ standard translation) uses them rather than re-deriving the rule.
63
+
64
+ A predicate name (``Pred.name``) follows the kit's own PREDICATE convention:
65
+ uppercase-initial, then alphanumerics or underscores — since 0.23.1 the grammar's
66
+ PREDICATE takes NAME's continuation class (which is how ``Has_bond_to`` and the rest
67
+ of the chem vocabulary are writable), and this module follows the same rule.
68
+
69
+ Global referent uniqueness (a deliberate simplification, not part of classical DRT)
70
+ -------------------------------------------------------------------------------------
71
+ Full Kamp/Reyle DRT allows the same referent-like variable name to be reused in disjoint
72
+ boxes (it is a bound variable in each; alpha-renaming makes the reuse harmless). This
73
+ module does NOT allow that: :meth:`DRS.validate` requires every referent name appearing
74
+ anywhere in one DRS tree to be declared by exactly ONE box. This sidesteps alpha-renaming
75
+ bookkeeping entirely (the standard translation in ``export.py`` can use referent names
76
+ directly as FOL variable names with no risk of one quantifier accidentally shadowing
77
+ another's), at the cost of requiring the caller to pick distinct names across the whole
78
+ discourse — a small, explicit, well-documented restriction, very much in the "loud
79
+ refusal over silent misreading" spirit of this kit: a shadowing collision is refused with
80
+ a clear message rather than silently producing a formula whose nested quantifiers reuse a
81
+ name in a way the caller may not have intended.
82
+
83
+ Construction-time vs. final validation
84
+ -----------------------------------------
85
+ A condition's referent-shaped arguments must ultimately refer to an ACCESSIBLE box — the
86
+ classical Kamp/Reyle accessibility relation, defined below. But construction is necessarily
87
+ bottom-up (an inner box, e.g. a conditional's consequent ``[| Beats(x, y)]``, is built
88
+ *before* the ``Impl`` that will make its antecedent's referents ``x``/``y`` accessible to
89
+ it), so at the moment ``DRS(referents=(), conditions=(Pred("Beats", ("x", "y")),))`` is
90
+ constructed there is no way to know yet whether ``x``/``y`` will end up accessible — they
91
+ might never become so, if the caller forgets to wrap it in the right ``Impl``. Rejecting
92
+ this at ``__init__`` would make the classical donkey-sentence construction pattern
93
+ impossible to build piece by piece. So ``__init__`` (on ``DRS`` and on each condition
94
+ type) validates only what is knowable locally and independent of assembly order: shapes,
95
+ types, no duplicate referents *within one box's own tuple*. The GLOBAL accessibility check
96
+ — "is every referent-shaped argument, everywhere in this tree, accessible from the box it
97
+ occurs in?" — is :meth:`DRS.validate`, meant to be called once the whole tree is assembled
98
+ (and is called automatically by ``unicode_logic_kit.drt.export.drs_to_fol`` and
99
+ ``unicode_logic_kit.drt.resolve.resolve_anaphora`` before they do anything with the tree).
100
+
101
+ The accessibility relation
102
+ -----------------------------
103
+ For a box ``K`` reached by walking down from the root, the referents ACCESSIBLE to K's own
104
+ direct conditions are exactly:
105
+
106
+ * K's own referents, plus
107
+ * every referent accessible to K's immediate container (the box housing the condition
108
+ that K sits inside, if any) — accessibility is inherited "outer to inner" recursively, plus
109
+ * — the one asymmetric rule, THE donkey-sentence rule — if K is the CONSEQUENT of an
110
+ ``Impl(A, K)``, then A's own referents are ALSO accessible to K (but not vice versa, and
111
+ not to anything outside the ``Impl`` condition).
112
+
113
+ Concretely, entering each condition type from a box with accessible set ``acc``:
114
+
115
+ * ``Neg(D)``: D's accessible set is ``acc | D.referents``. D's referents do NOT propagate
116
+ back out — a referent introduced under negation is invisible outside it (the classic "no
117
+ farmer owns a donkey; # it is grey" infelicity).
118
+ * ``Impl(A, B)``: A's accessible set is ``acc | A.referents``. B's accessible set is
119
+ ``acc | A.referents | B.referents`` (A's referents leak forward into B — the donkey
120
+ rule — but not backward into A, and neither A's nor B's referents propagate outside the
121
+ ``Impl`` condition).
122
+ * ``Or(D1, D2)``: D1's accessible set is ``acc | D1.referents``; D2's is
123
+ ``acc | D2.referents`` — independent alternatives, like ``Neg``: neither leaks into the
124
+ other or outside.
125
+
126
+ :func:`walk_boxes` computes this relation once for a whole tree (as an outer-to-inner
127
+ sequence of referent "layers" per box, so :mod:`unicode_logic_kit.drt.resolve` can also use it
128
+ for a recency ordering); :meth:`DRS.validate` is the accessibility checker built on top of it.
129
+ """
130
+
131
+ import re
132
+ from dataclasses import dataclass
133
+ from typing import Dict, Iterator, Tuple
134
+
135
+ _REF_RE = re.compile(r"^[a-z][0-9]*$")
136
+ _PRED_RE = re.compile(r"^[A-Z][a-zA-Z0-9_]*$")
137
+ # Lowercase-initial, >= 2 LETTERS somewhere, continuation may include `_`
138
+ # (0.23.0/0.23.1 widened the grammar's NAME the same way; verified against
139
+ # the live parser: a_b, ab_, john_smith are constants; a_1, x_ are not).
140
+ _BARE_CONST_RE = re.compile(r"^[a-z][a-zA-Z0-9_]*[a-zA-Z][a-zA-Z0-9_]*$")
141
+ _EXPLICIT_CONST_RE = re.compile(r"^c_[a-zA-Z0-9]+$")
142
+
143
+
144
+ def is_referent(name: str) -> bool:
145
+ """True iff ``name`` is a legal discourse-referent name (a single lowercase letter
146
+ optionally followed by digits — ``x``, ``y``, ``z1``, ``e12``, ...; the kit's own
147
+ FOL Variable convention, see the module docstring)."""
148
+ return isinstance(name, str) and bool(_REF_RE.fullmatch(name))
149
+
150
+
151
+ def is_predicate_name(name: str) -> bool:
152
+ """True iff ``name`` is a legal predicate name: uppercase-initial, then
153
+ alphanumerics or underscores — the kit's own PREDICATE convention. The
154
+ underscore is a CONTINUATION character (0.23.1 widened the grammar's
155
+ PREDICATE to NAME's continuation class precisely so that the chem
156
+ vocabulary's ``Has_bond_to`` is writable); this module tracked the old,
157
+ narrower rule until 0.24.0, which made every underscore predicate the
158
+ fol parser accepts unusable in a DRS."""
159
+ return isinstance(name, str) and bool(_PRED_RE.fullmatch(name))
160
+
161
+
162
+ def is_constant_name(name: str) -> bool:
163
+ """True iff ``name`` is a legal constant name: either a bare lowercase-initial
164
+ name with at least two LETTERS, underscores allowed in continuation (the kit's
165
+ NAME convention, e.g. ``john``, ``john_smith`` — but not ``a_1``, which has one
166
+ letter and would sit next to the referent namespace), or the explicit ``c_...``
167
+ form (the kit's CONSTANT convention, for names that don't fit NAME)."""
168
+ return isinstance(name, str) and bool(
169
+ _BARE_CONST_RE.fullmatch(name) or _EXPLICIT_CONST_RE.fullmatch(name))
170
+
171
+
172
+ def _check_term_name(a, *, context: str) -> None:
173
+ """Loudly refuse any Pred/Eq argument that is neither a legal referent nor a legal
174
+ constant name (see the module docstring's "Referent vs. constant" section)."""
175
+ if not isinstance(a, str) or not a:
176
+ raise ValueError(f"{context}: argument must be a non-empty string, got {a!r}")
177
+ if is_referent(a) or is_constant_name(a):
178
+ return
179
+ raise ValueError(
180
+ f"{context}: {a!r} is neither a legal referent name (a single lowercase letter "
181
+ f"optionally followed by digits, e.g. 'x', 'y1') nor a legal constant name (a "
182
+ f"lowercase-initial name of >= 2 characters, e.g. 'john', or the explicit "
183
+ f"'c_...' form). An arbitrary/quoted constant must be sanitized first — see "
184
+ f"unicode_logic_kit.drt.parser's quoted-constant handling.")
185
+
186
+
187
+ class Condition:
188
+ """Base class for the five DRS condition variants (see the module docstring)."""
189
+
190
+ def to_dict(self) -> dict:
191
+ raise NotImplementedError
192
+
193
+ def to_box_notation(self) -> str:
194
+ """Render this condition in the box notation :func:`unicode_logic_kit.drt.parser.parse_drs`
195
+ accepts, as it would appear inside a box's condition list."""
196
+ raise NotImplementedError
197
+
198
+
199
+ @dataclass(frozen=True)
200
+ class Pred(Condition):
201
+ """An atomic condition: ``name`` applied to one or more referent/constant ``args``.
202
+
203
+ ``name`` must be a legal predicate name (uppercase-initial, alphanumeric); each of
204
+ ``args`` must be a legal referent or constant name (see the module docstring). At
205
+ least one argument is required — DRT conditions always assert something OF one or
206
+ more discourse referents/constants, so a nullary condition has no discourse content.
207
+ """
208
+
209
+ name: str
210
+ args: Tuple[str, ...]
211
+
212
+ def __post_init__(self):
213
+ object.__setattr__(self, "args", tuple(self.args))
214
+ if not is_predicate_name(self.name):
215
+ raise ValueError(
216
+ f"Pred: predicate name {self.name!r} must be uppercase-initial, "
217
+ f"then alphanumerics/underscores (the kit PREDICATE convention, "
218
+ f"e.g. 'Farmer', 'Owns', 'Has_bond_to').")
219
+ if not self.args:
220
+ raise ValueError(
221
+ f"Pred({self.name!r}): needs at least one argument.")
222
+ for a in self.args:
223
+ _check_term_name(a, context=f"Pred({self.name!r}, {self.args!r})")
224
+
225
+ def to_dict(self) -> dict:
226
+ return {"_type": "Pred", "name": self.name, "args": list(self.args)}
227
+
228
+ def to_box_notation(self) -> str:
229
+ return f"{self.name}(" + ", ".join(self.args) + ")"
230
+
231
+
232
+ @dataclass(frozen=True)
233
+ class Eq(Condition):
234
+ """An equality condition ``a = b`` between two referents/constants."""
235
+
236
+ a: str
237
+ b: str
238
+
239
+ def __post_init__(self):
240
+ _check_term_name(self.a, context=f"Eq({self.a!r}, {self.b!r})")
241
+ _check_term_name(self.b, context=f"Eq({self.a!r}, {self.b!r})")
242
+
243
+ def to_dict(self) -> dict:
244
+ return {"_type": "Eq", "a": self.a, "b": self.b}
245
+
246
+ def to_box_notation(self) -> str:
247
+ return f"{self.a} = {self.b}"
248
+
249
+
250
+ #: The comparison operators :class:`Card` accepts, in kit spelling.
251
+ CARD_OPS = ("=", ">=", "<=", ">", "<")
252
+
253
+
254
+ @dataclass(frozen=True)
255
+ class Card(Condition):
256
+ """A group-cardinality condition: ``|ref| op n``.
257
+
258
+ ``ref`` is the group's referent (or constant), ``op`` one of
259
+ :data:`CARD_OPS`, ``n`` a non-negative int. Semantics via the standard
260
+ translation (``unicode_logic_kit.drt.export``): the number of ``x`` with
261
+ ``Part_of(x, ref)`` stands in ``op`` to ``n``, rendered as the kit's
262
+ counting quantifier ``Count`` — so ``Card`` inherits ``Count``'s exact
263
+ first-order expansion on every backend. ``Card(ref, "<", 0)`` is refused
264
+ at construction: nothing has fewer than zero parts, and a condition that
265
+ is FALSE by spelling is a mistake, not a statement.
266
+
267
+ Box notation: ``Card(g, >=, 3)`` — unambiguous against a ``Pred`` named
268
+ ``Card`` because no legal ``Pred`` argument can be a bare comparison
269
+ operator or integer.
270
+ """
271
+
272
+ ref: str
273
+ op: str
274
+ n: int
275
+
276
+ def __post_init__(self):
277
+ _check_term_name(self.ref, context=f"Card({self.ref!r}, ...)")
278
+ if self.op not in CARD_OPS:
279
+ raise ValueError(
280
+ f"Card: op {self.op!r} must be one of {CARD_OPS}.")
281
+ if not isinstance(self.n, int) or isinstance(self.n, bool) or self.n < 0:
282
+ raise ValueError(f"Card: n must be a non-negative int, got {self.n!r}.")
283
+ if self.op == "<" and self.n == 0:
284
+ raise ValueError(
285
+ "Card: '< 0' is unsatisfiable by spelling — nothing has "
286
+ "fewer than zero parts.")
287
+
288
+ def to_dict(self) -> dict:
289
+ return {"_type": "Card", "ref": self.ref, "op": self.op, "n": self.n}
290
+
291
+ def to_box_notation(self) -> str:
292
+ return f"Card({self.ref}, {self.op}, {self.n})"
293
+
294
+
295
+ @dataclass(frozen=True)
296
+ class Part(Condition):
297
+ """A membership condition: ``member`` is a part of the group ``group``.
298
+
299
+ Renders — and exports — as the distinguished binary predicate
300
+ ``Part_of``, chosen over the bare ``Part`` because ACE's noun "part"
301
+ would otherwise produce a UNARY ``Part`` predicate next to a binary one,
302
+ and one symbol with two arities breaks every ASCII export route (TPTP,
303
+ Prover9, z3 declarations). A hand-written ``Pred("Part_of", (m, g))``
304
+ means exactly the same thing at the FOL level; the typed condition
305
+ exists so the export and the plural machinery can FIND membership
306
+ without string-matching predicate names.
307
+ """
308
+
309
+ member: str
310
+ group: str
311
+
312
+ def __post_init__(self):
313
+ _check_term_name(self.member, context=f"Part({self.member!r}, {self.group!r})")
314
+ _check_term_name(self.group, context=f"Part({self.member!r}, {self.group!r})")
315
+
316
+ def to_dict(self) -> dict:
317
+ return {"_type": "Part", "member": self.member, "group": self.group}
318
+
319
+ def to_box_notation(self) -> str:
320
+ return f"Part_of({self.member}, {self.group})"
321
+
322
+
323
+ @dataclass(frozen=True)
324
+ class Neg(Condition):
325
+ """Negation of a sub-DRS: ``¬[drs]``. ``drs``'s own referents are accessible within
326
+ ``drs`` but do not escape (see the module docstring's accessibility relation)."""
327
+
328
+ drs: "DRS"
329
+
330
+ def __post_init__(self):
331
+ if not isinstance(self.drs, DRS):
332
+ raise TypeError(f"Neg: operand must be a DRS, got {type(self.drs).__name__}")
333
+
334
+ def to_dict(self) -> dict:
335
+ return {"_type": "Neg", "drs": self.drs.to_dict()}
336
+
337
+ def to_box_notation(self) -> str:
338
+ return f"~{self.drs.to_box_notation()}"
339
+
340
+
341
+ @dataclass(frozen=True)
342
+ class Impl(Condition):
343
+ """A duplex (conditional) condition ``[antecedent] => [consequent]`` — THE
344
+ donkey-sentence construct: ``antecedent``'s referents are accessible in
345
+ ``consequent`` (but nowhere else) — see the module docstring."""
346
+
347
+ antecedent: "DRS"
348
+ consequent: "DRS"
349
+
350
+ def __post_init__(self):
351
+ for field_name, val in (("antecedent", self.antecedent), ("consequent", self.consequent)):
352
+ if not isinstance(val, DRS):
353
+ raise TypeError(f"Impl: {field_name} must be a DRS, got {type(val).__name__}")
354
+
355
+ def to_dict(self) -> dict:
356
+ return {"_type": "Impl", "antecedent": self.antecedent.to_dict(),
357
+ "consequent": self.consequent.to_dict()}
358
+
359
+ def to_box_notation(self) -> str:
360
+ return f"{self.antecedent.to_box_notation()} -> {self.consequent.to_box_notation()}"
361
+
362
+
363
+ @dataclass(frozen=True)
364
+ class Or(Condition):
365
+ """A disjunctive condition ``[left] or [right]``; each side's referents are
366
+ accessible only within that side (see the module docstring)."""
367
+
368
+ left: "DRS"
369
+ right: "DRS"
370
+
371
+ def __post_init__(self):
372
+ for field_name, val in (("left", self.left), ("right", self.right)):
373
+ if not isinstance(val, DRS):
374
+ raise TypeError(f"Or: {field_name} must be a DRS, got {type(val).__name__}")
375
+
376
+ def to_dict(self) -> dict:
377
+ return {"_type": "Or", "left": self.left.to_dict(), "right": self.right.to_dict()}
378
+
379
+ def to_box_notation(self) -> str:
380
+ return f"{self.left.to_box_notation()} ∨ {self.right.to_box_notation()}"
381
+
382
+
383
+ @dataclass(frozen=True)
384
+ class DRS:
385
+ """A Discourse Representation Structure (a "box"): ``referents`` declared in this
386
+ box, and ``conditions`` asserted about them (and about whatever is accessible from
387
+ an enclosing box — see the module docstring).
388
+
389
+ Construction validates only local shape (each referent is a legal, box-locally-unique
390
+ referent name; each condition is a :class:`Condition` instance) — NOT cross-box
391
+ accessibility, which construction order makes impossible to check here (see the module
392
+ docstring). Call :meth:`validate` once a whole tree is assembled.
393
+ """
394
+
395
+ referents: Tuple[str, ...]
396
+ conditions: Tuple[Condition, ...]
397
+
398
+ def __post_init__(self):
399
+ object.__setattr__(self, "referents", tuple(self.referents))
400
+ object.__setattr__(self, "conditions", tuple(self.conditions))
401
+ seen = set()
402
+ for r in self.referents:
403
+ if not is_referent(r):
404
+ raise ValueError(
405
+ f"DRS: referent {r!r} is not a legal referent name (a single "
406
+ f"lowercase letter optionally followed by digits, e.g. 'x', 'y1').")
407
+ if r in seen:
408
+ raise ValueError(
409
+ f"DRS: duplicate referent {r!r} within one box's own referent "
410
+ f"list {self.referents!r}.")
411
+ seen.add(r)
412
+ for c in self.conditions:
413
+ if not isinstance(c, Condition):
414
+ raise TypeError(
415
+ f"DRS: condition {c!r} is not a Condition instance "
416
+ f"(got {type(c).__name__}).")
417
+
418
+ def to_dict(self) -> dict:
419
+ return {"_type": "DRS", "referents": list(self.referents),
420
+ "conditions": [c.to_dict() for c in self.conditions]}
421
+
422
+ def to_box_notation(self) -> str:
423
+ """Render this DRS in the box notation :func:`unicode_logic_kit.drt.parser.parse_drs`
424
+ accepts: ``parse_drs(drs.to_box_notation())`` reproduces an equal ``DRS`` (see
425
+ that module for the exact grammar; a top-level ``Neg``/``Impl``/``Or`` — i.e. a
426
+ DRS built as ``DRS((), (cond,))`` to wrap a bare duplex/negated condition — round
427
+ trips to an equal value, though through one extra bracket layer, since
428
+ ``parse_drs`` always accepts ``[ | <that condition>]`` as a plain box)."""
429
+ refs = ", ".join(self.referents)
430
+ conds = ", ".join(c.to_box_notation() for c in self.conditions)
431
+ return f"[{refs} | {conds}]"
432
+
433
+ def validate(self) -> bool:
434
+ """Check the classical Kamp/Reyle accessibility relation over the whole tree
435
+ rooted at ``self`` (see the module docstring), plus global referent-name
436
+ uniqueness (this kit's deliberate simplification, see the module docstring).
437
+
438
+ Raises:
439
+ ValueError: a referent name is declared by more than one box, or some
440
+ condition's referent-shaped argument is not accessible where it occurs
441
+ (message names the referent, the condition, and the accessible set).
442
+ Returns:
443
+ True on success (never returns False — failure is always an exception, per
444
+ this kit's "loud refusal over silent misreading" convention).
445
+ """
446
+ boxes = list(walk_boxes(self))
447
+
448
+ declared: Dict[str, "DRS"] = {}
449
+ for box, _ in boxes:
450
+ for r in box.referents:
451
+ if r in declared and declared[r] is not box:
452
+ raise ValueError(
453
+ f"DRS.validate: referent {r!r} is declared in more than one "
454
+ f"box in this tree. This DRT implementation requires globally "
455
+ f"distinct referent names (no shadowing/alpha-renaming, see "
456
+ f"the module docstring) — rename one of the occurrences.")
457
+ declared[r] = box
458
+
459
+ for box, layers in boxes:
460
+ acc = frozenset(n for layer in layers for n in layer)
461
+ for cond in box.conditions:
462
+ if isinstance(cond, Pred):
463
+ args = cond.args
464
+ elif isinstance(cond, Eq):
465
+ args = (cond.a, cond.b)
466
+ elif isinstance(cond, Card):
467
+ args = (cond.ref,)
468
+ elif isinstance(cond, Part):
469
+ args = (cond.member, cond.group)
470
+ else:
471
+ continue
472
+ for a in args:
473
+ if is_referent(a) and a not in acc:
474
+ raise ValueError(
475
+ f"DRS.validate: referent {a!r} in condition "
476
+ f"{cond.to_box_notation()!r} is not accessible at this "
477
+ f"point (accessible referents here: {sorted(acc)!r}). See "
478
+ f"the accessibility relation in the nodes module docstring.")
479
+ return True
480
+
481
+
482
+ def walk_boxes(
483
+ drs: DRS, _layers: Tuple[Tuple[str, ...], ...] = ()
484
+ ) -> Iterator[Tuple[DRS, Tuple[Tuple[str, ...], ...]]]:
485
+ """Yield every ``(box, layers)`` pair reachable from ``drs`` (``drs`` itself first,
486
+ pre-order), where ``layers`` is the sequence of referent tuples — outermost first,
487
+ ``box``'s own referents last — that together make up the referents ACCESSIBLE to
488
+ ``box``'s own direct conditions per the classical accessibility relation (see the
489
+ module docstring). ``frozenset(n for layer in layers for n in layer)`` is the flat
490
+ accessible SET; the ordered form is kept so
491
+ :mod:`unicode_logic_kit.drt.resolve` can rank candidates by recency (innermost layer,
492
+ then rightmost position within a layer, is "most recent").
493
+ """
494
+ if not isinstance(drs, DRS):
495
+ raise TypeError(f"walk_boxes: expected a DRS, got {type(drs).__name__}")
496
+ my_layers = _layers + (drs.referents,)
497
+ yield drs, my_layers
498
+ for cond in drs.conditions:
499
+ if isinstance(cond, Neg):
500
+ yield from walk_boxes(cond.drs, my_layers)
501
+ elif isinstance(cond, Impl):
502
+ yield from walk_boxes(cond.antecedent, my_layers)
503
+ yield from walk_boxes(cond.consequent, my_layers + (cond.antecedent.referents,))
504
+ elif isinstance(cond, Or):
505
+ yield from walk_boxes(cond.left, my_layers)
506
+ yield from walk_boxes(cond.right, my_layers)