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,195 @@
1
+ """Accessibility-respecting pronoun resolution (unicode_logic_kit.drt.resolve).
2
+
3
+ This is explicitly NOT an NLP guesser: it has no gender/number agreement, no world
4
+ knowledge, and no salience model. What it DOES do — and does precisely — is apply the
5
+ classical Kamp/Reyle DISCOURSE STRUCTURE constraint on pronoun resolution: a pronoun can
6
+ only corefer with a referent that is ACCESSIBLE to it (see the accessibility relation in
7
+ :mod:`unicode_logic_kit.drt.nodes`'s module docstring), and among the accessible candidates,
8
+ the "closest" one is preferred — the classical RIGHT-TO-LEFT (most recently mentioned)
9
+ default reading.
10
+
11
+ Marking a pronoun
12
+ ------------------
13
+ The caller marks a referent as an unresolved pronoun with the reserved unary condition
14
+ ``Pred(PRONOUN, (ref,))`` (``PRONOUN = "PRONOUN"``), placed in the SAME box that declares
15
+ ``ref`` — exactly like any other condition, using the ordinary five-variant condition
16
+ vocabulary from :mod:`unicode_logic_kit.drt.nodes` (no sixth "Condition" subtype is added
17
+ for this — a reserved predicate name is enough and keeps the classical Kamp/Reyle
18
+ condition inventory exactly as-is). :func:`resolve_anaphora` finds every such marker
19
+ (or just the ones named in ``pronouns``, if given) and REPLACES it with
20
+ ``Eq(ref, antecedent)`` — ``ref`` stays a bound referent in its box (so the standard
21
+ translation in :mod:`unicode_logic_kit.drt.export` still existentially/universally quantifies
22
+ it, exactly as any other referent), and the added equality forces it to denote the same
23
+ individual as its antecedent once translated to FOL.
24
+
25
+ Recency ranking
26
+ ------------------
27
+ :func:`~unicode_logic_kit.drt.nodes.walk_boxes` gives each box its accessible referents as an
28
+ ORDERED sequence of "layers", outermost first, the box's own referents last. A candidate's
29
+ recency rank is ``(layer_index, position_within_that_layer)`` — read: prefer the INNERMOST
30
+ layer (the pronoun's own box beats an ancestor's, which beats a more distant ancestor's);
31
+ within one layer (one box's own referents tuple), prefer the LATER position (the rightmost,
32
+ i.e. most recently written, referent in that box's own list). This is a total order with no
33
+ ties possible (two distinct referents can never share both coordinates), so "most recent"
34
+ always has a single well-defined answer whenever there is at least one candidate — see
35
+ "Ambiguity policy" below for what "ambiguous" means instead. Note that within a single box,
36
+ EVERY other referent in that box counts as a same-layer candidate regardless of whether it
37
+ was written before or after the pronoun in that box's referent tuple — DRT's own theory
38
+ treats a box's referents as an unordered set, so intra-box "before/after" is not a
39
+ structural distinction; only the WRITTEN tuple position (used purely as a tie-break, not as
40
+ an accessibility criterion) makes recency well-defined at that granularity.
41
+
42
+ Ambiguity policy
43
+ -------------------
44
+ * Zero accessible candidates: always :class:`ValueError` (there is nothing to resolve to,
45
+ strict or not).
46
+ * Exactly one accessible candidate: resolved automatically, regardless of ``strict`` — this
47
+ is the unambiguous case, no heuristic guessing involved.
48
+ * Two or more accessible candidates: this module will not silently pick one FOR you (it is
49
+ not a guesser) — with ``strict=True`` (the default) it raises :class:`ValueError` listing
50
+ every candidate, most-recent first, so the caller can disambiguate explicitly. With
51
+ ``strict=False`` it accepts the recency heuristic above and records which referent it
52
+ picked (:attr:`Resolution.ambiguous` is ``True`` in this case, so the choice is always
53
+ visible in the returned report, never silently made).
54
+ """
55
+
56
+ from dataclasses import dataclass
57
+ from typing import Dict, Optional, Sequence, Tuple
58
+
59
+ from .nodes import DRS, Pred, Eq, Neg, Impl, Or, walk_boxes
60
+
61
+ __all__ = ["PRONOUN", "Resolution", "ResolutionReport", "resolve_anaphora"]
62
+
63
+ PRONOUN = "PRONOUN"
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class Resolution:
68
+ """The outcome of resolving one pronoun.
69
+
70
+ ``candidates`` lists every accessible referent that was considered, MOST RECENT
71
+ FIRST (so ``candidates[0] == antecedent`` always); ``ambiguous`` is True iff there
72
+ was more than one (i.e. ``antecedent`` was chosen by the recency heuristic rather
73
+ than being the sole option).
74
+ """
75
+
76
+ referent: str
77
+ antecedent: str
78
+ candidates: Tuple[str, ...]
79
+ ambiguous: bool
80
+
81
+ def to_dict(self) -> dict:
82
+ return {"referent": self.referent, "antecedent": self.antecedent,
83
+ "candidates": list(self.candidates), "ambiguous": self.ambiguous}
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class ResolutionReport:
88
+ """The result of :func:`resolve_anaphora`: the resolved DRS (every processed
89
+ ``PRONOUN`` marker replaced by an ``Eq``) plus one :class:`Resolution` per pronoun,
90
+ in the order they were resolved."""
91
+
92
+ drs: DRS
93
+ resolutions: Tuple[Resolution, ...]
94
+
95
+ def to_dict(self) -> dict:
96
+ return {"drs": self.drs.to_dict(),
97
+ "resolutions": [r.to_dict() for r in self.resolutions]}
98
+
99
+
100
+ def _recency_sorted_candidates(layers, pronoun: str):
101
+ """Every OTHER referent visible in ``layers``, most-recent-first (see the module
102
+ docstring's recency ranking)."""
103
+ ranked = []
104
+ for i, layer in enumerate(layers):
105
+ for j, name in enumerate(layer):
106
+ if name != pronoun:
107
+ ranked.append((i, j, name))
108
+ ranked.sort(key=lambda t: (t[0], t[1]), reverse=True)
109
+ return tuple(name for _, _, name in ranked)
110
+
111
+
112
+ def _replace_markers(box: DRS, replacements: Dict[str, Eq]) -> DRS:
113
+ """Rebuild ``box`` with every resolved ``PRONOUN`` marker condition replaced by its
114
+ ``Eq``, recursing into every nested sub-DRS (Neg/Impl/Or operands)."""
115
+ new_conditions = []
116
+ for cond in box.conditions:
117
+ if (isinstance(cond, Pred) and cond.name == PRONOUN
118
+ and cond.args[0] in replacements):
119
+ new_conditions.append(replacements[cond.args[0]])
120
+ elif isinstance(cond, Neg):
121
+ new_conditions.append(Neg(_replace_markers(cond.drs, replacements)))
122
+ elif isinstance(cond, Impl):
123
+ new_conditions.append(Impl(_replace_markers(cond.antecedent, replacements),
124
+ _replace_markers(cond.consequent, replacements)))
125
+ elif isinstance(cond, Or):
126
+ new_conditions.append(Or(_replace_markers(cond.left, replacements),
127
+ _replace_markers(cond.right, replacements)))
128
+ else:
129
+ new_conditions.append(cond)
130
+ return DRS(box.referents, tuple(new_conditions))
131
+
132
+
133
+ def resolve_anaphora(drs: DRS, pronouns: Optional[Sequence[str]] = None, *,
134
+ strict: bool = True) -> ResolutionReport:
135
+ """Resolve every ``PRONOUN``-marked referent in ``drs`` (see the module docstring).
136
+
137
+ ``drs.validate()`` is called first (a pronoun's antecedent search is only meaningful
138
+ over an already-accessibility-valid tree). ``pronouns``, if given, restricts
139
+ resolution to those referent names (each MUST carry a ``Pred(PRONOUN, (ref,))``
140
+ marker somewhere in ``drs``, else :class:`ValueError`); ``None`` (the default)
141
+ resolves every marker found, in the order :func:`~unicode_logic_kit.drt.nodes.walk_boxes`
142
+ discovers them (pre-order, deterministic).
143
+
144
+ Raises:
145
+ ValueError: a named pronoun has no marker; a pronoun has zero accessible
146
+ candidates; or (with ``strict=True``, the default) a pronoun has two or more
147
+ accessible candidates (message lists them, most recent first).
148
+ """
149
+ drs.validate()
150
+ boxes = list(walk_boxes(drs))
151
+
152
+ marker_boxes: Dict[str, tuple] = {}
153
+ for box, layers in boxes:
154
+ for cond in box.conditions:
155
+ if isinstance(cond, Pred) and cond.name == PRONOUN:
156
+ if len(cond.args) != 1:
157
+ raise ValueError(
158
+ f"resolve_anaphora: a {PRONOUN} marker must have exactly one "
159
+ f"argument, got {cond.args!r}")
160
+ ref = cond.args[0]
161
+ if ref in marker_boxes:
162
+ raise ValueError(
163
+ f"resolve_anaphora: referent {ref!r} has more than one "
164
+ f"{PRONOUN} marker")
165
+ marker_boxes[ref] = (box, layers)
166
+
167
+ if pronouns is None:
168
+ targets = list(marker_boxes)
169
+ else:
170
+ targets = list(pronouns)
171
+ for p in targets:
172
+ if p not in marker_boxes:
173
+ raise ValueError(
174
+ f"resolve_anaphora: no {PRONOUN} marker found for referent {p!r}")
175
+
176
+ resolutions = []
177
+ replacements: Dict[str, Eq] = {}
178
+ for p in targets:
179
+ box, layers = marker_boxes[p]
180
+ candidates = _recency_sorted_candidates(layers, p)
181
+ if not candidates:
182
+ raise ValueError(
183
+ f"resolve_anaphora: pronoun {p!r} has no accessible referent to bind to.")
184
+ ambiguous = len(candidates) > 1
185
+ if ambiguous and strict:
186
+ raise ValueError(
187
+ f"resolve_anaphora: pronoun {p!r} has {len(candidates)} accessible "
188
+ f"candidates {candidates} (most recent first) — ambiguous under "
189
+ f"strict=True. Pass strict=False to accept the most-recent heuristic, "
190
+ f"or disambiguate by hand.")
191
+ antecedent = candidates[0]
192
+ resolutions.append(Resolution(p, antecedent, candidates, ambiguous))
193
+ replacements[p] = Eq(p, antecedent)
194
+
195
+ return ResolutionReport(_replace_markers(drs, replacements), tuple(resolutions))
@@ -0,0 +1,175 @@
1
+ """FOL → DRS (``fol_to_drs``): the standard translation, run backwards.
2
+
3
+ :func:`unicode_logic_kit.drt.export.drs_to_fol` maps every DRS onto a
4
+ first-order formula of a very particular SHAPE — outer ∃-chains for boxes,
5
+ ``∀-chain(antecedent → ∃-chain consequent)`` for duplex conditions,
6
+ ``¬∃-chain`` for negations, counting quantifiers over ``Part_of`` for
7
+ ``Card``. This module recognizes exactly that image and rebuilds the box
8
+ structure; everything outside it is refused by name
9
+ (:class:`FolToDrsError`), never approximated:
10
+
11
+ - modal/temporal/second-order operators — no classical DRS holds them
12
+ (the ACE formula route exists precisely because of that);
13
+ - a bare universal without an implication body — no box exports to it;
14
+ - a counting quantifier whose matrix is not ``Part_of(v, g)`` — that is
15
+ the FORMULA-level counting reading (``∃=2 x (Dog(x) ∧ …)``), which is
16
+ strictly more expressive than a ``Card`` condition;
17
+ - function or number terms in argument positions, and free variables
18
+ (caught by ``DRS.validate`` — an undeclared referent is exactly what a
19
+ free variable becomes).
20
+
21
+ Two deliberate canonicalizations, both semantically invisible:
22
+ ``Part_of(a, b)`` atoms come back as the typed ``Part`` condition (the
23
+ two export identically), and a strict ``Card`` bound that was shifted on
24
+ export returns in shifted form (``Card(g, >, 2)`` exports as ``∃≥3`` and
25
+ reads back as ``Card(g, >=, 3)`` — the same claim over natural counts).
26
+ The inverse property the tests pin is therefore at the FORMULA level:
27
+ ``drs_to_fol(fol_to_drs(f)) == f`` node-identically for every ``f`` in
28
+ the export's image, and Z3-equivalent to the originating DRS.
29
+
30
+ The practical use is the ACE chain: ``formula_to_ace`` =
31
+ :func:`~unicode_logic_kit.ace.verbalize.drs_to_ace` ∘ ``fol_to_drs`` — "is
32
+ this formula expressible as ACE?" becomes two refusal-checked steps with
33
+ the round trip as the safety net.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from typing import List, Tuple
39
+
40
+ from ..fol.nodes import (
41
+ And, Atom, Constant, Count, Implies, Node, Not, Number, Or as FolOr,
42
+ Quantifier, Variable,
43
+ )
44
+ from .nodes import DRS, Card, Condition, Eq, Impl, Neg, Or, Part, Pred
45
+
46
+ __all__ = ["fol_to_drs", "FolToDrsError"]
47
+
48
+
49
+ class FolToDrsError(ValueError):
50
+ """The formula lies outside the image of the standard translation."""
51
+
52
+
53
+ _COUNT_TO_CARD = {"ge": ">=", "le": "<=", "eq": "="}
54
+
55
+
56
+ def fol_to_drs(formula: Node) -> DRS:
57
+ """Rebuild the DRS a formula is the standard translation of.
58
+
59
+ Refuses (:class:`FolToDrsError`) anything outside
60
+ :func:`~unicode_logic_kit.drt.export.drs_to_fol`'s image — see the
61
+ module docstring for the exact boundary and the two canonicalizations.
62
+ The result always passes ``DRS.validate``.
63
+ """
64
+ drs = _box(formula)
65
+ try:
66
+ drs.validate()
67
+ except ValueError as exc:
68
+ raise FolToDrsError(
69
+ f"fol_to_drs: the rebuilt DRS fails validation — usually a "
70
+ f"free variable in the input formula ({exc})") from exc
71
+ return drs
72
+
73
+
74
+ def _box(formula: Node) -> DRS:
75
+ refs: List[str] = []
76
+ while isinstance(formula, Quantifier) and formula.type == "∃":
77
+ refs.append(formula.variable.name)
78
+ formula = formula.formula
79
+ conditions = tuple(_condition(c) for c in _conjuncts(formula))
80
+ try:
81
+ return DRS(tuple(refs), conditions)
82
+ except ValueError as exc:
83
+ raise FolToDrsError(f"fol_to_drs: {exc}") from exc
84
+
85
+
86
+ def _conjuncts(formula: Node) -> List[Node]:
87
+ if isinstance(formula, And):
88
+ return _conjuncts(formula.left) + _conjuncts(formula.right)
89
+ return [formula]
90
+
91
+
92
+ def _condition(formula: Node) -> Condition:
93
+ if isinstance(formula, Atom):
94
+ return _atom_condition(formula)
95
+ if isinstance(formula, Count):
96
+ return _card_condition(formula)
97
+ if isinstance(formula, Not):
98
+ return Neg(_box(formula.formula))
99
+ if isinstance(formula, FolOr):
100
+ return Or(_box(formula.left), _box(formula.right))
101
+ if isinstance(formula, Quantifier) and formula.type == "∀":
102
+ return _duplex_condition(formula)
103
+ if isinstance(formula, Quantifier):
104
+ # A ∃ HERE (not in an outer chain) means the input associated its
105
+ # conjunction around it — ∃x P ∧ Q is not a box shape the export
106
+ # produces (it puts every ∃ in front of its whole box).
107
+ raise FolToDrsError(
108
+ "fol_to_drs: an existential inside a conjunct — the export "
109
+ "always lifts ∃ in front of its whole box, so this formula is "
110
+ "not in its image")
111
+ raise FolToDrsError(
112
+ f"fol_to_drs: {type(formula).__name__} has no classical DRS "
113
+ "condition — outside the standard translation's image")
114
+
115
+
116
+ def _atom_condition(atom: Atom) -> Condition:
117
+ args = tuple(_term_name(t) for t in atom.args)
118
+ try:
119
+ if atom.predicate == "=" and len(args) == 2:
120
+ return Eq(args[0], args[1])
121
+ if atom.predicate == "Part_of" and len(args) == 2:
122
+ # Canonicalization: the typed Part condition and a plain
123
+ # Part_of atom export identically; the typed form wins.
124
+ return Part(args[0], args[1])
125
+ return Pred(atom.predicate, args)
126
+ except ValueError as exc:
127
+ raise FolToDrsError(f"fol_to_drs: {exc}") from exc
128
+
129
+
130
+ def _card_condition(count: Count) -> Condition:
131
+ body = count.formula
132
+ if not (isinstance(body, Atom) and body.predicate == "Part_of"
133
+ and len(body.args) == 2
134
+ and isinstance(body.args[0], Variable)
135
+ and body.args[0].name == count.variable.name):
136
+ raise FolToDrsError(
137
+ "fol_to_drs: a counting quantifier whose matrix is not "
138
+ "Part_of(v, g) is the FORMULA-level counting reading — "
139
+ "strictly more expressive than a Card condition, no DRS image")
140
+ group = _term_name(body.args[1])
141
+ try:
142
+ return Card(group, _COUNT_TO_CARD[count.op], count.n.value)
143
+ except ValueError as exc:
144
+ raise FolToDrsError(f"fol_to_drs: {exc}") from exc
145
+
146
+
147
+ def _duplex_condition(formula: Node) -> Condition:
148
+ refs: List[str] = []
149
+ while isinstance(formula, Quantifier) and formula.type == "∀":
150
+ refs.append(formula.variable.name)
151
+ formula = formula.formula
152
+ if not isinstance(formula, Implies):
153
+ raise FolToDrsError(
154
+ f"fol_to_drs: a universal without an implication body (got "
155
+ f"{type(formula).__name__}) — no box exports to ∀ without →, "
156
+ "so this formula is not in the standard translation's image")
157
+ antecedent_conds = tuple(_condition(c) for c in _conjuncts(formula.left))
158
+ try:
159
+ antecedent = DRS(tuple(refs), antecedent_conds)
160
+ except ValueError as exc:
161
+ raise FolToDrsError(f"fol_to_drs: {exc}") from exc
162
+ return Impl(antecedent, _box(formula.right))
163
+
164
+
165
+ def _term_name(term: Node) -> str:
166
+ if isinstance(term, (Variable, Constant)):
167
+ return term.name
168
+ if isinstance(term, Number):
169
+ raise FolToDrsError(
170
+ "fol_to_drs: a number in argument position — DRS conditions "
171
+ "take referents and constants only (values travel as c_ "
172
+ "constants, e.g. c_30)")
173
+ raise FolToDrsError(
174
+ f"fol_to_drs: a {type(term).__name__} term in argument position — "
175
+ "DRS conditions take referents and constants only")
@@ -0,0 +1,106 @@
1
+ """Evaluation utilities for NL→FOL work.
2
+
3
+ - :func:`canonicalize` puts a formula into a normal form that quotients out
4
+ bound-variable renaming, commutativity/associativity of the commutative
5
+ connectives, operand duplication, and double negation — while staying
6
+ logically equivalent. :func:`exact_match` uses it for a fair "canonical exact
7
+ match" that is stricter than logical equivalence but more forgiving than raw
8
+ string/structural equality.
9
+ - :func:`validate` / :func:`is_wellformed` / :func:`validate_text` report the
10
+ common defects in a generated formula (free variables, inconsistent predicate
11
+ or function arity, leftover lambda nodes, unparseable text).
12
+ - :func:`match_predicates` / :func:`formulas_are_matched_identical` /
13
+ :func:`formulas_are_identical` provide a lexical, predicate-aligned string
14
+ match (Levenshtein-based predicate renaming) — complementary to the AST-level
15
+ :func:`exact_match`, which instead quotients out the structural rewrites.
16
+ - :func:`align_symbols` / :func:`aligned_exact_match` are the parsed-AST
17
+ counterpart of the lexical matcher: separate predicate/function/constant
18
+ namespaces, arity-aware, injective and capture-free — combined with
19
+ :func:`exact_match` they quotient out BOTH vocabulary and structural noise.
20
+ - :func:`explain_countermodel` renders any countermodel witness (Kripke,
21
+ Tarski structure, Z3 assignment, Verdict-layer dict) as a few short,
22
+ deterministic English sentences; :func:`explain_proof` is its validity-side
23
+ counterpart, rendering a recorded proof object (tableau, TSTP derivation,
24
+ Twee proof, Z3 unsat core, cvc5 Alethe proof) the same way.
25
+ - :func:`~unicode_logic_kit.eval.converses.validate_converses` /
26
+ :func:`~unicode_logic_kit.eval.converses.converse_axioms` let a caller declare
27
+ that two predicates name the same relation with their arguments permuted
28
+ (:data:`~unicode_logic_kit.eval.converses.ConverseDeclaration`), an opt-in
29
+ bridge consumed only by :func:`equivalent`'s solver level.
30
+ - :func:`batch_decide` runs the prover chain over many formulas with a
31
+ content-addressed cache, process parallelism, and JSONL results.
32
+ - :mod:`~unicode_logic_kit.eval.datasets` holds the NL→logic benchmark
33
+ adapters (FOLIO, MALLS, …) with a shared example shape and gold-formula
34
+ self-audit.
35
+ """
36
+
37
+ from .canonical import canonicalize, exact_match
38
+ from .validate import validate, is_wellformed, validate_text, ValidationReport
39
+ from .predicate_match import (
40
+ formulas_are_identical,
41
+ match_predicates,
42
+ formulas_are_matched_identical,
43
+ align_symbols,
44
+ aligned_exact_match,
45
+ )
46
+ from .equivalence import EquivalenceResult, equivalent
47
+ from .explain import explain_countermodel, explain_proof
48
+ from .metric_hf import compute_fol_metrics
49
+ from .converses import ConverseDeclaration, validate_converses, converse_axioms
50
+ from .theory_check import (
51
+ Definitions, CyclicDefinition, UnfoldDepthExceeded,
52
+ dependency_graph, find_cycles, unfold,
53
+ SatisfiabilityResult, check_satisfiable,
54
+ SubsumptionResult, check_subsumption,
55
+ TheoryReport, check_theory,
56
+ )
57
+ from .generality import (
58
+ MinimalModelResult, minimal_model_size,
59
+ GeneralityReport, generality_report,
60
+ StrictlyStrongerResult, strictly_stronger,
61
+ VacuousSpecialisationResult, is_vacuous_specialisation,
62
+ )
63
+ from .batch import batch_decide
64
+ from .chem_batch import check_definitions, ChemBatchResult
65
+ from .exercise_gen import (
66
+ ValidInvalidPair, generate_valid_invalid_pair,
67
+ EntailmentExercise, generate_entailment_with_proof,
68
+ ModelSizeExercise, generate_theory_with_model_size,
69
+ )
70
+ from . import datasets # NL→logic benchmark adapters (datasets.load_folio, …)
71
+
72
+ __all__ = [
73
+ "check_definitions", "ChemBatchResult",
74
+ "canonicalize", "exact_match",
75
+ "validate", "is_wellformed", "validate_text", "ValidationReport",
76
+ "formulas_are_identical", "match_predicates", "formulas_are_matched_identical",
77
+ "align_symbols", "aligned_exact_match",
78
+ "EquivalenceResult", "equivalent",
79
+ "explain_countermodel", "explain_proof",
80
+ "compute_fol_metrics",
81
+ # Declared converse/argument-permutation axioms — an opt-in equivalence
82
+ # bridge consumed only by `equivalent`'s solver level (see
83
+ # unicode_logic_kit.eval.converses).
84
+ "ConverseDeclaration", "validate_converses", "converse_axioms",
85
+ # Deductive checks over a set of predicate DEFINITIONS: cycles, dead
86
+ # (unsatisfiable) definitions, subclass ⊨ superclass — with a
87
+ # countermodel whenever the answer is "no", not just "not proved".
88
+ "Definitions", "CyclicDefinition", "UnfoldDepthExceeded",
89
+ "dependency_graph", "find_cycles", "unfold",
90
+ "SatisfiabilityResult", "check_satisfiable",
91
+ "SubsumptionResult", "check_subsumption",
92
+ "TheoryReport", "check_theory",
93
+ # Logical generality analysis — how easily is a definition satisfied,
94
+ # and does a "specialisation" actually specialise anything?
95
+ "MinimalModelResult", "minimal_model_size",
96
+ "GeneralityReport", "generality_report",
97
+ "StrictlyStrongerResult", "strictly_stronger",
98
+ "VacuousSpecialisationResult", "is_vacuous_specialisation",
99
+ "batch_decide",
100
+ # Constructively-generated, independently-checked practice exercises --
101
+ # no LLM anywhere (unicode_logic_kit.eval.exercise_gen).
102
+ "ValidInvalidPair", "generate_valid_invalid_pair",
103
+ "EntailmentExercise", "generate_entailment_with_proof",
104
+ "ModelSizeExercise", "generate_theory_with_model_size",
105
+ "datasets",
106
+ ]