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,666 @@
1
+ """ACE-DRS → kit DRS: the pure fragment, with a per-condition verdict.
2
+
3
+ The reader (:mod:`unicode_logic_kit.ace.drs_reader`) delivers APE's DRS
4
+ verbatim; this module decides, condition by condition, what the kit's
5
+ classical DRS core (:mod:`unicode_logic_kit.drt.nodes`: ``Pred``/``Eq``/
6
+ ``Neg``/``Impl``/``Or``) can carry — and REFUSES the rest by name, with a
7
+ reason and the milestone that will change the verdict. A sentence maps
8
+ either completely or not at all: there is no partial DRS with conditions
9
+ silently dropped, because a DRS missing one condition of the original does
10
+ not mean "most of" the original — it means something else.
11
+
12
+ Vocabulary — what a mapped condition becomes
13
+ --------------------------------------------
14
+ Nouns, verbs, adjectives, adverbs and prepositions become kit predicates,
15
+ uppercased into the kit convention. Events stay, neo-Davidsonian::
16
+
17
+ object(A,man,countable,na,eq,1) -> Man(x1)
18
+ predicate(B,see,A,C) -> See(e1, x1, x2)
19
+ modifier_pp(B,in,G) -> In(e1, x3)
20
+ modifier_adv(B,loudly,pos) -> Loudly(e1)
21
+ property(A,rich,pos) -> Rich(x1)
22
+ property(A,tall,comp_than,B) -> Tall_comp_than(x1, x2)
23
+ relation(A,of,B) -> Of(x1, x2)
24
+ has_part(G,named('John')) -> Part_of(john, g1)
25
+ object(G,na,countable,na,eq,2) -> Card(g1, =, 2)
26
+ object(G,man,countable,na,geq,3) -> Card(g1, >=, 3),
27
+ [x2 | Part_of(x2, g1)] => [ | Man(x2)]
28
+
29
+ Since 0.24.0 the plural shapes land on the DRS core's ``Card``/``Part``
30
+ conditions: a coordination group contributes its cardinality, a counted
31
+ plural additionally distributes its noun over the members via a membership
32
+ duplex — while the group referent itself stays a term for the verb, which
33
+ is ACE's COLLECTIVE reading of the unmarked plural ("at least 3 men wait"
34
+ asserts a waiting group of >= 3 men, not three individual waits; "each of"
35
+ compiles to the duplex and distributes, exactly as APE hands it over).
36
+
37
+ A non-``pos`` degree is folded into the predicate NAME (``Tall_comp_than``)
38
+ rather than dropped — no morphology is attempted ("taller" is never
39
+ reconstructed from ``tall``), and the underscore forms are legal kit
40
+ predicates since 0.23.1. The copula follows Attempto's own reference
41
+ translation, measured on its TPTP output: ``predicate(B,be,X,Y)`` becomes
42
+ the EQUALITY ``X = Y`` and the be-event referent is dropped — but only when
43
+ that referent occurs nowhere else in the whole DRS; if something modifies
44
+ the be-event (nothing in the measured corpus does), the guard keeps it as a
45
+ ``Be(e, x, y)`` predicate instead of silently orphaning the modifier.
46
+
47
+ Referents are renamed to the kit's convention by ROLE, in order of
48
+ declaration: events (first argument of a ``predicate`` condition) become
49
+ ``e1, e2, …``, group referents (a ``na``-noun ``object`` or the whole of a
50
+ ``has_part``) become ``g1, …``, everything else ``x1, …``. Proper names
51
+ become kit constants (``named('John')`` → ``john``); values become ``c_``
52
+ constants (``int(30)`` → ``c_30``, ``string('Johnny')`` → ``c_Johnny``) —
53
+ each table is deterministic, collision-checked, and returned on the result.
54
+
55
+ What is refused, and where it goes
56
+ -----------------------------------
57
+ - **modal boxes** — no modal conditions in a classical DRS → ACE-3, done:
58
+ use :func:`unicode_logic_kit.ace.translate.ace_to_formula`;
59
+ - **question / query** — a question is not an assertion → likewise
60
+ ``ace_to_formula``;
61
+ - **command** — no imperative semantics decided (no milestone: undecided);
62
+ - **~ (negation as failure)** — not classical negation (no milestone);
63
+ - **the [...] list condition** — the maximality reading of ``exactly``/``at
64
+ most`` is a counting quantifier over a compound scope, a FORMULA-level
65
+ construct → ``ace_to_formula`` carries it (the distributive counting
66
+ reading, documented there);
67
+ - **formula / expr** — arithmetic terms have no place in a DRS condition's
68
+ referent/constant arguments → ``ace_to_formula`` carries it.
69
+
70
+ Everything else unseen (a functor or arity outside the measured corpus)
71
+ is refused as exactly that — "not in the measured corpus" — rather than
72
+ guessed at.
73
+ """
74
+
75
+ from __future__ import annotations
76
+
77
+ import re
78
+ from dataclasses import dataclass
79
+ from typing import Dict, List, Optional, Tuple, Union
80
+
81
+ from ..drt.nodes import DRS, Condition, Eq, Impl, Neg, Or, Pred
82
+ from .drs_reader import (
83
+ AceAtom, AceCommand, AceCondition, AceCondList, AceDrs, AceExpr, AceImpl,
84
+ AceInt, AceModal, AceNaf, AceNamed, AceNeg, AceOr, AceQuestion, AceReal,
85
+ AceString, AceTerm, AceTermApp, AceVar, parse_ape_drs,
86
+ )
87
+ from .runner import AceError, AceParseError, run_ape
88
+
89
+ __all__ = [
90
+ "map_ace_drs", "ace_to_drs", "DrsMapping", "ConditionReport",
91
+ "condition_statistics", "AceUnsupportedError",
92
+ ]
93
+
94
+
95
+ class AceUnsupportedError(AceError):
96
+ """The text is ACE, but at least one of its DRS conditions is outside
97
+ what the requested route can carry. ``rows`` holds the FULL per-condition
98
+ report — the unsupported entries name the reason and, where one exists,
99
+ the milestone that will change the verdict."""
100
+
101
+ def __init__(self, message: str, rows: Tuple["ConditionReport", ...]):
102
+ super().__init__(message)
103
+ self.rows = rows
104
+
105
+ @property
106
+ def unsupported(self) -> Tuple["ConditionReport", ...]:
107
+ return tuple(r for r in self.rows if r.verdict == "unsupported")
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class ConditionReport:
112
+ """One condition's verdict. ``condition`` is APE's own rendering (the
113
+ sub-boxes of a complex condition elided to ``…`` — their conditions get
114
+ rows of their own), ``target`` the kit rendering when mapped,
115
+ ``reason``/``milestone`` the refusal when not. ``sentence``/``token``
116
+ point back into the ACE text for atomic conditions; complex shells and
117
+ implicit conditions carry ``None``."""
118
+
119
+ condition: str
120
+ verdict: str # "mapped" | "unsupported"
121
+ target: str = ""
122
+ reason: str = ""
123
+ milestone: str = ""
124
+ sentence: Optional[int] = None
125
+ token: Optional[int] = None
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class DrsMapping:
130
+ """The result of :func:`map_ace_drs`.
131
+
132
+ ``drs`` is the kit DRS when EVERY condition mapped (``complete``), else
133
+ ``None`` — never a partial structure. ``rows`` is the per-condition
134
+ report either way. ``referents`` and ``constants`` are the two renaming
135
+ tables (APE's name / the source term's rendering → kit name), returned
136
+ so a caller can trace any kit symbol back to its origin."""
137
+
138
+ drs: Optional[DRS]
139
+ rows: Tuple[ConditionReport, ...]
140
+ referents: Dict[str, str]
141
+ constants: Dict[str, str]
142
+
143
+ @property
144
+ def complete(self) -> bool:
145
+ return self.drs is not None
146
+
147
+ @property
148
+ def unsupported(self) -> Tuple[ConditionReport, ...]:
149
+ return tuple(r for r in self.rows if r.verdict == "unsupported")
150
+
151
+
152
+ def condition_statistics(mappings) -> Dict[Tuple[str, str, str], int]:
153
+ """Aggregate report rows over many mappings — the ACE-5 data basis.
154
+
155
+ Keys are ``(condition label, verdict, milestone)``; the label is the
156
+ functor with arity for atoms (``object/6``) and the bare functor for
157
+ complex shells. Feed it ``map_ace_drs`` results over a corpus and the
158
+ counts say exactly which core extension pays for how many sentences.
159
+ """
160
+ counts: Dict[Tuple[str, str, str], int] = {}
161
+ for mapping in mappings:
162
+ for row in mapping.rows:
163
+ key = (_row_label(row), row.verdict, row.milestone)
164
+ counts[key] = counts.get(key, 0) + 1
165
+ return counts
166
+
167
+
168
+ def _row_label(row: ConditionReport) -> str:
169
+ # Strip the -S/T index an atomic render carries before reading the
170
+ # arity off the argument list.
171
+ body = re.sub(r"-\d+/(?:\d+|'')$", "", row.condition)
172
+ head = body.split("(", 1)[0]
173
+ if "…" in body or not body.endswith(")"):
174
+ return head or body
175
+ inner = body[len(head) + 1:-1]
176
+ depth = 0
177
+ arity = 1 if inner else 0
178
+ for ch in inner:
179
+ if ch in "([":
180
+ depth += 1
181
+ elif ch in ")]":
182
+ depth -= 1
183
+ elif ch == "," and depth == 0:
184
+ arity += 1
185
+ return f"{head}/{arity}"
186
+
187
+
188
+ # ---------------------------------------------------------------------------
189
+ # Renaming
190
+ # ---------------------------------------------------------------------------
191
+
192
+ _REFERENT_SHAPE = re.compile(r"^[a-z][0-9]*$")
193
+ _ALNUM = re.compile(r"[^a-zA-Z0-9]+")
194
+
195
+
196
+ def _named_constant(name: str) -> str:
197
+ """The proper-name rule, shared with the differential's alignment side:
198
+ ``John`` → ``john``; a name that would land in the kit's single-letter
199
+ REFERENT namespace (``E1``) takes the explicit ``c_`` form instead."""
200
+ base = _ALNUM.sub("", name)
201
+ base = base[0].lower() + base[1:] if base else "name"
202
+ if _REFERENT_SHAPE.match(base):
203
+ base = "c_" + (_ALNUM.sub("", name) or "name")
204
+ return base
205
+
206
+
207
+ class _Renamer:
208
+ """Deterministic APE-name → kit-name tables for referents and constants.
209
+
210
+ Role detection is a PRE-pass over the whole tree (an event referent is
211
+ one that appears as the first argument of any ``predicate`` condition;
212
+ a group referent as the ``na``-noun of an ``object`` or the whole of a
213
+ ``has_part``), so a referent's kit name never depends on which condition
214
+ happens to be visited first.
215
+ """
216
+
217
+ def __init__(self, root: AceDrs):
218
+ events, groups = set(), set()
219
+ for cond in root.walk_conditions():
220
+ if not isinstance(cond, AceAtom):
221
+ continue
222
+ if cond.functor == "predicate" and cond.args and isinstance(
223
+ cond.args[0], AceVar):
224
+ events.add(cond.args[0].name)
225
+ if (cond.functor == "object" and len(cond.args) == 6
226
+ and isinstance(cond.args[0], AceVar)):
227
+ _ref, noun, cls, _unit, op, count = cond.args
228
+ # A coordination group (na-noun) or a counted plural — both
229
+ # denote GROUPS, so both take g-names for traceability.
230
+ if noun == "na" or (op in _ACE_OPS and cls != "mass"
231
+ and not (op == "eq" and count == 1)):
232
+ groups.add(cond.args[0].name)
233
+ if (cond.functor == "has_part" and cond.args
234
+ and isinstance(cond.args[0], AceVar)):
235
+ groups.add(cond.args[0].name)
236
+ self._events, self._groups = events, groups
237
+ self.referents: Dict[str, str] = {}
238
+ self.constants: Dict[str, str] = {}
239
+ self._counters = {"e": 0, "g": 0, "x": 0}
240
+ for box in root.walk_boxes():
241
+ for ref in box.referents:
242
+ self._assign(ref.name)
243
+
244
+ def _assign(self, ape_name: str) -> None:
245
+ if ape_name in self.referents:
246
+ return
247
+ prefix = ("e" if ape_name in self._events
248
+ else "g" if ape_name in self._groups else "x")
249
+ self._counters[prefix] += 1
250
+ self.referents[ape_name] = f"{prefix}{self._counters[prefix]}"
251
+
252
+ def fresh(self, prefix: str = "x") -> str:
253
+ """A synthetic referent no APE variable maps to — the member variable
254
+ of a plural object's membership duplex, or a counting variable. Uses
255
+ the same per-prefix counters as :meth:`_assign`, so synthetic and
256
+ mapped names share one numbering and cannot collide."""
257
+ self._counters[prefix] = self._counters.get(prefix, 0) + 1
258
+ name = f"{prefix}{self._counters[prefix]}"
259
+ while name in self.referents.values():
260
+ self._counters[prefix] += 1
261
+ name = f"{prefix}{self._counters[prefix]}"
262
+ return name
263
+
264
+ def referent(self, var: AceVar) -> str:
265
+ if var.name not in self.referents:
266
+ # A variable used but never declared would be an APE invariant
267
+ # violation; name it visibly rather than crashing.
268
+ self._assign(var.name)
269
+ return self.referents[var.name]
270
+
271
+ def constant(self, term: Union[AceNamed, AceInt, AceReal, AceString]) -> str:
272
+ key = term.render()
273
+ if key in self.constants:
274
+ return self.constants[key]
275
+ if isinstance(term, AceNamed):
276
+ base = _named_constant(term.name)
277
+ elif isinstance(term, AceInt):
278
+ base = "c_" + (str(term.value) if term.value >= 0
279
+ else "m" + str(-term.value))
280
+ elif isinstance(term, AceReal):
281
+ base = "c_" + ("m" if term.value < 0 else "") + str(
282
+ abs(term.value)).replace(".", "p")
283
+ else: # AceString
284
+ cleaned = _ALNUM.sub("", term.value)
285
+ base = "c_" + (cleaned if cleaned else "str")
286
+ candidate, n = base, 1
287
+ while candidate in self.constants.values():
288
+ n += 1
289
+ candidate = f"{base}{n}" if not base.startswith("c_") \
290
+ else f"{base}x{n}"
291
+ self.constants[key] = candidate
292
+ return candidate
293
+
294
+
295
+ #: APE's cardinality operators in kit spelling (drt.CARD_OPS). ``na`` never
296
+ #: reaches this table (mass nouns are handled before it).
297
+ _ACE_OPS = {"eq": "=", "geq": ">=", "leq": "<=", "exactly": "=",
298
+ "greater": ">", "less": "<"}
299
+
300
+
301
+ def _kit_predicate(name: str) -> str:
302
+ """``man`` → ``Man``; anything non-alphanumeric becomes ``_`` (legal in
303
+ kit predicates since 0.23.1, e.g. the chem vocabulary's Has_bond_to)."""
304
+ cleaned = re.sub(r"[^a-zA-Z0-9_]+", "_", name).strip("_") or "P"
305
+ return cleaned[0].upper() + cleaned[1:]
306
+
307
+
308
+ def _degree_name(base: str, degree: str) -> str:
309
+ return _kit_predicate(base) if degree == "pos" \
310
+ else f"{_kit_predicate(base)}_{degree}"
311
+
312
+
313
+ # ---------------------------------------------------------------------------
314
+ # Atomic-condition mapping (shared with the ACE-3 formula route)
315
+ # ---------------------------------------------------------------------------
316
+
317
+ @dataclass(frozen=True)
318
+ class _Unsupported:
319
+ reason: str
320
+ milestone: str = ""
321
+
322
+
323
+ #: ("pred", name, args) | ("eq", left, right) | _Unsupported. The tuple
324
+ #: forms carry STRINGS (kit referent/constant names) so both consumers —
325
+ #: drt conditions here, fol atoms in .translate — build their own nodes.
326
+ _Mapped = Union[Tuple[str, str, Tuple[str, ...]], Tuple[str, str, str],
327
+ _Unsupported]
328
+
329
+
330
+ def _term_name(term: AceTerm, renamer: _Renamer) -> Union[str, _Unsupported]:
331
+ if isinstance(term, AceVar):
332
+ return renamer.referent(term)
333
+ if isinstance(term, (AceNamed, AceInt, AceReal, AceString)):
334
+ return renamer.constant(term)
335
+ if isinstance(term, AceExpr):
336
+ return _Unsupported("an arithmetic expression term", "ACE-4")
337
+ if isinstance(term, AceTermApp):
338
+ return _Unsupported(
339
+ f"a {term.functor}/{len(term.args)} term not in the measured corpus")
340
+ return _Unsupported(f"a bare atom {term!r} in referent position")
341
+
342
+
343
+ def _map_atom(cond: AceAtom, renamer: _Renamer,
344
+ be_event_dropped: frozenset) -> _Mapped:
345
+ """One atomic condition → its kit shape, or a named refusal.
346
+
347
+ ``be_event_dropped`` is the set of be-event referents (APE names) whose
348
+ equality translation drops them — computed once per DRS by the caller,
349
+ because whether ``be`` may become ``=`` depends on the WHOLE tree (see
350
+ the module docstring's copula paragraph).
351
+ """
352
+ f, args = cond.functor, cond.args
353
+
354
+ if f == "object" and len(args) == 6:
355
+ ref, noun, cls, _unit, op, count = args
356
+ if not isinstance(ref, AceVar) or not isinstance(noun, str):
357
+ return _Unsupported("an object condition with a non-atomic noun")
358
+ if (op == "eq" and count == 1) or (cls == "mass" and op == "na"):
359
+ return ("pred", _kit_predicate(noun), (renamer.referent(ref),))
360
+ if op not in _ACE_OPS or not isinstance(count, int):
361
+ return _Unsupported(
362
+ f"a cardinality operator outside the measured corpus "
363
+ f"({op!r} {count!r})")
364
+ if noun == "na":
365
+ # A coordination group ("John and Mary"): its members arrive as
366
+ # has_part conditions; the object contributes ONLY |g| op n.
367
+ return ("card", renamer.referent(ref), _ACE_OPS[op], count)
368
+ # A counted plural ("at least 3 men"): |g| op n, every member a noun.
369
+ # ACE reads the unmarked plural COLLECTIVELY, so the group referent
370
+ # stays and the verb keeps applying to it — the box mapper expands
371
+ # this into Card plus a membership duplex.
372
+ return ("group_noun", renamer.referent(ref), _kit_predicate(noun),
373
+ _ACE_OPS[op], count)
374
+
375
+ if f == "predicate" and len(args) in (3, 4, 5):
376
+ event, verb = args[0], args[1]
377
+ if not isinstance(event, AceVar) or not isinstance(verb, str):
378
+ return _Unsupported("a predicate condition with a non-atomic verb")
379
+ if verb == "be" and len(args) == 4:
380
+ if event.name in be_event_dropped:
381
+ left = _term_name(args[2], renamer)
382
+ right = _term_name(args[3], renamer)
383
+ for side in (left, right):
384
+ if isinstance(side, _Unsupported):
385
+ return side
386
+ return ("eq", left, right)
387
+ # The be-event is itself talked about (modified) — keep it.
388
+ # Not measured in the corpus; the guard exists so that if APE
389
+ # ever produces it, the modifier keeps its subject instead of
390
+ # dangling.
391
+ participants: List[str] = [renamer.referent(event)]
392
+ for arg in args[2:]:
393
+ name = _term_name(arg, renamer)
394
+ if isinstance(name, _Unsupported):
395
+ return name
396
+ participants.append(name)
397
+ return ("pred", _kit_predicate(verb), tuple(participants))
398
+
399
+ if f == "property" and len(args) in (3, 4):
400
+ ref, adjective, degree = args[0], args[1], args[2]
401
+ if (not isinstance(ref, AceVar) or not isinstance(adjective, str)
402
+ or not isinstance(degree, str)):
403
+ return _Unsupported("a property condition outside the measured shapes")
404
+ names: List[str] = [renamer.referent(ref)]
405
+ for extra in args[3:]:
406
+ name = _term_name(extra, renamer)
407
+ if isinstance(name, _Unsupported):
408
+ return name
409
+ names.append(name)
410
+ return ("pred", _degree_name(adjective, degree), tuple(names))
411
+
412
+ if f == "relation" and len(args) == 3 and args[1] == "of":
413
+ left = _term_name(args[0], renamer)
414
+ right = _term_name(args[2], renamer)
415
+ for side in (left, right):
416
+ if isinstance(side, _Unsupported):
417
+ return side
418
+ return ("pred", "Of", (left, right))
419
+
420
+ if f == "modifier_adv" and len(args) == 3:
421
+ event, adverb, degree = args
422
+ if (not isinstance(event, AceVar) or not isinstance(adverb, str)
423
+ or not isinstance(degree, str)):
424
+ return _Unsupported("a modifier_adv outside the measured shapes")
425
+ return ("pred", _degree_name(adverb, degree),
426
+ (renamer.referent(event),))
427
+
428
+ if f == "modifier_pp" and len(args) == 3:
429
+ event, preposition, obj = args
430
+ if not isinstance(event, AceVar) or not isinstance(preposition, str):
431
+ return _Unsupported("a modifier_pp outside the measured shapes")
432
+ name = _term_name(obj, renamer)
433
+ if isinstance(name, _Unsupported):
434
+ return name
435
+ return ("pred", _kit_predicate(preposition),
436
+ (renamer.referent(event), name))
437
+
438
+ if f == "has_part" and len(args) == 2:
439
+ # APE argument order is (group, member); the kit condition reads
440
+ # Part(member, group).
441
+ group, member = args
442
+ if not isinstance(group, AceVar):
443
+ return _Unsupported("a has_part whose group is not a referent")
444
+ member_name = _term_name(member, renamer)
445
+ if isinstance(member_name, _Unsupported):
446
+ return member_name
447
+ return ("part", member_name, renamer.referent(group))
448
+ if f == "query":
449
+ return _Unsupported(
450
+ "an interrogative marker: a question is not an assertion — "
451
+ "ace_to_formula carries it", "ACE-3")
452
+ if f == "formula":
453
+ return _Unsupported(
454
+ "arithmetic terms have no place in a DRS condition's "
455
+ "referent/constant arguments -- ace_to_formula carries it",
456
+ "ACE-4")
457
+ return _Unsupported(
458
+ f"{f}/{len(args)} is not in the measured corpus")
459
+
460
+
461
+ def _be_events_to_drop(root: AceDrs) -> frozenset:
462
+ """Referents of ``be`` events that occur NOWHERE besides their own
463
+ condition — exactly those may be translated away into an equality."""
464
+ occurrences: Dict[str, int] = {}
465
+ be_events: List[str] = []
466
+ for cond in root.walk_conditions():
467
+ if not isinstance(cond, AceAtom):
468
+ continue
469
+ if (cond.functor == "predicate" and len(cond.args) == 4
470
+ and cond.args[1] == "be" and isinstance(cond.args[0], AceVar)):
471
+ be_events.append(cond.args[0].name)
472
+ for arg in cond.args:
473
+ if isinstance(arg, AceVar):
474
+ occurrences[arg.name] = occurrences.get(arg.name, 0) + 1
475
+ return frozenset(e for e in be_events if occurrences.get(e, 0) == 1)
476
+
477
+
478
+ # ---------------------------------------------------------------------------
479
+ # Box mapping
480
+ # ---------------------------------------------------------------------------
481
+
482
+ def _shell_render(cond: AceCondition) -> str:
483
+ """A complex condition's row rendering: functor with boxes elided —
484
+ the boxes' own conditions get rows of their own."""
485
+ if isinstance(cond, AceImpl):
486
+ return "=>(…)"
487
+ if isinstance(cond, AceOr):
488
+ return "v(…)"
489
+ if isinstance(cond, AceNeg):
490
+ return "-(…)"
491
+ if isinstance(cond, AceNaf):
492
+ return "~(…)"
493
+ if isinstance(cond, AceModal):
494
+ return f"{cond.modality}(…)"
495
+ if isinstance(cond, AceQuestion):
496
+ return "question(…)"
497
+ if isinstance(cond, AceCommand):
498
+ return "command(…)"
499
+ return "[…]"
500
+
501
+
502
+ class _BoxMapper:
503
+ def __init__(self, root: AceDrs):
504
+ self.renamer = _Renamer(root)
505
+ self.dropped = _be_events_to_drop(root)
506
+ self.rows: List[ConditionReport] = []
507
+ self.complete = True
508
+
509
+ def map_box(self, box: AceDrs) -> Optional[DRS]:
510
+ referents = tuple(
511
+ self.renamer.referent(r) for r in box.referents
512
+ if r.name not in self.dropped)
513
+ conditions: List[Condition] = []
514
+ for cond in box.conditions:
515
+ conditions.extend(self.map_condition(cond))
516
+ if not self.complete:
517
+ return None
518
+ return DRS(referents, tuple(conditions))
519
+
520
+ def map_condition(self, cond: AceCondition) -> List[Condition]:
521
+ if isinstance(cond, AceAtom):
522
+ result = _map_atom(cond, self.renamer, self.dropped)
523
+ if isinstance(result, _Unsupported):
524
+ self.complete = False
525
+ self.rows.append(ConditionReport(
526
+ cond.render(), "unsupported", reason=result.reason,
527
+ milestone=result.milestone, sentence=cond.sentence,
528
+ token=cond.token))
529
+ return []
530
+ targets = self._kit_conditions(result)
531
+ self.rows.append(ConditionReport(
532
+ cond.render(), "mapped",
533
+ target=", ".join(c.to_box_notation() for c in targets),
534
+ sentence=cond.sentence, token=cond.token))
535
+ return targets
536
+ if isinstance(cond, AceNeg):
537
+ self.rows.append(ConditionReport("-(…)", "mapped", target="~[…]"))
538
+ sub = self.map_box(cond.drs)
539
+ return [Neg(sub)] if sub is not None else []
540
+ if isinstance(cond, AceImpl):
541
+ self.rows.append(ConditionReport("=>(…)", "mapped",
542
+ target="[…] -> […]"))
543
+ antecedent = self.map_box(cond.antecedent)
544
+ consequent = self.map_box(cond.consequent)
545
+ if antecedent is None or consequent is None:
546
+ return []
547
+ return [Impl(antecedent, consequent)]
548
+ if isinstance(cond, AceOr):
549
+ self.rows.append(ConditionReport("v(…)", "mapped",
550
+ target="[…] ∨ […]"))
551
+ left = self.map_box(cond.left)
552
+ right = self.map_box(cond.right)
553
+ if left is None or right is None:
554
+ return []
555
+ return [Or(left, right)]
556
+ # Everything below is a named refusal at THIS route; sub-box
557
+ # conditions still get their own rows so the report stays complete.
558
+ self.complete = False
559
+ reason, milestone = _complex_refusal(cond)
560
+ self.rows.append(ConditionReport(
561
+ _shell_render(cond), "unsupported", reason=reason,
562
+ milestone=milestone))
563
+ for sub in _iter_sub_boxes(cond):
564
+ self.map_box(sub)
565
+ return []
566
+
567
+ def _kit_conditions(self, result) -> List[Condition]:
568
+ """One mapped atom shape -> its kit condition(s). The plural object
569
+ is the one shape that expands to TWO: ``Card`` plus the membership
570
+ duplex distributing the noun over the parts (the group itself stays
571
+ a term for the verb -- ACE reads the unmarked plural collectively)."""
572
+ from ..drt.nodes import Card, Part
573
+
574
+ kind = result[0]
575
+ if kind == "eq":
576
+ return [Eq(result[1], result[2])]
577
+ if kind == "pred":
578
+ return [Pred(result[1], result[2])]
579
+ if kind == "card":
580
+ return [Card(result[1], result[2], result[3])]
581
+ if kind == "part":
582
+ return [Part(result[1], result[2])]
583
+ if kind == "group_noun":
584
+ _, group, noun, op, count = result
585
+ member = self.renamer.fresh("x")
586
+ membership = Impl(DRS((member,), (Part(member, group),)),
587
+ DRS((), (Pred(noun, (member,)),)))
588
+ return [Card(group, op, count), membership]
589
+ raise AssertionError(f"unknown mapped shape {kind!r}")
590
+
591
+
592
+ def _complex_refusal(cond: AceCondition) -> Tuple[str, str]:
593
+ if isinstance(cond, AceModal):
594
+ return (f"a modal box ({cond.modality}): no modal conditions in a "
595
+ "classical DRS — ace_to_formula carries it", "ACE-3")
596
+ if isinstance(cond, AceQuestion):
597
+ return ("a question is not an assertion — ace_to_formula carries it",
598
+ "ACE-3")
599
+ if isinstance(cond, AceCommand):
600
+ return ("an imperative: no command semantics decided", "")
601
+ if isinstance(cond, AceNaf):
602
+ return ("negation as failure is not classical negation; the "
603
+ "non-monotonic route is undecided", "")
604
+ if isinstance(cond, AceCondList):
605
+ return ("the maximality reading of exactly/at most needs a counting "
606
+ "quantifier over a compound scope, which is a FORMULA-level "
607
+ "construct -- ace_to_formula carries it", "ACE-4")
608
+ return ("not in the measured corpus", "")
609
+
610
+
611
+ def _iter_sub_boxes(cond: AceCondition):
612
+ from .drs_reader import _sub_boxes
613
+ yield from _sub_boxes(cond)
614
+
615
+
616
+ def map_ace_drs(ace_drs: AceDrs) -> DrsMapping:
617
+ """APE's DRS → :class:`DrsMapping` (kit DRS iff complete, report always).
618
+
619
+ The returned kit DRS has passed :meth:`unicode_logic_kit.drt.nodes.DRS
620
+ .validate` — the renamer assigns globally unique referent names, which
621
+ is exactly the invariant the kit core demands.
622
+ """
623
+ mapper = _BoxMapper(ace_drs)
624
+ drs = mapper.map_box(ace_drs)
625
+ if drs is not None:
626
+ drs.validate()
627
+ return DrsMapping(drs=drs, rows=tuple(mapper.rows),
628
+ referents=dict(mapper.renamer.referents),
629
+ constants=dict(mapper.renamer.constants))
630
+
631
+
632
+ def ace_to_drs(text: str, *, ulex: Optional[str] = None, guess: bool = False,
633
+ timeout: float = 30.0) -> DRS:
634
+ """ACE text → kit DRS, via APE and :func:`map_ace_drs`.
635
+
636
+ The whole text becomes ONE box (APE merges sentences and resolves
637
+ cross-sentence anaphora before this module ever sees the DRS). Feed the
638
+ result to :func:`unicode_logic_kit.drt.export.drs_to_fol` for FOL, or use
639
+ :func:`unicode_logic_kit.ace.translate.ace_to_formula` directly when the
640
+ text may contain modality or questions.
641
+
642
+ Raises:
643
+ unicode_logic_kit.ace.runner.AceParseError: the text is not ACE.
644
+ unicode_logic_kit.ace.mapping.AceUnsupportedError: a condition is
645
+ outside the classical DRS core; ``rows`` names every condition
646
+ and the refusals' milestones.
647
+ unicode_logic_kit.ace.runner.ApeUnavailableError: no APE binary
648
+ reachable.
649
+ """
650
+ result = run_ape(text, ulex=ulex, guess=guess, timeout=timeout)
651
+ if not result.accepted:
652
+ first = result.error_messages[0]
653
+ raise AceParseError(
654
+ f"not ACE: {first.value!r}"
655
+ + (f" (repair hint: {first.repair!r})" if first.repair else ""),
656
+ result.error_messages)
657
+ mapping = map_ace_drs(parse_ape_drs(result.drs))
658
+ if not mapping.complete:
659
+ problems = "; ".join(
660
+ f"{r.condition}: {r.reason}"
661
+ + (f" [{r.milestone}]" if r.milestone else "")
662
+ for r in mapping.unsupported)
663
+ raise AceUnsupportedError(
664
+ f"the classical DRS core cannot carry this text — {problems}",
665
+ mapping.rows)
666
+ return mapping.drs