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,1111 @@
|
|
|
1
|
+
"""Shared TPTP ``fof`` problem generation for the external TPTP-speaking backends.
|
|
2
|
+
|
|
3
|
+
:mod:`atp.vampire_entailment`, :mod:`atp.eprover_backend` (E and
|
|
4
|
+
Zipperposition), and :mod:`atp.twee_entailment` each build the IDENTICAL TPTP
|
|
5
|
+
problem shape from ``(premises, conclusion)``: one ``fof(premise_<i>, axiom,
|
|
6
|
+
...).`` line per premise (1-based) plus one ``fof(goal, conjecture, ...).``
|
|
7
|
+
line — three copies of the same seven lines that had already started to
|
|
8
|
+
drift apart in their docstrings. :func:`generate_tptp_problem` is the single
|
|
9
|
+
place that shape is written; the three backends' own ``_generate_*_input``
|
|
10
|
+
functions now delegate here (same name, same signature, same output — no
|
|
11
|
+
behaviour change for their callers or their tests).
|
|
12
|
+
|
|
13
|
+
**ASCII/legality sanitisation (problem-level seam).** ``Node.to_tptp()``
|
|
14
|
+
renders a predicate/function/constant name close to verbatim — only a
|
|
15
|
+
Constant is transliterated to ASCII (:func:`constant_name_to_ascii`), and
|
|
16
|
+
only the first character of any of the three is folded for TPTP's
|
|
17
|
+
lowercase-initial rule (:func:`tptp_fold_first_letter`). Neither step fixes
|
|
18
|
+
a DIGIT-LEADING name (``2008SummerOlympics`` stays digit-leading, which
|
|
19
|
+
TPTP's ``lower_word: [a-z][A-Za-z0-9_]*`` grammar forbids), nor an ASCII
|
|
20
|
+
character that is no word character at all (``has-part``, ``a.b``,
|
|
21
|
+
``owl:Thing``, ``$f``: Vampire reads ``has-part(X)`` as a parse error and E
|
|
22
|
+
stops at the ``-``), nor a leading underscore, and neither
|
|
23
|
+
``Atom.to_tptp`` nor ``Function.to_tptp`` transliterates non-ASCII at all —
|
|
24
|
+
gaps the toolkit's identifier grammar could not reach before it was widened
|
|
25
|
+
to accept Unicode letters and digit-leading names, but can now. Fixing
|
|
26
|
+
either INSIDE a node's own ``to_tptp()`` would be wrong: each node would
|
|
27
|
+
rename independently, two distinct kit-level names could collide on their
|
|
28
|
+
fix with no whole-problem view to catch it, and the rewrite could never
|
|
29
|
+
reach a caller that needs to translate a prover's answer back. So the fix
|
|
30
|
+
lives here instead, exactly where the pre-existing case-fold collision
|
|
31
|
+
guard below already lives: :func:`_sanitize_for_tptp` walks every premise
|
|
32
|
+
and the conclusion TOGETHER, replaces only the names that are not already
|
|
33
|
+
TPTP-legal (checked BEFORE any fold — see :func:`_is_tptp_safe`) with an
|
|
34
|
+
ASCII, letter-initial, whole-problem-injective replacement of ``[A-Za-z0-9_]``
|
|
35
|
+
only (every other character becomes the code-point escape ``uXXXX`` the
|
|
36
|
+
transliteration already uses: ``has-part`` is ``hasu002dpart``, see
|
|
37
|
+
:func:`~unicode_logic_kit.atp.tptp_tff._tptp_word_base`), and returns
|
|
38
|
+
a :class:`TptpNameMap` recording exactly what was renamed so a caller can
|
|
39
|
+
translate prover output back (:func:`apply_reverse_tptp`). A name that was
|
|
40
|
+
already TPTP-legal is returned completely untouched — the very same
|
|
41
|
+
``Node`` object, not a copy — so ``Node.to_tptp()``'s existing output for
|
|
42
|
+
every formula this module was already handling correctly is byte-identical
|
|
43
|
+
to before this sanitisation step existed.
|
|
44
|
+
|
|
45
|
+
**Soundness guard.** :meth:`~unicode_logic_kit.fol.nodes.Node.to_tptp` folds a
|
|
46
|
+
predicate/function/constant name for TPTP's lowercase-initial identifier rule
|
|
47
|
+
by lower-casing only its FIRST character (see
|
|
48
|
+
:func:`unicode_logic_kit.fol._fol_nodes.tptp_fold_first_letter`) — the exact
|
|
49
|
+
mirror of ``tptp_input.py``'s ``_cap()``, which capitalises only the first
|
|
50
|
+
character of a parsed predicate name on import. That fold is not injective by
|
|
51
|
+
itself: ``Foo`` and ``foo`` (or two constants, or two functions) still both
|
|
52
|
+
fold to ``foo``. The outermost ``to_tptp()`` call checks the ONE formula it
|
|
53
|
+
renders (it runs the very same check, :func:`unicode_logic_kit.fol._tptp_symbols
|
|
54
|
+
.check_symbols`), but it has no way to know whether some OTHER formula elsewhere
|
|
55
|
+
in the same problem folds to the same identifier, so the whole-problem check has
|
|
56
|
+
to happen here, where every premise and the conclusion are in view together. Two distinct kit-level names colliding on export would otherwise be
|
|
57
|
+
silently merged into ONE TPTP symbol — e.g. a premise ``Foo(a)`` and its
|
|
58
|
+
negation ``¬FOO(a)`` would both render as ``foo(a)``, making the exported
|
|
59
|
+
axiom set ``{foo(a), ~foo(a)}`` — internally CONTRADICTORY, so an external
|
|
60
|
+
prover proves any conjecture from it via ex falso quodlibet, a false
|
|
61
|
+
"Theorem" verdict for a query the premises never actually entail. Rather than
|
|
62
|
+
risk that, :func:`generate_tptp_problem` refuses with ``NotImplementedError``
|
|
63
|
+
naming both colliding kit-level names, mirroring
|
|
64
|
+
:func:`unicode_logic_kit.atp.tptp_ncl.to_tptp_ncl`'s own (separately
|
|
65
|
+
implemented, since NXF's modal connectives are outside ``to_tptp``'s
|
|
66
|
+
classical FOL fragment) collision guard for the NXF export path. This check
|
|
67
|
+
runs AFTER the ASCII sanitisation step above, over the sanitised formulas —
|
|
68
|
+
:func:`_sanitize_for_tptp` already avoids colliding with itself (a shared
|
|
69
|
+
reservation set covers both already-legal and newly-synthesised names in
|
|
70
|
+
each namespace), so in practice this guard only ever fires for the same
|
|
71
|
+
kind of pre-existing, already-legal-name case-fold collision it always did
|
|
72
|
+
(``Foo``/``foo``); it is not weakened or bypassed by the sanitisation step.
|
|
73
|
+
|
|
74
|
+
This check compares predicate names with predicate names and function/constant
|
|
75
|
+
names with function/constant names; it does not look across the two. Only two
|
|
76
|
+
DISTINCT names within the SAME kind colliding is refused here. Equality and
|
|
77
|
+
disequality (``=``, ``≠``) map to a fixed TPTP token (``=``, ``!=``), never through
|
|
78
|
+
the first-letter fold, so they cannot fold together with a NAME; they are no
|
|
79
|
+
identifier to begin with, so :func:`_sanitize_for_tptp` never offers them to the "is
|
|
80
|
+
this already legal?" test. The arithmetic comparisons and functions are a different
|
|
81
|
+
matter, see the next section. Variables are checked per formula (``x`` and ``X`` are
|
|
82
|
+
one TPTP variable, and the refusal says so).
|
|
83
|
+
|
|
84
|
+
**A numeral is a constant, and ``+ - * / < > ≤ ≥`` are ordinary symbols.** The kit
|
|
85
|
+
reads a :class:`~unicode_logic_kit.fol.nodes.Number` on every route that was not asked
|
|
86
|
+
for arithmetic as a constant identified by its VALUE (``1``, ``1.0`` and ``01`` are
|
|
87
|
+
one constant) about which nothing else is known, the four operators as uninterpreted
|
|
88
|
+
function symbols and the four comparisons as uninterpreted predicates. TPTP says
|
|
89
|
+
otherwise for the text the single renderers write (``Number.to_tptp``: ``1`` is an
|
|
90
|
+
``$int``; ``$sum``, ``$less``, ... are arithmetic): measured on Vampire 5.0.1 and E
|
|
91
|
+
3.5.1, the text ``p(1)`` is a type error for a predicate over individuals, and
|
|
92
|
+
``1 != 2``, ``$less(1,2)`` and ``$sum(1,1) = 2`` are THEOREMS of the prover's
|
|
93
|
+
arithmetic that this reading does not have. So the problem writers
|
|
94
|
+
(:func:`generate_tptp_problem`, :func:`generate_tptp_problem_with_mapping`,
|
|
95
|
+
:func:`generate_tptp_problem_for_prover` and, for the typed route,
|
|
96
|
+
:func:`~unicode_logic_kit.atp.tptp_tff.generate_tff_problem`) write neither: a numeral
|
|
97
|
+
is written as the constant named by the numeral's value (``1`` and ``1.0`` are ``1``,
|
|
98
|
+
``2.5`` is ``2.5``; ``unicode_logic_kit.fol._numeral_symbols.numeral_name``), an
|
|
99
|
+
operator as an ordinary function or predicate of its own name, and the ordinary renamer
|
|
100
|
+
(:func:`_sanitize_for_tptp`) gives each of them a word of a prover's grammar (``1`` is
|
|
101
|
+
``n1``, ``+`` is ``u002b``, ``<`` is ``u003c``): collision-free against every other symbol
|
|
102
|
+
of the problem (one word per value, never the word of a user's symbol, a sort or a
|
|
103
|
+
predicate), recorded in the returned :class:`TptpNameMap` (``term`` and ``predicate`` for
|
|
104
|
+
the operators, ``term`` and ``numerals`` for the numerals), and read back through it
|
|
105
|
+
(:func:`apply_reverse_tptp` hands a proof's ``n1`` back as ``Number(1)`` and ``u002b`` as
|
|
106
|
+
``+``). A numeral is never a double-quoted distinct object either: TPTP makes those
|
|
107
|
+
pairwise distinct, and two numerals of different value may denote one element. A
|
|
108
|
+
``Constant`` (or ``SortedConstant`` or ``Function``) spelled like a numeral of the same
|
|
109
|
+
problem, ``Number(1)`` next to ``Constant('1')``, would be the same name and is refused by
|
|
110
|
+
name. The arithmetic reading is asked for by name and written elsewhere: the TFA writer
|
|
111
|
+
(:mod:`~unicode_logic_kit.atp._tff_problem`, the ``sort=`` option of the backends) keeps
|
|
112
|
+
``$sum``, ``$less`` and the number literals, typed ``$int`` or ``$real``.
|
|
113
|
+
|
|
114
|
+
**A predicate and a function/constant that fold to the SAME identifier.** An
|
|
115
|
+
earlier version of this module argued that a predicate ``Agent`` and a role
|
|
116
|
+
function ``agent`` (both ``agent``) are harmless because a TPTP reader
|
|
117
|
+
resolves a bare identifier by its syntactic position. That is false for the
|
|
118
|
+
provers this module feeds: Vampire 5.0.1 answers ``Non-boolean term
|
|
119
|
+
agent(X0) of sort $i is used in a formula context`` and E 3.5.1 stops with a
|
|
120
|
+
parse error on ``agent(agent(X))``, so the problem is rejected before any SZS
|
|
121
|
+
status exists (and "no answer" read as a verdict looks like "undecided"). A
|
|
122
|
+
reader MAY resolve by position — this kit's own reader
|
|
123
|
+
(:mod:`~unicode_logic_kit.fol.tptp_input`) does — but a prover is not obliged
|
|
124
|
+
to, so the problem text must not depend on it. The writer therefore renames
|
|
125
|
+
the TERM-side symbol, the function or constant, whenever its rendered name
|
|
126
|
+
equals a predicate's, whatever the arities (:func:`_separate_term_names`): the
|
|
127
|
+
predicate keeps its natural name, the term becomes ``<name>_term`` (a numeric
|
|
128
|
+
suffix is added if that is taken), and the pair is recorded in
|
|
129
|
+
:class:`TptpNameMap`, so :func:`apply_reverse_tptp` and
|
|
130
|
+
:meth:`TptpNameMap.reverse_rendered` restore the original. The rename is exact
|
|
131
|
+
— a symbol is only a name — and a problem without such a clash is rendered
|
|
132
|
+
byte-for-byte as before. The same-kind guard above has already run on the
|
|
133
|
+
ASCII-sanitised formulas by then, so a refusal for two LEGAL names of one kind
|
|
134
|
+
(``car``/``Car``) can never be dodged by this rename. The guard predicates a
|
|
135
|
+
many-sorted node lowers to (``S(x)`` for a sort ``S``) count as predicate
|
|
136
|
+
names for BOTH checks: the sort guard ``Foo`` of ``∀x:Foo P(x)`` in one premise
|
|
137
|
+
and a predicate ``foo`` of another are one TPTP word, which no walk of the
|
|
138
|
+
source trees would see, so the whole-problem check is fed the non-emptiness
|
|
139
|
+
axioms too (their atoms ARE the guard predicates, one per sort), exactly as the
|
|
140
|
+
single-formula guard sees them by recording what it renders. A
|
|
141
|
+
:class:`~unicode_logic_kit.fol.nodes.Measure` writes the function ``measure`` (it
|
|
142
|
+
is that symbol: ``to_z3`` declares ``measure/2`` for it), so it is renamed like
|
|
143
|
+
``Function('measure', ...)`` when a predicate is written ``measure`` too, and
|
|
144
|
+
:func:`apply_reverse_tptp` hands it back as that function.
|
|
145
|
+
|
|
146
|
+
Seven node classes write a predicate, function or constant name: ``Atom``,
|
|
147
|
+
``Function``, ``Constant`` and ``SortedConstant`` (the four the sanitiser walks),
|
|
148
|
+
``Measure`` (the function ``measure``), and ``SortedQuantifier`` / ``SortedCount``
|
|
149
|
+
(the guard predicate of their sort); ``Number`` writes a numeral and ``Variable``
|
|
150
|
+
a variable, and no other class writes a symbol. Every name writer is seen by the
|
|
151
|
+
same-kind check and by the cross-kind separation; ``tests/test_tptp_writer_names.py``
|
|
152
|
+
derives the list from the node classes themselves, so a class added later cannot
|
|
153
|
+
be missed.
|
|
154
|
+
|
|
155
|
+
**One predicate at two arities.** ``Zed(a)`` and ``Zed(a, b)`` are written as
|
|
156
|
+
``zed(a)`` and ``zed(a,b)``, which a prover reads as two symbols (``zed/1`` and
|
|
157
|
+
``zed/2``). So does the kit's z3 route across formulas (each formula is
|
|
158
|
+
translated in its own environment and z3 overloads a name by its arity), so the
|
|
159
|
+
two agree and the writer says nothing; the TF0 and TFA writers, which declare one
|
|
160
|
+
type per name, refuse it by name. (Inside ONE formula ``to_z3`` raises on such a
|
|
161
|
+
pair, where the writers write two symbols.)
|
|
162
|
+
|
|
163
|
+
**No conclusion.** ``conclusion=None`` writes no ``conjecture`` line, for a prover
|
|
164
|
+
that is asked whether the premises are satisfiable (Vampire: ``SZS status
|
|
165
|
+
Satisfiable`` or ``Unsatisfiable``). Every check, the cross-kind separation, the
|
|
166
|
+
non-emptiness axioms and the returned map work on the premises alone, in the
|
|
167
|
+
``fof``, TF0 and TFA writers alike.
|
|
168
|
+
|
|
169
|
+
That asymmetry — a name that is not TPTP-legal, or that clashes across kinds,
|
|
170
|
+
is renamed and recorded, while two legal names of one kind that fold together
|
|
171
|
+
are refused by name — is deliberate for this release. The same-kind refusal
|
|
172
|
+
predates the name map and is kept so that no existing caller silently receives
|
|
173
|
+
a renamed symbol; it is not a claim that the two cases differ in principle.
|
|
174
|
+
|
|
175
|
+
**A variable that has no TPTP spelling.** A variable is written as the upper-case
|
|
176
|
+
of its name, and a TPTP variable is an upper-case letter followed by letters, digits
|
|
177
|
+
and underscores, so ``ä`` (written ``Ä``), ``x-1`` and ``1x`` cannot be written as
|
|
178
|
+
they are, and every prover rejects the text. A variable is BOUND, so the writer
|
|
179
|
+
renames it without recording anything
|
|
180
|
+
(:func:`unicode_logic_kit.fol._tptp_symbols.legalise_variables`): per formula,
|
|
181
|
+
injectively (``ä`` and ``Ä`` stay two variables) and capture-free (the new name,
|
|
182
|
+
``x0``, ``x1``, ... minted through :mod:`unicode_logic_kit.fol._identifiers`, equals no
|
|
183
|
+
variable of the formula as written). A formula whose variables are all legal is
|
|
184
|
+
passed on as the very same object. The single ``Node.to_tptp`` has no map and
|
|
185
|
+
refuses such a variable by name.
|
|
186
|
+
|
|
187
|
+
**``$true`` and ``$false``.** TPTP defines these two propositions and this kit's
|
|
188
|
+
reader produces them as the nullary atoms ``Atom('$true')`` / ``Atom('$false')``, so
|
|
189
|
+
a problem read with :func:`~unicode_logic_kit.fol.tptp_input.parse_tptp` and written
|
|
190
|
+
back must keep them: they are written verbatim, never renamed, never declared (TF0,
|
|
191
|
+
TFA) and never counted by the collision checks, and ``to_z3`` reads them as true and
|
|
192
|
+
false so that z3 and the provers agree. Any other ``$``-word as a predicate,
|
|
193
|
+
function or constant name, and ``$true`` itself with arguments, is an ordinary user
|
|
194
|
+
name that is not a TPTP word, and the writers rewrite it like ``has-part``
|
|
195
|
+
(``$foo`` is ``u0024foo``).
|
|
196
|
+
|
|
197
|
+
**Many-sorted (MSFOL) soundness.** The kit has ONE universe; a sort ``S`` is the
|
|
198
|
+
extension of the unary predicate ``S`` (the sort and the predicate of that name
|
|
199
|
+
are one symbol) and is never empty; sorts may overlap; a sorted constant ``c:S``
|
|
200
|
+
is in ``S`` (and in every other sort it is written with); an unannotated constant,
|
|
201
|
+
an unsorted variable and the value of a function may be any element. A sorted
|
|
202
|
+
quantifier/constant/count lowers (via ``Node.to_tptp()``'s auto-reduction,
|
|
203
|
+
``fol.nodes.to_fol``) to a plain unary predicate guard, which by itself says
|
|
204
|
+
neither that the guarded sort is non-empty nor that a sorted constant is in its
|
|
205
|
+
sort. This ``fof`` builder is the route that asks that question for every sorted
|
|
206
|
+
problem: the backends' automatic mode (``tff=None``) tries the native typed route
|
|
207
|
+
(:func:`~unicode_logic_kit.atp.tptp_tff.generate_tff_problem`) first and falls back
|
|
208
|
+
to THIS writer when the typed text would ask another question, or the typed writer
|
|
209
|
+
does not cover a node of the problem that this one does (see that module's
|
|
210
|
+
docstring, and :func:`generate_tptp_problem_for_prover`), and a caller forcing
|
|
211
|
+
``tff=False``, or :mod:`atp.twee_entailment` (which has no typed route), reaches it
|
|
212
|
+
directly. So :func:`generate_tptp_problem_with_mapping` adds two kinds of background axiom
|
|
213
|
+
lines, both with the ``axiom`` role — an assumption the conjecture's refutation
|
|
214
|
+
search may use freely, exactly what an entailment's premise side means — never the
|
|
215
|
+
``conjecture`` role, and never folded into ``Node.to_tptp()`` itself, which stays
|
|
216
|
+
polarity-blind:
|
|
217
|
+
|
|
218
|
+
* one ``fof(nonempty_sort_<i>, axiom, ...).`` line per distinct sort name in
|
|
219
|
+
``premises``/``conclusion``
|
|
220
|
+
(``unicode_logic_kit.fol._msfl_nodes.nonempty_sort_axioms``). Each axiom is rendered
|
|
221
|
+
straight from ``Node.to_tptp()`` on the RAW kit-level sort name, bypassing
|
|
222
|
+
:func:`_sanitize_for_tptp`'s renaming map entirely: a sorted node's own lazy
|
|
223
|
+
``to_fol`` reduction elsewhere in the SAME problem is equally unsanitised (see
|
|
224
|
+
this module's own ASCII-legality section — sort names are a narrower, pre-existing
|
|
225
|
+
gap this fix does not touch), so keeping the axiom unsanitised too is what keeps
|
|
226
|
+
both talking about the identical predicate. A sort name that is not a TPTP word is
|
|
227
|
+
therefore refused by name (the guard's legality check sees the predicate it is
|
|
228
|
+
written as, and says it is the guard of the sort S), not rewritten: the TF0
|
|
229
|
+
writer, which keeps sorts in a namespace of their own, rewrites such a sort. The
|
|
230
|
+
guard names are RESERVED in the sanitiser (:func:`_sanitize_for_tptp`), so a
|
|
231
|
+
predicate that has to be rewritten never lands on one: a sort ``Hasu002dpart``
|
|
232
|
+
next to a predicate ``has-part`` (written ``hasu002dpart`` before the guard was
|
|
233
|
+
reserved, which merged the two into one symbol) now gets ``Hasu002dpart2`` for
|
|
234
|
+
the predicate.
|
|
235
|
+
* one ``fof(sort_member_<i>, axiom, S(c)).`` line per distinct sorted constant
|
|
236
|
+
``c:S`` (``unicode_logic_kit.fol._msfl_nodes.sort_membership_axioms``; a constant
|
|
237
|
+
written with two sorts has two lines; in the order the pairs first occur). Unlike
|
|
238
|
+
a sort name a CONSTANT is renamed on its way into the problem (``human:Human`` is
|
|
239
|
+
written ``human_term`` because the sort guard is the word ``human``, ``9lives`` is
|
|
240
|
+
``n9lives``, ``sókrates`` is ``su00f3krates``), so these atoms are built from the
|
|
241
|
+
formulas AS SANITISED AND SEPARATED, whose constants are the tokens the premises
|
|
242
|
+
use; an atom built from the raw names would say ``human(human)`` about another
|
|
243
|
+
symbol and silently do nothing. The sort is the raw sort name, as in the
|
|
244
|
+
non-emptiness lines, and every constant is one of the formulas' own, so the
|
|
245
|
+
returned name map covers what the lines write.
|
|
246
|
+
|
|
247
|
+
Both are empty for a problem without a sorted node (and the second for one without
|
|
248
|
+
a sorted constant), so the generated text is byte-identical to before for those.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
import re
|
|
252
|
+
from dataclasses import dataclass, field
|
|
253
|
+
from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Tuple, Union
|
|
254
|
+
|
|
255
|
+
from ..fol._fol_nodes import constant_name_to_ascii, tptp_fold_first_letter
|
|
256
|
+
from ..fol._msfl_nodes import nonempty_sort_axioms, sort_membership_axioms
|
|
257
|
+
from ..fol._numeral_symbols import numeral_value, numerals_as_constants
|
|
258
|
+
from ..fol._tptp_symbols import (
|
|
259
|
+
check_no_symbol_collisions, is_tptp_boolean_atom, legalise_variables,
|
|
260
|
+
)
|
|
261
|
+
from ..fol.nodes import (
|
|
262
|
+
Atom, Constant, Function, Measure, Node, Number, SortedConstant, Variable, free_variables,
|
|
263
|
+
)
|
|
264
|
+
from ._ascii_names import ascii_safe_base, reserve_rendered
|
|
265
|
+
from ._writer_support import (
|
|
266
|
+
check_against_generated, normalise_premise_names, tptp_name_token,
|
|
267
|
+
)
|
|
268
|
+
# generate_tff_problem lives in tptp_tff.py (the native TF0/typed-TPTP
|
|
269
|
+
# writer, a sibling module rather than an addition to this fof-only one —
|
|
270
|
+
# see that module's docstring); re-exported here purely so a caller already
|
|
271
|
+
# depending on "the shared TPTP problem generator module" for the fof route
|
|
272
|
+
# finds the typed sibling at the same place, per the natural pairing with
|
|
273
|
+
# generate_tptp_problem above.
|
|
274
|
+
from .tptp_tff import (
|
|
275
|
+
Tf0Refusal, generate_tff_problem, generate_tff_problem_with_mapping,
|
|
276
|
+
problem_needs_tff, _separated_term_token, _tptp_word_base,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
__all__ = ["generate_tptp_problem", "generate_tptp_problem_with_mapping",
|
|
280
|
+
"generate_tptp_problem_for_prover", "TptpProblem",
|
|
281
|
+
"TptpNameMap", "apply_reverse_tptp", "generate_tff_problem",
|
|
282
|
+
"generate_tff_problem_with_mapping"]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# ASCII/legality sanitisation — see the module docstring's second section.
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
# A raw kit-level name that is ALREADY safe to hand to Node.to_tptp(): pure
|
|
290
|
+
# ASCII, letter-initial (so the fold turns it into a legal lower_word no
|
|
291
|
+
# matter its case), and containing only the characters lower_word allows
|
|
292
|
+
# after that. Names the widened parser can now produce never contain
|
|
293
|
+
# anything outside this (unicode letters/digits/underscore/combining marks
|
|
294
|
+
# only), so this is the exact complement of "needs a replacement".
|
|
295
|
+
_TPTP_SAFE_RE = re.compile(r"[A-Za-z][A-Za-z0-9_]*")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _is_tptp_safe(name: str) -> bool:
|
|
299
|
+
return bool(name) and name.isascii() and bool(_TPTP_SAFE_RE.fullmatch(name))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@dataclass
|
|
303
|
+
class _Renamer:
|
|
304
|
+
"""One symbol namespace's original->safe-token map, whole-problem-shared.
|
|
305
|
+
|
|
306
|
+
Two passes, run over the WHOLE problem before any text is rendered:
|
|
307
|
+
|
|
308
|
+
1. :meth:`collect` — called once per occurrence of every name in this
|
|
309
|
+
namespace, in problem order. An already-legal name is reserved
|
|
310
|
+
immediately and unconditionally (R1: it is never touched, so its
|
|
311
|
+
reservation cannot depend on what else is in the problem); anything
|
|
312
|
+
else is queued, deduplicated by first occurrence.
|
|
313
|
+
2. :meth:`finalize` — synthesises a token for every queued name, each
|
|
314
|
+
de-collided (:func:`reserve_rendered`) against ``used`` as it now
|
|
315
|
+
stands: every already-legal name in the WHOLE problem, not just the
|
|
316
|
+
ones that happened to appear earlier in iteration order.
|
|
317
|
+
|
|
318
|
+
Doing this in one combined pass (synthesise-as-you-go) would make
|
|
319
|
+
collision-avoidance depend on argument order: a synthesised name could
|
|
320
|
+
legitimately claim a token that a DIFFERENT, already-legal name
|
|
321
|
+
appearing LATER in the same problem also owns — since R1 forbids moving
|
|
322
|
+
the legal name off of it, that is a genuine, unavoidable ambiguity, but
|
|
323
|
+
one this two-pass split avoids ever manufacturing purely from processing
|
|
324
|
+
order (R2: "two different names never collide" holds regardless of
|
|
325
|
+
where in the problem each one appears). :meth:`get` is only valid after
|
|
326
|
+
:meth:`finalize` — every name this namespace will ever be asked about
|
|
327
|
+
must have gone through :meth:`collect` first.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
prefix: str
|
|
331
|
+
render: Callable[[str], str]
|
|
332
|
+
case_fix: Callable[[str], str]
|
|
333
|
+
mapping: Dict[str, str] = field(default_factory=dict)
|
|
334
|
+
used: set = field(default_factory=set)
|
|
335
|
+
_pending: List[str] = field(default_factory=list)
|
|
336
|
+
|
|
337
|
+
def collect(self, name: str) -> None:
|
|
338
|
+
if name in self.mapping or name in self._pending:
|
|
339
|
+
return
|
|
340
|
+
if _is_tptp_safe(name):
|
|
341
|
+
self.used.add(self.render(name))
|
|
342
|
+
self.mapping[name] = name
|
|
343
|
+
else:
|
|
344
|
+
self._pending.append(name)
|
|
345
|
+
|
|
346
|
+
def finalize(self) -> None:
|
|
347
|
+
for name in self._pending:
|
|
348
|
+
base = self.case_fix(_tptp_word_base(name, self.prefix))
|
|
349
|
+
token = reserve_rendered(base, self.used, self.render)
|
|
350
|
+
self.mapping[name] = token
|
|
351
|
+
self._pending = []
|
|
352
|
+
|
|
353
|
+
def get(self, name: str) -> str:
|
|
354
|
+
return self.mapping[name]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _predicate_base_case(base: str) -> str:
|
|
358
|
+
"""Force uppercase-initial — the kit's own PREDICATE convention.
|
|
359
|
+
|
|
360
|
+
Necessary for round-tripping, not merely stylistic: TPTP's own fold
|
|
361
|
+
always lower-cases whatever we export, and ``tptp_input.py``'s ``_cap()``
|
|
362
|
+
always UPPER-cases the first letter of whatever text a prover echoes
|
|
363
|
+
back — regardless of what we originally exported. A synthesised
|
|
364
|
+
predicate token therefore has to already BE upper-case-initial, or the
|
|
365
|
+
reverse mapping (keyed by the token we chose) would never match what
|
|
366
|
+
comes back from ``_cap()``. Function/constant names need no such fix:
|
|
367
|
+
neither export nor import case-folds them at all (see
|
|
368
|
+
:func:`_term_base_case`), so any ASCII letter-initial form round-trips
|
|
369
|
+
verbatim.
|
|
370
|
+
"""
|
|
371
|
+
return base[0].upper() + base[1:] if base else base
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _term_base_case(base: str) -> str:
|
|
375
|
+
"""Force lowercase-initial — the kit's own NAME (function/constant)
|
|
376
|
+
convention, and, since neither export nor import case-folds a
|
|
377
|
+
function/constant name at all, the form that makes ``render(candidate)
|
|
378
|
+
== candidate`` (no fold vs. no-fold asymmetry to reverse)."""
|
|
379
|
+
return base[0].lower() + base[1:] if base else base
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass
|
|
383
|
+
class TptpNameMap:
|
|
384
|
+
"""The renamings :func:`_sanitize_for_tptp` chose for one problem.
|
|
385
|
+
|
|
386
|
+
``predicate`` and ``term`` are original-kit-name -> raw-token dicts (the
|
|
387
|
+
exact string substituted into the sanitised AST, BEFORE ``Node.to_tptp``'s
|
|
388
|
+
own fold) for the predicate namespace and the shared function/constant
|
|
389
|
+
namespace respectively — mirroring the two-namespace split
|
|
390
|
+
:func:`_check_no_symbol_collisions` already uses. An original name that
|
|
391
|
+
was already TPTP-legal maps to itself (see :class:`_Renamer`), so
|
|
392
|
+
:meth:`reverse` inverts cleanly even for untouched names.
|
|
393
|
+
|
|
394
|
+
A function/constant whose rendered name equals a predicate's (the class
|
|
395
|
+
``Agent`` and the role function ``agent``) is recorded here too, under
|
|
396
|
+
``term``, mapped to its ``<name>_term`` replacement
|
|
397
|
+
(:func:`_separate_term_names`); no token of ``term`` renders like any token
|
|
398
|
+
of ``predicate``, so :meth:`reverse_rendered` never has to choose between
|
|
399
|
+
the two kinds for one piece of prover text.
|
|
400
|
+
|
|
401
|
+
A writer also records what it called its lines, which a symbol rename does not
|
|
402
|
+
cover: ``premises`` holds the premise names in order (the names the caller gave with
|
|
403
|
+
``premise_names=``, or ``premise_1``, ``premise_2``, ... by default; empty for a map
|
|
404
|
+
that no writer made) and ``background`` the axiom lines the writer added on its own
|
|
405
|
+
as ``(name, meaning)`` pairs (the non-emptiness line of a sort and the membership
|
|
406
|
+
line of a sorted constant). A reader of a prover's proof goes through them
|
|
407
|
+
(:func:`~unicode_logic_kit.atp.tstp.relevant_premises_from_tstp`). They are not renames,
|
|
408
|
+
so they take no part in ``==``: two maps are equal when they rename alike.
|
|
409
|
+
|
|
410
|
+
A numeral is written as a constant (see the module docstring), so it is a ``term`` entry:
|
|
411
|
+
its key is the name :func:`~unicode_logic_kit.fol._numeral_symbols.numeral_name` gives its
|
|
412
|
+
value (``'1'``, ``'2.5'``) and its value the word it was written as. ``numerals`` holds
|
|
413
|
+
those keys, which is what tells :func:`apply_reverse_tptp` that the word stands for a
|
|
414
|
+
:class:`~unicode_logic_kit.fol.nodes.Number` and not for a constant spelled alike (a problem
|
|
415
|
+
never has both: that is refused). The arithmetic operators are ``term`` entries (``'+'``)
|
|
416
|
+
and ``predicate`` entries (``'<'``) like any other name when the writer wrote them as
|
|
417
|
+
ordinary symbols.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
predicate: Dict[str, str] = field(default_factory=dict)
|
|
421
|
+
term: Dict[str, str] = field(default_factory=dict)
|
|
422
|
+
premises: Tuple[str, ...] = field(default=(), compare=False)
|
|
423
|
+
background: Tuple[Tuple[str, str], ...] = field(default=(), compare=False)
|
|
424
|
+
numerals: FrozenSet[str] = field(default=frozenset())
|
|
425
|
+
|
|
426
|
+
def reverse_numerals(self) -> Dict[str, Union[int, float]]:
|
|
427
|
+
"""Return ``{word: value}`` for every numeral the writer wrote: the word a prover's
|
|
428
|
+
text has (the token the writer chose for it) and the value of the
|
|
429
|
+
:class:`~unicode_logic_kit.fol.nodes.Number` it stands for."""
|
|
430
|
+
return {token: numeral_value(name) for name, token in self.term.items()
|
|
431
|
+
if name in self.numerals}
|
|
432
|
+
|
|
433
|
+
def reverse(self) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
434
|
+
"""Return ``(predicate_reverse, term_reverse)``: token -> original.
|
|
435
|
+
|
|
436
|
+
The token used as the reverse-dict KEY is exactly what a prover's
|
|
437
|
+
own output re-parsed via :func:`~unicode_logic_kit.fol.tptp_input
|
|
438
|
+
.parse_tptp_formula` produces for that symbol — see
|
|
439
|
+
:func:`_predicate_base_case`/:func:`_term_base_case`'s docstrings for
|
|
440
|
+
why that already equals the raw token stored in ``predicate``/
|
|
441
|
+
``term`` (no extra fold/cap step needed here). Use this for the
|
|
442
|
+
STRUCTURED Rückweg (:func:`apply_reverse_tptp`, and anything that
|
|
443
|
+
goes through it such as :func:`~unicode_logic_kit.atp.tstp
|
|
444
|
+
.reverse_map_derivation`) — never for raw, un-parsed prover text; see
|
|
445
|
+
:meth:`reverse_rendered` for that.
|
|
446
|
+
"""
|
|
447
|
+
return ({v: k for k, v in self.predicate.items()},
|
|
448
|
+
{v: k for k, v in self.term.items()})
|
|
449
|
+
|
|
450
|
+
def reverse_rendered(self) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
451
|
+
"""Return ``(predicate_reverse, term_reverse)`` keyed by the
|
|
452
|
+
RENDERED token — exactly the text ``Node.to_tptp()`` actually wrote
|
|
453
|
+
into the generated problem, and therefore exactly what a prover
|
|
454
|
+
echoes back UNPARSED (raw stdout, an SZS detail string, and similar
|
|
455
|
+
free text — R3's "Erklärungstexte").
|
|
456
|
+
|
|
457
|
+
:meth:`reverse` is keyed by the raw, pre-fold token instead, which is
|
|
458
|
+
the right key for the STRUCTURED Rückweg (:func:`apply_reverse_tptp`
|
|
459
|
+
re-parses a prover's TSTP text via
|
|
460
|
+
:func:`~unicode_logic_kit.fol.tptp_input.parse_tptp_formula` first,
|
|
461
|
+
which re-applies the kit's uppercase-initial predicate convention on
|
|
462
|
+
import — see :func:`_predicate_base_case` — undoing the export-time
|
|
463
|
+
fold before this mapping is ever consulted) but the WRONG key for
|
|
464
|
+
free text that was never re-parsed: a synthesised or already-legal
|
|
465
|
+
predicate token such as ``Human`` renders as ``human`` (only the
|
|
466
|
+
first character is folded, :func:`tptp_fold_first_letter`), so raw
|
|
467
|
+
prover stdout contains ``human``, not ``Human`` — a reverse dict
|
|
468
|
+
keyed by ``Human`` never matches it, and the original name is never
|
|
469
|
+
restored (this is exactly the bug this method fixes). Term
|
|
470
|
+
(function/constant) tokens are unaffected in practice — they are
|
|
471
|
+
already chosen/kept lowercase-initial (:func:`_term_base_case`), so
|
|
472
|
+
rendering them again is a no-op — but this method renders them the
|
|
473
|
+
same way regardless, so it stays correct even for a term name built
|
|
474
|
+
directly (e.g. a bare ``Constant("Foo")``) outside the parser's own
|
|
475
|
+
lowercase-initial NAME convention rather than assuming every caller
|
|
476
|
+
went through it.
|
|
477
|
+
|
|
478
|
+
Injective by construction: :class:`_Renamer` already de-collides
|
|
479
|
+
every name in a namespace on its RENDERED form (:func:`reserve_rendered`,
|
|
480
|
+
and the immediate ``self.used.add(self.render(name))`` for an
|
|
481
|
+
already-legal name in :meth:`_Renamer.collect`) before assigning it a
|
|
482
|
+
token, so two distinct original names can never render to the same
|
|
483
|
+
text within one namespace — this dict can never silently drop or
|
|
484
|
+
merge an entry.
|
|
485
|
+
"""
|
|
486
|
+
pred_rendered = {tptp_fold_first_letter(v): k for k, v in self.predicate.items()}
|
|
487
|
+
term_rendered = {tptp_fold_first_letter(constant_name_to_ascii(v)): k
|
|
488
|
+
for k, v in self.term.items()}
|
|
489
|
+
return pred_rendered, term_rendered
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _is_fixed_atom(atom: Atom, uninterpreted_arithmetic: bool) -> bool:
|
|
493
|
+
"""Whether ``atom`` is written with a token of the TPTP language itself and so is never
|
|
494
|
+
renamed: equality and disequality, ``$true`` / ``$false`` and, unless the problem reads
|
|
495
|
+
the comparisons as ordinary predicates, ``<``, ``>``, ``≤`` and ``≥``."""
|
|
496
|
+
return (atom.predicate in Atom.INFIX_PREDS_TPTP or is_tptp_boolean_atom(atom)
|
|
497
|
+
or (not uninterpreted_arithmetic and atom.predicate in Atom.PREFIX_PREDS_TPTP))
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _is_fixed_function(function: Function, uninterpreted_arithmetic: bool) -> bool:
|
|
501
|
+
"""Whether ``function`` is written with a dollar-word of TPTP (``$sum``, ...) and so is
|
|
502
|
+
never renamed: an arithmetic operator, unless the problem reads it as an ordinary
|
|
503
|
+
function."""
|
|
504
|
+
return not uninterpreted_arithmetic and function.name in Function.TPTP_ARITH_OPS
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _sanitize_node_for_tptp(node: Node, predicates: _Renamer, terms: _Renamer,
|
|
508
|
+
uninterpreted_arithmetic: bool = False) -> Node:
|
|
509
|
+
"""Rebuild ``node`` with every non-TPTP-legal symbol name replaced.
|
|
510
|
+
|
|
511
|
+
Structural recursion via ``Node.map_children`` (see
|
|
512
|
+
:mod:`fol.sanitize`'s ``_rewrite`` for the same pattern); an
|
|
513
|
+
already-legal name comes back as the exact same string, so a node whose
|
|
514
|
+
own name and every descendant's name were already legal is rebuilt with
|
|
515
|
+
identical field values throughout — ``Node.to_tptp()`` on the result is
|
|
516
|
+
therefore byte-identical to ``Node.to_tptp()`` on the original (R1).
|
|
517
|
+
|
|
518
|
+
``uninterpreted_arithmetic`` makes ``+ - * /`` and ``< > ≤ ≥`` names like any other
|
|
519
|
+
(renamed to a word of a prover's grammar); without it they are left alone, to be written
|
|
520
|
+
as TPTP's own ``$sum``, ``$less``, ... (the arithmetic reading).
|
|
521
|
+
"""
|
|
522
|
+
if isinstance(node, Atom):
|
|
523
|
+
if _is_fixed_atom(node, uninterpreted_arithmetic):
|
|
524
|
+
pred = node.predicate
|
|
525
|
+
else:
|
|
526
|
+
pred = predicates.get(node.predicate)
|
|
527
|
+
return Atom(pred, tuple(_sanitize_node_for_tptp(a, predicates, terms, uninterpreted_arithmetic)
|
|
528
|
+
for a in node.args))
|
|
529
|
+
if isinstance(node, Function):
|
|
530
|
+
if _is_fixed_function(node, uninterpreted_arithmetic):
|
|
531
|
+
name = node.name
|
|
532
|
+
else:
|
|
533
|
+
name = terms.get(node.name)
|
|
534
|
+
return Function(name, tuple(_sanitize_node_for_tptp(a, predicates, terms, uninterpreted_arithmetic)
|
|
535
|
+
for a in node.args))
|
|
536
|
+
if isinstance(node, Constant):
|
|
537
|
+
return Constant(terms.get(node.name))
|
|
538
|
+
if isinstance(node, SortedConstant):
|
|
539
|
+
# Renders (via to_fol) as the plain Constant of the same name, so it
|
|
540
|
+
# is the same symbol and must get the same token as that Constant.
|
|
541
|
+
return SortedConstant(terms.get(node.name), node.sort)
|
|
542
|
+
return node.map_children(
|
|
543
|
+
lambda c: _sanitize_node_for_tptp(c, predicates, terms, uninterpreted_arithmetic))
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _collect_names_for_tptp(node: Node, predicates: _Renamer, terms: _Renamer,
|
|
547
|
+
uninterpreted_arithmetic: bool = False) -> None:
|
|
548
|
+
"""First pass (see :class:`_Renamer`): register every predicate/
|
|
549
|
+
function/constant name ``node`` (and its descendants) uses, without
|
|
550
|
+
rewriting anything yet."""
|
|
551
|
+
for n in node.walk():
|
|
552
|
+
if isinstance(n, Atom):
|
|
553
|
+
if not _is_fixed_atom(n, uninterpreted_arithmetic):
|
|
554
|
+
predicates.collect(n.predicate)
|
|
555
|
+
elif isinstance(n, Function):
|
|
556
|
+
if not _is_fixed_function(n, uninterpreted_arithmetic):
|
|
557
|
+
terms.collect(n.name)
|
|
558
|
+
elif isinstance(n, (Constant, SortedConstant)):
|
|
559
|
+
terms.collect(n.name)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _sanitize_for_tptp(formulas: List[Node],
|
|
563
|
+
sort_guards: Tuple[str, ...] = (),
|
|
564
|
+
*, uninterpreted_arithmetic: bool = False,
|
|
565
|
+
numerals: FrozenSet[str] = frozenset()) -> Tuple[List[Node], TptpNameMap]:
|
|
566
|
+
"""Sanitise every formula's predicate/function/constant names for TPTP.
|
|
567
|
+
|
|
568
|
+
Returns ``(sanitised_formulas, mapping)`` — see the module docstring's
|
|
569
|
+
ASCII-sanitisation section. Both namespaces (predicate; function+constant)
|
|
570
|
+
are shared across ALL of ``formulas``, so the same original name maps to
|
|
571
|
+
the same token everywhere (R2), and a synthesised token can never
|
|
572
|
+
collide with any name anywhere in the problem, regardless of where each
|
|
573
|
+
one appears (:class:`_Renamer`'s two-pass collect/finalize split).
|
|
574
|
+
|
|
575
|
+
``sort_guards`` are the kit names of the sorts of the problem, which the fof
|
|
576
|
+
writer writes as predicates (the guard of a sort). They appear in no formula
|
|
577
|
+
this function walks, yet their words are in the problem, so they are RESERVED
|
|
578
|
+
in the predicate namespace: a token synthesised for a predicate that is not a
|
|
579
|
+
TPTP word never equals one (``has-part`` next to the sort ``Hasu002dpart``
|
|
580
|
+
becomes ``Hasu002dpart2``, not the sort's own word). Nothing is recorded in
|
|
581
|
+
the map for a guard: it is not renamed.
|
|
582
|
+
|
|
583
|
+
``uninterpreted_arithmetic`` reads ``+ - * /`` and ``< > ≤ ≥`` as ordinary function and
|
|
584
|
+
predicate names, renamed and recorded like any other; the default leaves them to be
|
|
585
|
+
written as TPTP's own arithmetic words. ``numerals`` are the names of the constants that
|
|
586
|
+
stand for numerals (:func:`~unicode_logic_kit.fol._numeral_symbols.numerals_as_constants`),
|
|
587
|
+
which the returned map records as such.
|
|
588
|
+
"""
|
|
589
|
+
predicates = _Renamer(prefix="p", render=tptp_fold_first_letter,
|
|
590
|
+
case_fix=_predicate_base_case)
|
|
591
|
+
terms = _Renamer(prefix="n", case_fix=_term_base_case,
|
|
592
|
+
render=lambda n: tptp_fold_first_letter(constant_name_to_ascii(n)))
|
|
593
|
+
for f in formulas:
|
|
594
|
+
_collect_names_for_tptp(f, predicates, terms, uninterpreted_arithmetic)
|
|
595
|
+
for guard in sort_guards:
|
|
596
|
+
predicates.used.add(predicates.render(guard))
|
|
597
|
+
predicates.finalize()
|
|
598
|
+
terms.finalize()
|
|
599
|
+
sanitised = [_sanitize_node_for_tptp(f, predicates, terms, uninterpreted_arithmetic)
|
|
600
|
+
for f in formulas]
|
|
601
|
+
mapping = TptpNameMap(predicate=predicates.mapping, term=terms.mapping, numerals=numerals)
|
|
602
|
+
return sanitised, mapping
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _rename_terms(node: Node, table: Dict[str, str]) -> Node:
|
|
606
|
+
"""Rebuild ``node`` with every function/constant name found in ``table``
|
|
607
|
+
replaced by its value; predicates, variables and every other name stay.
|
|
608
|
+
A node that carries no such name is rebuilt equal to the original.
|
|
609
|
+
|
|
610
|
+
A :class:`~unicode_logic_kit.fol.nodes.Measure` writes the binary function
|
|
611
|
+
``measure`` (it is that very symbol: ``to_z3`` declares ``measure/2`` for it
|
|
612
|
+
too), so it is renamed like ``Function('measure', ...)``, and when ``measure``
|
|
613
|
+
is in ``table`` it is rebuilt as that function."""
|
|
614
|
+
if isinstance(node, Atom):
|
|
615
|
+
return Atom(node.predicate, tuple(_rename_terms(a, table) for a in node.args))
|
|
616
|
+
if isinstance(node, Measure) and "measure" in table:
|
|
617
|
+
return Function(table["measure"], (_rename_terms(node.entity, table),
|
|
618
|
+
_rename_terms(node.dimension, table)))
|
|
619
|
+
if isinstance(node, Function):
|
|
620
|
+
name = node.name if node.name in Function.TPTP_ARITH_OPS else table.get(node.name, node.name)
|
|
621
|
+
return Function(name, tuple(_rename_terms(a, table) for a in node.args))
|
|
622
|
+
if isinstance(node, Constant):
|
|
623
|
+
return Constant(table.get(node.name, node.name))
|
|
624
|
+
if isinstance(node, SortedConstant):
|
|
625
|
+
return SortedConstant(table.get(node.name, node.name), node.sort)
|
|
626
|
+
return node.map_children(lambda c: _rename_terms(c, table))
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _separate_term_names(formulas: List[Node], mapping: TptpNameMap,
|
|
630
|
+
sort_names: Tuple[str, ...] = ()
|
|
631
|
+
) -> Tuple[List[Node], TptpNameMap]:
|
|
632
|
+
"""Make the problem injective ACROSS kinds: no function/constant may render
|
|
633
|
+
as the name of a predicate (see the module docstring's "A predicate and a
|
|
634
|
+
function/constant that fold to the SAME identifier").
|
|
635
|
+
|
|
636
|
+
``formulas``/``mapping`` are :func:`_sanitize_for_tptp`'s output, already
|
|
637
|
+
past :func:`_check_no_symbol_collisions`. Every function/constant token
|
|
638
|
+
whose rendered name (the text ``Node.to_tptp`` writes) equals the rendered
|
|
639
|
+
name of a predicate — or of a sort guard predicate named in
|
|
640
|
+
``sort_names`` — is replaced, in every formula, by a fresh token
|
|
641
|
+
(:func:`~unicode_logic_kit.atp.tptp_tff._separated_term_token`:
|
|
642
|
+
``<name>_term`` plus a numeric suffix if taken) that equals no rendered
|
|
643
|
+
predicate and no rendered term of the problem. The tokens are visited in
|
|
644
|
+
sorted order, so the result depends only on WHICH symbols the problem
|
|
645
|
+
contains, never on the order of its formulas or on any hash ordering.
|
|
646
|
+
|
|
647
|
+
Returns ``(formulas, mapping)``; ``mapping.term`` maps each affected
|
|
648
|
+
ORIGINAL kit name to its replacement, which is what lets
|
|
649
|
+
:func:`apply_reverse_tptp` restore it. With nothing to separate the inputs
|
|
650
|
+
are returned unchanged (the same objects), so the rendered text is
|
|
651
|
+
byte-identical to what it was before this pass existed.
|
|
652
|
+
"""
|
|
653
|
+
def render(token: str) -> str:
|
|
654
|
+
return tptp_fold_first_letter(constant_name_to_ascii(token))
|
|
655
|
+
|
|
656
|
+
predicate_names = {tptp_fold_first_letter(s) for s in sort_names}
|
|
657
|
+
tokens = set()
|
|
658
|
+
for formula in formulas:
|
|
659
|
+
for node in formula.walk():
|
|
660
|
+
if isinstance(node, Atom):
|
|
661
|
+
if (node.predicate not in Atom.INFIX_PREDS_TPTP and node.predicate not in Atom.PREFIX_PREDS_TPTP
|
|
662
|
+
and not is_tptp_boolean_atom(node)):
|
|
663
|
+
predicate_names.add(tptp_fold_first_letter(node.predicate))
|
|
664
|
+
elif isinstance(node, Function):
|
|
665
|
+
if node.name not in Function.TPTP_ARITH_OPS:
|
|
666
|
+
tokens.add(node.name)
|
|
667
|
+
elif isinstance(node, (Constant, SortedConstant)):
|
|
668
|
+
tokens.add(node.name)
|
|
669
|
+
elif isinstance(node, Measure):
|
|
670
|
+
tokens.add("measure") # the function a Measure node is written as
|
|
671
|
+
clashing = sorted(t for t in tokens if render(t) in predicate_names)
|
|
672
|
+
if not clashing:
|
|
673
|
+
return formulas, mapping
|
|
674
|
+
|
|
675
|
+
taken = predicate_names | {render(t) for t in tokens}
|
|
676
|
+
replacement = {t: _separated_term_token(t, taken, render) for t in clashing}
|
|
677
|
+
original_of = {token: original for original, token in mapping.term.items()}
|
|
678
|
+
term = dict(mapping.term)
|
|
679
|
+
for token, new in replacement.items():
|
|
680
|
+
term[original_of.get(token, token)] = new
|
|
681
|
+
separated = [_rename_terms(f, replacement) for f in formulas]
|
|
682
|
+
return separated, TptpNameMap(predicate=dict(mapping.predicate), term=term,
|
|
683
|
+
numerals=mapping.numerals)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def apply_reverse_tptp(node: Node, mapping: TptpNameMap) -> Node:
|
|
687
|
+
"""Translate a ``Node`` parsed from a TPTP-family prover's OWN output
|
|
688
|
+
(e.g. one TSTP proof step) back to original kit-level names.
|
|
689
|
+
|
|
690
|
+
Walks ``node`` exactly the way :func:`_sanitize_node_for_tptp` walked the
|
|
691
|
+
export direction, looking up each predicate/function/constant name in
|
|
692
|
+
``mapping.reverse()``'s tables. A name the prover introduced itself (a
|
|
693
|
+
Skolem constant, a CNF-clausification symbol — ``sK1``, ``esk1_0``, and
|
|
694
|
+
similar) was never one of ours to begin with, so it has no entry in
|
|
695
|
+
either table and is left exactly as the prover printed it, not guessed
|
|
696
|
+
at or dropped. The word a numeral was written as comes back as the
|
|
697
|
+
:class:`~unicode_logic_kit.fol.nodes.Number` of its value (``n1`` is ``Number(1)``), and the
|
|
698
|
+
word of an operator written as an ordinary symbol as the operator (``u002b`` is ``+``).
|
|
699
|
+
"""
|
|
700
|
+
pred_rev, term_rev = mapping.reverse()
|
|
701
|
+
return _apply_reverse_tptp(node, pred_rev, term_rev, mapping.reverse_numerals())
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _apply_reverse_tptp(node: Node, pred_rev: Dict[str, str], term_rev: Dict[str, str],
|
|
705
|
+
numeral_rev: Optional[Dict[str, Union[int, float]]] = None) -> Node:
|
|
706
|
+
numeral_rev = numeral_rev or {}
|
|
707
|
+
if isinstance(node, Atom):
|
|
708
|
+
if node.predicate in Atom.INFIX_PREDS_TPTP or node.predicate in Atom.PREFIX_PREDS_TPTP:
|
|
709
|
+
pred = node.predicate
|
|
710
|
+
else:
|
|
711
|
+
pred = pred_rev.get(node.predicate, node.predicate)
|
|
712
|
+
return Atom(pred, tuple(_apply_reverse_tptp(a, pred_rev, term_rev, numeral_rev)
|
|
713
|
+
for a in node.args))
|
|
714
|
+
if isinstance(node, Function):
|
|
715
|
+
# A parsed dollar-function statement (Vampire/E echoing $sum(...)
|
|
716
|
+
# etc. back) already comes out of parse_tptp_formula with the
|
|
717
|
+
# KIT-level operator name (tptp_input.py's dollar_func_app maps
|
|
718
|
+
# "$sum" -> "+" before this function ever sees it) — the same
|
|
719
|
+
# names Function.TPTP_ARITH_OPS is keyed by, not its dollar-word
|
|
720
|
+
# values, so this mirrors the forward-direction check exactly.
|
|
721
|
+
if node.name in Function.TPTP_ARITH_OPS:
|
|
722
|
+
name = node.name
|
|
723
|
+
else:
|
|
724
|
+
name = term_rev.get(node.name, node.name)
|
|
725
|
+
return Function(name, tuple(_apply_reverse_tptp(a, pred_rev, term_rev, numeral_rev)
|
|
726
|
+
for a in node.args))
|
|
727
|
+
if isinstance(node, Constant):
|
|
728
|
+
if node.name in numeral_rev:
|
|
729
|
+
return Number(numeral_rev[node.name])
|
|
730
|
+
return Constant(term_rev.get(node.name, node.name))
|
|
731
|
+
if isinstance(node, SortedConstant):
|
|
732
|
+
return SortedConstant(term_rev.get(node.name, node.name), node.sort)
|
|
733
|
+
return node.map_children(lambda c: _apply_reverse_tptp(c, pred_rev, term_rev, numeral_rev))
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
# ---------------------------------------------------------------------------
|
|
737
|
+
# Cross-formula case-fold collision guard (pre-existing; now runs on the
|
|
738
|
+
# ASCII-sanitised formulas — see the module docstring).
|
|
739
|
+
# ---------------------------------------------------------------------------
|
|
740
|
+
|
|
741
|
+
def _check_no_symbol_collisions(formulas: List[Node], *,
|
|
742
|
+
where: str = "generate_tptp_problem",
|
|
743
|
+
subject: str = "problem",
|
|
744
|
+
sorts: Tuple[str, ...] = ()) -> None:
|
|
745
|
+
"""Raise ``NotImplementedError`` if any two distinct predicate names, or
|
|
746
|
+
any two distinct function/constant names, across ``formulas`` would fold
|
|
747
|
+
to the same TPTP identifier under :meth:`Node.to_tptp` — see the module
|
|
748
|
+
docstring. The check itself is
|
|
749
|
+
:func:`unicode_logic_kit.fol._tptp_symbols.check_no_symbol_collisions`, the
|
|
750
|
+
ONE implementation that the outermost ``Node.to_tptp()`` call also runs
|
|
751
|
+
over the single formula it renders; here it runs over every premise and
|
|
752
|
+
the conclusion TOGETHER, which no single formula's check can — plus, in
|
|
753
|
+
:func:`generate_tptp_problem_with_mapping`, the non-emptiness axioms, whose
|
|
754
|
+
atoms are the guard predicates the sorted nodes lower to.
|
|
755
|
+
|
|
756
|
+
``where`` and ``subject`` name the writer that is calling and what it was
|
|
757
|
+
given, so a refusal says which entry point refused (the TFA writer and
|
|
758
|
+
:func:`~unicode_logic_kit.atp.tstp.to_tstp` run this check too); ``sorts`` are
|
|
759
|
+
the kit names of the sorts of the problem, so that the refusal of an illegal
|
|
760
|
+
guard predicate says it is a sort.
|
|
761
|
+
"""
|
|
762
|
+
check_no_symbol_collisions(formulas, where=where, subject=subject, sorts=sorts)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def generate_tptp_problem_with_mapping(premises: List[Node],
|
|
766
|
+
conclusion: Optional[Node] = None,
|
|
767
|
+
*, premise_names: Optional[Sequence[str]] = None
|
|
768
|
+
) -> Tuple[str, TptpNameMap]:
|
|
769
|
+
"""Like :func:`generate_tptp_problem`, but also returns the
|
|
770
|
+
:class:`TptpNameMap` recording every rename it applied: the ASCII-legality
|
|
771
|
+
ones and the function/constant renamed because its TPTP name equals a
|
|
772
|
+
predicate's (see the module docstring), the premise names and the background
|
|
773
|
+
axioms it added.
|
|
774
|
+
|
|
775
|
+
``conclusion=None`` writes a problem WITHOUT a conjecture, for a prover that
|
|
776
|
+
is asked whether the premises are satisfiable (Vampire answers ``SZS status
|
|
777
|
+
Satisfiable`` or ``Unsatisfiable``). Every check and rename below works on the
|
|
778
|
+
premises alone, and the map is the map of the premises.
|
|
779
|
+
|
|
780
|
+
``premise_names`` names the premises' ``axiom`` lines: one string per premise,
|
|
781
|
+
written as a TPTP name (a lower word or an integer as it is, anything else
|
|
782
|
+
single-quoted with ``\\`` and ``'`` escaped), pairwise distinct as written and
|
|
783
|
+
distinct from every name the writer gives its own lines (``goal``,
|
|
784
|
+
``nonempty_sort_<i>``, ``sort_member_<i>``). ``None`` keeps ``premise_<i>``. The
|
|
785
|
+
names are recorded, in order and also when they are the default ones, in the
|
|
786
|
+
returned map's ``premises``, and
|
|
787
|
+
:func:`~unicode_logic_kit.atp.tstp.relevant_premises_from_tstp` reads a prover's
|
|
788
|
+
proof back through them.
|
|
789
|
+
|
|
790
|
+
A free variable in a premise or the conclusion is refused by name: a prover
|
|
791
|
+
reads an unbound variable in a ``fof`` formula as a syntax error, and the answer
|
|
792
|
+
must not depend on whether the problem has a sort.
|
|
793
|
+
|
|
794
|
+
Raises:
|
|
795
|
+
TypeError: ``premise_names`` is a single string, or holds a non-string.
|
|
796
|
+
ValueError: ``premise_names`` has other than one name per premise, holds a
|
|
797
|
+
name no TPTP name can spell (the empty name, a control character), two
|
|
798
|
+
names that are the same as written, or a name the writer gives one of its
|
|
799
|
+
own lines.
|
|
800
|
+
NotImplementedError: as :func:`generate_tptp_problem`, and a free variable.
|
|
801
|
+
|
|
802
|
+
This is the way to build a problem for a prover. Never assemble one by
|
|
803
|
+
joining ``Node.to_tptp()`` strings: a single formula cannot know that two
|
|
804
|
+
of its symbols fold to one TPTP word in ANOTHER formula, which is what the
|
|
805
|
+
checks here are for.
|
|
806
|
+
|
|
807
|
+
Callers that need to translate a prover's OWN output (a proof, a
|
|
808
|
+
countermodel, an unsat core, ...) back to kit-level names — anything
|
|
809
|
+
reading a TSTP derivation via :mod:`atp.tstp`, for instance — must use
|
|
810
|
+
THIS function (not the plain :func:`generate_tptp_problem`) so they have
|
|
811
|
+
the mapping :func:`apply_reverse_tptp` needs. A caller that only wants
|
|
812
|
+
the problem text (nothing reads the answer's symbol names back) can keep
|
|
813
|
+
using :func:`generate_tptp_problem`.
|
|
814
|
+
"""
|
|
815
|
+
return _write_fof_problem(premises, conclusion, premise_names,
|
|
816
|
+
where="generate_tptp_problem_with_mapping")
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _refuse_free_variables(premises: List[Node], conclusion: Optional[Node],
|
|
820
|
+
*, where: str) -> None:
|
|
821
|
+
"""Refuse, by name, a premise or the conclusion with a free variable.
|
|
822
|
+
|
|
823
|
+
A TPTP ``fof`` formula has no implicit closure for a prover to apply: Vampire stops
|
|
824
|
+
with ``unquantified variable`` and E with ``Formula has free variables``, so the
|
|
825
|
+
problem would come back as a prover's parse error, whether or not it uses a sort.
|
|
826
|
+
The typed writers refuse a free variable too (:class:`~unicode_logic_kit.atp.tptp_tff
|
|
827
|
+
.Tf0Refusal`, and ``ValueError`` in the TFA writer)."""
|
|
828
|
+
for label, formula in ([(f"premise {i}", p) for i, p in enumerate(premises, start=1)]
|
|
829
|
+
+ ([] if conclusion is None else [("the conclusion", conclusion)])):
|
|
830
|
+
try:
|
|
831
|
+
free = sorted({v.name for v in free_variables(formula) if isinstance(v, Variable)})
|
|
832
|
+
except TypeError:
|
|
833
|
+
continue # a node ``free_variables`` does not know: to_tptp refuses it
|
|
834
|
+
if free:
|
|
835
|
+
shown = ", ".join(repr(n) for n in free)
|
|
836
|
+
raise NotImplementedError(
|
|
837
|
+
f"{where}: free variable{'s' if len(free) > 1 else ''} {shown} in {label} — "
|
|
838
|
+
"every variable of a TPTP formula must be bound, because a prover reads an "
|
|
839
|
+
"unbound variable in a fof formula as a syntax error (Vampire: "
|
|
840
|
+
"'unquantified variable', E: 'Formula has free variables') and this kit "
|
|
841
|
+
"does not pick a closure for it. Bind the variable with a quantifier "
|
|
842
|
+
"before writing the problem.")
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _nullary_functions_as_constants(formula: Node) -> Node:
|
|
846
|
+
"""``formula`` with every function of no arguments written as the constant of its name:
|
|
847
|
+
TPTP has no empty argument list (``f()`` is no term, and Vampire stops with a parse
|
|
848
|
+
error), and the kit defines a function of no arguments as the constant of its name
|
|
849
|
+
(the TF0 and Prover9 writers write it that way too). A formula without one comes back
|
|
850
|
+
as the very same object."""
|
|
851
|
+
stack: List[Node] = [formula]
|
|
852
|
+
while stack:
|
|
853
|
+
node = stack.pop()
|
|
854
|
+
if isinstance(node, Function) and not node.args:
|
|
855
|
+
break
|
|
856
|
+
stack.extend(node._child_nodes())
|
|
857
|
+
else:
|
|
858
|
+
return formula
|
|
859
|
+
|
|
860
|
+
def rewrite(node: Node) -> Node:
|
|
861
|
+
if isinstance(node, Function) and not node.args:
|
|
862
|
+
return Constant(node.name)
|
|
863
|
+
return node.map_children(rewrite)
|
|
864
|
+
|
|
865
|
+
return rewrite(formula)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _write_fof_problem(premises: Sequence[Node], conclusion: Optional[Node],
|
|
869
|
+
premise_names: Optional[Sequence[str]],
|
|
870
|
+
*, where: str) -> Tuple[str, TptpNameMap]:
|
|
871
|
+
"""The ``fof`` writer both :func:`generate_tptp_problem` and
|
|
872
|
+
:func:`generate_tptp_problem_with_mapping` are; ``where`` is the name of the one
|
|
873
|
+
that was called, which a refusal opens with."""
|
|
874
|
+
premises = list(premises)
|
|
875
|
+
names = normalise_premise_names(premise_names, len(premises), where=where)
|
|
876
|
+
formulas = premises + ([] if conclusion is None else [conclusion])
|
|
877
|
+
# A numeral is a constant (see the module docstring): written under the name of its
|
|
878
|
+
# value, so that the renamer below gives it a word and the map reads it back.
|
|
879
|
+
formulas, numerals = numerals_as_constants(formulas, where=where)
|
|
880
|
+
formulas = [_nullary_functions_as_constants(f) for f in formulas]
|
|
881
|
+
# Many-sorted non-emptiness axioms (see the module docstring) — computed
|
|
882
|
+
# first because their sort guard predicates take part in the same-kind check
|
|
883
|
+
# and in the cross-kind separation, exactly like a predicate written in the
|
|
884
|
+
# source: ``∀x:Foo P(x)`` is written with the guard predicate ``Foo`` and the
|
|
885
|
+
# axiom ``∃x Foo(x)``, which no walk of the SOURCE trees would otherwise see.
|
|
886
|
+
# The sanitiser reserves them too, so that a predicate it has to rewrite never
|
|
887
|
+
# lands on the word of a sort.
|
|
888
|
+
sort_axioms = nonempty_sort_axioms(*formulas)
|
|
889
|
+
sort_guards = tuple(a.predicate for ax in sort_axioms for a in ax.walk()
|
|
890
|
+
if isinstance(a, Atom))
|
|
891
|
+
sanitised, mapping = _sanitize_for_tptp(formulas, sort_guards, uninterpreted_arithmetic=True,
|
|
892
|
+
numerals=numerals)
|
|
893
|
+
# A variable that has no TPTP spelling is renamed (per formula, no record).
|
|
894
|
+
sanitised = [legalise_variables(f) for f in sanitised]
|
|
895
|
+
_check_no_symbol_collisions(sanitised + list(sort_axioms), sorts=sort_guards, where=where)
|
|
896
|
+
sanitised, mapping = _separate_term_names(sanitised, mapping, sort_guards)
|
|
897
|
+
# A free variable is refused only after the symbol checks above, which a problem that
|
|
898
|
+
# has both is refused for first (as it always was).
|
|
899
|
+
_refuse_free_variables(premises, conclusion, where=where)
|
|
900
|
+
sanitised_premises = sanitised if conclusion is None else sanitised[:-1]
|
|
901
|
+
# The membership atoms come from the SANITISED formulas (see below); the same atoms
|
|
902
|
+
# over the names as the caller wrote them say what each line means.
|
|
903
|
+
membership = sort_membership_axioms(*sanitised)
|
|
904
|
+
membership_meaning = [f"{a.args[0].name} is in the sort {a.predicate}"
|
|
905
|
+
for a in sort_membership_axioms(*formulas)]
|
|
906
|
+
generated = ([] if conclusion is None else [("goal", "the conjecture")])
|
|
907
|
+
generated += [(f"nonempty_sort_{i}", f"the non-emptiness axiom of the sort {sort!r}")
|
|
908
|
+
for i, sort in enumerate(sort_guards, start=1)]
|
|
909
|
+
generated += [(f"sort_member_{i}", f"the membership axiom ({meaning})")
|
|
910
|
+
for i, meaning in enumerate(membership_meaning, start=1)]
|
|
911
|
+
check_against_generated(names, generated, where=where)
|
|
912
|
+
lines: List[str] = []
|
|
913
|
+
for name, premise in zip(names, sanitised_premises):
|
|
914
|
+
lines.append(f"fof({tptp_name_token(name)}, axiom, {premise.to_tptp()}).")
|
|
915
|
+
# Many-sorted non-emptiness axioms — see the module docstring. Built
|
|
916
|
+
# from the ORIGINAL (pre-sanitisation) premises/conclusion so each one
|
|
917
|
+
# renders the exact raw sort-predicate name a sorted node's own lazy
|
|
918
|
+
# to_tptp()/to_fol reduction emits elsewhere in this same problem.
|
|
919
|
+
for i, axiom in enumerate(sort_axioms, start=1):
|
|
920
|
+
lines.append(f"fof(nonempty_sort_{i}, axiom, {axiom.to_tptp()}).")
|
|
921
|
+
# Sort membership of the sorted constants — the other half of the guard
|
|
922
|
+
# reading (see the module docstring). Unlike the sort NAME, a constant is
|
|
923
|
+
# renamed on its way into the problem (``human:Human`` is written
|
|
924
|
+
# ``human_term``, ``9lives`` is ``n9lives``, ``sókrates`` is transliterated), so
|
|
925
|
+
# the atoms are built from the formulas AS SANITISED AND SEPARATED, whose
|
|
926
|
+
# constants are the very tokens the premises above use: an atom built from the
|
|
927
|
+
# raw names would be about another symbol and silently do nothing. Every
|
|
928
|
+
# constant of these atoms is one of the formulas' own, so the name map already
|
|
929
|
+
# covers it.
|
|
930
|
+
for i, atom in enumerate(membership, start=1):
|
|
931
|
+
lines.append(f"fof(sort_member_{i}, axiom, {atom.to_tptp()}).")
|
|
932
|
+
if conclusion is not None:
|
|
933
|
+
lines.append(f"fof(goal, conjecture, {sanitised[-1].to_tptp()}).")
|
|
934
|
+
mapping.premises = names
|
|
935
|
+
mapping.background = tuple(
|
|
936
|
+
[(f"nonempty_sort_{i}", f"the sort {sort} is not empty")
|
|
937
|
+
for i, sort in enumerate(sort_guards, start=1)]
|
|
938
|
+
+ [(f"sort_member_{i}", meaning) for i, meaning in enumerate(membership_meaning, start=1)])
|
|
939
|
+
return "\n".join(lines) + "\n", mapping
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def generate_tptp_problem(premises: List[Node], conclusion: Optional[Node] = None,
|
|
943
|
+
*, premise_names: Optional[Sequence[str]] = None) -> str:
|
|
944
|
+
"""Build a TPTP ``fof`` problem string from premises and a conclusion.
|
|
945
|
+
|
|
946
|
+
Each premise becomes ``fof(premise_<i>, axiom, <tptp>).`` (1-based) and
|
|
947
|
+
the conclusion becomes ``fof(goal, conjecture, <tptp>).`` — or, with
|
|
948
|
+
``conclusion=None``, no conjecture line is written at all and the problem asks
|
|
949
|
+
whether the premises are satisfiable. The bodies
|
|
950
|
+
come from ``Node.to_tptp`` (variables upper-cased TPTP-style, predicate/
|
|
951
|
+
function/constant names folded on their first character only — see the
|
|
952
|
+
module docstring), after every name has been made TPTP-ASCII-legal (see
|
|
953
|
+
the module docstring's sanitisation section) — a name that was already
|
|
954
|
+
legal renders byte-identically to before that step existed. Shared
|
|
955
|
+
verbatim by :mod:`atp.vampire_entailment`, :mod:`atp.eprover_backend`,
|
|
956
|
+
and :mod:`atp.twee_entailment`.
|
|
957
|
+
|
|
958
|
+
Before rendering, checks that the export stays injective across every
|
|
959
|
+
premise and the conclusion TOGETHER — see the module docstring's
|
|
960
|
+
soundness-guard section and :func:`_check_no_symbol_collisions` — and
|
|
961
|
+
separates a function/constant from a predicate that would render as the
|
|
962
|
+
same word (:func:`_separate_term_names`; the term becomes
|
|
963
|
+
``<name>_term``, which only :func:`generate_tptp_problem_with_mapping`
|
|
964
|
+
reports back).
|
|
965
|
+
|
|
966
|
+
A caller that also needs to translate a prover's response back to
|
|
967
|
+
kit-level symbol names should call
|
|
968
|
+
:func:`generate_tptp_problem_with_mapping` instead, which returns the
|
|
969
|
+
same text plus the :class:`TptpNameMap` :func:`apply_reverse_tptp` needs.
|
|
970
|
+
|
|
971
|
+
A numeral is written as a constant and ``+ - * / < > ≤ ≥`` as ordinary symbols, each
|
|
972
|
+
under a word of its own (see the module docstring): ``⊢ 1 ≠ 2`` is the problem
|
|
973
|
+
``n1 != n2``, which is not a theorem, and ``⊢ 1 + 1 = 2`` is ``u002b(n1,n1) = n2``.
|
|
974
|
+
A function of no arguments is the constant of its name and is written as one
|
|
975
|
+
(``p(fzero)``, never ``p(fzero())``: TPTP has no empty argument list).
|
|
976
|
+
|
|
977
|
+
``premise_names`` names the premises' lines instead of ``premise_<i>`` — see
|
|
978
|
+
:func:`generate_tptp_problem_with_mapping`, which also records them. A free
|
|
979
|
+
variable in a premise or the conclusion is refused.
|
|
980
|
+
|
|
981
|
+
Raises:
|
|
982
|
+
TypeError: ``premise_names`` is a single string, or holds a non-string.
|
|
983
|
+
ValueError: ``premise_names`` has other than one name per premise, a name no
|
|
984
|
+
TPTP name can spell, two names that are the same as written, or a name
|
|
985
|
+
the writer gives one of its own lines (``goal``, ``nonempty_sort_<i>``,
|
|
986
|
+
``sort_member_<i>``).
|
|
987
|
+
NotImplementedError: either (a) two distinct predicate names, or two
|
|
988
|
+
distinct function/constant names, would render as the same TPTP
|
|
989
|
+
identifier (the collision guard above; a sort counts as the
|
|
990
|
+
predicate it is written as), or two LEGAL variables of one formula
|
|
991
|
+
would render as one TPTP variable (``x`` and ``X``), or a sort name
|
|
992
|
+
is not a TPTP word, or a constant, sorted constant or function is
|
|
993
|
+
spelled like a numeral of the problem (``Number(1)`` next to
|
|
994
|
+
``Constant('1')``: one name would be two symbols), or (b) a premise or the
|
|
995
|
+
conclusion contains a node outside the classical FOL fragment
|
|
996
|
+
``Node.to_tptp`` covers (modal / second-order / Łukasiewicz /
|
|
997
|
+
lambda) — surfaced by ``to_tptp`` itself, unchanged from before
|
|
998
|
+
this module existed.
|
|
999
|
+
"""
|
|
1000
|
+
text, _mapping = _write_fof_problem(premises, conclusion, premise_names,
|
|
1001
|
+
where="generate_tptp_problem")
|
|
1002
|
+
return text
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
@dataclass(frozen=True)
|
|
1006
|
+
class TptpProblem:
|
|
1007
|
+
"""A TPTP problem text with the record a backend needs to run and explain it.
|
|
1008
|
+
|
|
1009
|
+
Fields:
|
|
1010
|
+
|
|
1011
|
+
* ``text`` -- the problem, ``fof`` or ``tff``.
|
|
1012
|
+
* ``name_map`` -- the :class:`TptpNameMap` of the renames the writer applied.
|
|
1013
|
+
* ``dialect`` -- ``"fof"`` or ``"tff"``: which writer produced ``text``.
|
|
1014
|
+
* ``tff_refusal`` -- the typed writer's message when the automatic mode tried
|
|
1015
|
+
it, was refused, and wrote ``fof`` instead; ``None`` otherwise.
|
|
1016
|
+
"""
|
|
1017
|
+
|
|
1018
|
+
text: str
|
|
1019
|
+
name_map: TptpNameMap
|
|
1020
|
+
dialect: str
|
|
1021
|
+
tff_refusal: Optional[str] = None
|
|
1022
|
+
|
|
1023
|
+
@property
|
|
1024
|
+
def fallback_note(self) -> Optional[str]:
|
|
1025
|
+
"""The sentence a verdict's detail carries when the automatic mode fell back
|
|
1026
|
+
from the typed writer to ``fof`` (and why); ``None`` when it did not."""
|
|
1027
|
+
if self.tff_refusal is None:
|
|
1028
|
+
return None
|
|
1029
|
+
return ("the typed (TF0) writer refused this problem, so it was written as an "
|
|
1030
|
+
"untyped fof problem, which reads a sort as the unary predicate of its "
|
|
1031
|
+
"name and asserts that no sort is empty and that every sorted constant "
|
|
1032
|
+
f"is in its sort. The typed writer said: {self.tff_refusal}")
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def generate_tptp_problem_for_prover(premises: List[Node],
|
|
1036
|
+
conclusion: Optional[Node] = None,
|
|
1037
|
+
*, tff: Optional[bool] = None,
|
|
1038
|
+
fof_writer: Optional[Callable[..., Tuple[str, TptpNameMap]]] = None,
|
|
1039
|
+
premise_names: Optional[Sequence[str]] = None
|
|
1040
|
+
) -> TptpProblem:
|
|
1041
|
+
"""The problem a TPTP prover is given for ``premises ⊨ conclusion``, in the
|
|
1042
|
+
dialect ``tff`` selects.
|
|
1043
|
+
|
|
1044
|
+
* ``tff=False`` -- the ``fof`` writer
|
|
1045
|
+
(:func:`generate_tptp_problem_with_mapping`).
|
|
1046
|
+
* ``tff=True`` -- the typed TF0 writer
|
|
1047
|
+
(:func:`~unicode_logic_kit.atp.tptp_tff.generate_tff_problem_with_mapping`).
|
|
1048
|
+
What it refuses stays refused: :class:`~unicode_logic_kit.atp.tptp_tff.Tf0Refusal`
|
|
1049
|
+
(a ``ValueError`` and a ``NotImplementedError``) with its message, so a
|
|
1050
|
+
backend reports it as ``unknown`` / ``unsupported``.
|
|
1051
|
+
* ``tff=None`` -- the automatic mode: the typed writer when a sorted node
|
|
1052
|
+
occurs (:func:`~unicode_logic_kit.atp.tptp_tff.problem_needs_tff`), the ``fof``
|
|
1053
|
+
writer otherwise. EVERY refusal of the typed writer (a ``NotImplementedError``,
|
|
1054
|
+
which :class:`~unicode_logic_kit.atp.tptp_tff.Tf0Refusal` is a kind of) is
|
|
1055
|
+
answered with the ``fof`` problem: a problem whose typed text would not ask
|
|
1056
|
+
the kit's question (an unannotated constant or a function value that the type
|
|
1057
|
+
inference puts into a sort, an equation over an unsorted variable next to a
|
|
1058
|
+
sort, ...), and a node outside the typed writer's fragment that the ``fof``
|
|
1059
|
+
writer writes (a counting quantifier, ``Contrast``, ``Measure``). The result
|
|
1060
|
+
records that in ``tff_refusal``, and its ``fallback_note`` words it for a
|
|
1061
|
+
verdict's detail. A refusal of the ``fof`` writer itself
|
|
1062
|
+
(``NotImplementedError``) propagates, with the typed writer's refusal as its
|
|
1063
|
+
``__cause__``.
|
|
1064
|
+
|
|
1065
|
+
A node outside what BOTH writers cover raises ``NotImplementedError`` in every
|
|
1066
|
+
mode, as before.
|
|
1067
|
+
|
|
1068
|
+
``fof_writer`` is the function that writes the ``fof`` dialect, with the signature
|
|
1069
|
+
of :func:`generate_tptp_problem_with_mapping`, which is the default. A backend
|
|
1070
|
+
passes the name it has bound in its own module, so that the problem text a prover
|
|
1071
|
+
is given stays substitutable at the one place each backend reads it from. It is
|
|
1072
|
+
called with ``premise_names=`` only when the caller passed some.
|
|
1073
|
+
|
|
1074
|
+
``premise_names`` names the premises' lines in whichever dialect is written (see
|
|
1075
|
+
:func:`generate_tptp_problem_with_mapping`); the returned problem's ``name_map``
|
|
1076
|
+
records them. A refusal that is about the names (a ``ValueError`` or ``TypeError``
|
|
1077
|
+
that is no :class:`~unicode_logic_kit.atp.tptp_tff.Tf0Refusal`) is raised in every
|
|
1078
|
+
mode, and is not what the automatic mode falls back from.
|
|
1079
|
+
"""
|
|
1080
|
+
premises = list(premises)
|
|
1081
|
+
refusal: Optional[str] = None
|
|
1082
|
+
typed_refusal: Optional[NotImplementedError] = None
|
|
1083
|
+
if tff is None:
|
|
1084
|
+
use_tff = problem_needs_tff(premises, conclusion)
|
|
1085
|
+
else:
|
|
1086
|
+
use_tff = tff
|
|
1087
|
+
if use_tff:
|
|
1088
|
+
try:
|
|
1089
|
+
text, mapping = generate_tff_problem_with_mapping(
|
|
1090
|
+
premises, conclusion, premise_names=premise_names)
|
|
1091
|
+
except NotImplementedError as exc:
|
|
1092
|
+
# ``Tf0Refusal`` is one, and so is the typed writer's refusal of a node
|
|
1093
|
+
# outside its fragment (``Count``, ``Contrast``, ``Measure``) that the
|
|
1094
|
+
# ``fof`` writer does write.
|
|
1095
|
+
if tff is not None:
|
|
1096
|
+
raise
|
|
1097
|
+
typed_refusal = exc
|
|
1098
|
+
refusal = str(exc)
|
|
1099
|
+
else:
|
|
1100
|
+
return TptpProblem(text, mapping, "tff")
|
|
1101
|
+
write_fof = generate_tptp_problem_with_mapping if fof_writer is None else fof_writer
|
|
1102
|
+
try:
|
|
1103
|
+
if premise_names is None:
|
|
1104
|
+
text, mapping = write_fof(premises, conclusion)
|
|
1105
|
+
else:
|
|
1106
|
+
text, mapping = write_fof(premises, conclusion, premise_names=premise_names)
|
|
1107
|
+
except NotImplementedError as exc:
|
|
1108
|
+
if typed_refusal is not None:
|
|
1109
|
+
raise exc from typed_refusal
|
|
1110
|
+
raise
|
|
1111
|
+
return TptpProblem(text, mapping, "fof", refusal)
|