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,687 @@
|
|
|
1
|
+
"""Second-order logic → HOL/THF export (predicate quantification mapped natively).
|
|
2
|
+
|
|
3
|
+
Classical second-order logic (SOL) extends first-order logic with quantification
|
|
4
|
+
over *predicate* variables: ``∀P φ`` / ``∃P φ`` where ``P`` ranges over relations
|
|
5
|
+
of a fixed arity on the domain (the toolkit's finite-model partner is
|
|
6
|
+
:func:`unicode_logic_kit.semantics.secondorder.satisfies_so`, which brute-forces
|
|
7
|
+
every relation ``R ⊆ domain ** arity``). A higher-order logic prover already *has*
|
|
8
|
+
predicate quantification, so the embedding here is direct rather than the shallow
|
|
9
|
+
world-lifting used for modal logic:
|
|
10
|
+
|
|
11
|
+
- an object variable ``x`` has THF type ``$i`` (Isabelle: an uninterpreted ``i``);
|
|
12
|
+
- a predicate variable ``P`` of arity ``k`` has THF type ``$i > … > $i > $o``
|
|
13
|
+
(``k`` copies of ``$i``), i.e. Isabelle ``i ⇒ … ⇒ i ⇒ bool``;
|
|
14
|
+
- a first-order atom ``A(t̄)`` is the application ``( a @ t1 @ … @ tk )``;
|
|
15
|
+
- an object quantifier ``∀x`` / ``∃x`` is a THF ``! [X:$i]`` / ``? [X:$i]``;
|
|
16
|
+
- a **second-order** quantifier ``∀P/k`` / ``∃P/k`` is a THF
|
|
17
|
+
``! [P: ($i>…>$i>$o)]`` / ``? [P: …]`` — a quantifier over a predicate-typed
|
|
18
|
+
variable, exactly mirroring the SOL reading.
|
|
19
|
+
|
|
20
|
+
This is the **monadic-and-up relational** second order: predicate (relation)
|
|
21
|
+
variables of any arity, including arity 0 (a Boolean / propositional variable,
|
|
22
|
+
type ``$o``). It does NOT cover function-variable quantification or third order
|
|
23
|
+
and up — out of scope, matching ``satisfies_so``.
|
|
24
|
+
|
|
25
|
+
**Free symbols.** In the toolkit's ``second_order`` parser mode a bare lowercase
|
|
26
|
+
name parses to a :class:`~unicode_logic_kit.fol.nodes.Variable`. Any object
|
|
27
|
+
variable that is *not* bound by an enclosing object quantifier is a **free**
|
|
28
|
+
individual; in a closed THF problem / Isabelle theory a free variable is not
|
|
29
|
+
admissible, so such free variables are emitted as object **constants** (declared
|
|
30
|
+
``$i`` / ``consts … :: i``) — the natural reading of a free individual symbol, and
|
|
31
|
+
exactly how the finite-model evaluator would have to interpret it (via the
|
|
32
|
+
structure's ``constants``). Bound object variables render as THF uppercase
|
|
33
|
+
variables. A predicate name bound by some ``SecondOrderQuantifier`` is a bound
|
|
34
|
+
predicate variable everywhere it occurs (it is never declared as a problem
|
|
35
|
+
symbol); any *other* predicate is a free predicate and is declared in the
|
|
36
|
+
signature.
|
|
37
|
+
|
|
38
|
+
**Constants and free variables.** A free object variable is a PARAMETER of the problem (one
|
|
39
|
+
unknown element, the same in every formula, see :mod:`unicode_logic_kit.fol._free_parameters`)
|
|
40
|
+
and a constant is a particular element that its name stands for, so ``P(x)`` with a free ``x``
|
|
41
|
+
and ``P('x')`` are two statements. Both are declared as individuals, but as two symbols under
|
|
42
|
+
two functors even when they are spelled alike (``x`` and ``x_2``: the variable claims the plain
|
|
43
|
+
name). A constant is never looked up among the bound variables, so no binder, of any name,
|
|
44
|
+
captures it. A constant of any name has a legal identifier in both targets: a non-ASCII letter
|
|
45
|
+
is transliterated, any other character that is no letter, digit or underscore becomes an
|
|
46
|
+
underscore, a name that would start with a digit or an underscore gets a ``p`` in front, and the
|
|
47
|
+
functors are made unique over every kind of symbol, so two names that share a stem (``a b`` and
|
|
48
|
+
``a_b``) are still two symbols.
|
|
49
|
+
|
|
50
|
+
**Standard vs Henkin semantics.** In a real HOL prover the predicate quantifiers
|
|
51
|
+
range over the prover's full function space, i.e. **standard (full) second-order
|
|
52
|
+
semantics** — the same semantics ``satisfies_so`` realises on a finite domain.
|
|
53
|
+
SOL validity under standard semantics is **not semi-decidable** (no complete
|
|
54
|
+
proof procedure exists), so an external HOL prover (Leo-III, Satallax,
|
|
55
|
+
Sledgehammer) may fail to discharge a valid conjecture even though it is sound.
|
|
56
|
+
If you want Henkin (general-models) semantics instead — under which SOL is
|
|
57
|
+
effectively first-order and *is* axiomatisable — that is the user's job: add
|
|
58
|
+
comprehension axioms and restrict the predicate domain explicitly; this module
|
|
59
|
+
does not do it for you. The toolkit only **emits** the THF problem / Isabelle
|
|
60
|
+
theory; it does **not** run any prover.
|
|
61
|
+
|
|
62
|
+
Equality ``=`` / ``≠`` follows the toolkit convention used by the modal THF
|
|
63
|
+
export: an ordinary uninterpreted predicate over ``$i`` (rendered ``feq`` /
|
|
64
|
+
``fneq``), **not** primitive HOL identity. (The finite-model ``satisfies_so`` does
|
|
65
|
+
read ``=`` as identity via the structure; if you need that in the prover, add the
|
|
66
|
+
reflexivity / Leibniz axioms, or post-edit to primitive ``=``.)
|
|
67
|
+
|
|
68
|
+
**Numerals.** A numeral is an individual constant identified by its VALUE, written
|
|
69
|
+
``n1``, ``n2.5`` (``n2_5`` once sanitised): ``1`` and ``1.0`` are one constant, and
|
|
70
|
+
nothing else is known about it. ``+ - * /`` are free function symbols and ``< > ≤ ≥`` free
|
|
71
|
+
predicates. A numeral spelled like another symbol of the formula (the constant ``n1`` next
|
|
72
|
+
to the number ``1``, or a free object variable ``n1``, which this export declares as an
|
|
73
|
+
individual constant) is refused by name, never merged with it. The one numeral that stays
|
|
74
|
+
a number is the operand of a comparison with a cardinality, which :func:`to_isabelle_so`
|
|
75
|
+
reads over the naturals (see above).
|
|
76
|
+
|
|
77
|
+
Public API: :func:`to_thf_so`, :func:`to_isabelle_so`.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
from typing import Dict, FrozenSet, List, Set, Tuple
|
|
81
|
+
|
|
82
|
+
from ..fol.nodes import (
|
|
83
|
+
Node, Variable, Constant, Number, Function,
|
|
84
|
+
Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
85
|
+
SecondOrderQuantifier, Cardinality, SortedCardinality,
|
|
86
|
+
)
|
|
87
|
+
from ..fol._numeral_symbols import numeral_name, numerals_as_constants, prefixed_numeral_name
|
|
88
|
+
from ..fol._symbol_names import dedupe as _dedupe # shared de-collision helper
|
|
89
|
+
from ..fol._truth_constants import truth_value
|
|
90
|
+
from ._ho_common import FREE_VARIABLE, CONSTANT, constant_name, functor_stem
|
|
91
|
+
from ._isabelle_binders import (
|
|
92
|
+
PREDICATE, VARIABLE, binder_tokens, collect_binders, declared_names,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
_FORALL = "∀"
|
|
96
|
+
_EXISTS = "∃"
|
|
97
|
+
|
|
98
|
+
# `=` / `≠` are uninterpreted structure-relative predicates in this toolkit, NOT
|
|
99
|
+
# primitive HOL identity. Give them valid distinct THF/Isabelle functors.
|
|
100
|
+
_PRED_ALIAS = {"=": "feq", "≠": "fneq"}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ===========================================================================
|
|
104
|
+
# Name sanitising (shared spirit with qml._thf_name)
|
|
105
|
+
# ===========================================================================
|
|
106
|
+
|
|
107
|
+
def _functor(name: str) -> str:
|
|
108
|
+
"""Turn a free predicate/constant/function name into a HOL functor stem (NOT injective).
|
|
109
|
+
|
|
110
|
+
The stem is the kit's ASCII lower word (:func:`~unicode_logic_kit.hol._ho_common.functor_stem`):
|
|
111
|
+
a non-ASCII letter is transliterated, any other character that is no letter, digit or
|
|
112
|
+
underscore becomes an underscore, and a name that would start with a digit or an underscore
|
|
113
|
+
gets a ``p`` in front, so every name has a stem that both THF and Isabelle read as one
|
|
114
|
+
identifier. A name that is already a lower-case word is its own stem.
|
|
115
|
+
"""
|
|
116
|
+
if name in _PRED_ALIAS:
|
|
117
|
+
return _PRED_ALIAS[name]
|
|
118
|
+
return functor_stem(name)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ===========================================================================
|
|
122
|
+
# De-colliding name resolvers (THF only; the Isabelle path keeps _functor)
|
|
123
|
+
# ===========================================================================
|
|
124
|
+
#
|
|
125
|
+
# Two distinct collision hazards motivate these resolvers, and BOTH are specific
|
|
126
|
+
# to THF (where bound variables are uppercase and free symbols are lowercase
|
|
127
|
+
# functors, and every functor needs exactly one type):
|
|
128
|
+
#
|
|
129
|
+
# (1) FREE-SYMBOL COLLAPSE (soundness). A free predicate ``P`` and a free
|
|
130
|
+
# individual ``p`` both pass through :func:`_functor` to the SAME functor
|
|
131
|
+
# ``p`` but need DIFFERENT types (``$i>$o`` vs ``$i``); likewise ``Foo`` and
|
|
132
|
+
# ``foo``. Emitting both yields a duplicate ``p_decl`` with conflicting types
|
|
133
|
+
# and an ill-typed ``( p @ p )``. :class:`_FreeNames` assigns every free
|
|
134
|
+
# symbol a globally UNIQUE functor across the predicate / individual /
|
|
135
|
+
# function categories, and BOTH the declarations and the usages are routed
|
|
136
|
+
# through it so they always agree.
|
|
137
|
+
#
|
|
138
|
+
# (2) BOUND-VARIABLE CAPTURE (faithfulness). A bound predicate variable renders
|
|
139
|
+
# as ``name.upper()`` and a bound object variable renders as ``name.upper()``
|
|
140
|
+
# too, so case-folding-equal names in nested binders collide: ``∃P ∀p P(p)``
|
|
141
|
+
# would emit ``? [P:($i>$o)] : ! [P:$i] : ( P @ P )`` — the object binder
|
|
142
|
+
# shadows the predicate binder and ``P @ P`` is ill-typed. :class:`_Scope`
|
|
143
|
+
# gives bound predicate-variables and bound object-variables ONE shared,
|
|
144
|
+
# never-reused pool of uppercase tokens, so distinct bound ``(kind, name)``
|
|
145
|
+
# pairs always get distinct tokens.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class _FreeNames:
|
|
149
|
+
"""Resolve every FREE symbol to a globally-unique THF functor.
|
|
150
|
+
|
|
151
|
+
Built once from :func:`_signature` so that declarations (:func:`_thf_signature`)
|
|
152
|
+
and usages (:func:`_thf`, :func:`_thf_term`) draw the SAME functor for the
|
|
153
|
+
same symbol. Predicates, individuals (constants / numbers / free object
|
|
154
|
+
variables) and functions live in three lookup tables but share ONE pool of
|
|
155
|
+
emitted functors, so a free predicate ``P`` and a free individual ``p`` — both
|
|
156
|
+
naively ``_functor``-ing to ``p`` — get distinct functors (``p`` and, say,
|
|
157
|
+
``p_1``). Iteration is sorted within each category and categories are claimed
|
|
158
|
+
predicate → individual → function, so the result is deterministic.
|
|
159
|
+
|
|
160
|
+
A constant (``ind``) and a free object variable (``var``) are two symbols even when
|
|
161
|
+
they are spelled alike: the variable is a parameter of the problem, one unknown element,
|
|
162
|
+
and the constant a particular one, so ``P(x)`` with a free ``x`` and ``P('x')`` are two
|
|
163
|
+
statements. Both are declared as individuals of type ``$i``, under two functors; the free
|
|
164
|
+
variable claims the shared name first (``x``), the constant gets the next one (``x_2``).
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, formula: Node):
|
|
168
|
+
pred_arity, const_names, func_arity, free_vars = _signature(formula)
|
|
169
|
+
used = set()
|
|
170
|
+
self.pred: Dict[str, str] = {}
|
|
171
|
+
self.ind: Dict[str, str] = {}
|
|
172
|
+
self.var: Dict[str, str] = {}
|
|
173
|
+
self.func: Dict[str, str] = {}
|
|
174
|
+
# The names the Isabelle text gives its binders, ``{(kind, name): token}``: filled by
|
|
175
|
+
# :func:`to_isabelle_so` once the declarations are known (THF names its binders itself).
|
|
176
|
+
self.binder: Dict[Tuple[str, str], str] = {}
|
|
177
|
+
# Predicates first (they tend to keep the more meaningful name), then
|
|
178
|
+
# individuals, then functions — each de-colliding against the shared pool.
|
|
179
|
+
for name in sorted(pred_arity):
|
|
180
|
+
self.pred[name] = _dedupe(_functor(name), used)
|
|
181
|
+
for kind, name in _individual_symbols(const_names, free_vars):
|
|
182
|
+
(self.var if kind == FREE_VARIABLE else self.ind)[name] = _dedupe(_functor(name), used)
|
|
183
|
+
for name in sorted(func_arity):
|
|
184
|
+
self.func[name] = _dedupe(_functor(name), used)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class _Scope:
|
|
188
|
+
"""A shared, never-reused token pool for bound predicate AND object variables.
|
|
189
|
+
|
|
190
|
+
A binder calls :meth:`bind`, which returns a *new* :class:`_Scope` whose
|
|
191
|
+
mapping is the parent's extended with a fresh uppercase token for this
|
|
192
|
+
``(kind, name)`` — fresh against EVERY token in scope, regardless of kind, so
|
|
193
|
+
a predicate binder ``P`` and an inner object binder ``p`` never both render as
|
|
194
|
+
``P``. The token is looked up by ``(kind, name)`` so a bound variable resolves
|
|
195
|
+
to the same token at its binder and at every applied occurrence.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
__slots__ = ("_tokens", "_used")
|
|
199
|
+
|
|
200
|
+
def __init__(self, tokens=None, used=None):
|
|
201
|
+
self._tokens: Dict = dict(tokens or {})
|
|
202
|
+
self._used = set(used or ())
|
|
203
|
+
|
|
204
|
+
def bind(self, kind: str, name: str) -> "_Scope":
|
|
205
|
+
used = set(self._used)
|
|
206
|
+
base = _functor(name).upper() or "V"
|
|
207
|
+
# An all-non-alnum name could upper-case to something starting with a
|
|
208
|
+
# digit / underscore; guarantee a legal THF variable head.
|
|
209
|
+
if not base[:1].isalpha():
|
|
210
|
+
base = "V" + base
|
|
211
|
+
token = _dedupe(base, used)
|
|
212
|
+
tokens = dict(self._tokens)
|
|
213
|
+
tokens[(kind, name)] = token
|
|
214
|
+
return _Scope(tokens, used)
|
|
215
|
+
|
|
216
|
+
def token(self, kind: str, name: str) -> str:
|
|
217
|
+
return self._tokens[(kind, name)]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ===========================================================================
|
|
221
|
+
# Scope analysis: bound predicate vars, bound object vars, free symbols
|
|
222
|
+
# ===========================================================================
|
|
223
|
+
|
|
224
|
+
def _bound_preds(formula: Node) -> FrozenSet[str]:
|
|
225
|
+
"""Names bound by some ``SecondOrderQuantifier`` (-> bound predicate vars)."""
|
|
226
|
+
return frozenset(
|
|
227
|
+
n.predicate for n in formula.walk()
|
|
228
|
+
if isinstance(n, SecondOrderQuantifier)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _so_arity(formula: Node) -> Dict[str, int]:
|
|
233
|
+
"""Map each second-order-bound predicate name to its binder-recorded arity."""
|
|
234
|
+
return {
|
|
235
|
+
n.predicate: n.arity
|
|
236
|
+
for n in formula.walk()
|
|
237
|
+
if isinstance(n, SecondOrderQuantifier)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _free_object_vars(formula: Node, bound=frozenset()) -> FrozenSet[str]:
|
|
242
|
+
"""Object-variable names occurring free (not under an enclosing ∀x/∃x).
|
|
243
|
+
|
|
244
|
+
Recurses tracking the enclosing object-quantifier scope. SecondOrderQuantifier
|
|
245
|
+
binds a *predicate* name, not an object variable, so it does not change the
|
|
246
|
+
object-binding scope.
|
|
247
|
+
"""
|
|
248
|
+
if isinstance(formula, Variable):
|
|
249
|
+
return frozenset() if formula.name in bound else frozenset({formula.name})
|
|
250
|
+
if isinstance(formula, Quantifier):
|
|
251
|
+
return _free_object_vars(formula.formula, bound | {formula.variable.name})
|
|
252
|
+
if isinstance(formula, (Cardinality, SortedCardinality)):
|
|
253
|
+
# |{v : φ}| BINDS v over its matrix — a generic child walk would report
|
|
254
|
+
# the bound v as free (the binder-blind defect class fixed in 0.16.0).
|
|
255
|
+
return _free_object_vars(formula.formula, bound | {formula.variable.name})
|
|
256
|
+
free = set()
|
|
257
|
+
for child in formula._child_nodes():
|
|
258
|
+
free |= _free_object_vars(child, bound)
|
|
259
|
+
return frozenset(free)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _numerals_as_constants(formula: Node, where: str) -> Node:
|
|
263
|
+
"""``formula`` with every numeral written as the constant ``n1``, ``n2.5``, ... of its VALUE.
|
|
264
|
+
|
|
265
|
+
A numeral is a constant identified by its value (``1`` and ``1.0`` are one constant), and
|
|
266
|
+
``+ - * /`` and ``< > ≤ ≥`` are ordinary function and predicate symbols here. The one
|
|
267
|
+
numeral left as a ``Number`` is the operand of a comparison with a cardinality
|
|
268
|
+
(``|{x : P(x)}| ≥ 2``): that comparison is arithmetic over the naturals (HOL's ``card``),
|
|
269
|
+
and the operand is the number it compares the count with, not an individual.
|
|
270
|
+
|
|
271
|
+
Raises:
|
|
272
|
+
NotImplementedError: a constant, a function or a free object variable (which this
|
|
273
|
+
export reads as an individual constant) is spelled like the constant of a
|
|
274
|
+
numeral of the formula (``n1`` next to the number ``1``): they would be ONE
|
|
275
|
+
symbol, and a numeral is not the constant of the same spelling.
|
|
276
|
+
"""
|
|
277
|
+
[rewritten], names = numerals_as_constants([formula], where=where, spell=prefixed_numeral_name,
|
|
278
|
+
counting_comparisons=True)
|
|
279
|
+
clash = sorted(names & _free_object_vars(rewritten))
|
|
280
|
+
if clash:
|
|
281
|
+
raise NotImplementedError(
|
|
282
|
+
f"{where}: the free object variable {clash[0]!r} is exported as an individual "
|
|
283
|
+
"constant of that name, and so is the numeral whose constant is spelled alike, so "
|
|
284
|
+
"the two would read as one name. A numeral is a constant of its own, identified by "
|
|
285
|
+
f"its value, and the kit does not put it next to a variable of its spelling; "
|
|
286
|
+
f"rename the variable {clash[0]!r}.")
|
|
287
|
+
return rewritten
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _signature(formula: Node):
|
|
291
|
+
"""Collect free predicates, constants, functions and free object variables.
|
|
292
|
+
|
|
293
|
+
Returns ``(pred_arity, const_names, func_arity, free_vars)``:
|
|
294
|
+
|
|
295
|
+
- ``pred_arity``: free predicate name -> arity (atoms whose predicate is not a
|
|
296
|
+
bound second-order variable);
|
|
297
|
+
- ``const_names``: explicit constants (a numeral is the constant of its value, see
|
|
298
|
+
:func:`_numerals_as_constants`);
|
|
299
|
+
- ``func_arity``: function name -> arity;
|
|
300
|
+
- ``free_vars``: the free object variables, which the closed export declares as
|
|
301
|
+
individual constants too but which stay apart from a constant of their spelling.
|
|
302
|
+
"""
|
|
303
|
+
so_names = _bound_preds(formula)
|
|
304
|
+
free_vars = _free_object_vars(formula)
|
|
305
|
+
pred_arity: Dict[str, int] = {}
|
|
306
|
+
const_names: Set[str] = set()
|
|
307
|
+
func_arity: Dict[str, int] = {}
|
|
308
|
+
for n in formula.walk():
|
|
309
|
+
if isinstance(n, Atom):
|
|
310
|
+
if n.predicate in so_names or truth_value(n) is not None:
|
|
311
|
+
continue
|
|
312
|
+
pred_arity[n.predicate] = len(n.args)
|
|
313
|
+
elif isinstance(n, SortedCardinality):
|
|
314
|
+
# The sort guard becomes a free unary predicate over the matrix.
|
|
315
|
+
pred_arity.setdefault(n.sort, 1)
|
|
316
|
+
elif isinstance(n, Constant):
|
|
317
|
+
const_names.add(constant_name(n))
|
|
318
|
+
elif isinstance(n, Function):
|
|
319
|
+
func_arity[n.name] = len(n.args)
|
|
320
|
+
return pred_arity, const_names, func_arity, free_vars
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _individual_symbols(const_names, free_vars) -> List[Tuple[str, str]]:
|
|
324
|
+
"""The individual symbols as ``(kind, name)``, sorted by name, a free variable before a
|
|
325
|
+
constant of its spelling (for a formula without such a pair: the sorted names)."""
|
|
326
|
+
symbols = ([(FREE_VARIABLE, name) for name in free_vars]
|
|
327
|
+
+ [(CONSTANT, name) for name in const_names])
|
|
328
|
+
return sorted(symbols, key=lambda symbol: (symbol[1], symbol[0] != FREE_VARIABLE))
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# ===========================================================================
|
|
332
|
+
# (A) TPTP THF export
|
|
333
|
+
# ===========================================================================
|
|
334
|
+
|
|
335
|
+
def _thf_pred_type(arity: int) -> str:
|
|
336
|
+
"""THF type of an arity-``k`` predicate variable: ``$i > … > $i > $o``.
|
|
337
|
+
|
|
338
|
+
Arity 0 is the Boolean case, type ``$o``.
|
|
339
|
+
"""
|
|
340
|
+
return " > ".join(["$i"] * arity + ["$o"]) if arity else "$o"
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _thf_term(node: Node, scope: "_Scope", free: "_FreeNames") -> str:
|
|
344
|
+
"""Render an individual term in THF.
|
|
345
|
+
|
|
346
|
+
A ``Variable`` bound by an enclosing object quantifier renders as its scope
|
|
347
|
+
token (an uppercase THF variable, de-collided against every bound symbol);
|
|
348
|
+
otherwise it is a free individual functor drawn from ``free``. Constants /
|
|
349
|
+
numbers / functions resolve through ``free`` so a usage always matches its
|
|
350
|
+
declaration. A constant is never looked up among the bound variables.
|
|
351
|
+
"""
|
|
352
|
+
if isinstance(node, Variable):
|
|
353
|
+
try:
|
|
354
|
+
return scope.token("obj", node.name)
|
|
355
|
+
except KeyError:
|
|
356
|
+
return free.var[node.name] # free individual functor
|
|
357
|
+
if isinstance(node, Constant):
|
|
358
|
+
return free.ind[node.name]
|
|
359
|
+
if isinstance(node, Number):
|
|
360
|
+
raise NotImplementedError(
|
|
361
|
+
f"to_thf_so: the number {numeral_name(node.value)} is compared with a cardinality, "
|
|
362
|
+
"and TH0 has no built-in finite-set theory to state that — use to_isabelle_so, "
|
|
363
|
+
"which embeds |{v : φ}| as HOL's ``card {v. φ}`` over the naturals.")
|
|
364
|
+
if isinstance(node, Function):
|
|
365
|
+
head = free.func[node.name]
|
|
366
|
+
return "( " + " @ ".join([head] + [_thf_term(a, scope, free) for a in node.args]) + " )"
|
|
367
|
+
if isinstance(node, (Cardinality, SortedCardinality)):
|
|
368
|
+
raise NotImplementedError(
|
|
369
|
+
"to_thf_so: TH0 has no built-in finite-set theory, so a faithful "
|
|
370
|
+
"cardinality encoding is not available here — use to_isabelle_so, "
|
|
371
|
+
"which embeds |{v : φ}| as HOL's ``card {v. φ}``.")
|
|
372
|
+
raise NotImplementedError(
|
|
373
|
+
f"to_thf_so: unsupported term {type(node).__name__}.")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _thf(node: Node, scope: "_Scope", free: "_FreeNames") -> str:
|
|
377
|
+
"""Render a second-order formula as a THF term of type ``$o``.
|
|
378
|
+
|
|
379
|
+
``scope`` resolves currently-bound predicate-variable and object-variable
|
|
380
|
+
names to their (mutually de-collided) uppercase THF tokens; ``free`` resolves
|
|
381
|
+
free symbols to their unique functors.
|
|
382
|
+
"""
|
|
383
|
+
if isinstance(node, Atom):
|
|
384
|
+
if truth_value(node) is not None:
|
|
385
|
+
return "$true" if truth_value(node) else "$false"
|
|
386
|
+
try:
|
|
387
|
+
head = scope.token("pred", node.predicate) # bound predicate VARIABLE
|
|
388
|
+
except KeyError:
|
|
389
|
+
head = free.pred[node.predicate] # free predicate functor
|
|
390
|
+
if not node.args:
|
|
391
|
+
return head
|
|
392
|
+
return "( " + " @ ".join([head] + [_thf_term(a, scope, free) for a in node.args]) + " )"
|
|
393
|
+
if isinstance(node, Not):
|
|
394
|
+
return f"( ~ {_thf(node.formula, scope, free)} )"
|
|
395
|
+
if isinstance(node, And):
|
|
396
|
+
return f"( {_thf(node.left, scope, free)} & {_thf(node.right, scope, free)} )"
|
|
397
|
+
if isinstance(node, Or):
|
|
398
|
+
return f"( {_thf(node.left, scope, free)} | {_thf(node.right, scope, free)} )"
|
|
399
|
+
if isinstance(node, Xor):
|
|
400
|
+
return f"( {_thf(node.left, scope, free)} <~> {_thf(node.right, scope, free)} )"
|
|
401
|
+
if isinstance(node, Implies):
|
|
402
|
+
return f"( {_thf(node.left, scope, free)} => {_thf(node.right, scope, free)} )"
|
|
403
|
+
if isinstance(node, Iff):
|
|
404
|
+
return f"( {_thf(node.left, scope, free)} <=> {_thf(node.right, scope, free)} )"
|
|
405
|
+
if isinstance(node, Quantifier):
|
|
406
|
+
x = node.variable.name
|
|
407
|
+
q = "!" if node.type in (_FORALL, "forall") else "?"
|
|
408
|
+
inner_scope = scope.bind("obj", x)
|
|
409
|
+
inner = _thf(node.formula, inner_scope, free)
|
|
410
|
+
return f"( {q} [{inner_scope.token('obj', x)}: $i] : {inner} )"
|
|
411
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
412
|
+
# Predicate quantifier mapped DIRECTLY to a THF quantifier over a
|
|
413
|
+
# predicate-typed variable of the inferred arity.
|
|
414
|
+
p = node.predicate
|
|
415
|
+
q = "!" if node.type in (_FORALL, "forall") else "?"
|
|
416
|
+
typ = _thf_pred_type(node.arity)
|
|
417
|
+
inner_scope = scope.bind("pred", p)
|
|
418
|
+
inner = _thf(node.formula, inner_scope, free)
|
|
419
|
+
return f"( {q} [{inner_scope.token('pred', p)}: ( {typ} )] : {inner} )"
|
|
420
|
+
raise NotImplementedError(
|
|
421
|
+
f"to_thf_so: {type(node).__name__} is outside the second-order fragment "
|
|
422
|
+
"supported by the THF export.")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _thf_signature(formula: Node, free: "_FreeNames") -> List[str]:
|
|
426
|
+
"""Type declarations for free predicates / constants / functions.
|
|
427
|
+
|
|
428
|
+
Each free symbol is declared under the UNIQUE functor ``free`` assigned it, so
|
|
429
|
+
a free predicate and a free individual that would otherwise both lower-case to
|
|
430
|
+
the same name get distinct, single-typed declarations (no duplicate ``_decl``,
|
|
431
|
+
no conflicting types). Second-order-bound predicate variables are omitted:
|
|
432
|
+
they are THF bound variables introduced by their binders, not problem-level
|
|
433
|
+
symbols.
|
|
434
|
+
"""
|
|
435
|
+
pred_arity, const_names, func_arity, free_vars = _signature(formula)
|
|
436
|
+
decls = []
|
|
437
|
+
for name, arity in sorted(pred_arity.items()):
|
|
438
|
+
f = free.pred[name]
|
|
439
|
+
typ = _thf_pred_type(arity)
|
|
440
|
+
decls.append(f"thf({f}_decl, type, ( {f} : ( {typ} ) )).")
|
|
441
|
+
for kind, name in _individual_symbols(const_names, free_vars):
|
|
442
|
+
f = (free.var if kind == FREE_VARIABLE else free.ind)[name]
|
|
443
|
+
decls.append(f"thf({f}_decl, type, ( {f} : $i )).")
|
|
444
|
+
for name, arity in sorted(func_arity.items()):
|
|
445
|
+
f = free.func[name]
|
|
446
|
+
typ = " > ".join(["$i"] * (arity + 1))
|
|
447
|
+
decls.append(f"thf({f}_decl, type, ( {f} : ( {typ} ) )).")
|
|
448
|
+
return decls
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def to_thf_so(formula: Node, conjecture: bool = True) -> str:
|
|
452
|
+
"""Emit a complete TPTP **THF** problem for a second-order formula.
|
|
453
|
+
|
|
454
|
+
Object individuals inhabit ``$i``; a predicate variable ``P`` of arity ``k``
|
|
455
|
+
is quantified at THF type ``$i > … > $i > $o``, so each ``∀P`` / ``∃P`` maps
|
|
456
|
+
one-to-one to a higher-order quantifier — the natural SOL → HOL reading. The
|
|
457
|
+
output declares every free predicate / constant / function symbol (free object
|
|
458
|
+
variables are emitted as ``$i`` constants) and ends with the formula as a
|
|
459
|
+
``conjecture`` (``conjecture=True``) or an ``axiom`` (``conjecture=False``),
|
|
460
|
+
ready for a higher-order ATP (Leo-III, Satallax).
|
|
461
|
+
|
|
462
|
+
Semantics is **standard / full** second order in such a prover (predicate
|
|
463
|
+
quantifiers range over the full function space), matching ``satisfies_so``.
|
|
464
|
+
SOL validity under standard semantics is **not semi-decidable**, so a sound
|
|
465
|
+
prover may still fail to close a valid conjecture; the toolkit only emits the
|
|
466
|
+
problem, it does not run any prover. For Henkin (general-models) semantics,
|
|
467
|
+
add your own comprehension axioms.
|
|
468
|
+
|
|
469
|
+
Equality ``=`` / ``≠`` is emitted as an ordinary uninterpreted relation over
|
|
470
|
+
``$i`` (``feq`` / ``fneq``), not primitive HOL identity.
|
|
471
|
+
"""
|
|
472
|
+
formula = _numerals_as_constants(formula, "to_thf_so")
|
|
473
|
+
role = "conjecture" if conjecture else "axiom"
|
|
474
|
+
lines = [
|
|
475
|
+
"% Direct second-order -> HOL embedding (predicate quantifiers are native).",
|
|
476
|
+
"% Standard (full) second-order semantics in a HOL prover; SOL validity is",
|
|
477
|
+
"% NOT semi-decidable, so a sound prover may fail to close a valid goal.",
|
|
478
|
+
]
|
|
479
|
+
free = _FreeNames(formula)
|
|
480
|
+
lines += _thf_signature(formula, free)
|
|
481
|
+
lines.append(f"thf(goal, {role}, ( {_thf(formula, _Scope(), free)} )).")
|
|
482
|
+
return "\n".join(lines) + "\n"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# ===========================================================================
|
|
486
|
+
# (B) Isabelle/HOL export
|
|
487
|
+
# ===========================================================================
|
|
488
|
+
|
|
489
|
+
# Isabelle symbol macros (ASCII \<...> forms render as the Unicode glyphs).
|
|
490
|
+
_ISA = {
|
|
491
|
+
"not": "\\<not>",
|
|
492
|
+
"and": "\\<and>",
|
|
493
|
+
"or": "\\<or>",
|
|
494
|
+
"imp": "\\<longrightarrow>",
|
|
495
|
+
"iff": "\\<longleftrightarrow>",
|
|
496
|
+
"forall": "\\<forall>",
|
|
497
|
+
"exists": "\\<exists>",
|
|
498
|
+
"Rightarrow": "\\<Rightarrow>",
|
|
499
|
+
}
|
|
500
|
+
_ARROW = _ISA["Rightarrow"]
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _isa_pred_type(arity: int) -> str:
|
|
504
|
+
"""Isabelle type of an arity-``k`` predicate variable: ``i ⇒ … ⇒ i ⇒ bool``."""
|
|
505
|
+
return (" " + _ARROW + " ").join(["i"] * arity + ["bool"]) if arity else "bool"
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _isa_fun_type(arity: int) -> str:
|
|
509
|
+
"""Isabelle type of an arity-``k`` function: ``i ⇒ … ⇒ i`` (k+1 copies of i)."""
|
|
510
|
+
return (" " + _ARROW + " ").join(["i"] * (arity + 1))
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _isa_term(node: Node, bvars: FrozenSet[str], free: "_FreeNames") -> str:
|
|
514
|
+
"""Render an individual term in Isabelle (curried application, no commas).
|
|
515
|
+
|
|
516
|
+
A bound object variable keeps its name unless a symbol of the theory is spelled like it
|
|
517
|
+
(``free.binder``, see :mod:`unicode_logic_kit.hol._isabelle_binders`); a free object
|
|
518
|
+
variable is a declared individual constant whose functor is drawn from ``free`` — the SAME
|
|
519
|
+
de-colliding resolver the declarations use, so a free individual never collides
|
|
520
|
+
with a free predicate (which would emit two ``consts`` of the same name).
|
|
521
|
+
"""
|
|
522
|
+
if isinstance(node, Variable):
|
|
523
|
+
return free.binder.get((VARIABLE, node.name), node.name) if node.name in bvars else free.var[node.name]
|
|
524
|
+
if isinstance(node, Constant):
|
|
525
|
+
return free.ind[node.name]
|
|
526
|
+
if isinstance(node, Number):
|
|
527
|
+
raise NotImplementedError(
|
|
528
|
+
f"to_isabelle_so: the number {numeral_name(node.value)} is an operand of a comparison "
|
|
529
|
+
"with a cardinality (a natural number), not an individual: it cannot be the argument "
|
|
530
|
+
"of a predicate or a function.")
|
|
531
|
+
if isinstance(node, Function):
|
|
532
|
+
head = free.func[node.name]
|
|
533
|
+
return "(" + " ".join([head] + [_isa_term(a, bvars, free) for a in node.args]) + ")"
|
|
534
|
+
if isinstance(node, (Cardinality, SortedCardinality)):
|
|
535
|
+
raise NotImplementedError(
|
|
536
|
+
"to_isabelle_so: a cardinality term is supported only as an operand "
|
|
537
|
+
"of a comparison (= ≠ < > ≤ ≥), where it embeds as HOL's "
|
|
538
|
+
"``card {v. φ}``; as an argument of an uninterpreted predicate or "
|
|
539
|
+
"function it would need type nat where individuals are expected.")
|
|
540
|
+
raise NotImplementedError(
|
|
541
|
+
f"to_isabelle_so: unsupported term {type(node).__name__}.")
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
#: HOL spellings of the numeric comparison operators used for cardinalities.
|
|
545
|
+
_CARD_COMPARE = {"=": "=", "<": "<", ">": ">",
|
|
546
|
+
"≤": "\\<le>", "≥": "\\<ge>"}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _isa_card_operand(node: Node, bpreds: FrozenSet[str], bvars: FrozenSet[str],
|
|
550
|
+
free: "_FreeNames") -> str:
|
|
551
|
+
"""Render one operand of a numeric cardinality comparison (type ``nat``).
|
|
552
|
+
|
|
553
|
+
A cardinality embeds as HOL's native finite-set cardinality
|
|
554
|
+
``card {v. φ}`` (``Finite_Set.card``, available from ``Main``); the sorted
|
|
555
|
+
variant guards the matrix with its sort predicate. A number is a ``nat``
|
|
556
|
+
literal. Anything else is a category error — the comparison is numeric, so
|
|
557
|
+
an individual-typed operand cannot appear (the same rule the Tarskian
|
|
558
|
+
evaluator enforces).
|
|
559
|
+
"""
|
|
560
|
+
if isinstance(node, (Cardinality, SortedCardinality)):
|
|
561
|
+
v = free.binder.get((VARIABLE, node.variable.name), node.variable.name)
|
|
562
|
+
matrix = _isa(node.formula, bpreds, bvars | {node.variable.name}, free)
|
|
563
|
+
if isinstance(node, SortedCardinality):
|
|
564
|
+
guard = free.pred[node.sort]
|
|
565
|
+
matrix = f"(({guard} {v}) \\<and> {matrix})"
|
|
566
|
+
return f"(card {{{v}. {matrix}}})"
|
|
567
|
+
if isinstance(node, Number):
|
|
568
|
+
return numeral_name(node.value) # a whole-number float is the natural it equals: 2.0 is 2
|
|
569
|
+
raise NotImplementedError(
|
|
570
|
+
"to_isabelle_so: a comparison with a cardinality operand is NUMERIC — "
|
|
571
|
+
"the other operand must be a Number or another cardinality term, not an "
|
|
572
|
+
f"individual ({type(node).__name__}).")
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _isa(node: Node, bpreds: FrozenSet[str], bvars: FrozenSet[str], free: "_FreeNames") -> str:
|
|
576
|
+
"""Render a second-order formula as an Isabelle/HOL boolean term."""
|
|
577
|
+
if isinstance(node, Atom):
|
|
578
|
+
if truth_value(node) is not None:
|
|
579
|
+
return "True" if truth_value(node) else "False"
|
|
580
|
+
if (node.predicate in ("=", "≠") or node.predicate in _CARD_COMPARE) \
|
|
581
|
+
and len(node.args) == 2 \
|
|
582
|
+
and any(isinstance(a, (Cardinality, SortedCardinality))
|
|
583
|
+
for a in node.args):
|
|
584
|
+
# Numeric reading: a cardinality IS a natural number (HOL's card),
|
|
585
|
+
# so the comparison is arithmetic over nat, not an uninterpreted
|
|
586
|
+
# relation over individuals — mirroring semantics.tarski.
|
|
587
|
+
left = _isa_card_operand(node.args[0], bpreds, bvars, free)
|
|
588
|
+
right = _isa_card_operand(node.args[1], bpreds, bvars, free)
|
|
589
|
+
if node.predicate == "≠":
|
|
590
|
+
return f"(\\<not> ({left} = {right}))"
|
|
591
|
+
return f"({left} {_CARD_COMPARE[node.predicate]} {right})"
|
|
592
|
+
head = (free.binder.get((PREDICATE, node.predicate), node.predicate) if node.predicate in bpreds
|
|
593
|
+
else free.pred[node.predicate])
|
|
594
|
+
if not node.args:
|
|
595
|
+
return head
|
|
596
|
+
return "(" + " ".join([head] + [_isa_term(a, bvars, free) for a in node.args]) + ")"
|
|
597
|
+
if isinstance(node, Not):
|
|
598
|
+
return f"({_ISA['not']} {_isa(node.formula, bpreds, bvars, free)})"
|
|
599
|
+
if isinstance(node, And):
|
|
600
|
+
return f"({_isa(node.left, bpreds, bvars, free)} {_ISA['and']} {_isa(node.right, bpreds, bvars, free)})"
|
|
601
|
+
if isinstance(node, Or):
|
|
602
|
+
return f"({_isa(node.left, bpreds, bvars, free)} {_ISA['or']} {_isa(node.right, bpreds, bvars, free)})"
|
|
603
|
+
if isinstance(node, Xor):
|
|
604
|
+
# x XOR y ≡ ¬(x ↔ y)
|
|
605
|
+
return f"({_ISA['not']} ({_isa(node.left, bpreds, bvars, free)} {_ISA['iff']} {_isa(node.right, bpreds, bvars, free)}))"
|
|
606
|
+
if isinstance(node, Implies):
|
|
607
|
+
return f"({_isa(node.left, bpreds, bvars, free)} {_ISA['imp']} {_isa(node.right, bpreds, bvars, free)})"
|
|
608
|
+
if isinstance(node, Iff):
|
|
609
|
+
return f"({_isa(node.left, bpreds, bvars, free)} {_ISA['iff']} {_isa(node.right, bpreds, bvars, free)})"
|
|
610
|
+
if isinstance(node, Quantifier):
|
|
611
|
+
x = node.variable.name
|
|
612
|
+
q = _ISA["forall"] if node.type in (_FORALL, "forall") else _ISA["exists"]
|
|
613
|
+
inner = _isa(node.formula, bpreds, bvars | {x}, free)
|
|
614
|
+
return f"({q}{free.binder.get((VARIABLE, x), x)}::i. {inner})"
|
|
615
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
616
|
+
p = node.predicate
|
|
617
|
+
q = _ISA["forall"] if node.type in (_FORALL, "forall") else _ISA["exists"]
|
|
618
|
+
typ = _isa_pred_type(node.arity)
|
|
619
|
+
inner = _isa(node.formula, bpreds | {p}, bvars, free)
|
|
620
|
+
return f"({q}{free.binder.get((PREDICATE, p), p)}::{typ}. {inner})"
|
|
621
|
+
raise NotImplementedError(
|
|
622
|
+
f"to_isabelle_so: {type(node).__name__} is outside the second-order "
|
|
623
|
+
"fragment supported by the Isabelle export.")
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _isa_signature(formula: Node, free: "_FreeNames") -> List[str]:
|
|
627
|
+
"""``consts`` declarations for free predicates / constants / functions.
|
|
628
|
+
|
|
629
|
+
Each symbol is declared under the unique functor ``free`` assigned it, so a free
|
|
630
|
+
predicate and a free individual that both lower-case to the same name get two
|
|
631
|
+
DISTINCT ``consts`` (Isabelle rejects duplicate constant declarations).
|
|
632
|
+
"""
|
|
633
|
+
pred_arity, const_names, func_arity, free_vars = _signature(formula)
|
|
634
|
+
decls = []
|
|
635
|
+
for kind, name in _individual_symbols(const_names, free_vars):
|
|
636
|
+
decls.append(f"consts {(free.var if kind == FREE_VARIABLE else free.ind)[name]} :: \"i\"")
|
|
637
|
+
for name, arity in sorted(func_arity.items()):
|
|
638
|
+
decls.append(f"consts {free.func[name]} :: \"{_isa_fun_type(arity)}\"")
|
|
639
|
+
for name, arity in sorted(pred_arity.items()):
|
|
640
|
+
decls.append(f"consts {free.pred[name]} :: \"{_isa_pred_type(arity)}\"")
|
|
641
|
+
return decls
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def to_isabelle_so(formula: Node, name: str = "SO_Goal") -> str:
|
|
645
|
+
"""Emit a self-contained Isabelle/HOL theory for a second-order formula.
|
|
646
|
+
|
|
647
|
+
Individuals inhabit an uninterpreted type ``i``; a predicate variable ``P`` of
|
|
648
|
+
arity ``k`` is bound at type ``i ⇒ … ⇒ i ⇒ bool``, so each ``∀P`` / ``∃P``
|
|
649
|
+
becomes a native HOL ``∀`` / ``∃`` over a predicate type — the direct SOL → HOL
|
|
650
|
+
reading. Free predicate / constant / function symbols (and free object
|
|
651
|
+
variables, as individual constants) are introduced with ``consts``; the
|
|
652
|
+
formula is stated as a ``lemma`` left ``oops`` (replace with ``by auto`` /
|
|
653
|
+
``by blast`` / ``sledgehammer``).
|
|
654
|
+
|
|
655
|
+
As with :func:`to_thf_so`, this is **standard (full)** second-order semantics
|
|
656
|
+
in Isabelle/HOL; SOL validity is not semi-decidable, so Sledgehammer may fail
|
|
657
|
+
on a valid lemma. The toolkit emits the theory only — it does not run
|
|
658
|
+
Isabelle. Equality ``=`` / ``≠`` is an uninterpreted relation (``feq`` /
|
|
659
|
+
``fneq``), not HOL ``=``.
|
|
660
|
+
|
|
661
|
+
``name`` becomes the theory name; it must be a legal Isabelle identifier (it is
|
|
662
|
+
also conventionally the ``.thy`` file's base name).
|
|
663
|
+
"""
|
|
664
|
+
formula = _numerals_as_constants(formula, "to_isabelle_so")
|
|
665
|
+
free = _FreeNames(formula)
|
|
666
|
+
declarations = _isa_signature(formula, free)
|
|
667
|
+
# a binder shadows a constant of its own spelling inside its scope: it is printed under a
|
|
668
|
+
# name that no declared symbol has
|
|
669
|
+
free.binder = binder_tokens(collect_binders([formula]), declared_names(declarations))
|
|
670
|
+
body = _isa(formula, frozenset(), frozenset(), free)
|
|
671
|
+
lines = [
|
|
672
|
+
"(* Direct second-order -> HOL embedding (predicate quantifiers native). *)",
|
|
673
|
+
"(* Standard (full) second-order semantics; SOL validity is NOT *)",
|
|
674
|
+
"(* semi-decidable, so Sledgehammer may fail on a valid lemma. *)",
|
|
675
|
+
f"theory {name}",
|
|
676
|
+
" imports Main",
|
|
677
|
+
"begin",
|
|
678
|
+
"",
|
|
679
|
+
"typedecl i \\<comment> \\<open>individuals\\<close>",
|
|
680
|
+
]
|
|
681
|
+
lines += declarations
|
|
682
|
+
lines.append("")
|
|
683
|
+
lines.append(f"lemma \"{body}\"")
|
|
684
|
+
lines.append(" oops \\<comment> \\<open>try: by auto / by blast / sledgehammer\\<close>")
|
|
685
|
+
lines.append("")
|
|
686
|
+
lines.append("end")
|
|
687
|
+
return "\n".join(lines) + "\n"
|