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
|
+
"""One TPTP word is one kit symbol: the shared collision check and the render guard.
|
|
2
|
+
|
|
3
|
+
``Node.to_tptp`` writes a predicate, function or constant under its kit name with
|
|
4
|
+
the FIRST character folded to lower-case (:func:`~unicode_logic_kit.fol._fol_nodes
|
|
5
|
+
.tptp_fold_first_letter`), so two DISTINCT kit names of one kind (``gaseous`` and
|
|
6
|
+
``Gaseous``; ``Foo`` and ``foo``; ``Bar`` and ``bar``) are written as one word.
|
|
7
|
+
Rendered inside one formula that turns a non-theorem into a tautology:
|
|
8
|
+
``p(gaseous) <=> p(Gaseous)`` is written ``(p(gaseous) <=> p(gaseous))``.
|
|
9
|
+
|
|
10
|
+
This module is the ONE implementation of the check that refuses it. Two callers
|
|
11
|
+
share it:
|
|
12
|
+
|
|
13
|
+
* the problem writers (:mod:`unicode_logic_kit.atp._tptp_problem` and the TFF
|
|
14
|
+
writers), which check every premise and the conclusion TOGETHER through
|
|
15
|
+
:func:`check_no_symbol_collisions`; and
|
|
16
|
+
* every ``Node.to_tptp()`` call, through the guard below, which checks the ONE
|
|
17
|
+
formula it renders.
|
|
18
|
+
|
|
19
|
+
**What is refused.** Every case below is two kit symbols written as one word, or
|
|
20
|
+
a word no prover reads; the message names both symbols (or the one name) and the
|
|
21
|
+
word:
|
|
22
|
+
|
|
23
|
+
* two DISTINCT names of one namespace that fold to one word (the case above);
|
|
24
|
+
* an arithmetic or comparison symbol and a user symbol that is written like it:
|
|
25
|
+
``+`` is written ``$sum``, so a function that is itself named ``$sum`` is one
|
|
26
|
+
word with it (``<`` is ``$less``, ``=`` is ``=``, and so on);
|
|
27
|
+
* a number and a constant spelled like it: ``Number(1)`` and ``Constant('1')`` are
|
|
28
|
+
both written ``1``;
|
|
29
|
+
* two VARIABLES that are written as one TPTP variable. A variable is written as
|
|
30
|
+
the upper-case of its name, so ``x`` and ``X`` (and ``ı`` and ``i``) are one
|
|
31
|
+
variable and ``∀x ∃X R(x, X)`` would be written ``![X]: ?[X]: r(X,X)``. The
|
|
32
|
+
scope of a variable is one formula, so the writers check this per formula;
|
|
33
|
+
* a predicate, function or constant name that is written as something that is not
|
|
34
|
+
a TPTP word. An unquoted TPTP name is a lower-case letter followed by letters,
|
|
35
|
+
digits and underscores (``has-part``, ``2008SummerOlympics``, ``_x`` and a
|
|
36
|
+
non-ASCII predicate name are not), and the text of such a rendering is rejected
|
|
37
|
+
by Vampire and by E. A name is refused, never written as it is. A name that is
|
|
38
|
+
written with a leading ``$`` (``$foo``) is one of TPTP's OWN words, so it is
|
|
39
|
+
refused as a RESERVED word, not as a malformed one. (The problem writers rewrite
|
|
40
|
+
such a predicate, function or constant name and record the rewrite, and the
|
|
41
|
+
replacement is chosen so that it equals no sort guard of the problem either, so
|
|
42
|
+
a problem built by them meets this refusal only for a SORT name: the fof writer
|
|
43
|
+
reads a sort as its guard predicate and does not rewrite it.)
|
|
44
|
+
* a variable that is written as something that is not a TPTP variable. A TPTP
|
|
45
|
+
variable is an upper-case letter followed by letters, digits and underscores, and
|
|
46
|
+
a variable is written as the upper-case of its name, so ``ä`` (written ``Ä``),
|
|
47
|
+
``x-1`` and ``1x`` have no rendering. A variable is BOUND, so the problem writers
|
|
48
|
+
rename it to a fresh legal one without recording anything
|
|
49
|
+
(:func:`legalise_variables`); a single formula has no map to hand back and
|
|
50
|
+
refuses it by name.
|
|
51
|
+
|
|
52
|
+
**The two defined propositions.** ``$true`` and ``$false`` are TPTP's own
|
|
53
|
+
propositions, and this kit's reader (:mod:`unicode_logic_kit.fol.tptp_input`) reads
|
|
54
|
+
them as the NULLARY atoms ``Atom('$true')`` / ``Atom('$false')``
|
|
55
|
+
(:func:`is_tptp_boolean_atom`). They are not symbols of the user's: the single
|
|
56
|
+
formula writes them verbatim, the problem writers neither rewrite nor declare them,
|
|
57
|
+
and the checks here never count them. ``to_z3`` reads them as the constants true
|
|
58
|
+
and false, so that z3 and the TPTP provers answer the question the TPTP text asks.
|
|
59
|
+
Any OTHER ``$``-word, and ``$true`` itself WITH arguments, is refused as reserved.
|
|
60
|
+
The nullary atoms named like the glyphs ``⊤`` and ``⊥`` are the same two propositions
|
|
61
|
+
(:func:`truth_constant_word`): they are written as ``$true`` and ``$false``, never as
|
|
62
|
+
a letter.
|
|
63
|
+
|
|
64
|
+
**Namespaces.** Predicates are one namespace; functions, constants, numerals and
|
|
65
|
+
the arithmetic function words together are the other (a TPTP reader resolves a
|
|
66
|
+
bare identifier by its position, and this kit's own reader does); variables are a
|
|
67
|
+
third, checked per formula. Only two DISTINCT symbols inside the SAME namespace
|
|
68
|
+
that are written as one word are refused. A predicate and a function/constant that
|
|
69
|
+
share a word are not refused here (see the module docstring of
|
|
70
|
+
:mod:`unicode_logic_kit.atp._tptp_problem` for why the WRITERS rename the term
|
|
71
|
+
side instead): the text of one formula is unambiguous by position, and only a
|
|
72
|
+
writer can hand back the name map a caller needs to translate a proof back.
|
|
73
|
+
|
|
74
|
+
**A sort is the guard predicate of its name.** ``∀x:S φ`` is written
|
|
75
|
+
``![X]: (s(X) => φ)``: the kit's semantics of a sort is that predicate (it is what
|
|
76
|
+
``to_z3``, the fof writer and the Prover9 writer do), so the sort ``Foo`` and a
|
|
77
|
+
predicate ``foo`` are one word of the predicate namespace and are refused like any
|
|
78
|
+
other pair, and the sort ``Car`` next to the predicate ``Car`` is ONE symbol. The
|
|
79
|
+
TF0 writer cannot say that (it declares a type and a predicate), so it refuses a
|
|
80
|
+
sort and a predicate that share a word (:mod:`unicode_logic_kit.atp.tptp_tff`).
|
|
81
|
+
|
|
82
|
+
**The asymmetry is deliberate, for this release.** A name TPTP cannot spell, or
|
|
83
|
+
a predicate that clashes with a function/constant, is renamed by the writers and
|
|
84
|
+
recorded in the name map; two LEGAL names of one kind that fold together are
|
|
85
|
+
refused by name, never renamed. The same-kind refusal predates the name map and
|
|
86
|
+
is kept so that no existing caller silently receives a symbol renamed behind its
|
|
87
|
+
back; it is not a claim that the two situations differ in principle.
|
|
88
|
+
|
|
89
|
+
**The guard.** ``Node.to_tptp`` is implemented recursively in about seventy
|
|
90
|
+
places across the node families, and several families reduce before rendering
|
|
91
|
+
(a ``SortedQuantifier`` renders through a FOL reduction that introduces a sort
|
|
92
|
+
guard predicate; a ``Count`` renders through its distinct-witnesses expansion; a
|
|
93
|
+
``Measure`` writes the function ``measure``). A scan of the SOURCE tree would
|
|
94
|
+
miss every name a reduction introduces, so the guard records what is actually
|
|
95
|
+
RENDERED instead: while an outermost ``to_tptp`` call is running, every node
|
|
96
|
+
whose ``to_tptp`` is entered, at any depth, reports the symbol it writes
|
|
97
|
+
(:meth:`~unicode_logic_kit.fol._fol_nodes.Node._tptp_symbol`), and when the
|
|
98
|
+
outermost call returns the recorded symbols are checked once.
|
|
99
|
+
|
|
100
|
+
It is installed centrally. :meth:`Node.__init_subclass__
|
|
101
|
+
<unicode_logic_kit.fol._fol_nodes.Node.__init_subclass__>` replaces whatever
|
|
102
|
+
``to_tptp`` a new node class resolves to (its own, or one inherited from a mixin)
|
|
103
|
+
with a :class:`GuardedToTptp` descriptor, so no family edits its methods and a
|
|
104
|
+
future family is covered without anyone remembering to ask.
|
|
105
|
+
|
|
106
|
+
* **Only the outermost call scans.** A contextvar holds the log of the render in
|
|
107
|
+
progress; a call that finds one is nested and only records. The contextvar is
|
|
108
|
+
reset in a ``finally``, so a refusal, or a family's own ``NotImplementedError``
|
|
109
|
+
(a modal node has no TPTP form), leaves the next call guarded again. A
|
|
110
|
+
contextvar rather than a module global makes it safe across threads (a new
|
|
111
|
+
thread starts with no render in progress) and across re-entrancy.
|
|
112
|
+
* **Nested calls cost no stack frame per level.** The descriptor's ``__get__``
|
|
113
|
+
returns the ORIGINAL bound method while a render is in progress, so a recursive
|
|
114
|
+
render has the stack depth it had before the guard existed plus ONE frame (the
|
|
115
|
+
outermost call's wrapper) and none per level: a left-nested conjunction of about
|
|
116
|
+
990 conjuncts still renders, one level fewer than without the guard (measured:
|
|
117
|
+
992 and 991 against the interpreter's limit of 1000). A wrapper function on
|
|
118
|
+
every level would double the frames per level. The guard is not free in time:
|
|
119
|
+
every rendered node names its symbol once, which makes a render about 1.5 to 2
|
|
120
|
+
times slower than the same render without the guard (measured on a left-nested
|
|
121
|
+
chain of 800 conjuncts and on a balanced formula of 2048 leaves).
|
|
122
|
+
* **Byte-identical output.** The guard never touches the text: for a formula it
|
|
123
|
+
accepts, the string returned is the string the unguarded method returned.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
import contextvars
|
|
127
|
+
import functools
|
|
128
|
+
import inspect
|
|
129
|
+
import re
|
|
130
|
+
import types
|
|
131
|
+
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
|
|
132
|
+
|
|
133
|
+
#: ``(namespace, rendered word, kit name, kind label)`` — one symbol a text
|
|
134
|
+
#: contains, which is what :func:`check_symbols` works on. ``namespace`` is
|
|
135
|
+
#: ``"predicate"``, ``"term"`` (functions, constants, numerals and the arithmetic
|
|
136
|
+
#: function words share it) or ``"variable"``. ``kind label`` says what the symbol
|
|
137
|
+
#: is: ``"predicate"``, ``"function"`` and ``"constant/function"`` are NAMES a
|
|
138
|
+
#: user chose; ``"reserved predicate"`` / ``"reserved function"`` are the
|
|
139
|
+
#: comparison and arithmetic symbols, which TPTP spells with a word of its own
|
|
140
|
+
#: (``<`` is ``$less``, ``+`` is ``$sum``); ``"numeral"`` is a number; ``"variable"``
|
|
141
|
+
#: is a variable.
|
|
142
|
+
Symbol = Tuple[str, str, str, str]
|
|
143
|
+
|
|
144
|
+
#: What a node reports it writes (``Node._tptp_symbol``): ``(resolver, kit
|
|
145
|
+
#: name)``. The hook runs once per node of every rendered formula, so it only
|
|
146
|
+
#: names the symbol; ``resolver(kit name)`` (the first-letter fold, the ASCII
|
|
147
|
+
#: transliteration) runs once per DISTINCT name, when the symbols are checked.
|
|
148
|
+
#: A resolver returns ``(namespace, word, kind label)``, optionally followed by
|
|
149
|
+
#: the name to show in a refusal when that is not the kit name itself (a number
|
|
150
|
+
#: shows the text it is written as), or ``None`` for a token that writes nothing.
|
|
151
|
+
Writes = Tuple[Callable[[Any], Optional[Tuple[str, ...]]], Any]
|
|
152
|
+
|
|
153
|
+
#: The kinds that are NAMES a user chose, which must be words TPTP reads.
|
|
154
|
+
_NAME_KINDS = frozenset({"predicate", "function", "constant/function"})
|
|
155
|
+
|
|
156
|
+
#: An unquoted TPTP variable: an upper-case letter, then letters, digits and
|
|
157
|
+
#: underscores (``upper_word`` of the TPTP grammar).
|
|
158
|
+
_LEGAL_VARIABLE = re.compile(r"[A-Z][A-Za-z0-9_]*")
|
|
159
|
+
|
|
160
|
+
#: TPTP's two DEFINED propositions, which the kit's own TPTP reader produces as
|
|
161
|
+
#: nullary atoms of exactly these names. See the module docstring.
|
|
162
|
+
TPTP_BOOLEAN_CONSTANTS = frozenset({"$true", "$false"})
|
|
163
|
+
|
|
164
|
+
#: The unicode glyphs of the same two constants, as the NAME of a nullary atom, and
|
|
165
|
+
#: the TPTP word each one stands for. ``Atom('⊤')`` prints as the glyph, the glyph
|
|
166
|
+
#: reads back as ``Atom('$true')``, and the two atoms are ONE constant on every
|
|
167
|
+
#: route: no route may read the glyph-named atom as a propositional letter.
|
|
168
|
+
TRUTH_GLYPH_ATOMS = {"⊤": "$true", "⊥": "$false"}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def truth_constant_word(atom) -> Optional[str]:
|
|
172
|
+
"""The TPTP word of the truth constant ``atom`` is, or ``None`` for any other atom.
|
|
173
|
+
|
|
174
|
+
``'$true'`` for the nullary atoms ``$true`` and ``⊤``, ``'$false'`` for the nullary
|
|
175
|
+
atoms ``$false`` and ``⊥``. An atom WITH arguments is a user predicate that happens
|
|
176
|
+
to be spelled like one of these names and is none of them.
|
|
177
|
+
"""
|
|
178
|
+
if atom.args:
|
|
179
|
+
return None
|
|
180
|
+
if atom.predicate in TPTP_BOOLEAN_CONSTANTS:
|
|
181
|
+
return atom.predicate
|
|
182
|
+
return TRUTH_GLYPH_ATOMS.get(atom.predicate)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def is_tptp_boolean_atom(atom) -> bool:
|
|
186
|
+
"""Whether ``atom`` is one of the truth constants: the nullary atom ``$true`` or
|
|
187
|
+
``$false`` (the propositions TPTP itself defines) or the nullary atom named like
|
|
188
|
+
one of the glyphs ``⊤`` / ``⊥`` (:data:`TRUTH_GLYPH_ATOMS`). None of these is a
|
|
189
|
+
symbol of the user's. A name of these WITH arguments is a user predicate that
|
|
190
|
+
happens to be spelled like a reserved word, and is not."""
|
|
191
|
+
return truth_constant_word(atom) is not None
|
|
192
|
+
|
|
193
|
+
#: An unquoted TPTP predicate, function or constant: a lower-case letter, then
|
|
194
|
+
#: letters, digits and underscores (``lower_word`` of the TPTP grammar).
|
|
195
|
+
_LEGAL_WORD = re.compile(r"[a-z][A-Za-z0-9_]*")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# The check — one implementation, two callers.
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def _label(kind: str) -> str:
|
|
203
|
+
return "constant" if kind == "constant/function" else kind
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _describe(original: str, kind: str) -> str:
|
|
207
|
+
if kind == "numeral":
|
|
208
|
+
return f"the number {original}"
|
|
209
|
+
if kind == "reserved predicate":
|
|
210
|
+
return f"the comparison {original!r}"
|
|
211
|
+
if kind == "reserved function":
|
|
212
|
+
return f"the arithmetic function {original!r}"
|
|
213
|
+
return f"the {_label(kind)} {original!r}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _why(first_kind: str, second_kind: str, dialect: str = "tptp") -> str:
|
|
217
|
+
kinds = {first_kind, second_kind}
|
|
218
|
+
if "variable" in kinds:
|
|
219
|
+
if dialect == "prover9":
|
|
220
|
+
return ("under prolog_style_variables a Prover9 variable is an "
|
|
221
|
+
"upper-case word, so Node.to_prover9 upper-cases a variable's name")
|
|
222
|
+
return ("a TPTP variable is an upper-case word, so Node.to_tptp "
|
|
223
|
+
"upper-cases a variable's name")
|
|
224
|
+
if "numeral" in kinds:
|
|
225
|
+
return "a number is written as its own text, which is also how a constant of that name is written"
|
|
226
|
+
return ("TPTP spells that comparison or arithmetic symbol with a dollar-word "
|
|
227
|
+
"of its own, which a symbol named like it would also be written as")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _refusal(where: str, subject: str, first: Tuple[str, str],
|
|
231
|
+
second: Tuple[str, str], rendered: str,
|
|
232
|
+
dialect: str = "tptp") -> NotImplementedError:
|
|
233
|
+
(prior, prior_kind), (original, kind) = first, second
|
|
234
|
+
if prior_kind in _NAME_KINDS and kind in _NAME_KINDS:
|
|
235
|
+
return NotImplementedError(
|
|
236
|
+
f"{where}: distinct {kind} names {prior!r} and {original!r} would "
|
|
237
|
+
f"both render as the TPTP identifier {rendered!r} (Node.to_tptp folds "
|
|
238
|
+
"only the first character to lower-case, so it cannot tell these two "
|
|
239
|
+
"apart) — refusing to silently merge two distinct symbols into one; "
|
|
240
|
+
f"rename one of them before exporting this {subject}."
|
|
241
|
+
)
|
|
242
|
+
return NotImplementedError(
|
|
243
|
+
f"{where}: {_describe(prior, prior_kind)} and {_describe(original, kind)} would "
|
|
244
|
+
f"both render as the {'Prover9' if dialect == 'prover9' else 'TPTP'} identifier "
|
|
245
|
+
f"{rendered!r} ({_why(prior_kind, kind, dialect)}) "
|
|
246
|
+
"— refusing to silently merge two distinct symbols into one; "
|
|
247
|
+
f"rename one of them before exporting this {subject}."
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _illegal(where: str, subject: str, kind: str, original: str,
|
|
252
|
+
rendered: str, *, sort: bool = False) -> NotImplementedError:
|
|
253
|
+
remedy = (
|
|
254
|
+
"rename it, or build the problem with generate_tptp_problem_with_mapping "
|
|
255
|
+
"or generate_tff_problem_with_mapping, which write such a name under a "
|
|
256
|
+
"legal replacement and return the map that undoes it"
|
|
257
|
+
if subject == "formula" else
|
|
258
|
+
"rename it (generate_tff_problem_with_mapping writes a sort under a legal "
|
|
259
|
+
"replacement; the fof writer does not)")
|
|
260
|
+
what = (f"the sort {original!r} (its guard predicate {rendered!r})" if sort
|
|
261
|
+
else f"the {_label(kind)} name {original!r}")
|
|
262
|
+
if rendered.startswith("$"):
|
|
263
|
+
why = (f"which is a RESERVED TPTP word (every word that starts with '$' is "
|
|
264
|
+
"TPTP's own: $true, $false, $sum, $less, the types $i and $o, and the "
|
|
265
|
+
"'$$' system words; a prover reads it as that word or rejects it, so "
|
|
266
|
+
"it can never name a symbol of the user's)")
|
|
267
|
+
else:
|
|
268
|
+
why = ("which is not a TPTP word (an unquoted TPTP name is a lower-case letter "
|
|
269
|
+
"followed by letters, digits and underscores, and no prover reads any "
|
|
270
|
+
"other)")
|
|
271
|
+
return NotImplementedError(
|
|
272
|
+
f"{where}: {what} would be written as {rendered!r}, "
|
|
273
|
+
f"{why} — refusing to write text that is not TPTP; "
|
|
274
|
+
f"{remedy}."
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _illegal_variable(where: str, subject: str, original: str,
|
|
279
|
+
rendered: str) -> NotImplementedError:
|
|
280
|
+
remedy = (
|
|
281
|
+
"rename the variable, or build the problem with generate_tptp_problem_with_mapping, "
|
|
282
|
+
"generate_tff_problem_with_mapping or generate_tff_arith_problem, which write a "
|
|
283
|
+
"variable under a fresh legal name (a variable is bound, so nothing is recorded)"
|
|
284
|
+
if subject == "formula" else
|
|
285
|
+
"rename the variable")
|
|
286
|
+
return NotImplementedError(
|
|
287
|
+
f"{where}: the variable name {original!r} would be written as {rendered!r}, "
|
|
288
|
+
"which is not a TPTP variable (an unquoted TPTP variable is an upper-case "
|
|
289
|
+
"letter followed by letters, digits and underscores, and no prover reads any "
|
|
290
|
+
"other; a variable is written as the upper-case of its name) — refusing to "
|
|
291
|
+
f"write text that is not TPTP; {remedy}."
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def check_symbols(symbols: Iterable[Symbol], *, where: str, subject: str,
|
|
296
|
+
dialect: str = "tptp", sorts: Iterable[str] = ()) -> None:
|
|
297
|
+
"""Raise ``NotImplementedError`` if two DISTINCT kit symbols of one namespace
|
|
298
|
+
are written as the same TPTP word, or if a name is written as a word that is
|
|
299
|
+
not TPTP.
|
|
300
|
+
|
|
301
|
+
``symbols`` is every symbol the text contains, in the order they were met;
|
|
302
|
+
the same kit name repeating (the ordinary case of one predicate used twice)
|
|
303
|
+
is not a collision, and a function and a constant of one name are one symbol.
|
|
304
|
+
The message names both kit symbols and the shared word; ``where`` prefixes it
|
|
305
|
+
and ``subject`` ("problem" / "formula") ends it. Collisions are decided over
|
|
306
|
+
every symbol first, then each written name must be a legal TPTP word and each
|
|
307
|
+
written variable a legal TPTP variable, so the more specific refusal wins.
|
|
308
|
+
|
|
309
|
+
``dialect="prover9"`` asks for the collision check of the VARIABLES only, worded
|
|
310
|
+
for Prover9 (the same ``upper-case of the name``), and none of the TPTP
|
|
311
|
+
legality rules. ``sorts`` names the kit sorts of the problem: a predicate that
|
|
312
|
+
is the guard of one is reported as that sort when its word is illegal.
|
|
313
|
+
"""
|
|
314
|
+
seen: Dict[Tuple[str, str], Tuple[str, str]] = {}
|
|
315
|
+
written: Dict[Tuple[str, str, str], str] = {}
|
|
316
|
+
variables: Dict[Tuple[str, str], None] = {}
|
|
317
|
+
for namespace, rendered, original, kind in symbols:
|
|
318
|
+
key = (namespace, rendered)
|
|
319
|
+
prior = seen.get(key)
|
|
320
|
+
if prior is None:
|
|
321
|
+
seen[key] = (original, kind)
|
|
322
|
+
elif (prior[0], prior[1] == "numeral") != (original, kind == "numeral"):
|
|
323
|
+
raise _refusal(where, subject, prior, (original, kind), rendered, dialect)
|
|
324
|
+
if kind in _NAME_KINDS:
|
|
325
|
+
written.setdefault((namespace, rendered, original), kind)
|
|
326
|
+
elif kind == "variable":
|
|
327
|
+
variables.setdefault((rendered, original))
|
|
328
|
+
if dialect != "tptp":
|
|
329
|
+
return
|
|
330
|
+
sort_names = frozenset(sorts)
|
|
331
|
+
for (namespace, rendered, original), kind in written.items():
|
|
332
|
+
if not _LEGAL_WORD.fullmatch(rendered):
|
|
333
|
+
raise _illegal(where, subject, kind, original, rendered,
|
|
334
|
+
sort=kind == "predicate" and original in sort_names)
|
|
335
|
+
for rendered, original in variables:
|
|
336
|
+
if not _LEGAL_VARIABLE.fullmatch(rendered):
|
|
337
|
+
raise _illegal_variable(where, subject, original, rendered)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _resolve(writes: Writes) -> Optional[Symbol]:
|
|
341
|
+
resolver, name = writes
|
|
342
|
+
found = resolver(name)
|
|
343
|
+
if found is None:
|
|
344
|
+
return None
|
|
345
|
+
return (found[0], found[1], found[3] if len(found) > 3 else name, found[2])
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _symbols_of(formula) -> Iterator[Symbol]:
|
|
349
|
+
for node in formula.walk():
|
|
350
|
+
writes = node._tptp_symbol()
|
|
351
|
+
if writes is not None:
|
|
352
|
+
symbol = _resolve(writes)
|
|
353
|
+
if symbol is not None:
|
|
354
|
+
yield symbol
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def check_no_symbol_collisions(formulas: Iterable, *, where: str,
|
|
358
|
+
subject: str = "problem",
|
|
359
|
+
sorts: Iterable[str] = ()) -> None:
|
|
360
|
+
""":func:`check_symbols` over every node of every formula in ``formulas``,
|
|
361
|
+
walked in pre-order — the whole-problem check of the writers.
|
|
362
|
+
|
|
363
|
+
Predicates, functions, constants and numerals are one pool for the whole
|
|
364
|
+
problem. A VARIABLE is scoped to its own formula (``∀x P(x)`` in one premise
|
|
365
|
+
and ``∀X Q(X)`` in another are two quantifiers that bind separately), so the
|
|
366
|
+
variables are checked once per formula. ``sorts`` is passed on to
|
|
367
|
+
:func:`check_symbols`."""
|
|
368
|
+
sorts = frozenset(sorts)
|
|
369
|
+
|
|
370
|
+
def pooled() -> Iterator[Symbol]:
|
|
371
|
+
for formula in formulas:
|
|
372
|
+
variables: List[Symbol] = []
|
|
373
|
+
for symbol in _symbols_of(formula):
|
|
374
|
+
if symbol[0] == "variable":
|
|
375
|
+
variables.append(symbol)
|
|
376
|
+
else:
|
|
377
|
+
yield symbol
|
|
378
|
+
check_symbols(variables, where=where, subject=subject)
|
|
379
|
+
check_symbols(pooled(), where=where, subject=subject, sorts=sorts)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def check_variable_names(formula, *, where: str, subject: str = "problem",
|
|
383
|
+
dialect: str = "tptp") -> None:
|
|
384
|
+
"""The variable part of :func:`check_no_symbol_collisions` for ONE formula:
|
|
385
|
+
refuse two distinct variable names that are written as one TPTP variable
|
|
386
|
+
(``x`` and ``X``), and a variable that is written as no TPTP variable at all.
|
|
387
|
+
For a writer that renders a formula itself rather than through
|
|
388
|
+
``Node.to_tptp``, which runs this check on its own.
|
|
389
|
+
|
|
390
|
+
``dialect="prover9"`` is the same check for the Prover9 writer: Prover9 writes
|
|
391
|
+
a variable as the upper-case of its name too, so ``x`` and ``X`` are one
|
|
392
|
+
variable there as well; only the collision is refused (Prover9's own reading
|
|
393
|
+
of a name is not the TPTP grammar)."""
|
|
394
|
+
check_symbols((symbol for symbol in _symbols_of(formula) if symbol[0] == "variable"),
|
|
395
|
+
where=where, subject=subject, dialect=dialect)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def legalise_variables(formula):
|
|
399
|
+
"""``formula`` with every variable that has no TPTP spelling renamed to a fresh
|
|
400
|
+
one; the very same object when there is none.
|
|
401
|
+
|
|
402
|
+
A variable is written as the upper-case of its name, and a TPTP variable is an
|
|
403
|
+
upper-case letter followed by letters, digits and underscores, so ``ä`` (written
|
|
404
|
+
``Ä``), ``x-1`` and ``1x`` cannot be written as they are. A variable is BOUND,
|
|
405
|
+
which is why a writer may rename it without recording anything: the rename is
|
|
406
|
+
alpha-conversion, applied to the binder and to every occurrence alike.
|
|
407
|
+
|
|
408
|
+
* **per formula** — the scope of a variable is one formula, so the caller passes
|
|
409
|
+
one formula at a time;
|
|
410
|
+
* **injective** — distinct illegal names get distinct new names (``ä`` and ``Ä``
|
|
411
|
+
stay two variables, which writing both as ``Ä`` would have made one);
|
|
412
|
+
* **capture-free** — a new name is one of :func:`unicode_logic_kit.fol._identifiers
|
|
413
|
+
.fresh_variables` (``x0``, ``x1``, ... written ``X0``, ``X1``, ...) that equals
|
|
414
|
+
no variable of the formula *as written*: it avoids every name of the formula
|
|
415
|
+
and the lower-case of every written form (``X0`` blocks ``x0``), so a legal
|
|
416
|
+
variable is never taken over.
|
|
417
|
+
|
|
418
|
+
Two LEGAL names that are written as one (``x`` and ``X``) are not touched; they
|
|
419
|
+
are refused by :func:`check_variable_names`, as they always were."""
|
|
420
|
+
from . import _identifiers
|
|
421
|
+
from ._fol_nodes import Variable
|
|
422
|
+
|
|
423
|
+
names: List[str] = []
|
|
424
|
+
seen = set()
|
|
425
|
+
for node in formula.walk():
|
|
426
|
+
if isinstance(node, Variable) and node.name not in seen:
|
|
427
|
+
seen.add(node.name)
|
|
428
|
+
names.append(node.name)
|
|
429
|
+
illegal = [n for n in names if not _LEGAL_VARIABLE.fullmatch(n.upper())]
|
|
430
|
+
if not illegal:
|
|
431
|
+
return formula
|
|
432
|
+
avoid = set(_identifiers.variable_names(formula))
|
|
433
|
+
avoid |= {n.upper().lower() for n in avoid}
|
|
434
|
+
fresh = _identifiers.fresh_variables(len(illegal), letter="x", avoid=avoid)
|
|
435
|
+
table = dict(zip(illegal, fresh))
|
|
436
|
+
|
|
437
|
+
def rename(node):
|
|
438
|
+
if isinstance(node, Variable):
|
|
439
|
+
return Variable(table.get(node.name, node.name))
|
|
440
|
+
return node.map_children(rename)
|
|
441
|
+
|
|
442
|
+
return rename(formula)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ---------------------------------------------------------------------------
|
|
446
|
+
# The guard — one scan per outermost ``to_tptp`` call.
|
|
447
|
+
# ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
class _RenderLog:
|
|
450
|
+
"""What one outermost render has written so far: every distinct
|
|
451
|
+
``(resolver, kit name)`` a node reported, in the order first met."""
|
|
452
|
+
|
|
453
|
+
__slots__ = ("symbols",)
|
|
454
|
+
|
|
455
|
+
def __init__(self) -> None:
|
|
456
|
+
self.symbols: Dict[Writes, None] = {}
|
|
457
|
+
|
|
458
|
+
def note(self, node) -> None:
|
|
459
|
+
writes = node._tptp_symbol()
|
|
460
|
+
if writes is not None:
|
|
461
|
+
self.symbols.setdefault(writes)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
#: The render in progress in THIS context (thread / task), or ``None``.
|
|
465
|
+
_RENDERING: "contextvars.ContextVar[Optional[_RenderLog]]" = contextvars.ContextVar(
|
|
466
|
+
"unicode_logic_kit_tptp_render", default=None)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _scan_rendered_symbols(log: _RenderLog) -> None:
|
|
470
|
+
"""The scan: called exactly once per outermost ``to_tptp`` call that
|
|
471
|
+
returned a text. Looked up by name at call time so a test can count it."""
|
|
472
|
+
resolved = (_resolve(writes) for writes in log.symbols)
|
|
473
|
+
check_symbols((symbol for symbol in resolved if symbol is not None),
|
|
474
|
+
where="Node.to_tptp", subject="formula")
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _outermost(function: Callable) -> Callable:
|
|
478
|
+
@functools.wraps(function)
|
|
479
|
+
def to_tptp(node, *args, **kwargs):
|
|
480
|
+
log = _RENDERING.get()
|
|
481
|
+
if log is not None: # nested: record, do not scan
|
|
482
|
+
log.note(node)
|
|
483
|
+
return function(node, *args, **kwargs)
|
|
484
|
+
log = _RenderLog()
|
|
485
|
+
token = _RENDERING.set(log)
|
|
486
|
+
try:
|
|
487
|
+
log.note(node)
|
|
488
|
+
text = function(node, *args, **kwargs)
|
|
489
|
+
finally:
|
|
490
|
+
_RENDERING.reset(token)
|
|
491
|
+
_scan_rendered_symbols(log)
|
|
492
|
+
return text
|
|
493
|
+
return to_tptp
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class GuardedToTptp:
|
|
497
|
+
"""The descriptor :func:`guard_class` puts in place of a ``to_tptp`` method.
|
|
498
|
+
|
|
499
|
+
``Class.to_tptp`` is the guarded plain function. ``node.to_tptp`` is the
|
|
500
|
+
guarded bound method when no render is in progress, and the ORIGINAL bound
|
|
501
|
+
method (after recording the node) when one is — which is what keeps a
|
|
502
|
+
nested call from costing a stack frame. ``__wrapped__`` is the original
|
|
503
|
+
function.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
def __init__(self, function: Callable) -> None:
|
|
507
|
+
for attribute in ("__module__", "__name__", "__qualname__", "__doc__"):
|
|
508
|
+
setattr(self, attribute, getattr(function, attribute))
|
|
509
|
+
self.__wrapped__ = function
|
|
510
|
+
self._function = function
|
|
511
|
+
self._guarded = _outermost(function)
|
|
512
|
+
|
|
513
|
+
def __get__(self, node, owner=None):
|
|
514
|
+
if node is None:
|
|
515
|
+
return self._guarded
|
|
516
|
+
log = _RENDERING.get()
|
|
517
|
+
if log is None:
|
|
518
|
+
return types.MethodType(self._guarded, node)
|
|
519
|
+
# Nested: record what this node writes (``_RenderLog.note``, inlined —
|
|
520
|
+
# this runs once per node of every rendered formula) and hand back the
|
|
521
|
+
# ORIGINAL method.
|
|
522
|
+
writes = node._tptp_symbol()
|
|
523
|
+
if writes is not None:
|
|
524
|
+
log.symbols.setdefault(writes)
|
|
525
|
+
return types.MethodType(self._function, node)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def is_guarded(cls: type) -> bool:
|
|
529
|
+
"""Whether ``cls.to_tptp`` (resolved through the MRO, unbound) is guarded."""
|
|
530
|
+
return isinstance(inspect.getattr_static(cls, "to_tptp", None), GuardedToTptp)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def guard_class(cls: type) -> None:
|
|
534
|
+
"""Make ``cls.to_tptp`` guarded, whatever it resolves to through the MRO.
|
|
535
|
+
|
|
536
|
+
Called from ``Node.__init_subclass__`` for every node class, and once for
|
|
537
|
+
``Node`` itself. A ``to_tptp`` that is already guarded (inherited) is left
|
|
538
|
+
alone; one inherited from a mixin is wrapped on ``cls``. A ``to_tptp`` that
|
|
539
|
+
is not a plain function (a ``staticmethod``, a property) cannot be guarded
|
|
540
|
+
and is refused at class-creation time rather than left as a hole.
|
|
541
|
+
"""
|
|
542
|
+
current = inspect.getattr_static(cls, "to_tptp", None)
|
|
543
|
+
if current is None or isinstance(current, GuardedToTptp):
|
|
544
|
+
return
|
|
545
|
+
if not isinstance(current, types.FunctionType):
|
|
546
|
+
raise TypeError(
|
|
547
|
+
f"{cls.__qualname__}.to_tptp is a {type(current).__name__}, not a plain "
|
|
548
|
+
"method: the single-formula collision guard (fol/_tptp_symbols.py) "
|
|
549
|
+
"can only wrap a plain method. Define to_tptp(self) as a regular "
|
|
550
|
+
"method.")
|
|
551
|
+
setattr(cls, "to_tptp", GuardedToTptp(current))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""The two truth constants ``$true`` and ``$false``, as every route reads them.
|
|
2
|
+
|
|
3
|
+
TPTP defines two propositions of its own, ``$true`` and ``$false``, and this kit
|
|
4
|
+
reads them (:mod:`unicode_logic_kit.fol.tptp_input`, the SMT-LIB reader, the QMLTP
|
|
5
|
+
reader) as the NULLARY atoms ``Atom('$true')`` / ``Atom('$false')``. They are the
|
|
6
|
+
constants truth and falsity, not propositional letters of the user's:
|
|
7
|
+
|
|
8
|
+
* a classical route reads them as true and false (``$false ⊢ P`` is valid,
|
|
9
|
+
``⊢ $true`` is valid, ``⊢ $false`` is not, ``$true ⊢ P`` is not);
|
|
10
|
+
* a modal route reads them the same at every world, an intuitionistic route
|
|
11
|
+
forces ``$true`` at every world and ``$false`` at none, a many-valued route
|
|
12
|
+
gives them the top and the bottom value of the matrix;
|
|
13
|
+
* a logic that has no agreed reading of them refuses them BY NAME, through
|
|
14
|
+
:func:`refuse_truth_constants` (relevant logic has two truths and two falsities,
|
|
15
|
+
linear logic has an additive and a multiplicative unit of each, the Lambek
|
|
16
|
+
calculus has no propositional constants at all).
|
|
17
|
+
|
|
18
|
+
The unicode glyphs are ``⊤`` and ``⊥`` (LaTeX ``\\top`` / ``\\bot``); every unicode
|
|
19
|
+
grammar mode that has propositional atoms reads them back to these two atoms. A
|
|
20
|
+
nullary atom that is merely NAMED like a glyph (``Atom('⊥', ())``, which prints as
|
|
21
|
+
``⊥`` exactly like ``Atom('$false', ())`` does, and which reads back as
|
|
22
|
+
``$false``) is the same constant: one glyph has one meaning, so no route reads
|
|
23
|
+
either atom as a propositional letter. ``$true`` / ``⊤`` WITH arguments is an
|
|
24
|
+
ordinary (if oddly spelled) user predicate and is neither constant:
|
|
25
|
+
:func:`truth_value` answers ``None`` for it.
|
|
26
|
+
|
|
27
|
+
This module only RECOGNISES the constants. It builds no formula and has no
|
|
28
|
+
dependency on any route, so every evaluator and prover can import it.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from typing import Iterable, Optional, Tuple
|
|
32
|
+
|
|
33
|
+
from ._fol_nodes import Atom, Node
|
|
34
|
+
from ._tptp_symbols import truth_constant_word
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"TRUE_PREDICATE", "FALSE_PREDICATE", "TRUE", "FALSE",
|
|
38
|
+
"truth_value", "is_true_constant", "is_false_constant", "is_truth_constant",
|
|
39
|
+
"truth_constants_in", "refuse_truth_constants",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
#: The predicate names of the two constants, as the TPTP reader spells them.
|
|
43
|
+
TRUE_PREDICATE = "$true"
|
|
44
|
+
FALSE_PREDICATE = "$false"
|
|
45
|
+
|
|
46
|
+
#: The two constants as nodes.
|
|
47
|
+
TRUE: Atom = Atom(TRUE_PREDICATE, ())
|
|
48
|
+
FALSE: Atom = Atom(FALSE_PREDICATE, ())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def truth_value(node: Node) -> Optional[bool]:
|
|
52
|
+
"""``True`` for the nullary atoms ``$true`` and ``⊤``, ``False`` for ``$false`` and
|
|
53
|
+
``⊥``, else ``None``.
|
|
54
|
+
|
|
55
|
+
``None`` also for these names WITH arguments (a user predicate that
|
|
56
|
+
happens to be spelled like a reserved word) and for every node that is not an
|
|
57
|
+
atom, so ``truth_value(node) is None`` reads "an ordinary node".
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(node, Atom):
|
|
60
|
+
word = truth_constant_word(node)
|
|
61
|
+
if word is not None:
|
|
62
|
+
return word == TRUE_PREDICATE
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_true_constant(node: Node) -> bool:
|
|
67
|
+
"""Whether ``node`` is the truth constant true: the nullary atom ``$true`` or ``⊤``."""
|
|
68
|
+
return truth_value(node) is True
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_false_constant(node: Node) -> bool:
|
|
72
|
+
"""Whether ``node`` is the falsity constant: the nullary atom ``$false`` or ``⊥``."""
|
|
73
|
+
return truth_value(node) is False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_truth_constant(node: Node) -> bool:
|
|
77
|
+
"""Whether ``node`` is one of the two truth constants."""
|
|
78
|
+
return truth_value(node) is not None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def truth_constants_in(formulas: Iterable[Node]) -> Tuple[Atom, ...]:
|
|
82
|
+
"""The distinct truth constants occurring anywhere in ``formulas``, in order.
|
|
83
|
+
|
|
84
|
+
Each constant is listed once, as its first occurrence: ``$false`` and ``⊥`` are
|
|
85
|
+
one constant, so a formula that holds both lists the one that comes first.
|
|
86
|
+
"""
|
|
87
|
+
seen = []
|
|
88
|
+
values = []
|
|
89
|
+
for formula in formulas:
|
|
90
|
+
for node in formula.walk():
|
|
91
|
+
value = truth_value(node)
|
|
92
|
+
if value is not None and value not in values:
|
|
93
|
+
values.append(value)
|
|
94
|
+
seen.append(node)
|
|
95
|
+
return tuple(seen)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def refuse_truth_constants(formulas: Iterable[Node], route: str, why: str,
|
|
99
|
+
error: type = NotImplementedError) -> None:
|
|
100
|
+
"""Raise ``error`` naming ``route`` if ``formulas`` use a truth constant.
|
|
101
|
+
|
|
102
|
+
For a route whose logic has no agreed reading of ``$true`` / ``$false``. The
|
|
103
|
+
message names the route, the constant, and ``why`` the logic has no reading, so
|
|
104
|
+
the refusal is a statement and not a guess. ``error`` is the exception class the
|
|
105
|
+
route already uses for input it does not support (``NotImplementedError`` unless
|
|
106
|
+
the route says otherwise). Returns ``None`` when the formulas use neither
|
|
107
|
+
constant.
|
|
108
|
+
"""
|
|
109
|
+
found = truth_constants_in(formulas)
|
|
110
|
+
if not found:
|
|
111
|
+
return
|
|
112
|
+
names = " and ".join(a.predicate for a in found)
|
|
113
|
+
raise error(
|
|
114
|
+
f"{route}: the truth constant {names} has no agreed reading here ({why}); "
|
|
115
|
+
"this route refuses it by name rather than read it as a propositional "
|
|
116
|
+
"letter. Write the formula without the constant, or decide it in a logic "
|
|
117
|
+
"that has one (classical, intuitionistic, modal, K3 / LP / FDE, fuzzy).")
|