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,753 @@
|
|
|
1
|
+
r"""Free logic → HOL export: TPTP **THF** problems and Isabelle/HOL theories.
|
|
2
|
+
|
|
3
|
+
Free logic (:mod:`unicode_logic_kit.semantics.free_logic`) drops classical FOL's
|
|
4
|
+
assumption that every term denotes an *existing* individual: quantifiers range
|
|
5
|
+
over an *inner* domain of existing objects, while a constant or function term may
|
|
6
|
+
denote an *outer* (merely possible) object, or fail to denote at all. This module
|
|
7
|
+
embeds that into HOL the same way :mod:`unicode_logic_kit.hol.classical` embeds
|
|
8
|
+
classical MSFOL — by GUARD-RELATIVIZATION — using **two** uninterpreted unary
|
|
9
|
+
guard predicates over a single flat individual type ``e``:
|
|
10
|
+
|
|
11
|
+
* ``D(t)`` — "``t`` denotes" (some object of the outer domain);
|
|
12
|
+
* ``E!(t)`` — "``t`` exists" (an object of the INNER domain; ``E!`` is strictly
|
|
13
|
+
narrower than ``D``, tied together by the axiom ``E!(x) → D(x)``).
|
|
14
|
+
|
|
15
|
+
The translation, matching :func:`~unicode_logic_kit.semantics.free_logic.free_satisfies`
|
|
16
|
+
clause by clause:
|
|
17
|
+
|
|
18
|
+
* an ordinary atom ``P(t1, …, tn)`` becomes ``D*(t1) ∧ … ∧ D*(tn) ∧ P(t1, …, tn)``
|
|
19
|
+
(mirrors ``_atom``'s "non-denoting ⇒ false" reading — a real free-logic
|
|
20
|
+
``NOT``, so this is NOT the same as classically negating the un-guarded atom).
|
|
21
|
+
``D*`` applies ``D`` to the term AND to every compound subterm of it,
|
|
22
|
+
``D*(f(s)) = D*(s) ∧ D(f(s))``: ``free_logic._term_value`` makes ``f(s)``
|
|
23
|
+
non-denoting whenever ``s`` is, while a HOL function is total and could map a
|
|
24
|
+
non-denoting argument's carrier value into ``D``;
|
|
25
|
+
* the object-language existence predicate ``E!(t)`` — the SAME predicate
|
|
26
|
+
:data:`~unicode_logic_kit.semantics.free_logic._EXISTS_PRED` names — is emitted
|
|
27
|
+
as the guard predicate itself, guarded only by ``D*`` of the proper subterms
|
|
28
|
+
of ``t`` (bare for a variable or constant: with the tie ``E!(x) → D(x)`` it
|
|
29
|
+
already reads "``t`` denotes AND is existing");
|
|
30
|
+
* ``=`` is native HOL identity under the same guard,
|
|
31
|
+
``s = t ↦ D*(s) ∧ D*(t) ∧ s = t``; under ``policy="positive"`` a *self*-identity
|
|
32
|
+
atom ``t = t`` (the SAME term written twice) is exempted from the guard and
|
|
33
|
+
emitted bare (mirrors ``_atom``'s ``policy == "positive"`` carve-out for the
|
|
34
|
+
non-denoting branch). ``≠`` is translated as the negation of the (possibly
|
|
35
|
+
exempted) ``=`` translation, matching ``_atom``'s own ``eq`` / ``not eq``
|
|
36
|
+
symmetry between the two;
|
|
37
|
+
* a quantifier is relativized to ``E!``: ``∀x φ ↦ ∀x. E!(x) → φ``,
|
|
38
|
+
``∃x φ ↦ ∃x. E!(x) ∧ φ`` — the same shape
|
|
39
|
+
:func:`~unicode_logic_kit.hol.classical`'s MSFOL sort-relativization uses, with
|
|
40
|
+
``E!`` playing the role of the sort guard.
|
|
41
|
+
|
|
42
|
+
The tie ``E!(x) → D(x)`` is the ONLY background fact. There is deliberately no
|
|
43
|
+
``∃x. E!(x)``: an empty inner domain is a legitimate free-logic model
|
|
44
|
+
(:func:`~unicode_logic_kit.semantics.free_logic.free_is_valid` searches it by
|
|
45
|
+
default), so ``(∀x P(x)) → ∃x P(x)`` must come out invalid here as well. The tie
|
|
46
|
+
is a THF ``axiom`` and, in Isabelle, a **premise of the goal**, never a
|
|
47
|
+
persistent ``axiomatization`` fact: exactly like
|
|
48
|
+
:mod:`~unicode_logic_kit.hol.isabelle_conditional`'s ``nested Sel`` premise, this
|
|
49
|
+
lets nitpick construct ``D``/``E!`` itself when searching for a countermodel and
|
|
50
|
+
certify it as *genuine* — an ``axiomatization`` would downgrade that to
|
|
51
|
+
``quasi_genuine`` (nitpick cannot verify an axiomatised fact's consistency) and
|
|
52
|
+
lose the refutation half of the decision procedure.
|
|
53
|
+
|
|
54
|
+
**Why the embedding is faithful in both directions.** A :class:`FreeModel`
|
|
55
|
+
becomes a HOL model over ``outer ∪ {⊥}``: ``D`` is ``outer``, ``E!`` is
|
|
56
|
+
``existing``, and a non-denoting constant as well as every undefined or
|
|
57
|
+
``⊥``-involving function value is ``⊥``; then ``D*(t)`` holds exactly when ``t``
|
|
58
|
+
denotes, and the guarded formula has ``free_holds``'s truth value. Conversely a
|
|
59
|
+
HOL model gives a :class:`FreeModel` with ``outer = D`` (plus one fresh
|
|
60
|
+
non-existing object if ``D`` is empty — no formula can observe it),
|
|
61
|
+
``existing = E!`` (inside ``D`` by the tie), a constant denoting its HOL value iff
|
|
62
|
+
that value lies in ``D``, and a function table defined exactly where the
|
|
63
|
+
arguments and the value lie in ``D``; by induction ``D*(t)`` holds iff ``t``
|
|
64
|
+
denotes there, with the same value. So a HOL proof certifies a free-logic
|
|
65
|
+
validity, and a genuine HOL countermodel is a free-logic countermodel.
|
|
66
|
+
|
|
67
|
+
**Equality.** ``=`` is the target format's own identity (THF's infix
|
|
68
|
+
``( S = T )``, Isabelle's polymorphic ``(s = t)``), never an uninterpreted
|
|
69
|
+
``feq`` predicate: ``free_logic._atom`` compares the referents of two denoting
|
|
70
|
+
terms by identity, and the ``D*`` guard already covers every non-denoting case.
|
|
71
|
+
An uninterpreted ``feq`` would let nitpick certify a "countermodel" to
|
|
72
|
+
``∀x ∀y ((x = y ∧ P(x)) → P(y))``, which is free-logically valid. This departs
|
|
73
|
+
on purpose from :mod:`~unicode_logic_kit.hol.classical`'s default export, whose
|
|
74
|
+
documented reading of ``=`` is an uninterpreted predicate unless
|
|
75
|
+
``native_equality=True``; the order glyphs ``< > ≤ ≥`` stay uninterpreted
|
|
76
|
+
predicates here, as they are ordinary predicates to ``free_holds``.
|
|
77
|
+
|
|
78
|
+
**Scope.** Classical connectives (``¬ ∧ ∨ ⊕ → ↔``), quantifiers, and atoms over
|
|
79
|
+
variable / constant / number / function terms only — exactly the fragment
|
|
80
|
+
:func:`~unicode_logic_kit.semantics.free_logic.free_satisfies` interprets. Modal,
|
|
81
|
+
second-order, Łukasiewicz, substructural and lambda nodes are outside free
|
|
82
|
+
logic's own scope and raise ``NotImplementedError`` by name, never silently
|
|
83
|
+
approximated.
|
|
84
|
+
|
|
85
|
+
**``policy="supervaluation"``.** Refused with ``NotImplementedError`` from every
|
|
86
|
+
entry point in this module. Supervaluationist truth is a property of a whole
|
|
87
|
+
MODEL's set of truth-value GAPS (true under every classical completion of that
|
|
88
|
+
specific model — see the ``semantics.free_logic`` module docstring), not a fact
|
|
89
|
+
derivable from a single formula's guarded translation the way negative/positive
|
|
90
|
+
are: encoding it soundly would need second-order quantification over per-model
|
|
91
|
+
gap assignments, a different and much larger undertaking this module does not
|
|
92
|
+
fold in. Use :func:`~unicode_logic_kit.semantics.free_logic.free_holds` with
|
|
93
|
+
``policy="supervaluation"`` directly instead.
|
|
94
|
+
|
|
95
|
+
**Honesty / scope.** This module only *emits* problems and theories; it does
|
|
96
|
+
**not** run Leo-III, Satallax, Vampire, Isabelle, or Sledgehammer. The embedded
|
|
97
|
+
logic is first-order, so it is semi-decidable only, same as
|
|
98
|
+
:mod:`~unicode_logic_kit.hol.classical`.
|
|
99
|
+
|
|
100
|
+
Public API: :func:`to_thf_free`, :func:`to_isabelle_free`, :func:`free_theory`.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
from typing import Dict, List, Optional, Tuple
|
|
104
|
+
|
|
105
|
+
from ..fol._fol_nodes import constant_name_to_ascii
|
|
106
|
+
from ..fol._numeral_symbols import numerals_as_constants, prefixed_numeral_name
|
|
107
|
+
from ..fol._symbol_names import dedupe
|
|
108
|
+
from ..fol._truth_constants import truth_value
|
|
109
|
+
from ..fol.nodes import (
|
|
110
|
+
Node, Variable, Constant, Number, Function,
|
|
111
|
+
Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
112
|
+
)
|
|
113
|
+
from .classical import _refuse_open_assertion
|
|
114
|
+
|
|
115
|
+
#: The object-language existence predicate — the SAME name
|
|
116
|
+
#: :mod:`~unicode_logic_kit.semantics.free_logic` reserves for it
|
|
117
|
+
#: (``_EXISTS_PRED``) and this module's own quantifier/E!-guard.
|
|
118
|
+
_EXISTS_PRED = "E!"
|
|
119
|
+
|
|
120
|
+
#: This module's own denotation guard. ``semantics.free_logic`` has no surface
|
|
121
|
+
#: syntax for it, and — UNLIKE ``_EXISTS_PRED``/``"E!"`` — no special meaning
|
|
122
|
+
#: for it AT ALL: it is purely an artefact of this HOL translation, invisible
|
|
123
|
+
#: to :func:`~unicode_logic_kit.semantics.free_logic.free_holds`. Named ``"D!"``
|
|
124
|
+
#: (bang-suffixed, matching ``_EXISTS_PRED``'s own convention), NOT bare
|
|
125
|
+
#: ``"D"``: a user formula plausibly has its OWN ordinary predicate named a
|
|
126
|
+
#: single letter like ``D`` (e.g. "Dog"/"Drinks" abbreviated), and that must
|
|
127
|
+
#: stay a distinct, un-guarded symbol rather than being silently reinterpreted
|
|
128
|
+
#: as this guard. A formula that itself uses the reserved ``"D!"`` name
|
|
129
|
+
#: (unreachable from the parser, but constructible directly as an AST) is
|
|
130
|
+
#: REFUSED with a ``ValueError`` by :func:`_guard_atom`: to ``free_holds`` such
|
|
131
|
+
#: an atom is an ordinary predicate, so merging it with the guard would
|
|
132
|
+
#: silently change its meaning.
|
|
133
|
+
_DENOTES_PRED = "D!"
|
|
134
|
+
|
|
135
|
+
# Comparison glyphs and the two guard predicates get valid, collision-priority
|
|
136
|
+
# THF/Isabelle identifiers (the SAME flt/fgt/fle/fge stems as classical.py's
|
|
137
|
+
# _PRED_ALIAS, extended with the guard predicates' own stems). "=" is absent on
|
|
138
|
+
# purpose: it is always printed as native identity, and "≠" is rewritten to
|
|
139
|
+
# the negation of "=" before printing (see the module docstring's Equality).
|
|
140
|
+
_PRED_ALIAS = {
|
|
141
|
+
"<": "flt", ">": "fgt", "≤": "fle", "≥": "fge",
|
|
142
|
+
_DENOTES_PRED: "denotes", _EXISTS_PRED: "existsBang",
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_FORALL = "∀"
|
|
146
|
+
_EXISTS = "∃"
|
|
147
|
+
|
|
148
|
+
_POLICIES = ("negative", "positive", "supervaluation")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _check_policy(policy: str, who: str) -> None:
|
|
152
|
+
"""Validate ``policy``; refuse ``"supervaluation"`` loudly (see module docstring)."""
|
|
153
|
+
if policy == "supervaluation":
|
|
154
|
+
raise NotImplementedError(
|
|
155
|
+
f"{who}: policy='supervaluation' has no guarded-translation reading. "
|
|
156
|
+
"Supervaluationist truth is a property of a MODEL's set of gap atoms "
|
|
157
|
+
"(true under every classical completion of THAT model), not a fact "
|
|
158
|
+
"derivable from a single formula's guarded translation the way "
|
|
159
|
+
"negative/positive are -- encoding it soundly would need second-order "
|
|
160
|
+
"quantification over per-model gap assignments. Use "
|
|
161
|
+
"semantics.free_logic.free_holds(..., policy='supervaluation') instead.")
|
|
162
|
+
if policy not in _POLICIES:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"{who}: unknown policy {policy!r} (negative / positive; "
|
|
165
|
+
"supervaluation is refused with NotImplementedError, see above).")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ===========================================================================
|
|
169
|
+
# The guard rewrite: free-logic AST -> classical FOL AST over D / E!
|
|
170
|
+
# ===========================================================================
|
|
171
|
+
|
|
172
|
+
def _denotes(term: Node) -> Node:
|
|
173
|
+
"""``D*(t)`` — ``t`` denotes: ``D`` of ``t`` and of every compound subterm.
|
|
174
|
+
|
|
175
|
+
``D*(f(s1, …, sn)) = D*(s1) ∧ … ∧ D*(sn) ∧ D(f(s1, …, sn))``, mirroring
|
|
176
|
+
``free_logic._term_value``: a function term is non-denoting as soon as one
|
|
177
|
+
argument is, whatever the (total) HOL function does with that argument's
|
|
178
|
+
carrier value. A variable, constant or number is guarded by ``D`` alone.
|
|
179
|
+
"""
|
|
180
|
+
guard: Node = Atom(_DENOTES_PRED, [term])
|
|
181
|
+
if isinstance(term, Function):
|
|
182
|
+
for arg in reversed(term.args):
|
|
183
|
+
guard = And(_denotes(arg), guard)
|
|
184
|
+
return guard
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _is_identity(node: Node) -> bool:
|
|
188
|
+
"""True for an ``s = t`` atom, which both printers render as native identity.
|
|
189
|
+
|
|
190
|
+
See the module docstring's **Equality** section: identity of referents is
|
|
191
|
+
what ``free_logic._atom`` computes once both sides denote, and the ``D*``
|
|
192
|
+
guard handles every non-denoting case.
|
|
193
|
+
"""
|
|
194
|
+
return isinstance(node, Atom) and node.predicate == "=" and len(node.args) == 2
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _guard_atom(atom: Atom, policy: str) -> Node:
|
|
198
|
+
"""Translate one atom per the module docstring's clause-by-clause table."""
|
|
199
|
+
if atom.predicate == _DENOTES_PRED:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"hol.free: {_DENOTES_PRED!r} is reserved for the denotation guard of "
|
|
202
|
+
"this embedding; to free_holds it would be an ordinary predicate, so "
|
|
203
|
+
"rename that predicate.")
|
|
204
|
+
if atom.predicate == _EXISTS_PRED and len(atom.args) == 1:
|
|
205
|
+
# E! is genuinely the free-logic existence predicate (free_holds's own
|
|
206
|
+
# _EXISTS_PRED special case): E!(t) is true iff t denotes an existing
|
|
207
|
+
# object. With the tie E!(x) -> D(x), only the PROPER subterms of t
|
|
208
|
+
# still need their D* guard.
|
|
209
|
+
term = atom.args[0]
|
|
210
|
+
guarded: Node = atom
|
|
211
|
+
if isinstance(term, Function):
|
|
212
|
+
for arg in reversed(term.args):
|
|
213
|
+
guarded = And(_denotes(arg), guarded)
|
|
214
|
+
return guarded
|
|
215
|
+
if atom.predicate == "≠" and len(atom.args) == 2:
|
|
216
|
+
# _atom's own eq/not-eq symmetry: "≠" is always the negation of what
|
|
217
|
+
# "=" would return for the SAME arguments (both policies, both the
|
|
218
|
+
# denoting and non-denoting branches) — see the module docstring.
|
|
219
|
+
return Not(_guard_atom(Atom("=", atom.args), policy))
|
|
220
|
+
if (atom.predicate == "=" and len(atom.args) == 2 and policy == "positive"
|
|
221
|
+
and atom.args[0] == atom.args[1]):
|
|
222
|
+
# Positive free logic: self-identity (the literal SAME term twice)
|
|
223
|
+
# holds unconditionally -- _atom's policy == "positive" carve-out. No
|
|
224
|
+
# D-guard is added; the printers render it as native identity, which
|
|
225
|
+
# is reflexively true.
|
|
226
|
+
return atom
|
|
227
|
+
if not atom.args:
|
|
228
|
+
return atom # nothing to guard
|
|
229
|
+
guarded: Node = atom
|
|
230
|
+
for term in reversed(atom.args):
|
|
231
|
+
guarded = And(_denotes(term), guarded)
|
|
232
|
+
return guarded
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _guard(node: Node, policy: str) -> Node:
|
|
236
|
+
"""Recursively translate ``node`` into the guarded classical FOL AST.
|
|
237
|
+
|
|
238
|
+
Reused, unchanged, by both :func:`to_thf_free` and :func:`to_isabelle_free`
|
|
239
|
+
(and, through them, :func:`free_theory`) — a single guard rewrite that both
|
|
240
|
+
printers render, so the two formats can never drift on what "guarded" means.
|
|
241
|
+
"""
|
|
242
|
+
if isinstance(node, Atom):
|
|
243
|
+
return _guard_atom(node, policy)
|
|
244
|
+
if isinstance(node, Not):
|
|
245
|
+
return Not(_guard(node.formula, policy))
|
|
246
|
+
if isinstance(node, And):
|
|
247
|
+
return And(_guard(node.left, policy), _guard(node.right, policy))
|
|
248
|
+
if isinstance(node, Or):
|
|
249
|
+
return Or(_guard(node.left, policy), _guard(node.right, policy))
|
|
250
|
+
if isinstance(node, Xor):
|
|
251
|
+
return Xor(_guard(node.left, policy), _guard(node.right, policy))
|
|
252
|
+
if isinstance(node, Implies):
|
|
253
|
+
return Implies(_guard(node.left, policy), _guard(node.right, policy))
|
|
254
|
+
if isinstance(node, Iff):
|
|
255
|
+
return Iff(_guard(node.left, policy), _guard(node.right, policy))
|
|
256
|
+
if isinstance(node, Quantifier):
|
|
257
|
+
body = _guard(node.formula, policy)
|
|
258
|
+
exists_here = Atom(_EXISTS_PRED, [node.variable])
|
|
259
|
+
if node.type in (_FORALL, "forall"):
|
|
260
|
+
return Quantifier(_FORALL, node.variable, Implies(exists_here, body))
|
|
261
|
+
if node.type in (_EXISTS, "exists"):
|
|
262
|
+
return Quantifier(_EXISTS, node.variable, And(exists_here, body))
|
|
263
|
+
raise ValueError(f"hol.free: unknown quantifier type {node.type!r}.")
|
|
264
|
+
raise NotImplementedError(
|
|
265
|
+
f"hol.free: {type(node).__name__} is outside the free-logic fragment this "
|
|
266
|
+
"export covers -- classical connectives (not/and/or/xor/implies/iff), "
|
|
267
|
+
"quantifiers, and atoms over variable/constant/number/function terms "
|
|
268
|
+
"only, matching semantics.free_logic.free_satisfies. Modal / second-order "
|
|
269
|
+
"/ Łukasiewicz / substructural / lambda nodes have no free-logic reading "
|
|
270
|
+
"here."
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _free_variables(formula: Node) -> List[str]:
|
|
275
|
+
"""Names of variables occurring free in ``formula`` (first-occurrence order).
|
|
276
|
+
|
|
277
|
+
``Quantifier`` binds its variable; everything else is structural. Mirrors
|
|
278
|
+
:func:`unicode_logic_kit.hol.classical._free_variables`, restricted to the
|
|
279
|
+
node types this module's guard rewrite supports.
|
|
280
|
+
"""
|
|
281
|
+
out: List[str] = []
|
|
282
|
+
seen = set()
|
|
283
|
+
|
|
284
|
+
def rec(node: Node, bound: frozenset) -> None:
|
|
285
|
+
if isinstance(node, Variable):
|
|
286
|
+
if node.name not in bound and node.name not in seen:
|
|
287
|
+
seen.add(node.name)
|
|
288
|
+
out.append(node.name)
|
|
289
|
+
return
|
|
290
|
+
if isinstance(node, Quantifier):
|
|
291
|
+
rec(node.formula, bound | {node.variable.name})
|
|
292
|
+
return
|
|
293
|
+
for child in node._child_nodes():
|
|
294
|
+
rec(child, bound)
|
|
295
|
+
|
|
296
|
+
rec(formula, frozenset())
|
|
297
|
+
return out
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _close(formula: Node) -> Node:
|
|
301
|
+
"""Universally close every free variable of ``formula`` (deterministic order).
|
|
302
|
+
|
|
303
|
+
Mirrors :func:`unicode_logic_kit.hol.classical.to_thf_fol`'s own closure step:
|
|
304
|
+
TPTP/Isabelle formula roles do not admit free variables, and closing BEFORE
|
|
305
|
+
:func:`_guard` runs means the closure-introduced quantifiers get the SAME
|
|
306
|
+
``E!``-relativization as every other quantifier in the formula.
|
|
307
|
+
"""
|
|
308
|
+
closed = formula
|
|
309
|
+
for name in reversed(_free_variables(formula)):
|
|
310
|
+
closed = Quantifier(_FORALL, Variable(name), closed)
|
|
311
|
+
return closed
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ===========================================================================
|
|
315
|
+
# Shared: name sanitisation and signature extraction
|
|
316
|
+
# ===========================================================================
|
|
317
|
+
|
|
318
|
+
def _sanitize(name: str) -> str:
|
|
319
|
+
"""Lower-case-initial ASCII alnum/underscore functor stem for THF/Isabelle.
|
|
320
|
+
|
|
321
|
+
Structurally identical to
|
|
322
|
+
:func:`unicode_logic_kit.hol.classical._sanitize` (see there for the full
|
|
323
|
+
rationale): equality/comparison glyphs and the two guard predicates map
|
|
324
|
+
through ``_PRED_ALIAS`` first; every other name is transliterated to ASCII
|
|
325
|
+
via :func:`~unicode_logic_kit.fol._fol_nodes.constant_name_to_ascii`, filtered
|
|
326
|
+
to alnum-or-underscore, and lower-cased at the initial character (THF/
|
|
327
|
+
Isabelle both read an upper-case initial as a VARIABLE, not a constant).
|
|
328
|
+
De-collision across distinct source symbols is :class:`_SymbolResolver`'s
|
|
329
|
+
job, not this function's — never emit its output directly for a
|
|
330
|
+
declaration or a usage.
|
|
331
|
+
"""
|
|
332
|
+
if name in _PRED_ALIAS:
|
|
333
|
+
return _PRED_ALIAS[name]
|
|
334
|
+
ascii_name = constant_name_to_ascii(name)
|
|
335
|
+
safe = "".join(c if (c.isalnum() or c == "_") else "_" for c in ascii_name)
|
|
336
|
+
if not safe:
|
|
337
|
+
return "p"
|
|
338
|
+
if safe[0].isdigit():
|
|
339
|
+
safe = "p" + safe
|
|
340
|
+
return safe[:1].lower() + safe[1:]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _signature(formula: Node):
|
|
344
|
+
"""Collect ``(preds, funcs, consts)`` with arities from the GUARDED ``formula``.
|
|
345
|
+
|
|
346
|
+
Same shape as :func:`unicode_logic_kit.hol.classical._signature`. ``D`` and
|
|
347
|
+
``E!`` are always FORCED into ``preds`` (arity 1) regardless of whether they
|
|
348
|
+
happen to occur in this particular formula, because the tie axiom
|
|
349
|
+
(``E!(x) -> D(x)``) references both of them unconditionally — see
|
|
350
|
+
:func:`to_thf_free` / :func:`to_isabelle_free`. Identity atoms are native
|
|
351
|
+
and need no declaration.
|
|
352
|
+
"""
|
|
353
|
+
preds, funcs, consts = set(), set(), set()
|
|
354
|
+
for n in formula.walk():
|
|
355
|
+
if isinstance(n, Atom):
|
|
356
|
+
if not _is_identity(n) and truth_value(n) is None:
|
|
357
|
+
preds.add((n.predicate, len(n.args)))
|
|
358
|
+
elif isinstance(n, Function):
|
|
359
|
+
funcs.add((n.name, len(n.args)))
|
|
360
|
+
elif isinstance(n, Constant):
|
|
361
|
+
consts.add(n.name)
|
|
362
|
+
elif isinstance(n, Number):
|
|
363
|
+
consts.add("n" + str(n.value))
|
|
364
|
+
preds |= {(_DENOTES_PRED, 1), (_EXISTS_PRED, 1)}
|
|
365
|
+
return preds, funcs, consts
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ===========================================================================
|
|
369
|
+
# Global symbol resolver — DISTINCT source symbols map to DISTINCT identifiers
|
|
370
|
+
# ===========================================================================
|
|
371
|
+
|
|
372
|
+
_CAT_PRED = "pred"
|
|
373
|
+
_CAT_FUNC = "func"
|
|
374
|
+
_CAT_CONST = "const"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class _SymbolResolver:
|
|
378
|
+
"""Assign a UNIQUE, valid emitted identifier to every source symbol.
|
|
379
|
+
|
|
380
|
+
Structurally identical to
|
|
381
|
+
:class:`unicode_logic_kit.hol.classical._SymbolResolver` (see there for the
|
|
382
|
+
full rationale): keyed by ``(category, raw_name, arity)``, so a predicate
|
|
383
|
+
used at two arities — or a predicate/function/constant sharing a raw name —
|
|
384
|
+
gets distinct identifiers. The ``_PRED_ALIAS`` targets (``flt``/``fgt``/…
|
|
385
|
+
and ``D``/``E!``'s own ``denotes``/``existsBang`` stems) are assigned FIRST,
|
|
386
|
+
so they claim their natural stem and a colliding user symbol (e.g. a
|
|
387
|
+
predicate literally named ``D``) is de-collided away from them instead of
|
|
388
|
+
the other way round.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
def __init__(self, formula: Node):
|
|
392
|
+
self._used = set()
|
|
393
|
+
self._map: Dict[Tuple[str, str, int], str] = {}
|
|
394
|
+
preds, funcs, consts = _signature(formula)
|
|
395
|
+
for name, arity in sorted(preds):
|
|
396
|
+
if name in _PRED_ALIAS:
|
|
397
|
+
self._assign(_CAT_PRED, name, arity)
|
|
398
|
+
for name, arity in sorted(preds):
|
|
399
|
+
if name not in _PRED_ALIAS:
|
|
400
|
+
self._assign(_CAT_PRED, name, arity)
|
|
401
|
+
for name, arity in sorted(funcs):
|
|
402
|
+
self._assign(_CAT_FUNC, name, arity)
|
|
403
|
+
for name in sorted(consts):
|
|
404
|
+
self._assign(_CAT_CONST, name, 0)
|
|
405
|
+
|
|
406
|
+
def _assign(self, category: str, raw: str, arity: int) -> str:
|
|
407
|
+
key = (category, raw, arity)
|
|
408
|
+
if key in self._map:
|
|
409
|
+
return self._map[key]
|
|
410
|
+
cand = dedupe(_sanitize(raw), self._used)
|
|
411
|
+
self._map[key] = cand
|
|
412
|
+
return cand
|
|
413
|
+
|
|
414
|
+
def name(self, category: str, raw: str, arity: int) -> str:
|
|
415
|
+
"""Return the unique emitted identifier for ``(category, raw, arity)``."""
|
|
416
|
+
key = (category, raw, arity)
|
|
417
|
+
if key not in self._map:
|
|
418
|
+
return self._assign(category, raw, arity)
|
|
419
|
+
return self._map[key]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class _VarResolver:
|
|
423
|
+
"""Map DISTINCT bound/free variable names to DISTINCT emitted variable tokens.
|
|
424
|
+
|
|
425
|
+
Identical to :class:`unicode_logic_kit.hol.classical._VarResolver`: THF
|
|
426
|
+
upper-cases variable tokens, so two source names differing only by case
|
|
427
|
+
would otherwise collide on one token. ``used`` is the set of tokens a variable
|
|
428
|
+
must not take (shared, not copied): the Isabelle export passes the tokens of the
|
|
429
|
+
theory's constants, functions and predicates, because a binder shadows a
|
|
430
|
+
constant of its own name inside its scope.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
def __init__(self, render, used=None):
|
|
434
|
+
self._render = render
|
|
435
|
+
self._used = set() if used is None else used
|
|
436
|
+
self._map: Dict[str, str] = {}
|
|
437
|
+
|
|
438
|
+
def token(self, raw: str) -> str:
|
|
439
|
+
if raw in self._map:
|
|
440
|
+
return self._map[raw]
|
|
441
|
+
cand = dedupe(self._render(raw), self._used)
|
|
442
|
+
self._map[raw] = cand
|
|
443
|
+
return cand
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# ===========================================================================
|
|
447
|
+
# (A) TPTP THF export
|
|
448
|
+
# ===========================================================================
|
|
449
|
+
|
|
450
|
+
def _thf_term(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
|
|
451
|
+
"""Render an individual (``$i``) term in THF applicative (``@``) form."""
|
|
452
|
+
if isinstance(node, Variable):
|
|
453
|
+
return vars_.token(node.name)
|
|
454
|
+
if isinstance(node, Constant):
|
|
455
|
+
return syms.name(_CAT_CONST, node.name, 0)
|
|
456
|
+
if isinstance(node, Number):
|
|
457
|
+
return syms.name(_CAT_CONST, "n" + str(node.value), 0)
|
|
458
|
+
if isinstance(node, Function):
|
|
459
|
+
head = syms.name(_CAT_FUNC, node.name, len(node.args))
|
|
460
|
+
return "( " + " @ ".join([head] + [_thf_term(a, syms, vars_) for a in node.args]) + " )"
|
|
461
|
+
raise NotImplementedError(
|
|
462
|
+
f"to_thf_free: unsupported term {type(node).__name__} (free-logic terms: "
|
|
463
|
+
"variable / constant / number / function only)."
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _thf_formula(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
|
|
468
|
+
"""Render the GUARDED formula as a THF ``$o`` term (classical connectives only)."""
|
|
469
|
+
def f(n):
|
|
470
|
+
return _thf_formula(n, syms, vars_)
|
|
471
|
+
if isinstance(node, Atom):
|
|
472
|
+
if truth_value(node) is not None:
|
|
473
|
+
return "$true" if truth_value(node) else "$false"
|
|
474
|
+
if _is_identity(node):
|
|
475
|
+
# Native THF identity (see the module docstring's Equality section).
|
|
476
|
+
left = _thf_term(node.args[0], syms, vars_)
|
|
477
|
+
right = _thf_term(node.args[1], syms, vars_)
|
|
478
|
+
return f"( {left} = {right} )"
|
|
479
|
+
head = syms.name(_CAT_PRED, node.predicate, len(node.args))
|
|
480
|
+
if not node.args:
|
|
481
|
+
return head
|
|
482
|
+
return "( " + " @ ".join([head] + [_thf_term(a, syms, vars_) for a in node.args]) + " )"
|
|
483
|
+
if isinstance(node, Not):
|
|
484
|
+
return f"( ~ {f(node.formula)} )"
|
|
485
|
+
if isinstance(node, And):
|
|
486
|
+
return f"( {f(node.left)} & {f(node.right)} )"
|
|
487
|
+
if isinstance(node, Or):
|
|
488
|
+
return f"( {f(node.left)} | {f(node.right)} )"
|
|
489
|
+
if isinstance(node, Implies):
|
|
490
|
+
return f"( {f(node.left)} => {f(node.right)} )"
|
|
491
|
+
if isinstance(node, Iff):
|
|
492
|
+
return f"( {f(node.left)} <=> {f(node.right)} )"
|
|
493
|
+
if isinstance(node, Xor):
|
|
494
|
+
return f"( {f(node.left)} <~> {f(node.right)} )"
|
|
495
|
+
if isinstance(node, Quantifier):
|
|
496
|
+
x = vars_.token(node.variable.name)
|
|
497
|
+
binder = "!" if node.type in (_FORALL, "forall") else "?"
|
|
498
|
+
return f"( {binder} [{x}: $i] : {f(node.formula)} )"
|
|
499
|
+
raise NotImplementedError(
|
|
500
|
+
f"to_thf_free: {type(node).__name__} should not occur in the guarded AST "
|
|
501
|
+
"this printer renders."
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _thf_signature_decls(formula: Node, syms: "_SymbolResolver") -> List[str]:
|
|
506
|
+
"""Type declarations for every predicate / function / constant, ``D``/``E!`` included."""
|
|
507
|
+
preds, funcs, consts = _signature(formula)
|
|
508
|
+
decls: List[str] = []
|
|
509
|
+
for name, arity in sorted(preds):
|
|
510
|
+
ident = syms.name(_CAT_PRED, name, arity)
|
|
511
|
+
typ = " > ".join(["$i"] * arity + ["$o"]) if arity else "$o"
|
|
512
|
+
decls.append(f"thf({ident}_decl, type, ( {ident} : ( {typ} ) )).")
|
|
513
|
+
for name, arity in sorted(funcs):
|
|
514
|
+
ident = syms.name(_CAT_FUNC, name, arity)
|
|
515
|
+
typ = " > ".join(["$i"] * (arity + 1))
|
|
516
|
+
decls.append(f"thf({ident}_decl, type, ( {ident} : ( {typ} ) )).")
|
|
517
|
+
for name in sorted(consts):
|
|
518
|
+
ident = syms.name(_CAT_CONST, name, 0)
|
|
519
|
+
decls.append(f"thf({ident}_decl, type, ( {ident} : $i )).")
|
|
520
|
+
return decls
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def to_thf_free(formula: Node, *, policy: str = "negative", conjecture: bool = True) -> str:
|
|
524
|
+
"""Emit a complete TPTP **THF** problem for a free-logic ``formula``.
|
|
525
|
+
|
|
526
|
+
Guards every atom with ``D`` and every quantifier with ``E!`` (see the
|
|
527
|
+
module docstring), then declares a typed THF constant for every predicate /
|
|
528
|
+
function / constant in the GUARDED signature (``D``/``E!`` always included),
|
|
529
|
+
plus the ``E!(x) -> D(x)`` tie as an ``axiom`` (no nonempty-existence fact —
|
|
530
|
+
an empty inner domain is a free-logic model), and the guarded translation
|
|
531
|
+
itself as the ``conjecture`` (default) or an ``axiom``.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
formula: the free-logic AST node (classical connectives / quantifiers /
|
|
535
|
+
atoms — see the module docstring for the exact fragment).
|
|
536
|
+
policy: ``"negative"`` (default) or ``"positive"`` — see
|
|
537
|
+
:mod:`~unicode_logic_kit.semantics.free_logic`.
|
|
538
|
+
``"supervaluation"`` raises ``NotImplementedError``.
|
|
539
|
+
conjecture: emit the goal as ``conjecture`` (default) or ``axiom``. A free
|
|
540
|
+
variable is a parameter: one unknown individual that exists. For a
|
|
541
|
+
conjecture that is the formula closed universally under the ``E!``
|
|
542
|
+
guard, which is what is written. For an axiom it is not (``∀x P(x)``
|
|
543
|
+
says more than ``P(x)``), so an asserted formula with a free variable
|
|
544
|
+
is refused by name.
|
|
545
|
+
|
|
546
|
+
Raises:
|
|
547
|
+
NotImplementedError: for ``policy="supervaluation"``, for a node
|
|
548
|
+
outside the free-logic fragment (modal / second-order /
|
|
549
|
+
Łukasiewicz / substructural / lambda), or for ``conjecture=False``
|
|
550
|
+
with a formula that has a free variable.
|
|
551
|
+
ValueError: for any other unknown ``policy``.
|
|
552
|
+
"""
|
|
553
|
+
_check_policy(policy, "to_thf_free")
|
|
554
|
+
# A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
|
|
555
|
+
# a user constant spelled like it is refused, not merged with it.
|
|
556
|
+
[formula], _ = numerals_as_constants([formula], where="to_thf_free",
|
|
557
|
+
spell=prefixed_numeral_name)
|
|
558
|
+
_refuse_open_assertion(formula, conjecture, "to_thf_free")
|
|
559
|
+
guarded = _guard(_close(formula), policy)
|
|
560
|
+
role = "conjecture" if conjecture else "axiom"
|
|
561
|
+
syms = _SymbolResolver(guarded)
|
|
562
|
+
vars_ = _VarResolver(lambda raw: _sanitize(raw).upper())
|
|
563
|
+
d_name = syms.name(_CAT_PRED, _DENOTES_PRED, 1)
|
|
564
|
+
e_name = syms.name(_CAT_PRED, _EXISTS_PRED, 1)
|
|
565
|
+
lines = [
|
|
566
|
+
"% Free logic embedded into THF via denotation/existence guards.",
|
|
567
|
+
f"% D(t): 't denotes'; {e_name}(t): 't exists' (E! implies D). policy={policy!r}.",
|
|
568
|
+
]
|
|
569
|
+
lines += _thf_signature_decls(guarded, syms)
|
|
570
|
+
lines.append(f"thf(free_tie, axiom, "
|
|
571
|
+
f"( ! [X: $i] : ( ( {e_name} @ X ) => ( {d_name} @ X ) ) )).")
|
|
572
|
+
lines.append(f"thf(goal, {role}, {_thf_formula(guarded, syms, vars_)}).")
|
|
573
|
+
return "\n".join(lines) + "\n"
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# ===========================================================================
|
|
577
|
+
# (B) Isabelle/HOL export
|
|
578
|
+
# ===========================================================================
|
|
579
|
+
|
|
580
|
+
_ISA_BINOP = {And: "\\<and>", Or: "\\<or>", Implies: "\\<longrightarrow>",
|
|
581
|
+
Iff: "\\<longleftrightarrow>"}
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _isa_term(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
|
|
585
|
+
"""Render an individual term in Isabelle/HOL term syntax (curried application)."""
|
|
586
|
+
if isinstance(node, Variable):
|
|
587
|
+
return vars_.token(node.name)
|
|
588
|
+
if isinstance(node, Constant):
|
|
589
|
+
return syms.name(_CAT_CONST, node.name, 0)
|
|
590
|
+
if isinstance(node, Number):
|
|
591
|
+
return syms.name(_CAT_CONST, "n" + str(node.value), 0)
|
|
592
|
+
if isinstance(node, Function):
|
|
593
|
+
head = syms.name(_CAT_FUNC, node.name, len(node.args))
|
|
594
|
+
return "(" + " ".join([head] + [_isa_term(a, syms, vars_) for a in node.args]) + ")"
|
|
595
|
+
raise NotImplementedError(
|
|
596
|
+
f"to_isabelle_free: unsupported term {type(node).__name__} (free-logic "
|
|
597
|
+
"terms: variable / constant / number / function only)."
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _isa_formula(node: Node, syms: "_SymbolResolver", vars_: "_VarResolver") -> str:
|
|
602
|
+
"""Render the GUARDED formula in Isabelle/HOL syntax (classical connectives only)."""
|
|
603
|
+
def g(n):
|
|
604
|
+
return _isa_formula(n, syms, vars_)
|
|
605
|
+
if isinstance(node, Atom):
|
|
606
|
+
if truth_value(node) is not None:
|
|
607
|
+
return "True" if truth_value(node) else "False"
|
|
608
|
+
if _is_identity(node):
|
|
609
|
+
# Native Isabelle identity (see the module docstring's Equality section).
|
|
610
|
+
left = _isa_term(node.args[0], syms, vars_)
|
|
611
|
+
right = _isa_term(node.args[1], syms, vars_)
|
|
612
|
+
return f"({left} = {right})"
|
|
613
|
+
head = syms.name(_CAT_PRED, node.predicate, len(node.args))
|
|
614
|
+
if not node.args:
|
|
615
|
+
return head
|
|
616
|
+
return "(" + " ".join([head] + [_isa_term(a, syms, vars_) for a in node.args]) + ")"
|
|
617
|
+
if isinstance(node, Not):
|
|
618
|
+
return f"(\\<not> {g(node.formula)})"
|
|
619
|
+
if type(node) in _ISA_BINOP:
|
|
620
|
+
op = _ISA_BINOP[type(node)]
|
|
621
|
+
return f"({g(node.left)} {op} {g(node.right)})"
|
|
622
|
+
if isinstance(node, Xor):
|
|
623
|
+
inner = f"({g(node.left)} \\<longleftrightarrow> {g(node.right)})"
|
|
624
|
+
return f"(\\<not> {inner})"
|
|
625
|
+
if isinstance(node, Quantifier):
|
|
626
|
+
binder = "\\<forall>" if node.type in (_FORALL, "forall") else "\\<exists>"
|
|
627
|
+
return f"({binder} {vars_.token(node.variable.name)}. {g(node.formula)})"
|
|
628
|
+
raise NotImplementedError(
|
|
629
|
+
f"to_isabelle_free: {type(node).__name__} should not occur in the guarded "
|
|
630
|
+
"AST this printer renders."
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _isa_consts_block(formula: Node, syms: "_SymbolResolver") -> List[str]:
|
|
635
|
+
"""``consts`` declarations for every predicate / function / constant, ``D``/``E!`` included.
|
|
636
|
+
|
|
637
|
+
Individuals live in a single uninterpreted HOL type ``e`` (the "outer
|
|
638
|
+
domain" of the module docstring — existing or merely possible).
|
|
639
|
+
"""
|
|
640
|
+
preds, funcs, consts = _signature(formula)
|
|
641
|
+
lines: List[str] = []
|
|
642
|
+
arrow = " \\<Rightarrow> "
|
|
643
|
+
for name, arity in sorted(preds):
|
|
644
|
+
ident = syms.name(_CAT_PRED, name, arity)
|
|
645
|
+
typ = arrow.join(["e"] * arity + ["bool"]) if arity else "bool"
|
|
646
|
+
lines.append(f'consts {ident} :: "{typ}"')
|
|
647
|
+
for name, arity in sorted(funcs):
|
|
648
|
+
ident = syms.name(_CAT_FUNC, name, arity)
|
|
649
|
+
typ = arrow.join(["e"] * (arity + 1))
|
|
650
|
+
lines.append(f'consts {ident} :: "{typ}"')
|
|
651
|
+
for name in sorted(consts):
|
|
652
|
+
ident = syms.name(_CAT_CONST, name, 0)
|
|
653
|
+
lines.append(f'consts {ident} :: "e"')
|
|
654
|
+
return lines
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def to_isabelle_free(formula: Node, *, policy: str = "negative",
|
|
658
|
+
theory_name: str = "Free_Export", lemma_name: str = "goal",
|
|
659
|
+
proof: str = "oops") -> str:
|
|
660
|
+
r"""Emit a loadable **Isabelle/HOL** theory with the guarded ``formula`` as a lemma.
|
|
661
|
+
|
|
662
|
+
The theory declares a single uninterpreted individual type ``e`` (the outer
|
|
663
|
+
domain — existing or merely possible) and a ``consts`` entry for every
|
|
664
|
+
predicate / function / constant in the GUARDED signature (``D``/``E!``
|
|
665
|
+
always included, see the module docstring), then states
|
|
666
|
+
``lemma <lemma_name>: "<E!->D tie> \<Longrightarrow> <guarded formula>"`` —
|
|
667
|
+
the tie is a PREMISE of the goal, not ``axiomatization``, so nitpick can
|
|
668
|
+
construct ``D``/``E!`` itself and certify a countermodel as genuine (mirrors
|
|
669
|
+
:mod:`~unicode_logic_kit.hol.isabelle_conditional`'s ``nested Sel`` premise).
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
formula: the free-logic AST node.
|
|
673
|
+
policy: ``"negative"`` (default) or ``"positive"``.
|
|
674
|
+
``"supervaluation"`` raises ``NotImplementedError``.
|
|
675
|
+
theory_name: the Isabelle theory identifier.
|
|
676
|
+
lemma_name: the lemma identifier.
|
|
677
|
+
proof: the proof text under the lemma; defaults to ``"oops"`` (state,
|
|
678
|
+
don't discharge — this function does not run Isabelle). Since the
|
|
679
|
+
premises are stated with the bare ``P1 \<Longrightarrow> P2 \<Longrightarrow> Q``
|
|
680
|
+
lemma syntax (no ``assumes``/``shows``), a proof method sees them as
|
|
681
|
+
an ordinary implication goal — no ``using`` clause is required to
|
|
682
|
+
reach them.
|
|
683
|
+
|
|
684
|
+
Raises:
|
|
685
|
+
NotImplementedError: for ``policy="supervaluation"``, for a node
|
|
686
|
+
outside the free-logic fragment, or for a constant or function that is spelled
|
|
687
|
+
like a numeral of the formula (``Number(1)`` next to ``Constant('n1')``): a
|
|
688
|
+
numeral is a constant of its own, named ``n1`` here, and the two would be one
|
|
689
|
+
symbol of the theory, so the writer refuses to merge them.
|
|
690
|
+
ValueError: for any other unknown ``policy``, or for a numeral that is ``inf``,
|
|
691
|
+
``-inf`` or ``nan``.
|
|
692
|
+
"""
|
|
693
|
+
_check_policy(policy, "to_isabelle_free")
|
|
694
|
+
# A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
|
|
695
|
+
# a user constant spelled like it is refused, not merged with it.
|
|
696
|
+
[formula], _ = numerals_as_constants([formula], where="to_isabelle_free",
|
|
697
|
+
spell=prefixed_numeral_name)
|
|
698
|
+
guarded = _guard(_close(formula), policy)
|
|
699
|
+
syms = _SymbolResolver(guarded)
|
|
700
|
+
vars_ = _VarResolver(_sanitize, used=syms._used)
|
|
701
|
+
d_name = syms.name(_CAT_PRED, _DENOTES_PRED, 1)
|
|
702
|
+
e_name = syms.name(_CAT_PRED, _EXISTS_PRED, 1)
|
|
703
|
+
tie = f"(\\<forall>x. {e_name} x \\<longrightarrow> {d_name} x)"
|
|
704
|
+
lines = [
|
|
705
|
+
f"theory {theory_name}",
|
|
706
|
+
" imports Main",
|
|
707
|
+
"begin",
|
|
708
|
+
"",
|
|
709
|
+
"(* Free logic embedded into Isabelle/HOL over an uninterpreted individual",
|
|
710
|
+
" type e (existing or merely possible), guarded by D (denotes) and",
|
|
711
|
+
f" {e_name} (exists; implies D). policy={policy!r}.",
|
|
712
|
+
" The E!->D tie is a PREMISE of the goal, not `axiomatization`, so nitpick",
|
|
713
|
+
" constructs D/E! itself and can certify a countermodel as genuine (mirrors",
|
|
714
|
+
" hol.isabelle_conditional's `nested`). No nonempty-existence fact: an empty",
|
|
715
|
+
" inner domain is a free-logic model. '=' is HOL identity under the D guard. *)",
|
|
716
|
+
"",
|
|
717
|
+
"typedecl e \\<comment> \\<open>outer domain: existing or merely possible\\<close>",
|
|
718
|
+
]
|
|
719
|
+
lines += _isa_consts_block(guarded, syms)
|
|
720
|
+
lines.append("")
|
|
721
|
+
lines.append(f'lemma {lemma_name}: "{tie} \\<Longrightarrow> '
|
|
722
|
+
f'{_isa_formula(guarded, syms, vars_)}"')
|
|
723
|
+
lines.append(f" {proof}")
|
|
724
|
+
lines.append("")
|
|
725
|
+
lines.append("end")
|
|
726
|
+
return "\n".join(lines) + "\n"
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def free_theory(formula: Node, *, policy: str = "negative",
|
|
730
|
+
theory_name: str = "FreeDecide", lemma_name: str = "goal",
|
|
731
|
+
proof: Optional[str] = None) -> str:
|
|
732
|
+
"""Emit a self-contained theory asserting ``formula``'s free-logic validity.
|
|
733
|
+
|
|
734
|
+
A thin wrapper around :func:`to_isabelle_free`, named and keyword-shaped
|
|
735
|
+
like
|
|
736
|
+
:func:`~unicode_logic_kit.hol.isabelle_conditional.isabelle_conditional_theory`
|
|
737
|
+
for :mod:`~unicode_logic_kit.hol.isabelle_runner`'s prove/refute two-step
|
|
738
|
+
protocol (:func:`~unicode_logic_kit.hol.isabelle_runner.isabelle_decide_free`):
|
|
739
|
+
``proof`` defaults to ``"oops"`` when omitted (state, don't discharge), and
|
|
740
|
+
``theory_name`` / ``lemma_name`` default to values distinct from
|
|
741
|
+
:func:`to_isabelle_free`'s own so both can be called back to back in one
|
|
742
|
+
script without a duplicate-theory-name collision.
|
|
743
|
+
|
|
744
|
+
Raises:
|
|
745
|
+
NotImplementedError: for ``policy="supervaluation"``, for a node
|
|
746
|
+
outside the free-logic fragment, or for a numeral next to a constant or
|
|
747
|
+
function spelled like it (see :func:`to_isabelle_free`).
|
|
748
|
+
ValueError: for any other unknown ``policy``, or for a numeral that is
|
|
749
|
+
``inf``, ``-inf`` or ``nan``.
|
|
750
|
+
"""
|
|
751
|
+
return to_isabelle_free(formula, policy=policy, theory_name=theory_name,
|
|
752
|
+
lemma_name=lemma_name,
|
|
753
|
+
proof=proof if proof is not None else "oops")
|