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,452 @@
1
+ """ACE text → ONE kit formula — including what a classical DRS cannot hold.
2
+
3
+ The DRS route (:func:`unicode_logic_kit.ace.mapping.ace_to_drs`) stops honestly
4
+ at modality and questions: the kit's classical DRS core has no place for
5
+ them. THIS route translates APE's DRS straight to a kit FORMULA, where the
6
+ kit does have places — the modal family for ACE's four modal boxes, and an
7
+ open formula for a wh-question:
8
+
9
+ - ``must`` → ``□`` (:class:`~unicode_logic_kit.fol._modal_nodes.Box`) —
10
+ Attempto's reading: necessity;
11
+ - ``can`` → ``◇`` (:class:`~unicode_logic_kit.fol._modal_nodes.Diamond`) —
12
+ possibility;
13
+ - ``should`` → ``Ⓞ`` (:class:`~unicode_logic_kit.fol._modal_nodes.Obligatory`)
14
+ — recommendation, read deontically;
15
+ - ``may`` → ``Ⓟ`` (:class:`~unicode_logic_kit.fol._modal_nodes.Permitted`) —
16
+ admissibility, read deontically.
17
+
18
+ The first two are alethic, the second two deontic — that split is a
19
+ DOCUMENTED CHOICE, not something ACE fixes: Attempto glosses ``should``/
20
+ ``may`` as recommendation/admissibility, which is what the kit's deontic
21
+ operators mean, while ``must``/``can`` gloss as necessity/possibility. A
22
+ caller who wants "must" read as obligation can rewrite ``Box`` → ``Obligatory``
23
+ on the result; nothing here is lost, only labeled.
24
+
25
+ The translation is the standard Kamp/Reyle one — the same
26
+ :func:`unicode_logic_kit.drt.export.drs_to_fol` performs on the pure fragment,
27
+ extended pointwise at the box operators — and it REUSES the mapping module's
28
+ atomic-condition table and renamer, so both routes speak one vocabulary.
29
+ That overlap is pinned by a three-way differential in the tests: on every
30
+ corpus sentence the DRS route covers, this route's formula, the DRS route's
31
+ formula and Attempto's own TPTP are pairwise Z3-equivalent.
32
+
33
+ Questions come out as :class:`unicode_logic_kit.ace.AceFormula` with a ``kind``:
34
+
35
+ - a wh-question ("Who waits?") yields an OPEN formula — the queried referent
36
+ stays free, named in ``query_variables`` with its question word — so
37
+ answering is model finding (``api.countermodel`` on the negation, or
38
+ ``semantics``' model enumeration) rather than proof;
39
+ - a yes/no question ("Does John wait?") yields a closed formula and
40
+ ``kind="yesno_question"`` — answering is an entailment check against
41
+ whatever theory the caller holds. This is exactly the sentence the TPTP
42
+ route refuses (APE renders it as a ``conjecture``, which ``ace_to_fol``
43
+ must not flatten into an assertion); here the interrogative force survives
44
+ in ``kind`` instead of being dropped.
45
+ - a question box mixed with assertions in one text raises: the merged box
46
+ shares referents across both parts, and splitting them would either break
47
+ the binding or quantify the premises into the question — split the TEXT.
48
+
49
+ Commands (``command``) and negation as failure (``~``) still raise
50
+ :class:`~unicode_logic_kit.ace.mapping.AceUnsupportedError`: the kit has no
51
+ imperative semantics, and ``~`` is not classical negation — those verdicts
52
+ are unchanged from the DRS route, and unchanged on purpose.
53
+ """
54
+
55
+ from __future__ import annotations
56
+
57
+ from dataclasses import dataclass
58
+ from typing import List, Optional, Tuple
59
+
60
+ from ..drt.export import _CARD_TO_COUNT
61
+ from ..drt.nodes import is_referent
62
+ from ..fol.nodes import (
63
+ And, Atom, Box, Constant, Count, Diamond, Function, Implies, Node, Not,
64
+ Number, Obligatory, Or, Permitted, Quantifier, Variable,
65
+ )
66
+ from .drs_reader import (
67
+ AceAtom, AceCommand, AceCondition, AceCondList, AceDrs, AceExpr, AceImpl,
68
+ AceInt, AceModal, AceNaf, AceNamed, AceNeg, AceOr, AceQuestion, AceReal,
69
+ AceVar, parse_ape_drs,
70
+ )
71
+ from .mapping import (
72
+ AceUnsupportedError, ConditionReport, _ACE_OPS, _be_events_to_drop,
73
+ _map_atom, _Renamer, _shell_render, _Unsupported,
74
+ )
75
+ from .runner import AceParseError, run_ape
76
+
77
+ __all__ = ["ace_to_formula", "ace_drs_to_formula", "AceFormula"]
78
+
79
+ _MODAL_NODES = {"must": Box, "can": Diamond, "should": Obligatory,
80
+ "may": Permitted}
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class AceFormula:
85
+ """What an ACE text says, as one kit formula plus its illocution.
86
+
87
+ ``kind`` is ``"assertion"``, ``"wh_question"`` or ``"yesno_question"``.
88
+ For a wh-question the formula is OPEN: each ``query_variables`` entry is
89
+ ``(kit variable name, question word)`` — ``(("x1", "who"),)`` for "Who
90
+ waits?" — and those variables are deliberately unquantified.
91
+ ``modalities`` lists which of ACE's modal boxes occurred, in first-use
92
+ order, so a caller can tell a plain formula from one that needs a modal
93
+ backend without walking the tree."""
94
+
95
+ formula: Node
96
+ kind: str = "assertion"
97
+ query_variables: Tuple[Tuple[str, str], ...] = ()
98
+ modalities: Tuple[str, ...] = ()
99
+
100
+
101
+ class _Stop(Exception):
102
+ """Internal: abort translation at the first unsupported condition."""
103
+
104
+ def __init__(self, render: str, reason: str, milestone: str,
105
+ sentence=None, token=None):
106
+ super().__init__(reason)
107
+ self.row = ConditionReport(render, "unsupported", reason=reason,
108
+ milestone=milestone, sentence=sentence,
109
+ token=token)
110
+
111
+
112
+ class _Translator:
113
+ def __init__(self, root: AceDrs):
114
+ self.renamer = _Renamer(root)
115
+ self.dropped = _be_events_to_drop(root)
116
+ self.modalities: List[str] = []
117
+
118
+ # -- pieces ------------------------------------------------------------
119
+ def term(self, name: str) -> Node:
120
+ return Variable(name) if is_referent(name) else Constant(name)
121
+
122
+ def atom(self, cond: AceAtom) -> Node:
123
+ if cond.functor == "formula":
124
+ return self.arithmetic(cond)
125
+ mapped = _map_atom(cond, self.renamer, self.dropped)
126
+ if isinstance(mapped, _Unsupported):
127
+ raise _Stop(cond.render(), mapped.reason, mapped.milestone,
128
+ cond.sentence, cond.token)
129
+ kind = mapped[0]
130
+ if kind == "eq":
131
+ return Atom("=", (self.term(mapped[1]), self.term(mapped[2])))
132
+ if kind == "pred":
133
+ return Atom(mapped[1], tuple(self.term(a) for a in mapped[2]))
134
+ if kind == "card":
135
+ return self.card(mapped[1], mapped[2], mapped[3])
136
+ if kind == "part":
137
+ return Atom("Part_of", (self.term(mapped[1]),
138
+ self.term(mapped[2])))
139
+ # group_noun: |g| op n, every part a noun; the group referent stays
140
+ # a term for the verb (ACE's collective reading), mirroring the
141
+ # mapping route's Card + membership-duplex expansion.
142
+ _, group, noun, op, count = mapped
143
+ member = self.renamer.fresh("x")
144
+ membership = Quantifier(
145
+ "∀", Variable(member),
146
+ Implies(Atom("Part_of", (Variable(member), self.term(group))),
147
+ Atom(noun, (Variable(member),))))
148
+ return And(self.card(group, op, count), membership)
149
+
150
+ def card(self, group: str, op: str, n: int) -> Node:
151
+ """``|group| op n`` as the kit's counting quantifier over parts —
152
+ the same lowering drt.export gives the Card condition."""
153
+ count_op, shift = _CARD_TO_COUNT[op]
154
+ counter = self.renamer.fresh("p")
155
+ return Count(count_op, Number(n + shift), Variable(counter),
156
+ Atom("Part_of", (Variable(counter), self.term(group))))
157
+
158
+ def arithmetic(self, cond: AceAtom) -> Node:
159
+ """``formula(expr, op, expr)`` → a native kit comparison atom over
160
+ Function/Number terms — ``1 + 2 = 3.`` becomes exactly what the
161
+ kit's own parser produces for ``1 + 2 = 3``. The default FOL→z3
162
+ route reads ``+`` as an UNINTERPRETED symbol; the decision procedure
163
+ for this fragment is :func:`unicode_logic_kit.atp.z3_arith.is_valid_arith`
164
+ (measured: it proves ``1+2=3`` and refutes ``1+2=4``; the default
165
+ route proves neither)."""
166
+ if len(cond.args) != 3 or not isinstance(cond.args[1], str):
167
+ raise _Stop(cond.render(), "a formula condition outside the "
168
+ "measured shapes", "")
169
+ left, op, right = cond.args
170
+ if op not in ("=", "<", ">"):
171
+ raise _Stop(cond.render(),
172
+ f"comparison operator {op!r} is not in the measured "
173
+ "corpus", "")
174
+ return Atom(op, (self.arith_term(left, cond),
175
+ self.arith_term(right, cond)))
176
+
177
+ def arith_term(self, term, cond: AceAtom) -> Node:
178
+ if isinstance(term, AceInt):
179
+ return Number(term.value)
180
+ if isinstance(term, AceReal):
181
+ return Number(term.value)
182
+ if isinstance(term, AceExpr):
183
+ if term.op not in ("+", "-", "*", "/"):
184
+ raise _Stop(cond.render(),
185
+ f"arithmetic operator {term.op!r} is not in the "
186
+ "measured corpus", "")
187
+ return Function(term.op, (self.arith_term(term.left, cond),
188
+ self.arith_term(term.right, cond)))
189
+ if isinstance(term, AceVar):
190
+ return Variable(self.renamer.referent(term))
191
+ if isinstance(term, AceNamed):
192
+ return self.term(self.renamer.constant(term))
193
+ raise _Stop(cond.render(), "an arithmetic term outside the measured "
194
+ "shapes", "")
195
+
196
+ def conjoin(self, nodes: List[Node], box: AceDrs) -> Node:
197
+ if not nodes:
198
+ raise _Stop(box.render(), "an empty box (no conditions) is not "
199
+ "in the measured corpus", "")
200
+ result = nodes[0]
201
+ for node in nodes[1:]:
202
+ result = And(result, node)
203
+ return result
204
+
205
+ def exists(self, box: AceDrs, body: Node) -> Node:
206
+ for ref in reversed(self._box_refs(box)):
207
+ body = Quantifier("∃", Variable(ref), body)
208
+ return body
209
+
210
+ def _box_refs(self, box: AceDrs) -> List[str]:
211
+ return [self.renamer.referent(r) for r in box.referents
212
+ if r.name not in self.dropped]
213
+
214
+ # -- the standard translation, extended at the box operators -----------
215
+ def box(self, box: AceDrs) -> Node:
216
+ lists = [c for c in box.conditions if isinstance(c, AceCondList)]
217
+ if lists:
218
+ return self.box_with_lists(box, lists)
219
+ return self.exists(box, self.conjoin(
220
+ [self.condition(c) for c in box.conditions], box))
221
+
222
+ def box_with_lists(self, box: AceDrs, lists) -> Node:
223
+ """The counting reading of ``exactly``/``at most`` — APE's list
224
+ condition, translated as a counting quantifier over INDIVIDUALS.
225
+
226
+ "Exactly 2 dogs bark." arrives as ``[predicate(B,bark,A),
227
+ object(A,dog,...,exactly,2)]`` inside ``drs([A,B], [ [...] ])``: the
228
+ list marks that the cardinality is a MAXIMALITY claim over
229
+ everything satisfying the scope, not the size of one witness group.
230
+ First-order, that is ``∃=2 a (Dog(a) ∧ ∃b Bark(b, a))`` — count the
231
+ individuals, distribute the scope over them. This is a DOCUMENTED
232
+ SEMANTIC CHOICE: a collective reading of a maximality claim ("exactly
233
+ 2 men lift the table TOGETHER, and no other group does") is not
234
+ first-order expressible, and the corpus scopes (bark, wait) are
235
+ naturally distributive; the choice is stated here, in the guide and
236
+ in the CHANGELOG rather than buried.
237
+
238
+ Mechanics: the list's counted ``object`` names the count variable;
239
+ the list's other conditions form the matrix; enclosing referents
240
+ used ONLY inside the list (the scope's events) are ∃-bound INSIDE
241
+ the matrix. A referent shared between the list and the rest of the
242
+ box would make the rebinding change meaning — refused, unmeasured.
243
+ """
244
+ outside_vars: set = set()
245
+ for cond in box.conditions:
246
+ if not isinstance(cond, AceCondList):
247
+ outside_vars |= _vars_of(cond)
248
+ list_only: set = set()
249
+ pieces: List[Node] = []
250
+ for group in lists:
251
+ counted = [c for c in group.conditions
252
+ if isinstance(c, AceAtom) and c.functor == "object"
253
+ and len(c.args) == 6 and c.args[4] in _ACE_OPS
254
+ and not (c.args[4] == "eq" and c.args[5] == 1)]
255
+ if len(counted) != 1:
256
+ raise _Stop("[…]", f"{len(counted)} counted objects in one "
257
+ "list condition — the measured corpus has "
258
+ "exactly one", "")
259
+ obj = counted[0]
260
+ ref, noun, _cls, _unit, op, count = obj.args
261
+ if not isinstance(ref, AceVar) or ref.name in outside_vars:
262
+ raise _Stop(obj.render(),
263
+ "the counted referent is shared with conditions "
264
+ "outside the list — rebinding it would change "
265
+ "their meaning", "")
266
+ rest = [c for c in group.conditions if c is not obj]
267
+ inner_vars = set()
268
+ for c in rest:
269
+ inner_vars |= _vars_of(c)
270
+ inner_vars.discard(ref.name)
271
+ shared = inner_vars & outside_vars
272
+ if shared:
273
+ raise _Stop("[…]",
274
+ f"referents {sorted(shared)!r} are used inside "
275
+ "AND outside the list condition — not in the "
276
+ "measured corpus", "")
277
+ count_var = self.renamer.referent(ref)
278
+ matrix_parts = [Atom(_kit_pred(noun), (Variable(count_var),))]
279
+ matrix_parts += [self.condition(c) for c in rest]
280
+ matrix: Node = matrix_parts[0]
281
+ for part in matrix_parts[1:]:
282
+ matrix = And(matrix, part)
283
+ for inner in sorted(inner_vars):
284
+ declared = [r for r in box.referents if r.name == inner]
285
+ if declared:
286
+ matrix = Quantifier(
287
+ "∃", Variable(self.renamer.referent(declared[0])),
288
+ matrix)
289
+ count_op, shift = _CARD_TO_COUNT[_ACE_OPS[op]]
290
+ pieces.append(Count(count_op, Number(count + shift),
291
+ Variable(count_var), matrix))
292
+ list_only |= inner_vars | {ref.name}
293
+ other = [self.condition(c) for c in box.conditions
294
+ if not isinstance(c, AceCondList)]
295
+ body = pieces[0]
296
+ for piece in pieces[1:] + other:
297
+ body = And(body, piece)
298
+ remaining = AceDrs(
299
+ tuple(r for r in box.referents if r.name not in list_only),
300
+ ())
301
+ return self.exists(remaining, body)
302
+
303
+ def condition(self, cond: AceCondition) -> Node:
304
+ if isinstance(cond, AceAtom):
305
+ return self.atom(cond)
306
+ if isinstance(cond, AceNeg):
307
+ return Not(self.box(cond.drs))
308
+ if isinstance(cond, AceImpl):
309
+ # The duplex rule (donkey sentences): the antecedent's referents
310
+ # go universal over the whole conditional — same construction as
311
+ # drt.export.drs_to_fol.
312
+ result = Implies(
313
+ self.conjoin([self.condition(c)
314
+ for c in cond.antecedent.conditions],
315
+ cond.antecedent),
316
+ self.box(cond.consequent))
317
+ for ref in reversed(self._box_refs(cond.antecedent)):
318
+ result = Quantifier("∀", Variable(ref), result)
319
+ return result
320
+ if isinstance(cond, AceOr):
321
+ return Or(self.box(cond.left), self.box(cond.right))
322
+ if isinstance(cond, AceModal):
323
+ if cond.modality not in self.modalities:
324
+ self.modalities.append(cond.modality)
325
+ return _MODAL_NODES[cond.modality](self.box(cond.drs))
326
+ if isinstance(cond, AceQuestion):
327
+ raise _Stop("question(…)",
328
+ "a question box below the top level — the merged "
329
+ "text shares referents across assertion and "
330
+ "question; split the text", "")
331
+ if isinstance(cond, AceCommand):
332
+ raise _Stop("command(…)",
333
+ "an imperative: no command semantics decided", "")
334
+ if isinstance(cond, AceNaf):
335
+ raise _Stop("~(…)",
336
+ "negation as failure is not classical negation; "
337
+ "the non-monotonic route is undecided", "")
338
+ if isinstance(cond, AceCondList):
339
+ # Handled at the BOX level (see box()) because the counting
340
+ # reading rebinds enclosing referents; reaching it here means
341
+ # a list nested somewhere the corpus never produces one.
342
+ raise _Stop("[…]", "a list condition below a box's top level "
343
+ "is not in the measured corpus", "")
344
+ raise _Stop(_shell_render(cond), "not in the measured corpus", "")
345
+
346
+ # -- questions at the top ----------------------------------------------
347
+ def question(self, box: AceDrs) -> AceFormula:
348
+ query_vars: List[Tuple[str, str]] = []
349
+ plain: List[AceCondition] = []
350
+ for cond in box.conditions:
351
+ if isinstance(cond, AceAtom) and cond.functor == "query":
352
+ if not (len(cond.args) == 2 and isinstance(cond.args[0], AceVar)
353
+ and isinstance(cond.args[1], str)):
354
+ raise _Stop(cond.render(), "a query condition outside "
355
+ "the measured shapes", "")
356
+ query_vars.append((self.renamer.referent(cond.args[0]),
357
+ cond.args[1]))
358
+ else:
359
+ plain.append(cond)
360
+ body = self.conjoin([self.condition(c) for c in plain], box)
361
+ queried = {name for name, _ in query_vars}
362
+ for ref in reversed(self._box_refs(box)):
363
+ if ref not in queried: # the queried referents stay FREE
364
+ body = Quantifier("∃", Variable(ref), body)
365
+ if query_vars:
366
+ return AceFormula(body, "wh_question", tuple(query_vars),
367
+ tuple(self.modalities))
368
+ return AceFormula(body, "yesno_question", (),
369
+ tuple(self.modalities))
370
+
371
+
372
+ def _vars_of(cond) -> set:
373
+ """Every APE variable name occurring anywhere in a condition."""
374
+ names = set()
375
+ stack = [cond]
376
+ while stack:
377
+ current = stack.pop()
378
+ if isinstance(current, AceAtom):
379
+ stack.extend(current.args)
380
+ elif isinstance(current, AceVar):
381
+ names.add(current.name)
382
+ elif isinstance(current, AceCondList):
383
+ stack.extend(current.conditions)
384
+ elif isinstance(current, AceDrs):
385
+ stack.extend(current.conditions)
386
+ stack.extend(current.referents)
387
+ elif isinstance(current, (AceNeg, AceNaf, AceModal, AceQuestion,
388
+ AceCommand)):
389
+ stack.append(current.drs)
390
+ elif isinstance(current, AceImpl):
391
+ stack.extend((current.antecedent, current.consequent))
392
+ elif isinstance(current, AceOr):
393
+ stack.extend((current.left, current.right))
394
+ elif isinstance(current, AceExpr):
395
+ stack.extend((current.left, current.right))
396
+ return names
397
+
398
+
399
+ def _kit_pred(noun: str) -> str:
400
+ from .mapping import _kit_predicate
401
+ return _kit_predicate(noun)
402
+
403
+
404
+ def ace_drs_to_formula(ace_drs: AceDrs) -> AceFormula:
405
+ """APE's DRS → :class:`unicode_logic_kit.ace.AceFormula` — the pure function
406
+ behind :func:`ace_to_formula`, offline-testable on recorded DRS terms."""
407
+ translator = _Translator(ace_drs)
408
+ questions = [c for c in ace_drs.conditions if isinstance(c, AceQuestion)]
409
+ try:
410
+ if questions:
411
+ if len(questions) > 1 or len(ace_drs.conditions) > 1 \
412
+ or ace_drs.referents:
413
+ raise _Stop("question(…)",
414
+ "a question mixed with assertions (or a second "
415
+ "question) in one text — the merged box shares "
416
+ "referents across the parts; split the text", "")
417
+ return translator.question(questions[0].drs)
418
+ formula = translator.box(ace_drs)
419
+ except _Stop as stop:
420
+ raise AceUnsupportedError(
421
+ f"this route cannot carry the text — {stop.row.condition}: "
422
+ f"{stop.row.reason}"
423
+ + (f" [{stop.row.milestone}]" if stop.row.milestone else ""),
424
+ (stop.row,)) from None
425
+ return AceFormula(formula, "assertion", (), tuple(translator.modalities))
426
+
427
+
428
+ def ace_to_formula(text: str, *, ulex: Optional[str] = None,
429
+ guess: bool = False, timeout: float = 30.0) -> AceFormula:
430
+ """ACE text → one kit formula with modality, questions and all.
431
+
432
+ The union of what this and :func:`~unicode_logic_kit.ace.mapping.ace_to_drs`
433
+ accept is deliberate: everything the DRS route carries, plus modal boxes
434
+ and top-level questions. What neither carries — commands, negation as
435
+ failure, real cardinalities, arithmetic — raises
436
+ :class:`~unicode_logic_kit.ace.mapping.AceUnsupportedError` here too, with
437
+ the same reasons and milestones.
438
+
439
+ Raises:
440
+ unicode_logic_kit.ace.runner.AceParseError: the text is not ACE.
441
+ unicode_logic_kit.ace.mapping.AceUnsupportedError: see above.
442
+ unicode_logic_kit.ace.runner.ApeUnavailableError: no APE binary
443
+ reachable.
444
+ """
445
+ result = run_ape(text, ulex=ulex, guess=guess, timeout=timeout)
446
+ if not result.accepted:
447
+ first = result.error_messages[0]
448
+ raise AceParseError(
449
+ f"not ACE: {first.value!r}"
450
+ + (f" (repair hint: {first.repair!r})" if first.repair else ""),
451
+ result.error_messages)
452
+ return ace_drs_to_formula(parse_ape_drs(result.drs))