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.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Drive the Attempto Parsing Engine (APE) — ACE text in, kit formulas out.
|
|
2
|
+
|
|
3
|
+
`Attempto Controlled English <https://github.com/Attempto/APE>`_ (ACE) is a
|
|
4
|
+
controlled natural language with exactly one reading per sentence: ambiguity is
|
|
5
|
+
resolved by CONVENTION (documented interpretation rules), not by a guesser. Its
|
|
6
|
+
reference parser APE translates ACE into a Discourse Representation Structure
|
|
7
|
+
and, from there, into several formats including TPTP. This module runs APE as a
|
|
8
|
+
subprocess and routes its TPTP output through the kit's own reader
|
|
9
|
+
(:func:`unicode_logic_kit.fol.tptp_input.parse_tptp`).
|
|
10
|
+
|
|
11
|
+
APE is DRIVEN, not reimplemented, for the same reason the kit drives Isabelle,
|
|
12
|
+
E and HETS instead of cloning them: a partial re-implementation of APE's DCG
|
|
13
|
+
grammar, lexicon and anaphora resolution would not be ACE — it would be an
|
|
14
|
+
ACE-shaped language with undocumented differences, which is the worst possible
|
|
15
|
+
property for a tool whose entire point is that the interpretation rules are
|
|
16
|
+
fixed. APE is LGPL and stays an external binary; nothing of it is vendored.
|
|
17
|
+
|
|
18
|
+
Version pin
|
|
19
|
+
-----------
|
|
20
|
+
Everything measured in this module and recorded in the test fixtures comes from
|
|
21
|
+
APE at commit ``5f4d5354a45fb772763bf1a9543f508f15b28982`` (2024-04-21, the
|
|
22
|
+
Attempto/APE default branch), built with ``make install`` under SWI-Prolog
|
|
23
|
+
(8.4.2 and 9.x both work; the saved state is bound to the exact SWI-Prolog
|
|
24
|
+
that built it, which is why CI rebuilds APE instead of caching the binary —
|
|
25
|
+
the build is seconds, unlike E's minutes-long C build). Bump the pin and the
|
|
26
|
+
fixtures together or not at all; the fixture filename carries the commit.
|
|
27
|
+
|
|
28
|
+
What one APE call looks like
|
|
29
|
+
-----------------------------
|
|
30
|
+
``ape.exe -text "..." -cdrs -ctptp`` prints one XML document::
|
|
31
|
+
|
|
32
|
+
<apeResult>
|
|
33
|
+
<duration .../>
|
|
34
|
+
<drs>drs([A],[predicate(A,wait,named('John'))-1/2])</drs>
|
|
35
|
+
<tptp>fof(f1, axiom, (? [A] : (predicate1(A,wait,'John')))).</tptp>
|
|
36
|
+
<messages/>
|
|
37
|
+
</apeResult>
|
|
38
|
+
|
|
39
|
+
and the three outcomes this module distinguishes are all visible in it
|
|
40
|
+
(measured, not assumed — each of these is pinned by a recorded fixture):
|
|
41
|
+
|
|
42
|
+
- **accepted, TPTP available**: non-empty ``<tptp>``. Event predicates come
|
|
43
|
+
out arity-marked (``predicate1`` intransitive, ``predicate2`` transitive,
|
|
44
|
+
``predicate3`` ditransitive), simple singular nouns are prettified to unary
|
|
45
|
+
predicates (``man(A)``), proper names become quoted constants (``'John'``).
|
|
46
|
+
- **accepted, TPTP unavailable**: non-trivial ``<drs>`` but EMPTY ``<tptp>``.
|
|
47
|
+
In XML mode that refusal is SILENT — nothing on stderr (measured); only a
|
|
48
|
+
``-solo tptp`` call prints the reason, e.g. ``ERROR: DRS condition not
|
|
49
|
+
supported: must: tptp/4: must(drs(...))``, so this module makes one extra
|
|
50
|
+
~20 ms call on exactly this path to harvest it (never on the happy path).
|
|
51
|
+
This is Attempto's own translator declining, and
|
|
52
|
+
it is exactly the set of constructs the kit can do better with than plain
|
|
53
|
+
FOL: the four modal boxes (``must``/``can``/``should``/``may``), negation
|
|
54
|
+
as failure (``~``, "it is not provable that"), ``question(...)`` and
|
|
55
|
+
``command(...)`` boxes. Routing those into the kit's modal family is
|
|
56
|
+
milestone ACE-3; until then they surface as
|
|
57
|
+
:class:`AceTptpUnsupportedError` carrying the DRS text.
|
|
58
|
+
- **not ACE**: ``<messages>`` holds ``importance="error"`` entries with the
|
|
59
|
+
sentence number, the failing token, and often a repair suggestion; the DRS
|
|
60
|
+
is the trivial ``drs([],[])``. APE's exit code is 0 in EVERY one of these
|
|
61
|
+
cases, so exit codes decide nothing here — the XML does.
|
|
62
|
+
|
|
63
|
+
One caveat the numbers force on us: a non-trivial cardinality SURVIVES the
|
|
64
|
+
TPTP route but only reified — "At least 3 men wait." becomes
|
|
65
|
+
``? [A,B] : predicate1(A,wait,B) & object(B,man,countable,na,geq,3)``, a
|
|
66
|
+
single witness ``B`` plus an inert 6-ary annotation. As kit-level FOL that
|
|
67
|
+
does NOT mean "at least three": :func:`ace_coverage` flags such sentences
|
|
68
|
+
(``reified_cardinality=True``) rather than letting the under-translation
|
|
69
|
+
pass silently; the honest Count-based translation landed with ACE-4/5 on
|
|
70
|
+
the DRS and formula routes (:mod:`unicode_logic_kit.ace.mapping`,
|
|
71
|
+
:mod:`unicode_logic_kit.ace.translate`) — this route keeps Attempto's own
|
|
72
|
+
export verbatim, flag included.
|
|
73
|
+
|
|
74
|
+
Discovery
|
|
75
|
+
---------
|
|
76
|
+
Same contract as ``atp.eprover_backend``: ``$UFK_APE_CMD`` (prefix ``wsl:``
|
|
77
|
+
to force the WSL route, e.g. ``wsl:/home/me/APE/ape.exe``) → native
|
|
78
|
+
``ape.exe``/``ape`` on PATH → ``wsl.exe which ape.exe`` → finally
|
|
79
|
+
``$HOME/APE/ape.exe`` inside WSL, because ``git clone … ~/APE && make
|
|
80
|
+
install`` is the documented build and probing its default location makes a
|
|
81
|
+
fresh build work with zero configuration. Only the binary probes are cached;
|
|
82
|
+
the env override is read fresh on every call so a test can monkeypatch it.
|
|
83
|
+
|
|
84
|
+
The user lexicon is passed as TEXT (``-ulextext``), not as a file: a Windows
|
|
85
|
+
file path is meaningless inside WSL, and translating paths (``wslpath``) for
|
|
86
|
+
a lexicon that is typically a handful of generated lines buys nothing over
|
|
87
|
+
handing APE the lines themselves.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
from __future__ import annotations
|
|
91
|
+
|
|
92
|
+
import os
|
|
93
|
+
import re
|
|
94
|
+
import shutil
|
|
95
|
+
import subprocess
|
|
96
|
+
import xml.etree.ElementTree as _ET
|
|
97
|
+
from dataclasses import dataclass, field
|
|
98
|
+
from typing import List, Optional, Sequence, Tuple
|
|
99
|
+
|
|
100
|
+
from ..fol.nodes import Node
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"ape_available", "run_ape", "ace_to_fol", "ace_coverage",
|
|
104
|
+
"ApeResult", "ApeMessage", "CoverageRow",
|
|
105
|
+
"AceError", "ApeUnavailableError", "AceParseError",
|
|
106
|
+
"AceTptpUnsupportedError", "AceTptpUnreadError",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
#: The APE commit everything here was measured against (see module docstring).
|
|
110
|
+
APE_PINNED_COMMIT = "5f4d5354a45fb772763bf1a9543f508f15b28982"
|
|
111
|
+
|
|
112
|
+
#: The DRS of an empty (or entirely rejected) text — APE's "nothing".
|
|
113
|
+
_TRIVIAL_DRS = "drs([],[])"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AceError(Exception):
|
|
117
|
+
"""Base class for everything this module raises on purpose."""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ApeUnavailableError(AceError):
|
|
121
|
+
"""No APE binary reachable (env, PATH, WSL) — see the module docstring."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AceParseError(AceError):
|
|
125
|
+
"""The text is not ACE. ``messages`` carries APE's own diagnosis:
|
|
126
|
+
sentence number, failing token, and (for unknown words) a repair
|
|
127
|
+
suggestion — surfaced verbatim because APE's repair hints ("waitz" →
|
|
128
|
+
"wait") are the single most useful thing to show a caller."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, message: str, messages: Tuple["ApeMessage", ...]):
|
|
131
|
+
super().__init__(message)
|
|
132
|
+
self.messages = messages
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class AceTptpUnsupportedError(AceError):
|
|
136
|
+
"""The text IS ACE but Attempto's own TPTP translator cannot express it
|
|
137
|
+
(modal boxes, negation as failure, questions, commands — see the module
|
|
138
|
+
docstring). ``drs`` carries APE's DRS term so a caller — and, from
|
|
139
|
+
milestone ACE-3 on, the kit's own DRS reader — can still work with the
|
|
140
|
+
sentence; ``reason`` is APE's stderr line naming the unsupported
|
|
141
|
+
condition."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, message: str, drs: str, reason: str):
|
|
144
|
+
super().__init__(message)
|
|
145
|
+
self.drs = drs
|
|
146
|
+
self.reason = reason
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class AceTptpUnreadError(AceError):
|
|
150
|
+
"""APE DID produce TPTP, but the kit's TPTP reader does not accept it.
|
|
151
|
+
|
|
152
|
+
The measured case: "1 + 2 = 3." comes out as ``fof(f1, axiom, (1+2=3)).``
|
|
153
|
+
— infix arithmetic over plain integers, which is neither standard FOF
|
|
154
|
+
(that would be TFA's ``$sum``) nor inside the fragment
|
|
155
|
+
:func:`unicode_logic_kit.fol.tptp_input.parse_tptp` covers. Arithmetic
|
|
156
|
+
reaches the kit in milestone ACE-4 through the DRS (``formula``/``expr``
|
|
157
|
+
conditions → the z3_arith fragment), not by teaching the TPTP reader
|
|
158
|
+
Attempto's private dialect. ``tptp`` carries APE's raw output, ``cause``
|
|
159
|
+
the reader's error.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(self, message: str, tptp: str, cause: Exception):
|
|
163
|
+
super().__init__(message)
|
|
164
|
+
self.tptp = tptp
|
|
165
|
+
self.cause = cause
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass(frozen=True)
|
|
169
|
+
class ApeMessage:
|
|
170
|
+
"""One ``<message>`` element from APE's output.
|
|
171
|
+
|
|
172
|
+
``importance`` is ``"error"`` or ``"warning"``; ``type`` is APE's own
|
|
173
|
+
category (``"word"``, ``"sentence"``, ...). ``sentence``/``token`` are
|
|
174
|
+
``None`` when APE printed them empty (word-level messages carry no token
|
|
175
|
+
index). ``repair`` is APE's suggestion and may be empty.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
importance: str
|
|
179
|
+
type: str
|
|
180
|
+
sentence: Optional[int]
|
|
181
|
+
token: Optional[int]
|
|
182
|
+
value: str
|
|
183
|
+
repair: str
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def is_error(self) -> bool:
|
|
187
|
+
return self.importance == "error"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class ApeResult:
|
|
192
|
+
"""One APE run, undigested: the raw DRS term, the raw TPTP text, every
|
|
193
|
+
message, and stderr (where APE reports TPTP-translator gaps)."""
|
|
194
|
+
|
|
195
|
+
drs: str
|
|
196
|
+
tptp: str
|
|
197
|
+
messages: Tuple[ApeMessage, ...]
|
|
198
|
+
stderr: str
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def error_messages(self) -> Tuple[ApeMessage, ...]:
|
|
202
|
+
return tuple(m for m in self.messages if m.is_error)
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def accepted(self) -> bool:
|
|
206
|
+
"""True iff APE parsed the whole text as ACE (no error messages)."""
|
|
207
|
+
return not self.error_messages
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def tptp_supported(self) -> bool:
|
|
211
|
+
"""True iff Attempto's own TPTP translator produced output."""
|
|
212
|
+
return bool(self.tptp.strip())
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# (argv-prefix, use_wsl) — or None when nothing was found. Cached because
|
|
216
|
+
# discovery may spawn WSL probe processes; the env override is NOT cached.
|
|
217
|
+
_DISCOVERY_CACHE: dict = {}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _probe_wsl(argv: Sequence[str]) -> Optional[str]:
|
|
221
|
+
"""Run a short WSL probe; first stdout line or None. Never raises."""
|
|
222
|
+
try:
|
|
223
|
+
result = subprocess.run(list(argv), capture_output=True, text=True,
|
|
224
|
+
encoding="utf-8", errors="replace", timeout=15)
|
|
225
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
226
|
+
return None
|
|
227
|
+
line = (result.stdout or "").strip().splitlines()
|
|
228
|
+
return line[0] if result.returncode == 0 and line else None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _discover() -> Optional[Tuple[List[str], bool]]:
|
|
232
|
+
"""Resolve APE to ``(argv_prefix, use_wsl)``, or ``None``.
|
|
233
|
+
|
|
234
|
+
``argv_prefix`` is what to put before APE's own arguments — either
|
|
235
|
+
``[command]`` natively or ``["wsl.exe", "-e", command]``. ``-e`` matters:
|
|
236
|
+
without it wsl.exe hands the command line to the default LOGIN SHELL,
|
|
237
|
+
which re-parses quoting, and ACE text is full of apostrophes ("John's
|
|
238
|
+
dog"); ``-e`` executes the binary directly with argv passed verbatim.
|
|
239
|
+
"""
|
|
240
|
+
override = os.environ.get("UFK_APE_CMD")
|
|
241
|
+
if override:
|
|
242
|
+
if override.startswith("wsl:"):
|
|
243
|
+
return (["wsl.exe", "-e", override[4:]], True)
|
|
244
|
+
return ([override], False)
|
|
245
|
+
if "probe" in _DISCOVERY_CACHE:
|
|
246
|
+
return _DISCOVERY_CACHE["probe"]
|
|
247
|
+
found: Optional[Tuple[List[str], bool]] = None
|
|
248
|
+
for name in ("ape.exe", "ape"):
|
|
249
|
+
native = shutil.which(name)
|
|
250
|
+
if native:
|
|
251
|
+
found = ([native], False)
|
|
252
|
+
break
|
|
253
|
+
if found is None:
|
|
254
|
+
# The documented build location (`git clone … ~/APE && make install`
|
|
255
|
+
# leaves ape.exe in the checkout) — probed so a fresh build works
|
|
256
|
+
# with zero configuration. Natively first (a Linux/macOS home), then
|
|
257
|
+
# the same two probes inside WSL (a Windows host driving a WSL
|
|
258
|
+
# build; on a wsl.exe-less machine the probes fail fast to None).
|
|
259
|
+
home_build = os.path.join(os.path.expanduser("~"), "APE", "ape.exe")
|
|
260
|
+
if os.access(home_build, os.X_OK):
|
|
261
|
+
found = ([home_build], False)
|
|
262
|
+
if found is None:
|
|
263
|
+
path = _probe_wsl(["wsl.exe", "-e", "sh", "-c", "which ape.exe"])
|
|
264
|
+
if path is None:
|
|
265
|
+
path = _probe_wsl(["wsl.exe", "-e", "sh", "-c",
|
|
266
|
+
'test -x "$HOME/APE/ape.exe" && echo "$HOME/APE/ape.exe"'])
|
|
267
|
+
if path is not None:
|
|
268
|
+
found = (["wsl.exe", "-e", path], True)
|
|
269
|
+
_DISCOVERY_CACHE["probe"] = found
|
|
270
|
+
return found
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def ape_available() -> bool:
|
|
274
|
+
"""Pure discovery: is an APE binary reachable (env, PATH, or WSL)?"""
|
|
275
|
+
return _discover() is not None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _parse_ape_xml(stdout: str) -> Tuple[str, str, Tuple[ApeMessage, ...]]:
|
|
279
|
+
"""``<apeResult>`` XML → ``(drs, tptp, messages)``; raises on non-XML."""
|
|
280
|
+
try:
|
|
281
|
+
root = _ET.fromstring(stdout)
|
|
282
|
+
except _ET.ParseError as exc:
|
|
283
|
+
raise RuntimeError(
|
|
284
|
+
f"APE printed something that is not the expected <apeResult> "
|
|
285
|
+
f"XML — broken installation or changed CLI? First 200 chars: "
|
|
286
|
+
f"{stdout[:200]!r}") from exc
|
|
287
|
+
|
|
288
|
+
def _index(raw: Optional[str]) -> Optional[int]:
|
|
289
|
+
return int(raw) if raw and raw.isdigit() else None
|
|
290
|
+
|
|
291
|
+
messages = tuple(
|
|
292
|
+
ApeMessage(
|
|
293
|
+
importance=m.get("importance", ""),
|
|
294
|
+
type=m.get("type", ""),
|
|
295
|
+
sentence=_index(m.get("sentence")),
|
|
296
|
+
token=_index(m.get("token")),
|
|
297
|
+
value=m.get("value", ""),
|
|
298
|
+
repair=m.get("repair", ""),
|
|
299
|
+
)
|
|
300
|
+
for m in root.iter("message"))
|
|
301
|
+
drs = (root.findtext("drs") or "").strip()
|
|
302
|
+
tptp = (root.findtext("tptp") or "").strip()
|
|
303
|
+
return drs, tptp, messages
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def run_ape(text: str, *, ulex: Optional[str] = None, guess: bool = False,
|
|
307
|
+
timeout: float = 30.0) -> ApeResult:
|
|
308
|
+
"""Run APE on ``text``; the undigested :class:`ApeResult`.
|
|
309
|
+
|
|
310
|
+
``ulex`` is user-lexicon TEXT in APE's ``-ulextext`` format (one Prolog
|
|
311
|
+
clause per line, e.g. ``noun_sg(molecule, molecule, neutr).``) — see the
|
|
312
|
+
module docstring for why there is no file variant. ``guess=True`` turns
|
|
313
|
+
on APE's unknown-word guessing; it defaults to off because guessing
|
|
314
|
+
trades the "not ACE" verdict for a silently different lexicon, and this
|
|
315
|
+
kit's callers want the verdict.
|
|
316
|
+
|
|
317
|
+
Raises :class:`ApeUnavailableError` when discovery finds no binary, and
|
|
318
|
+
``subprocess.TimeoutExpired`` when APE (a ~20 ms tool, measured) blows a
|
|
319
|
+
``timeout`` that generous — at which point something is genuinely wrong
|
|
320
|
+
with the installation, not with the text.
|
|
321
|
+
"""
|
|
322
|
+
found = _discover()
|
|
323
|
+
if found is None:
|
|
324
|
+
raise ApeUnavailableError(
|
|
325
|
+
"ape: no binary found ($UFK_APE_CMD, PATH, WSL, ~/APE/ape.exe). "
|
|
326
|
+
"Build one: git clone https://github.com/Attempto/APE ~/APE && "
|
|
327
|
+
f"cd ~/APE && git checkout {APE_PINNED_COMMIT[:7]} && make install "
|
|
328
|
+
"(needs swi-prolog).")
|
|
329
|
+
prefix, _use_wsl = found
|
|
330
|
+
argv = [*prefix, "-text", text, "-cdrs", "-ctptp"]
|
|
331
|
+
if ulex is not None:
|
|
332
|
+
argv += ["-ulextext", ulex]
|
|
333
|
+
if guess:
|
|
334
|
+
argv += ["-guess"]
|
|
335
|
+
result = subprocess.run(argv, capture_output=True, text=True,
|
|
336
|
+
encoding="utf-8", errors="replace",
|
|
337
|
+
timeout=timeout)
|
|
338
|
+
drs, tptp, messages = _parse_ape_xml(result.stdout or "")
|
|
339
|
+
return ApeResult(drs=drs, tptp=tptp, messages=messages,
|
|
340
|
+
stderr=(result.stderr or "").strip())
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# APE at the pinned commit has one pretty-printer bug in its TPTP output:
|
|
344
|
+
# in a COLLECTIVE reading ("John and Mary lift a table.") the noun atom for
|
|
345
|
+
# the lifted object is printed juxtaposed — ``(table C)`` — instead of
|
|
346
|
+
# applied — ``table(C)`` — while every other sentence shape prints ``man(A)``
|
|
347
|
+
# correctly. The intent is unambiguous (there is no legal TPTP in which a
|
|
348
|
+
# lower-word is followed by a bare variable inside parentheses, so this
|
|
349
|
+
# pattern can ONLY match APE's malformation, never legal output), hence the
|
|
350
|
+
# repair below rather than a refusal. The raw, unrepaired text stays
|
|
351
|
+
# available on :class:`ApeResult`; :class:`CoverageRow` reports the repair.
|
|
352
|
+
_JUXTAPOSED_ATOM = re.compile(r"\(([a-z][a-zA-Z0-9_]*) ([A-Z][A-Za-z0-9]*)\)")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _repair_ape_tptp(tptp: str) -> Tuple[str, int]:
|
|
356
|
+
"""``(table C)`` → ``(table(C))``; returns (text, number of repairs)."""
|
|
357
|
+
return _JUXTAPOSED_ATOM.subn(r"(\1(\2))", tptp)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _formulas_from(result: ApeResult) -> Tuple[List[Node], bool]:
|
|
361
|
+
"""TPTP text of an accepted result → kit formulas, plus whether the
|
|
362
|
+
upstream-bug repair above had to fire.
|
|
363
|
+
|
|
364
|
+
Role guard: a yes/no question ("Does John wait?") survives APE's TPTP
|
|
365
|
+
route — as ``fof(f1, conjecture, …)``, a statement to PROVE (measured;
|
|
366
|
+
wh-questions do not survive at all, their ``query/2`` condition is
|
|
367
|
+
refused). :func:`ace_to_fol` returns assertions, and dropping the role
|
|
368
|
+
would silently turn the question into the claim that John waits — so a
|
|
369
|
+
non-axiom role raises instead, same category as the constructs Attempto
|
|
370
|
+
itself refuses."""
|
|
371
|
+
from ..fol.tptp_input import TptpParsingError, parse_tptp
|
|
372
|
+
|
|
373
|
+
tptp_text, n_repairs = _repair_ape_tptp(result.tptp)
|
|
374
|
+
try:
|
|
375
|
+
parsed = parse_tptp(tptp_text)
|
|
376
|
+
except TptpParsingError as exc:
|
|
377
|
+
raise AceTptpUnreadError(
|
|
378
|
+
f"APE produced TPTP the kit reader does not accept: {exc} — "
|
|
379
|
+
"raw output on .tptp (arithmetic reaches the kit via the DRS "
|
|
380
|
+
"in milestone ACE-4)",
|
|
381
|
+
tptp=result.tptp, cause=exc) from exc
|
|
382
|
+
non_axiom = [tf for tf in parsed if tf.role != "axiom"]
|
|
383
|
+
if non_axiom:
|
|
384
|
+
raise AceTptpUnsupportedError(
|
|
385
|
+
"the text contains a yes/no question: APE renders it as "
|
|
386
|
+
f"fof role {non_axiom[0].role!r} — a statement to PROVE, which "
|
|
387
|
+
"ace_to_fol must not flatten into an assertion (question "
|
|
388
|
+
"routing is milestone ACE-3); the DRS is on .drs",
|
|
389
|
+
drs=result.drs,
|
|
390
|
+
reason=f"non-axiom fof role {non_axiom[0].role!r} "
|
|
391
|
+
f"({len(non_axiom)} of {len(parsed)} clauses)")
|
|
392
|
+
return [tf.formula for tf in parsed], n_repairs > 0
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def ace_to_fol(text: str, *, ulex: Optional[str] = None,
|
|
396
|
+
guess: bool = False, timeout: float = 30.0) -> List[Node]:
|
|
397
|
+
"""ACE text → kit formulas, via APE's own TPTP output.
|
|
398
|
+
|
|
399
|
+
Returns one :class:`~unicode_logic_kit.fol.nodes.Node` per ``fof`` clause
|
|
400
|
+
APE emitted, in order. APE materializes cross-sentence anaphora into the
|
|
401
|
+
clauses themselves, so the formulas are self-contained; an empty text
|
|
402
|
+
yields ``[]``.
|
|
403
|
+
|
|
404
|
+
Symbol conventions are the TPTP reader's, documented there: predicates
|
|
405
|
+
upper-cased into the kit convention (``man`` → ``Man``, and events keep
|
|
406
|
+
APE's arity marker: ``Predicate1``/``Predicate2``/``Predicate3``),
|
|
407
|
+
quoted proper names read as constants.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
AceParseError: the text is not ACE (carries APE's messages).
|
|
411
|
+
AceTptpUnsupportedError: the text is ACE, but its DRS uses a
|
|
412
|
+
condition Attempto's own TPTP translator refuses — modality,
|
|
413
|
+
negation as failure, a question or a command (carries the DRS
|
|
414
|
+
and APE's stderr reason). These become kit-expressible in
|
|
415
|
+
milestone ACE-3, not silently mistranslated today.
|
|
416
|
+
ApeUnavailableError: no APE binary reachable.
|
|
417
|
+
"""
|
|
418
|
+
return _ace_to_fol_detailed(text, ulex=ulex, guess=guess,
|
|
419
|
+
timeout=timeout)[0]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _ace_to_fol_detailed(text: str, *, ulex: Optional[str], guess: bool,
|
|
423
|
+
timeout: float) -> Tuple[List[Node], bool]:
|
|
424
|
+
""":func:`ace_to_fol` plus the did-the-upstream-bug-repair-fire bit,
|
|
425
|
+
which :func:`ace_coverage` reports and the public signature drops."""
|
|
426
|
+
result = run_ape(text, ulex=ulex, guess=guess, timeout=timeout)
|
|
427
|
+
if not result.accepted:
|
|
428
|
+
first = result.error_messages[0]
|
|
429
|
+
where = (f"sentence {first.sentence}" if first.sentence is not None
|
|
430
|
+
else "input")
|
|
431
|
+
hint = f" (repair hint: {first.repair!r})" if first.repair else ""
|
|
432
|
+
raise AceParseError(
|
|
433
|
+
f"not ACE at {where}: {first.value!r}{hint} — "
|
|
434
|
+
f"{len(result.error_messages)} error message(s) in .messages",
|
|
435
|
+
result.error_messages)
|
|
436
|
+
if not result.tptp_supported:
|
|
437
|
+
if result.drs == _TRIVIAL_DRS:
|
|
438
|
+
return [], False # empty text: nothing asserted
|
|
439
|
+
reason = result.stderr or _tptp_refusal_reason(text, ulex=ulex,
|
|
440
|
+
guess=guess,
|
|
441
|
+
timeout=timeout)
|
|
442
|
+
raise AceTptpUnsupportedError(
|
|
443
|
+
"ACE accepted, but Attempto's TPTP translator does not cover "
|
|
444
|
+
f"this construct: {reason or 'reason not reported'} — the DRS "
|
|
445
|
+
"is on .drs (modality/naf/question/command routing is "
|
|
446
|
+
"milestone ACE-3)",
|
|
447
|
+
drs=result.drs, reason=reason)
|
|
448
|
+
return _formulas_from(result)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _tptp_refusal_reason(text: str, *, ulex: Optional[str], guess: bool,
|
|
452
|
+
timeout: float) -> str:
|
|
453
|
+
"""One ``-solo tptp`` call, stderr only — the refusal reason.
|
|
454
|
+
|
|
455
|
+
In XML mode APE's TPTP translator declines SILENTLY (empty ``<tptp>``,
|
|
456
|
+
empty stderr — measured); the diagnostic line naming the unsupported
|
|
457
|
+
condition (``must``, ``query/2``, ``~``, ``command``) is printed only in
|
|
458
|
+
solo mode. Runs exclusively on the already-failed path, so the happy
|
|
459
|
+
path stays a single process spawn. Never raises: worst case is an empty
|
|
460
|
+
reason on an exception that is being raised anyway.
|
|
461
|
+
"""
|
|
462
|
+
found = _discover()
|
|
463
|
+
if found is None:
|
|
464
|
+
return ""
|
|
465
|
+
prefix, _use_wsl = found
|
|
466
|
+
argv = [*prefix, "-text", text, "-solo", "tptp"]
|
|
467
|
+
if ulex is not None:
|
|
468
|
+
argv += ["-ulextext", ulex]
|
|
469
|
+
if guess:
|
|
470
|
+
argv += ["-guess"]
|
|
471
|
+
try:
|
|
472
|
+
result = subprocess.run(argv, capture_output=True, text=True,
|
|
473
|
+
encoding="utf-8", errors="replace",
|
|
474
|
+
timeout=timeout)
|
|
475
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
476
|
+
return ""
|
|
477
|
+
return (result.stderr or "").strip()
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@dataclass(frozen=True)
|
|
481
|
+
class CoverageRow:
|
|
482
|
+
"""One corpus sentence's fate on the APE → TPTP → kit route.
|
|
483
|
+
|
|
484
|
+
``status`` is one of ``"ok"`` (kit formulas produced),
|
|
485
|
+
``"tptp_unsupported"`` (ACE, but Attempto's TPTP translator declined —
|
|
486
|
+
detail carries the stderr reason), ``"tptp_unread"`` (ACE and TPTP
|
|
487
|
+
produced, but outside the kit reader's fragment — Attempto's infix
|
|
488
|
+
arithmetic, see :class:`AceTptpUnreadError`), ``"not_ace"`` (rejected —
|
|
489
|
+
detail carries APE's first error), or ``"infra"`` (APE itself failed).
|
|
490
|
+
``reified_cardinality`` is the honesty flag from the module docstring:
|
|
491
|
+
the TPTP contains a reified ``object(...)`` atom, so a plural/cardinality
|
|
492
|
+
was under-translated by Attempto's own export and the kit formula does
|
|
493
|
+
NOT carry its intended counting force (the DRS and formula routes do,
|
|
494
|
+
since ACE-4/5).
|
|
495
|
+
``tptp_repaired`` reports that the juxtaposed-atom repair (see
|
|
496
|
+
``_JUXTAPOSED_ATOM``) had to fire on APE's raw TPTP.
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
sentence: str
|
|
500
|
+
status: str
|
|
501
|
+
reified_cardinality: bool = False
|
|
502
|
+
tptp_repaired: bool = False
|
|
503
|
+
detail: str = ""
|
|
504
|
+
formulas: Tuple[Node, ...] = field(default=())
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def ace_coverage(sentences: Sequence[str], *, ulex: Optional[str] = None,
|
|
508
|
+
timeout: float = 30.0) -> List[CoverageRow]:
|
|
509
|
+
"""Classify each sentence's fate on the TPTP route — the ACE-1 report.
|
|
510
|
+
|
|
511
|
+
Mechanical, one APE call per sentence (~20 ms each, measured): no
|
|
512
|
+
judgment calls beyond the three-way outcome split plus the
|
|
513
|
+
``reified_cardinality`` flag, both defined at :class:`CoverageRow`.
|
|
514
|
+
"""
|
|
515
|
+
rows: List[CoverageRow] = []
|
|
516
|
+
for sentence in sentences:
|
|
517
|
+
try:
|
|
518
|
+
formulas, repaired = _ace_to_fol_detailed(
|
|
519
|
+
sentence, ulex=ulex, guess=False, timeout=timeout)
|
|
520
|
+
except AceParseError as exc:
|
|
521
|
+
first = exc.messages[0]
|
|
522
|
+
rows.append(CoverageRow(sentence, "not_ace",
|
|
523
|
+
detail=f"{first.value!r}"
|
|
524
|
+
+ (f" -> {first.repair!r}"
|
|
525
|
+
if first.repair else "")))
|
|
526
|
+
except AceTptpUnsupportedError as exc:
|
|
527
|
+
rows.append(CoverageRow(sentence, "tptp_unsupported",
|
|
528
|
+
detail=exc.reason))
|
|
529
|
+
except AceTptpUnreadError as exc:
|
|
530
|
+
rows.append(CoverageRow(sentence, "tptp_unread",
|
|
531
|
+
detail=str(exc.cause).splitlines()[0]))
|
|
532
|
+
except (RuntimeError, subprocess.TimeoutExpired) as exc:
|
|
533
|
+
rows.append(CoverageRow(sentence, "infra", detail=str(exc)))
|
|
534
|
+
else:
|
|
535
|
+
rows.append(CoverageRow(sentence, "ok",
|
|
536
|
+
reified_cardinality=_has_reified_object(formulas),
|
|
537
|
+
tptp_repaired=repaired,
|
|
538
|
+
formulas=tuple(formulas)))
|
|
539
|
+
return rows
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _has_reified_object(formulas: Sequence[Node]) -> bool:
|
|
543
|
+
"""True iff any formula contains a reified ``Object`` atom — Attempto's
|
|
544
|
+
TPTP export keeps ``object/6`` verbatim exactly when it could not
|
|
545
|
+
prettify a noun phrase into a unary predicate (any cardinality other
|
|
546
|
+
than a simple singular), so this atom's presence IS the
|
|
547
|
+
under-translation marker :class:`CoverageRow` documents."""
|
|
548
|
+
from ..fol.nodes import Atom
|
|
549
|
+
|
|
550
|
+
return any(isinstance(sub, Atom) and sub.predicate == "Object"
|
|
551
|
+
for formula in formulas for sub in formula.walk())
|