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,1743 @@
|
|
|
1
|
+
r"""Third-order MODAL logic → HOL: the shallow embedding, worlds made explicit.
|
|
2
|
+
|
|
3
|
+
Third-order modal logic is where a formula can say ``Positive(G)`` — a predicate
|
|
4
|
+
of a PROPERTY — inside a ``□``. Nothing first-order carries it and no
|
|
5
|
+
propositional modal calculus reaches it, but a higher-order prover does, through
|
|
6
|
+
the same shallow (Benzmüller-style) embedding the rest of this package uses: a
|
|
7
|
+
proposition is not a truth value but a FUNCTION FROM WORLDS to truth values, and
|
|
8
|
+
every connective is lifted to act pointwise on those functions. The modal
|
|
9
|
+
operators then become ordinary quantifiers over the accessibility relation, and
|
|
10
|
+
what a HOL prover sees is plain higher-order logic.
|
|
11
|
+
|
|
12
|
+
The three levels get three types::
|
|
13
|
+
|
|
14
|
+
i individuals
|
|
15
|
+
world worlds
|
|
16
|
+
sigma = world => bool propositions (a "truth value" per world)
|
|
17
|
+
i => sigma properties (the third order's argument type)
|
|
18
|
+
(i => sigma) => sigma predicates OF properties -- e.g. Positive
|
|
19
|
+
|
|
20
|
+
and the lifted vocabulary is emitted as Isabelle ``abbreviation``\ s rather than
|
|
21
|
+
``definition``\ s **on purpose**: an abbreviation is unfolded by the parser, so
|
|
22
|
+
`blast`/`metis`/`auto` see through the embedding to plain HOL without being told
|
|
23
|
+
to unfold anything. A ``definition`` would hide the goal behind a constant and
|
|
24
|
+
turn every proof into an unfolding exercise.
|
|
25
|
+
|
|
26
|
+
``mall`` / ``mex`` are polymorphic (``('a => sigma) => sigma``), so ONE pair of
|
|
27
|
+
binders serves individual quantification and property quantification alike —
|
|
28
|
+
which is the embedding's own reason for existing: the object logic's orders are
|
|
29
|
+
distinguished by the TYPE at the binder, not by separate machinery.
|
|
30
|
+
|
|
31
|
+
**What this module emits, and what it does not.** It emits a self-contained
|
|
32
|
+
Isabelle theory (or a THF problem) — types, lifted vocabulary, the frame axioms
|
|
33
|
+
for the chosen system, the signature read off the formulas, the axioms, and the
|
|
34
|
+
goals with whatever proof text the caller supplies. It does not run a prover;
|
|
35
|
+
:func:`unicode_logic_kit.hol.isabelle_runner.check_theory` does that.
|
|
36
|
+
|
|
37
|
+
**Fragment.** Alethic ``□`` / ``◇``, agent-indexed epistemic/doxastic/assertive/
|
|
38
|
+
bouletic (``K_a`` / ``B_a`` / ``Say_a`` / ``Want_a``), deontic (``O`` / ``P``,
|
|
39
|
+
serial), temporal (``Always`` / ``Eventually`` / ``Next`` / ``Until`` / ``Since``
|
|
40
|
+
and their past mirrors ``Historically`` / ``Once`` / ``Previous``) and hybrid
|
|
41
|
+
(nominals / ``@``) — the same non-counterfactual family
|
|
42
|
+
:mod:`unicode_logic_kit.hol.thf_modal` / :mod:`unicode_logic_kit.hol.isabelle_modal`
|
|
43
|
+
carry at first order, ported here to the third-order shallow embedding rather
|
|
44
|
+
than reinvented. Two things stay refused BY NAME rather than approximated: the
|
|
45
|
+
Lewis counterfactuals ``Would`` / ``Might`` (they read a similarity ordering of
|
|
46
|
+
worlds, not an accessibility relation — see
|
|
47
|
+
:mod:`unicode_logic_kit.hol.isabelle_conditional`), and the GROUP-epistemic
|
|
48
|
+
operators ``EverybodyKnows`` / ``DistributedKnowledge`` / ``CommonKnowledge``
|
|
49
|
+
(``C_G`` needs a transitive closure this embedding does not attempt).
|
|
50
|
+
|
|
51
|
+
**Equality is rigid, or refused.** ``t₁ = t₂`` / ``t₁ ≠ t₂`` between INDIVIDUALS is
|
|
52
|
+
read exactly as :func:`unicode_logic_kit.fol.qml.qml_is_valid`,
|
|
53
|
+
:mod:`unicode_logic_kit.hol.thf_modal` and :mod:`unicode_logic_kit.hol.isabelle_modal`
|
|
54
|
+
read it: HOL's own ``=`` over the individual type ``i`` with NO world argument
|
|
55
|
+
(Isabelle ``(\<lambda>_. a = b)``, THF the ``meq`` macro of ``thf_modal``), so it
|
|
56
|
+
cannot vary with the world. ``a = a`` and ``a = b → □(a = b)`` are theorems,
|
|
57
|
+
``□(a = b) → a = b`` holds exactly on a SERIAL frame (every world has a successor,
|
|
58
|
+
which every reflexive frame does), and ``a = a`` holds under every ``mode=``
|
|
59
|
+
(identity is not existence-guarded). ``≠`` is lowered to
|
|
60
|
+
``¬(=)`` first, by :func:`unicode_logic_kit.hol.isabelle_modal._lower_identity` — which is
|
|
61
|
+
:func:`unicode_logic_kit.fol.qml._st_equality` — and nothing is declared for either: no
|
|
62
|
+
``feq`` / ``fneq`` exists in the emitted theory or problem. Two cases are refused, both
|
|
63
|
+
loudly, both before anything is rendered:
|
|
64
|
+
|
|
65
|
+
* a ``=`` / ``≠`` atom that does not have exactly two terms raises ``ValueError``
|
|
66
|
+
(``qml``'s rule: the glyphs are reserved for identity and are never a world-relative
|
|
67
|
+
predicate);
|
|
68
|
+
* identity at a PROPERTY type — a predicate name or a λ-abstraction as a term of ``=`` —
|
|
69
|
+
raises ``NotImplementedError`` through
|
|
70
|
+
:func:`unicode_logic_kit.semantics._modal_reject.reject_equality`. That is not rigid
|
|
71
|
+
OBJECT identity but a different question. The grammar cannot even write it, the
|
|
72
|
+
signature analysis types the two slots of ``=`` independently (so ``G = H`` with ``G``
|
|
73
|
+
unary and ``H`` binary would pass and be ill-typed), and HOL's ``=`` at ``i ⇒ σ`` is
|
|
74
|
+
one particular answer — necessary coextension — to a question the kit has no oracle
|
|
75
|
+
for. State the relation you mean (``∀x □(P(x) ↔ Q(x))``) instead.
|
|
76
|
+
|
|
77
|
+
The ordering atoms ``<`` ``>`` ``≤`` ``≥`` stay ordinary uninterpreted relations,
|
|
78
|
+
world-relativised, as in ``qml``.
|
|
79
|
+
|
|
80
|
+
**Constants and free variables.** A constant is a particular individual that its name
|
|
81
|
+
stands for; a free variable is a parameter of the problem, one unknown element shared by
|
|
82
|
+
every formula (see :mod:`unicode_logic_kit.fol._free_parameters`). The two are different
|
|
83
|
+
symbols even when they are spelled alike, so ``P(x)`` with a free ``x`` and ``P('x')`` are
|
|
84
|
+
two statements: each is declared under a name of its own (``x`` for the variable, ``x_2``
|
|
85
|
+
for the constant) and a constant is never looked up among the bound variables, so no binder
|
|
86
|
+
of any name captures it. A constant of any name has a legal identifier in both targets (a
|
|
87
|
+
name that is an Isabelle identifier is written as it is, any other name under the kit's ASCII
|
|
88
|
+
stem of it; THF always uses the stem), and every symbol of the problem — predicate, function,
|
|
89
|
+
free variable, constant, and the embedding's own vocabulary (``R``, ``mall``, ``Rk``, ``nom_i``,
|
|
90
|
+
…) — gets a name of its own, so two names that share a stem are still two symbols.
|
|
91
|
+
|
|
92
|
+
**Domains — two independent axes.** ``mode=`` (default ``"constant"``, i.e.
|
|
93
|
+
possibilist) accepts the same domain-regime vocabulary as
|
|
94
|
+
:mod:`unicode_logic_kit.hol.isabelle_modal`'s ``_ACTUALIST_MODES``: ``"varying"``,
|
|
95
|
+
``"increasing"``, ``"cumulative"``, ``"decreasing"``, on top of the
|
|
96
|
+
constant/possibilist default. The **one genuinely new judgment call this port
|
|
97
|
+
makes** (first order has no property quantifier to decide about): the
|
|
98
|
+
``existsAt``-guard an actualist mode adds applies ONLY to the
|
|
99
|
+
INDIVIDUAL-typed use of the polymorphic binder (a :class:`~unicode_logic_kit.fol.nodes.Quantifier`
|
|
100
|
+
node, ranging over type ``i``) — a PROPERTY quantifier
|
|
101
|
+
(:class:`~unicode_logic_kit.fol.nodes.SecondOrderQuantifier`, type ``i ⇒ sigma``)
|
|
102
|
+
always stays ``mall``/``mex``, unguarded, constant across worlds, matching how
|
|
103
|
+
the ontological-argument literature and Fitting-style actualist QML treat
|
|
104
|
+
properties (an object may cease to exist; the property "being golden" does not
|
|
105
|
+
along with it). Under the default constant mode, individual quantification is
|
|
106
|
+
`mall`/`mex` too — textually and semantically identical to today, so
|
|
107
|
+
``hol.goedel`` and this module's own docs example are unaffected; only an
|
|
108
|
+
actualist ``mode=`` pays for the ``existsAt :: i ⇒ world ⇒ bool`` guard's
|
|
109
|
+
extra vocabulary and axioms, reusing the pattern of isabelle_modal.py's
|
|
110
|
+
``_quant_block``/``_domain_axioms`` (Isabelle abbreviations) and thf_modal.py's
|
|
111
|
+
``mforall``/``mexists`` THF definitions plus their domain axioms.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
from dataclasses import dataclass
|
|
115
|
+
from typing import Dict, List, Optional, Sequence
|
|
116
|
+
|
|
117
|
+
from ..fol.nodes import (
|
|
118
|
+
Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
119
|
+
SecondOrderQuantifier, PredicateTerm,
|
|
120
|
+
Variable, Constant, Function, LambdaVar, Lambda,
|
|
121
|
+
Box, Diamond,
|
|
122
|
+
Knows, Believes, Says, Wants,
|
|
123
|
+
Obligatory, Permitted,
|
|
124
|
+
Always, Eventually, Next, Until, Since,
|
|
125
|
+
Historically, Once, Previous,
|
|
126
|
+
Nominal, At,
|
|
127
|
+
analyse_signatures,
|
|
128
|
+
)
|
|
129
|
+
# Down (the ↓ binder, N1) is not yet re-exported through fol.nodes / fol's
|
|
130
|
+
# public __init__ / the top-level unicode_logic_kit package (that three-file
|
|
131
|
+
# edit is outside this change's file ownership — see the change's own
|
|
132
|
+
# report); imported directly from its defining module in the meantime, the
|
|
133
|
+
# same class object either import path would give.
|
|
134
|
+
from ..fol._hybrid_nodes import Down
|
|
135
|
+
from ..fol._ho_nodes import INDIVIDUAL
|
|
136
|
+
from ..fol._truth_constants import truth_value
|
|
137
|
+
from ..fol.frames import FRAMES, resolve_frame, UnsupportedFrameCondition
|
|
138
|
+
from ._ho_common import (
|
|
139
|
+
UnsupportedHigherOrderNode, ORDERING, FREE_VARIABLE, CONSTANT, ISABELLE_BUILT_IN,
|
|
140
|
+
peel_lambdas, rename_apart, bound_pred_names, atom_predicates,
|
|
141
|
+
function_symbols, individual_symbols, ThfNames, IsabelleNames, bound_token,
|
|
142
|
+
)
|
|
143
|
+
from ._isabelle_binders import (
|
|
144
|
+
PREDICATE, VARIABLE, BinderScope, binder_tokens, collect_binders, declared_names,
|
|
145
|
+
)
|
|
146
|
+
from ..semantics._modal_reject import (
|
|
147
|
+
EQUALITY_PREDICATES, is_equality_atom, reject_equality,
|
|
148
|
+
)
|
|
149
|
+
# Single-sourced domain-regime vocabulary: importing the frozensets (not the
|
|
150
|
+
# axiom TEXT, which stays local -- see the module docstring) keeps this
|
|
151
|
+
# module's ``mode=`` meaning the same set of names as the first-order sibling
|
|
152
|
+
# it ports the actualist guard from, so the two can never drift apart about
|
|
153
|
+
# what "varying" or "constant" means.
|
|
154
|
+
from .isabelle_modal import _ACTUALIST_MODES, _CONSTANT_MODES
|
|
155
|
+
# Rigid identity is single-sourced the same way: the lowering (``≠`` -> ``¬(=)``,
|
|
156
|
+
# a non-binary atom refused) is fol.qml._st_equality reached through
|
|
157
|
+
# isabelle_modal._lower_identity, and the THF macro that lifts HOL's own ``=``
|
|
158
|
+
# is thf_modal's -- so the first-order, second-order and third-order modal routes
|
|
159
|
+
# cannot disagree about what an identity atom is.
|
|
160
|
+
from .isabelle_modal import _has_identity, _lower_identity
|
|
161
|
+
from .thf_modal import _THF_RIGID_EQ, _THF_RIGID_EQ_DEF
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Isabelle's ASCII escapes, spelled once.
|
|
165
|
+
_ALL = r"\<forall>"
|
|
166
|
+
_EX = r"\<exists>"
|
|
167
|
+
_AND = r"\<and>"
|
|
168
|
+
_OR = r"\<or>"
|
|
169
|
+
_IMP = r"\<longrightarrow>"
|
|
170
|
+
_NOT = r"\<not>"
|
|
171
|
+
_LAM = r"\<lambda>"
|
|
172
|
+
_FUN = r"\<Rightarrow>"
|
|
173
|
+
_EQUIV = r"\<equiv>"
|
|
174
|
+
_NEQ = r"\<noteq>"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# --------------------------------------------------------------------------
|
|
178
|
+
# The lifted vocabulary
|
|
179
|
+
# --------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
_DEFINITIONS = [
|
|
182
|
+
'typedecl i \\<comment> \\<open>individuals\\<close>',
|
|
183
|
+
'typedecl world \\<comment> \\<open>worlds\\<close>',
|
|
184
|
+
f'type_synonym sigma = "world {_FUN} bool"'
|
|
185
|
+
' \\<comment> \\<open>a proposition: one truth value per world\\<close>',
|
|
186
|
+
'',
|
|
187
|
+
f'consts R :: "world {_FUN} world {_FUN} bool"'
|
|
188
|
+
' \\<comment> \\<open>accessibility\\<close>',
|
|
189
|
+
'',
|
|
190
|
+
'\\<comment> \\<open>Abbreviations, not definitions: the automation must see '
|
|
191
|
+
'THROUGH the embedding.\\<close>',
|
|
192
|
+
f'abbreviation mnot :: "sigma {_FUN} sigma"'
|
|
193
|
+
f' where "mnot p {_EQUIV} {_LAM}v. {_NOT} p v"',
|
|
194
|
+
f'abbreviation mand :: "sigma {_FUN} sigma {_FUN} sigma"'
|
|
195
|
+
f' where "mand p q {_EQUIV} {_LAM}v. p v {_AND} q v"',
|
|
196
|
+
f'abbreviation mor :: "sigma {_FUN} sigma {_FUN} sigma"'
|
|
197
|
+
f' where "mor p q {_EQUIV} {_LAM}v. p v {_OR} q v"',
|
|
198
|
+
f'abbreviation mimp :: "sigma {_FUN} sigma {_FUN} sigma"'
|
|
199
|
+
f' where "mimp p q {_EQUIV} {_LAM}v. p v {_IMP} q v"',
|
|
200
|
+
f'abbreviation miff :: "sigma {_FUN} sigma {_FUN} sigma"'
|
|
201
|
+
f' where "miff p q {_EQUIV} {_LAM}v. p v = q v"',
|
|
202
|
+
f'abbreviation mxor :: "sigma {_FUN} sigma {_FUN} sigma"'
|
|
203
|
+
f' where "mxor p q {_EQUIV} {_LAM}v. p v {_NEQ} q v"',
|
|
204
|
+
f'abbreviation mbox :: "sigma {_FUN} sigma"'
|
|
205
|
+
f' where "mbox p {_EQUIV} {_LAM}v. {_ALL}u. R v u {_IMP} p u"',
|
|
206
|
+
f'abbreviation mdia :: "sigma {_FUN} sigma"'
|
|
207
|
+
f' where "mdia p {_EQUIV} {_LAM}v. {_EX}u. R v u {_AND} p u"',
|
|
208
|
+
f'abbreviation mall :: "(\'a {_FUN} sigma) {_FUN} sigma"'
|
|
209
|
+
f' where "mall P {_EQUIV} {_LAM}v. {_ALL}x. P x v"',
|
|
210
|
+
f'abbreviation mex :: "(\'a {_FUN} sigma) {_FUN} sigma"'
|
|
211
|
+
f' where "mex P {_EQUIV} {_LAM}v. {_EX}x. P x v"',
|
|
212
|
+
f'abbreviation mvalid :: "sigma {_FUN} bool"'
|
|
213
|
+
f' where "mvalid p {_EQUIV} {_ALL}v. p v"',
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def ho_modal_definitions() -> str:
|
|
218
|
+
"""Return the Isabelle preamble: the three types, ``R``, and the lifted vocabulary.
|
|
219
|
+
|
|
220
|
+
Emitted verbatim by :func:`isabelle_ho_modal_theory`; exposed separately so a
|
|
221
|
+
caller writing its own theory around the same embedding can reuse exactly
|
|
222
|
+
the vocabulary the kit's emitted goals are stated in.
|
|
223
|
+
"""
|
|
224
|
+
return "\n".join(_DEFINITIONS)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# --------------------------------------------------------------------------
|
|
228
|
+
# Modal-family usage scan
|
|
229
|
+
# --------------------------------------------------------------------------
|
|
230
|
+
#
|
|
231
|
+
# What relations/vocabulary a theory needs is a property of the FORMULAS, not
|
|
232
|
+
# of a caller-supplied flag -- so it is read off the AST once, the same way
|
|
233
|
+
# hol.isabelle_modal's ``_Sig``/``_scan`` do at first order. Unlike that
|
|
234
|
+
# scanner this one does not ALSO collect the free signature (analyse_signatures
|
|
235
|
+
# / _signature_lines already do that, over the SAME third-order AST both HOL
|
|
236
|
+
# exporters share); it only answers "which modal families occur", which is
|
|
237
|
+
# what decides which relation consts / abbreviations / axioms this theory
|
|
238
|
+
# needs.
|
|
239
|
+
|
|
240
|
+
class _FamilyUsage:
|
|
241
|
+
"""Which non-alethic modal families a formula set uses, and whether it quantifies."""
|
|
242
|
+
|
|
243
|
+
def __init__(self):
|
|
244
|
+
self.epistemic = False # Knows -- relation Rk
|
|
245
|
+
self.doxastic = False # Believes -- relation Rb
|
|
246
|
+
self.assertive = False # Says -- relation Rs
|
|
247
|
+
self.bouletic = False # Wants -- relation Rw
|
|
248
|
+
self.deontic = False # Obligatory / Permitted -- relation Rd (serial)
|
|
249
|
+
self.temporal = False # Always / Eventually / Historically / Once -- Rt
|
|
250
|
+
self.next_ = False # Next -- Rn
|
|
251
|
+
self.previous = False # Previous (converse-of-Rn reader)
|
|
252
|
+
self.until = False # Until (inductive muntil over Rn)
|
|
253
|
+
self.since = False # Since (inductive msince over converse Rn)
|
|
254
|
+
self.hybrid = False # Nominal / At -- world constants Nom_<name>
|
|
255
|
+
self.has_quant = False # a plain individual Quantifier occurs
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def needs_next_rel(self) -> bool:
|
|
259
|
+
"""Whether the one-step relation ``Rn`` must be declared.
|
|
260
|
+
|
|
261
|
+
Needed by ``Next`` (mnext), ``Previous`` (mprevious -- its converse),
|
|
262
|
+
and the inductive ``muntil`` / ``msince`` (one-step forward/backward
|
|
263
|
+
path search) -- exactly mirroring
|
|
264
|
+
:attr:`unicode_logic_kit.hol.isabelle_modal._Sig.needs_next_rel`.
|
|
265
|
+
"""
|
|
266
|
+
return self.next_ or self.previous or self.until or self.since
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# The four agent-indexed families a ``systems=`` dict may name -- each is
|
|
270
|
+
# also the exact attribute name on _FamilyUsage that reports whether the
|
|
271
|
+
# formula set uses it (see _validate_systems).
|
|
272
|
+
_AGENT_FAMILIES = frozenset({"epistemic", "doxastic", "assertive", "bouletic"})
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _isa_family_rel(family: str) -> str:
|
|
276
|
+
"""The Isabelle relation constant for an agent-indexed family."""
|
|
277
|
+
return {"epistemic": "Rk", "doxastic": "Rb",
|
|
278
|
+
"assertive": "Rs", "bouletic": "Rw"}[family]
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _scan_family_usage(formulas: Sequence[Node]) -> _FamilyUsage:
|
|
282
|
+
"""Walk ``formulas`` and report which modal families / quantifiers occur.
|
|
283
|
+
|
|
284
|
+
Uses the generic :meth:`~unicode_logic_kit.fol._fol_nodes.Node.walk`, which
|
|
285
|
+
reaches every structural child regardless of node type (including a
|
|
286
|
+
:class:`~unicode_logic_kit.fol.nodes.Knows`/``Obligatory``/... node's own
|
|
287
|
+
``agent``/``formula`` fields) -- so a new node type nested anywhere in the
|
|
288
|
+
tree is still visited; only the classification below is by hand.
|
|
289
|
+
"""
|
|
290
|
+
usage = _FamilyUsage()
|
|
291
|
+
for formula in formulas:
|
|
292
|
+
for node in formula.walk():
|
|
293
|
+
if isinstance(node, Down):
|
|
294
|
+
# Checked FIRST (ahead of even the generic walk classification
|
|
295
|
+
# below) so a ↓-formula is refused before any vocab/signature
|
|
296
|
+
# work happens on it — never let it fall through to the
|
|
297
|
+
# generic "hybrid" bucket (True for Down too, for free, since
|
|
298
|
+
# Down.variable is itself a Nominal) unnoticed.
|
|
299
|
+
raise UnsupportedHigherOrderNode(_NO_DOWN)
|
|
300
|
+
if isinstance(node, Knows):
|
|
301
|
+
usage.epistemic = True
|
|
302
|
+
elif isinstance(node, Believes):
|
|
303
|
+
usage.doxastic = True
|
|
304
|
+
elif isinstance(node, Says):
|
|
305
|
+
usage.assertive = True
|
|
306
|
+
elif isinstance(node, Wants):
|
|
307
|
+
usage.bouletic = True
|
|
308
|
+
elif isinstance(node, (Obligatory, Permitted)):
|
|
309
|
+
usage.deontic = True
|
|
310
|
+
elif isinstance(node, (Always, Eventually)):
|
|
311
|
+
usage.temporal = True
|
|
312
|
+
elif isinstance(node, (Historically, Once)):
|
|
313
|
+
usage.temporal = True
|
|
314
|
+
elif isinstance(node, Next):
|
|
315
|
+
usage.next_ = True
|
|
316
|
+
elif isinstance(node, Previous):
|
|
317
|
+
usage.previous = True
|
|
318
|
+
elif isinstance(node, Until):
|
|
319
|
+
usage.until = True
|
|
320
|
+
elif isinstance(node, Since):
|
|
321
|
+
usage.since = True
|
|
322
|
+
elif isinstance(node, (Nominal, At)):
|
|
323
|
+
usage.hybrid = True
|
|
324
|
+
elif isinstance(node, Quantifier):
|
|
325
|
+
usage.has_quant = True
|
|
326
|
+
return usage
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# --------------------------------------------------------------------------
|
|
330
|
+
# Agent-indexed per-system frame axioms (K_a / B_a / Say_a / Want_a)
|
|
331
|
+
# --------------------------------------------------------------------------
|
|
332
|
+
#
|
|
333
|
+
# Ported verbatim (same five Horn schemas, same "leave the agent schematic"
|
|
334
|
+
# reading) from hol.isabelle_modal's _AGENT_CONDS -- kept as its own local
|
|
335
|
+
# copy rather than imported so the schema text stays self-contained in this
|
|
336
|
+
# file like everything else here, the same choice thf_modal.py makes for its
|
|
337
|
+
# own _agent_frame_axioms rather than importing isabelle_modal's Isabelle text.
|
|
338
|
+
_ISA_AGENT_CONDS = {
|
|
339
|
+
"refl": '{tag}_refl: "{_ALL}a w. {rel} a w w"',
|
|
340
|
+
"trans": ('{tag}_trans: "{_ALL}a w v u. {rel} a w v {_IMP} '
|
|
341
|
+
'{rel} a v u {_IMP} {rel} a w u"'),
|
|
342
|
+
"sym": '{tag}_sym: "{_ALL}a w v. {rel} a w v {_IMP} {rel} a v w"',
|
|
343
|
+
"serial": '{tag}_serial: "{_ALL}a w. {_EX}v. {rel} a w v"',
|
|
344
|
+
"eucl": ('{tag}_eucl: "{_ALL}a w v u. {rel} a w v {_IMP} '
|
|
345
|
+
'{rel} a w u {_IMP} {rel} a v u"'),
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _agent_system_axioms(family: str, system: str) -> List[str]:
|
|
350
|
+
"""Per-agent frame axioms constraining ``family``'s relation under ``system``.
|
|
351
|
+
|
|
352
|
+
Each axiom universally quantifies the agent explicitly (``\\<forall>a``), so
|
|
353
|
+
the property holds for EVERY agent -- matching
|
|
354
|
+
:func:`unicode_logic_kit.hol.isabelle_modal._agent_system_axioms`'s reading
|
|
355
|
+
and, semantically, the per-agent relations of
|
|
356
|
+
:func:`unicode_logic_kit.semantics.kripke.satisfies_modal`. Only systems
|
|
357
|
+
whose conditions are plain Horn frame properties are accepted; a system
|
|
358
|
+
needing Löb / directedness / connectedness has no per-agent schema here
|
|
359
|
+
and raises rather than silently emitting a WEAKER logic than requested --
|
|
360
|
+
the same refusal :func:`~unicode_logic_kit.hol.isabelle_modal._agent_system_axioms`
|
|
361
|
+
makes.
|
|
362
|
+
"""
|
|
363
|
+
if system not in FRAMES:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"isabelle_ho_modal_theory: unknown system {system!r} for "
|
|
366
|
+
f"{family} (use one of {sorted(FRAMES)})."
|
|
367
|
+
)
|
|
368
|
+
conditions = resolve_frame(system)
|
|
369
|
+
unsupported = [c for c in conditions if c not in _ISA_AGENT_CONDS]
|
|
370
|
+
if unsupported:
|
|
371
|
+
raise NotImplementedError(
|
|
372
|
+
f"isabelle_ho_modal_theory: system {system!r} for {family} needs "
|
|
373
|
+
f"the frame condition(s) {unsupported}, which have no per-agent "
|
|
374
|
+
f"axiom schema here; use frame= on the alethic relation for those "
|
|
375
|
+
f"systems."
|
|
376
|
+
)
|
|
377
|
+
rel = _isa_family_rel(family)
|
|
378
|
+
return [
|
|
379
|
+
"axiomatization where " + _ISA_AGENT_CONDS[c].format(
|
|
380
|
+
rel=rel, tag=rel, _ALL=_ALL, _EX=_EX, _IMP=_IMP)
|
|
381
|
+
for c in conditions
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _validate_systems(usage: _FamilyUsage, systems: Optional[dict],
|
|
386
|
+
caller: str = "isabelle_ho_modal_theory") -> dict:
|
|
387
|
+
"""Validate ``systems=`` and return it (``{}`` if ``None``).
|
|
388
|
+
|
|
389
|
+
Every key must be one of the four agent-indexed families; a family named
|
|
390
|
+
that the formula set never actually uses is refused (the axiom would be
|
|
391
|
+
dead weight the caller never asked to constrain, and silently accepting
|
|
392
|
+
it risks a typo -- e.g. ``"epistemc"`` -- going unnoticed). Shared between
|
|
393
|
+
:func:`isabelle_ho_modal_theory` and :func:`to_thf_ho_modal` (``caller``
|
|
394
|
+
only changes what the error names), so the two HOL routes can never
|
|
395
|
+
accept a different ``systems=`` for the same input.
|
|
396
|
+
"""
|
|
397
|
+
if not systems:
|
|
398
|
+
return {}
|
|
399
|
+
unknown = sorted(set(systems) - set(_AGENT_FAMILIES))
|
|
400
|
+
if unknown:
|
|
401
|
+
raise ValueError(
|
|
402
|
+
f"{caller}: unknown systems= famil{'y' if len(unknown) == 1 else 'ies'} "
|
|
403
|
+
f"{unknown} (use any of {sorted(_AGENT_FAMILIES)})."
|
|
404
|
+
)
|
|
405
|
+
unused = [fam for fam in systems if not getattr(usage, fam)]
|
|
406
|
+
if unused:
|
|
407
|
+
raise ValueError(
|
|
408
|
+
f"{caller}: systems= names {sorted(unused)}, but "
|
|
409
|
+
f"the formula set contains no matching operator "
|
|
410
|
+
f"(K_a / B_a / Say_a / Want_a respectively)."
|
|
411
|
+
)
|
|
412
|
+
return dict(systems)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# --------------------------------------------------------------------------
|
|
416
|
+
# Extended vocabulary: agent-indexed / deontic / temporal / hybrid families
|
|
417
|
+
# --------------------------------------------------------------------------
|
|
418
|
+
#
|
|
419
|
+
# Emitted CONDITIONALLY (only the relations/abbreviations a formula set
|
|
420
|
+
# actually needs), matching hol.isabelle_modal's own conditional emission --
|
|
421
|
+
# unlike this module's THF side (see _THF_FAMILY_DEFS below), which follows
|
|
422
|
+
# thf_modal.py's convention of declaring every relation unconditionally.
|
|
423
|
+
# Isabelle keeps the conditional discipline the ALETHIC frame axioms already
|
|
424
|
+
# use here (``if frame_lines: ...``), so a theory that never mentions K_a
|
|
425
|
+
# never declares Rk either.
|
|
426
|
+
|
|
427
|
+
def _family_vocab_lines(usage: _FamilyUsage) -> List[str]:
|
|
428
|
+
"""``consts``/``abbreviation`` lines for every family ``usage`` reports used."""
|
|
429
|
+
out: List[str] = []
|
|
430
|
+
for family in ("epistemic", "doxastic", "assertive", "bouletic"):
|
|
431
|
+
if not getattr(usage, family):
|
|
432
|
+
continue
|
|
433
|
+
rel = _isa_family_rel(family)
|
|
434
|
+
macro = {"epistemic": "mknows", "doxastic": "mbelieves",
|
|
435
|
+
"assertive": "msays", "bouletic": "mwants"}[family]
|
|
436
|
+
out.append(f'consts {rel} :: "i {_FUN} world {_FUN} world {_FUN} bool"')
|
|
437
|
+
out.append(f'abbreviation {macro} :: "i {_FUN} sigma {_FUN} sigma" where')
|
|
438
|
+
out.append(f' "{macro} a p {_EQUIV} {_LAM}v. {_ALL}u. {rel} a v u {_IMP} p u"')
|
|
439
|
+
if usage.deontic:
|
|
440
|
+
out += [
|
|
441
|
+
f'consts Rd :: "world {_FUN} world {_FUN} bool"',
|
|
442
|
+
f'abbreviation mobl :: "sigma {_FUN} sigma"'
|
|
443
|
+
f' where "mobl p {_EQUIV} {_LAM}v. {_ALL}u. Rd v u {_IMP} p u"',
|
|
444
|
+
f'abbreviation mperm :: "sigma {_FUN} sigma"'
|
|
445
|
+
f' where "mperm p {_EQUIV} {_LAM}v. {_EX}u. Rd v u {_AND} p u"',
|
|
446
|
+
]
|
|
447
|
+
if usage.temporal:
|
|
448
|
+
out += [
|
|
449
|
+
f'consts Rt :: "world {_FUN} world {_FUN} bool"'
|
|
450
|
+
' \\<comment> \\<open>henceforth (reflexive-transitive) reader for Always/Eventually\\<close>',
|
|
451
|
+
f'abbreviation malways :: "sigma {_FUN} sigma"'
|
|
452
|
+
f' where "malways p {_EQUIV} {_LAM}v. {_ALL}u. Rt v u {_IMP} p u"',
|
|
453
|
+
f'abbreviation meventually :: "sigma {_FUN} sigma"'
|
|
454
|
+
f' where "meventually p {_EQUIV} {_LAM}v. {_EX}u. Rt v u {_AND} p u"',
|
|
455
|
+
f'abbreviation mhistorically :: "sigma {_FUN} sigma"'
|
|
456
|
+
f' where "mhistorically p {_EQUIV} {_LAM}v. {_ALL}u. Rt u v {_IMP} p u"',
|
|
457
|
+
f'abbreviation monce :: "sigma {_FUN} sigma"'
|
|
458
|
+
f' where "monce p {_EQUIV} {_LAM}v. {_EX}u. Rt u v {_AND} p u"',
|
|
459
|
+
]
|
|
460
|
+
if usage.needs_next_rel:
|
|
461
|
+
out.append(
|
|
462
|
+
f'consts Rn :: "world {_FUN} world {_FUN} bool"'
|
|
463
|
+
' \\<comment> \\<open>one-step reader for Next/Previous/Until/Since\\<close>')
|
|
464
|
+
if usage.next_:
|
|
465
|
+
out.append(f'abbreviation mnext :: "sigma {_FUN} sigma"'
|
|
466
|
+
f' where "mnext p {_EQUIV} {_LAM}v. {_ALL}u. Rn v u {_IMP} p u"')
|
|
467
|
+
if usage.previous:
|
|
468
|
+
out.append(f'abbreviation mprevious :: "sigma {_FUN} sigma"'
|
|
469
|
+
f' where "mprevious p {_EQUIV} {_LAM}v. {_ALL}u. Rn u v {_IMP} p u"')
|
|
470
|
+
if usage.until:
|
|
471
|
+
# Strong Until as a least-fixpoint INDUCTIVE predicate over Rn -- not
|
|
472
|
+
# an abbreviation, because it is a genuine recursive definition, the
|
|
473
|
+
# same reason hol.isabelle_modal's own ``inductive muntil`` is not one
|
|
474
|
+
# either (see that module's _until_block). ``phi``/``psi`` are
|
|
475
|
+
# world-indexed (type sigma, i.e. exactly isabelle_modal's "i =>
|
|
476
|
+
# bool" reading at THAT module's world type), so muntil phi psi
|
|
477
|
+
# partially applied already has type sigma.
|
|
478
|
+
out += [
|
|
479
|
+
'inductive muntil :: "sigma \\<Rightarrow> sigma \\<Rightarrow> world \\<Rightarrow> bool"',
|
|
480
|
+
' for phi :: sigma and psi :: sigma where',
|
|
481
|
+
' muntil_base: "psi w \\<Longrightarrow> muntil phi psi w"',
|
|
482
|
+
'| muntil_step: "phi w \\<Longrightarrow> Rn w v \\<Longrightarrow> muntil phi psi v '
|
|
483
|
+
'\\<Longrightarrow> muntil phi psi w"',
|
|
484
|
+
]
|
|
485
|
+
if usage.since:
|
|
486
|
+
out += [
|
|
487
|
+
'inductive msince :: "sigma \\<Rightarrow> sigma \\<Rightarrow> world \\<Rightarrow> bool"',
|
|
488
|
+
' for phi :: sigma and psi :: sigma where',
|
|
489
|
+
' msince_base: "psi w \\<Longrightarrow> msince phi psi w"',
|
|
490
|
+
'| msince_step: "phi w \\<Longrightarrow> Rn v w \\<Longrightarrow> msince phi psi v '
|
|
491
|
+
'\\<Longrightarrow> msince phi psi w"',
|
|
492
|
+
]
|
|
493
|
+
return out
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _hybrid_vocab_lines(nominal_consts: Sequence[str]) -> List[str]:
|
|
497
|
+
"""``consts nom_<name> :: world`` -- one world constant per nominal (H(@))."""
|
|
498
|
+
return [f'consts {c} :: "world"'
|
|
499
|
+
' \\<comment> \\<open>the world named by a hybrid nominal\\<close>'
|
|
500
|
+
for c in nominal_consts]
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _family_axiom_lines(usage: _FamilyUsage, systems: dict,
|
|
504
|
+
temporal_closure: bool) -> List[str]:
|
|
505
|
+
"""``axiomatization`` lines for every family/system ``usage``/``systems`` need.
|
|
506
|
+
|
|
507
|
+
Order mirrors :func:`unicode_logic_kit.hol.isabelle_modal._collect_axioms`:
|
|
508
|
+
per-agent systems, then deontic seriality, then the temporal
|
|
509
|
+
closure/linking axioms -- so the two HOL routes read the same way.
|
|
510
|
+
"""
|
|
511
|
+
lines: List[str] = []
|
|
512
|
+
for family in sorted(systems):
|
|
513
|
+
lines += _agent_system_axioms(family, systems[family])
|
|
514
|
+
if usage.deontic:
|
|
515
|
+
lines.append('axiomatization where Rd_serial: "{0}v. {1}u. Rd v u"'
|
|
516
|
+
.format(_ALL, _EX))
|
|
517
|
+
if usage.temporal and temporal_closure:
|
|
518
|
+
lines += [
|
|
519
|
+
f'axiomatization where Rt_refl: "{_ALL}w. Rt w w"',
|
|
520
|
+
f'axiomatization where Rt_trans: "{_ALL}w v u. Rt w v {_IMP} '
|
|
521
|
+
f'Rt v u {_IMP} Rt w u"',
|
|
522
|
+
]
|
|
523
|
+
# tnext ⊆ t*: every one-step Rn-successor (what Next/Previous/Until/Since
|
|
524
|
+
# read) is henceforth-reachable (what Always/Eventually read) -- without
|
|
525
|
+
# this link Always(P) -> Next(P), valid for satisfies_modal (X and G/F
|
|
526
|
+
# both close over the SAME one-step "temporal" relation there), would be
|
|
527
|
+
# a non-theorem of the embedding (Rt and Rn otherwise unconstrained
|
|
528
|
+
# relative to each other). Mirrors isabelle_modal.py's n_in_t /
|
|
529
|
+
# thf_modal.py's tnext_in_t exactly.
|
|
530
|
+
if usage.temporal and usage.needs_next_rel:
|
|
531
|
+
lines.append(f'axiomatization where Rn_in_Rt: "{_ALL}w v. Rn w v '
|
|
532
|
+
f'{_IMP} Rt w v"')
|
|
533
|
+
return lines
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# Frame condition -> its Isabelle axiom over R, universally quantified so the
|
|
537
|
+
# axiom is a schema about R and cannot be narrowed by a constant that happens to
|
|
538
|
+
# share a variable's name (see hol/isabelle_modal.py's note on that trap).
|
|
539
|
+
#
|
|
540
|
+
# "loeb"/"mckinsey"/"grz" are schemas over PROPOSITIONS, not conditions on R
|
|
541
|
+
# alone (there is no first-order frame correspondence for any of the three),
|
|
542
|
+
# so each states the Löb / McKinsey / Grzegorczyk schema directly, exactly as
|
|
543
|
+
# hol.isabelle_modal's r_loeb/r_mckinsey/r_grz do at first order -- with ONE
|
|
544
|
+
# difference in how the schema predicate P is written: it is bound EXPLICITLY
|
|
545
|
+
# here (`\<forall>P::sigma.`) rather than left a free/schematic variable the
|
|
546
|
+
# way isabelle_modal's first-order port leaves it. That first-order module
|
|
547
|
+
# gets away with a free P only via a side convention (_safe_name lower-cases
|
|
548
|
+
# every user predicate's leading character, so a free schematic `P` can never
|
|
549
|
+
# collide with a user symbol); this module carries no such convention, and
|
|
550
|
+
# does not need one for a different reason: isabelle_ho_modal_theory always
|
|
551
|
+
# emits the frame axioms (this dict) BEFORE the signature's `consts`
|
|
552
|
+
# declarations (see its body), so at the point a free P in, say, R_loeb would
|
|
553
|
+
# be elaborated, no user-declared `consts P` exists yet for it to resolve to
|
|
554
|
+
# -- Isar turns a free identifier with no existing constant of that name into
|
|
555
|
+
# a schematic variable of the statement, which is exactly the generalisation
|
|
556
|
+
# a bound `\<forall>P` gives explicitly. (Reversing that emission order would
|
|
557
|
+
# reintroduce a real capture: verified live against local Isabelle by moving
|
|
558
|
+
# `consts P` before a free-variable R_loeb and watching a proof for a
|
|
559
|
+
# DIFFERENT proposition Q, which should need R_loeb's full generality, stall
|
|
560
|
+
# instead of discharging.) The explicit binder is kept anyway, as
|
|
561
|
+
# defense-in-depth against that emission order ever changing and because it
|
|
562
|
+
# makes the schema's universal scope visible in the source text rather than
|
|
563
|
+
# resting on an unstated fact about Isar's free-variable elaboration. The THF
|
|
564
|
+
# side keeps the same explicit binder for the same reason; there a user's own
|
|
565
|
+
# ``P`` is a constant, spelled as the lower word ``p`` (see
|
|
566
|
+
# _ho_common.ThfNames), so it could not meet the schema variable in any case.
|
|
567
|
+
_FRAME_AXIOMS = {
|
|
568
|
+
"refl": f'"{_ALL}x. R x x"',
|
|
569
|
+
"sym": f'"{_ALL}x y. R x y {_IMP} R y x"',
|
|
570
|
+
"trans": f'"{_ALL}x y z. R x y {_IMP} R y z {_IMP} R x z"',
|
|
571
|
+
"serial": f'"{_ALL}x. {_EX}y. R x y"',
|
|
572
|
+
"eucl": f'"{_ALL}x y z. R x y {_IMP} R x z {_IMP} R y z"',
|
|
573
|
+
"directed": f'"{_ALL}x y z. R x y {_IMP} R x z {_IMP} ({_EX}u. R y u {_AND} R z u)"',
|
|
574
|
+
"connected": f'"{_ALL}x y z. R x y {_IMP} R x z {_IMP} (R y z {_OR} R z y)"',
|
|
575
|
+
"functional": f'"{_ALL}x y z. R x y {_IMP} R x z {_IMP} y = z"',
|
|
576
|
+
"dense": f'"{_ALL}x y. R x y {_IMP} ({_EX}z. R x z {_AND} R z y)"',
|
|
577
|
+
"shift_refl": f'"{_ALL}x y. R x y {_IMP} R y y"',
|
|
578
|
+
"empty": f'"{_ALL}x y. {_NOT} R x y"',
|
|
579
|
+
# Löb: box(box P -> P) -> box P, i.e. (∀y. Rxy -> ((∀z. Ryz -> Pz) -> Py))
|
|
580
|
+
# -> (∀y. Rxy -> Py), for every world x and every proposition P.
|
|
581
|
+
"loeb": (f'"{_ALL}P::sigma. {_ALL}x. ({_ALL}y. R x y {_IMP} '
|
|
582
|
+
f'(({_ALL}z. R y z {_IMP} P z) {_IMP} P y)) {_IMP} '
|
|
583
|
+
f'({_ALL}y. R x y {_IMP} P y)"'),
|
|
584
|
+
# McKinsey: box(diamond P) -> diamond(box P).
|
|
585
|
+
"mckinsey": (f'"{_ALL}P::sigma. {_ALL}x. ({_ALL}y. R x y {_IMP} '
|
|
586
|
+
f'({_EX}z. R y z {_AND} P z)) {_IMP} '
|
|
587
|
+
f'({_EX}y. R x y {_AND} ({_ALL}z. R y z {_IMP} P z))"'),
|
|
588
|
+
# Grzegorczyk: box(box(P -> box P) -> P) -> P.
|
|
589
|
+
"grz": (f'"{_ALL}P::sigma. {_ALL}x. ({_ALL}y. R x y {_IMP} '
|
|
590
|
+
f'(({_ALL}z. R y z {_IMP} (P z {_IMP} ({_ALL}u. R z u {_IMP} P u))) '
|
|
591
|
+
f'{_IMP} P y)) {_IMP} P x"'),
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _frame_axiom_lines(frame: str) -> List[str]:
|
|
596
|
+
"""Return the ``axiomatization`` lines constraining ``R`` for ``frame``.
|
|
597
|
+
|
|
598
|
+
Reads :data:`unicode_logic_kit.fol.frames.FRAMES` like every other modal route
|
|
599
|
+
in the kit, so a system means the same thing here as it does there. Every
|
|
600
|
+
condition the registry lists has an entry in ``_FRAME_AXIOMS`` — including
|
|
601
|
+
Löb, McKinsey and Grz, which have no first-order correspondence and are
|
|
602
|
+
instead stated as schemas over propositions, each with its schema variable
|
|
603
|
+
bound explicitly (see the comment above ``_FRAME_AXIOMS``). A name the
|
|
604
|
+
registry itself does not know is caught earlier, by ``resolve_frame``; this
|
|
605
|
+
loop's own raise is a defence against ``_FRAME_AXIOMS`` and the registry
|
|
606
|
+
drifting apart, not a live refusal path for any currently-registered
|
|
607
|
+
condition.
|
|
608
|
+
"""
|
|
609
|
+
conditions = resolve_frame(frame)
|
|
610
|
+
lines = []
|
|
611
|
+
for condition in conditions:
|
|
612
|
+
if condition not in _FRAME_AXIOMS:
|
|
613
|
+
raise UnsupportedFrameCondition(
|
|
614
|
+
f"ho_modal: frame condition {condition!r} (system {frame!r}) has no "
|
|
615
|
+
f"axiom registered in this embedding's _FRAME_AXIOMS; "
|
|
616
|
+
f"fol.frames.resolve_frame accepted it but this module does not "
|
|
617
|
+
f"yet state it."
|
|
618
|
+
)
|
|
619
|
+
lines.append(f'axiomatization where R_{condition}: {_FRAME_AXIOMS[condition]}')
|
|
620
|
+
return lines
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
# --------------------------------------------------------------------------
|
|
624
|
+
# Rigid identity
|
|
625
|
+
# --------------------------------------------------------------------------
|
|
626
|
+
#
|
|
627
|
+
# Object identity is NOT a world-relativised predicate (fol.qml's "Equality is
|
|
628
|
+
# rigid"). It is HOL's own ``=`` over the individual type ``i``, lifted to the
|
|
629
|
+
# type of a proposition by a world binder it never uses, so it cannot vary with the
|
|
630
|
+
# world. Both exporters read an identity atom through the two helpers below and
|
|
631
|
+
# nowhere else, so a formula is lowered and checked ONCE, up front -- never lazily
|
|
632
|
+
# at the atom, where a route that short-circuits could skip it.
|
|
633
|
+
|
|
634
|
+
#: How the refusal of a PROPERTY-typed identity speaks (see :func:`_rigid_identity`).
|
|
635
|
+
_PROPERTY_IDENTITY_ROUTE = "the third-order modal embedding at a property type"
|
|
636
|
+
_PROPERTY_IDENTITY_ATOM_READING = (
|
|
637
|
+
"'=' is read between INDIVIDUALS only, as HOL's own identity over the type i; "
|
|
638
|
+
"a property is a world-indexed function i => ... => sigma, and whether two of "
|
|
639
|
+
"them are 'identical' (necessarily coextensive? coextensive at one world?) is a "
|
|
640
|
+
"choice this embedding does not make for you"
|
|
641
|
+
)
|
|
642
|
+
#: Not the propositional routes' failure. They key an atom by its rendered form
|
|
643
|
+
#: and so get 'a = a' FALSE — a wrong answer. Here there is no single right
|
|
644
|
+
#: answer to give: HOL's own ``=`` at a property type IS one reading (necessary
|
|
645
|
+
#: coextension), and picking it silently would answer a question the caller did
|
|
646
|
+
#: not ask rather than the one they did.
|
|
647
|
+
_PROPERTY_IDENTITY_CONSEQUENCE = (
|
|
648
|
+
"so reading it as HOL's own identity at that type would silently pick "
|
|
649
|
+
"necessary coextension as what 'the same property' means"
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
_PROPERTY_IDENTITY_INSTEAD = (
|
|
653
|
+
"State the relation you mean between the properties instead: necessary "
|
|
654
|
+
"coextension is ∀x □(P(x) ↔ Q(x)), coextension at the current world is "
|
|
655
|
+
"∀x (P(x) ↔ Q(x)). Identity of INDIVIDUALS (a = b) is supported, and rigid."
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _rigid_identity(formulas: Sequence[Node], caller: str) -> List[Node]:
|
|
660
|
+
"""Read every identity atom of ``formulas`` as rigid identity, or refuse it by name.
|
|
661
|
+
|
|
662
|
+
Two steps, both BEFORE any signature analysis or rendering:
|
|
663
|
+
|
|
664
|
+
1. An identity atom with a PROPERTY-typed term (a predicate name or a
|
|
665
|
+
λ-abstraction, i.e. identity at type ``i ⇒ … ⇒ σ`` rather than ``i``) is
|
|
666
|
+
refused by name through
|
|
667
|
+
:func:`~unicode_logic_kit.semantics._modal_reject.reject_equality`. This is not
|
|
668
|
+
rigid identity gone missing but a different question: the grammar cannot even
|
|
669
|
+
write it (``G = H`` does not parse), :func:`~unicode_logic_kit.fol.nodes.analyse_signatures`
|
|
670
|
+
types the two slots of ``=`` independently (so ``G = H`` with ``G`` unary and
|
|
671
|
+
``H`` binary would be accepted and ill-typed in HOL), and HOL's ``=`` at a
|
|
672
|
+
function type is one particular answer -- necessary coextension -- to a question
|
|
673
|
+
the kit has no oracle for. Rather than guess, the case is refused. It is checked
|
|
674
|
+
BEFORE the lowering so the message names the atom as the caller wrote it
|
|
675
|
+
(``≠``, not the ``¬(=)`` it would have become).
|
|
676
|
+
2. :func:`~unicode_logic_kit.hol.isabelle_modal._lower_identity` -- the very rule
|
|
677
|
+
:func:`unicode_logic_kit.fol.qml.qml_translate` applies (``t₁ ≠ t₂`` becomes
|
|
678
|
+
``¬(t₁ = t₂)``) -- and a non-binary ``=`` / ``≠`` raises ``ValueError``, as in
|
|
679
|
+
``qml``. A formula without an identity atom is returned unchanged.
|
|
680
|
+
|
|
681
|
+
The returned list is parallel to ``formulas``.
|
|
682
|
+
"""
|
|
683
|
+
for formula in formulas:
|
|
684
|
+
for node in formula.walk():
|
|
685
|
+
# A non-binary atom is step 2's ValueError, whatever its terms are.
|
|
686
|
+
if is_equality_atom(node) and len(node.args) == 2 and any(
|
|
687
|
+
isinstance(arg, (PredicateTerm, Lambda)) for arg in node.args):
|
|
688
|
+
reject_equality(node, caller, _PROPERTY_IDENTITY_ROUTE,
|
|
689
|
+
atom_reading=_PROPERTY_IDENTITY_ATOM_READING,
|
|
690
|
+
consequence=_PROPERTY_IDENTITY_CONSEQUENCE,
|
|
691
|
+
instead=_PROPERTY_IDENTITY_INSTEAD)
|
|
692
|
+
return [_lower_identity(f, caller) for f in formulas]
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _identity_sides(node: Atom):
|
|
696
|
+
"""The two individual terms of an identity atom that :func:`_rigid_identity` accepted.
|
|
697
|
+
|
|
698
|
+
The renderers call this on every ``=`` atom. An atom that is not a binary ``=``
|
|
699
|
+
has bypassed the lowering (``≠`` becomes ``¬(=)`` there, a non-binary atom is
|
|
700
|
+
refused there); rendering it anyway would route it through the generic predicate
|
|
701
|
+
path, where THF's name resolver turns ``≠`` into ``fneq`` -- an uninterpreted
|
|
702
|
+
reading of identity, which is exactly what this embedding refuses. So it raises.
|
|
703
|
+
"""
|
|
704
|
+
if node.predicate != "=" or len(node.args) != 2:
|
|
705
|
+
raise UnsupportedHigherOrderNode(
|
|
706
|
+
f"ho_modal: the identity atom {node.to_unicode_str()!r} reached the renderer "
|
|
707
|
+
f"without being lowered (≠ becomes ¬(=), and a non-binary =/≠ is refused, "
|
|
708
|
+
f"before any rendering). Render through isabelle_ho_modal_theory / "
|
|
709
|
+
f"to_thf_ho_modal, which do that; identity is never rendered as a "
|
|
710
|
+
f"world-relative predicate."
|
|
711
|
+
)
|
|
712
|
+
return node.args
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
# --------------------------------------------------------------------------
|
|
716
|
+
# Isabelle rendering
|
|
717
|
+
# --------------------------------------------------------------------------
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _prop_type(arity: int) -> str:
|
|
721
|
+
"""Return the Isabelle type of a property of ``arity`` arguments: ``i => … => sigma``."""
|
|
722
|
+
return "".join(f"i {_FUN} " for _ in range(arity)) + "sigma"
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _nominal_const(name: str) -> str:
|
|
726
|
+
"""The Isabelle ``world`` constant naming a hybrid nominal (``i`` -> ``nom_i``).
|
|
727
|
+
|
|
728
|
+
A plain prefix, not a full de-collision registry: nominal names are
|
|
729
|
+
already legal (lowercase) identifiers, so two distinct source names stay
|
|
730
|
+
distinct after prefixing, and ``nom_<name>`` cannot coincide with any of
|
|
731
|
+
this module's own fixed identifiers (``R``, ``Rk``, ``mall``, a bound
|
|
732
|
+
``w``/``v``/``u``/``x``, …) or with a free individual/predicate constant
|
|
733
|
+
(a DIFFERENT Isabelle type, ``world`` rather than ``i``/``sigma``, so even
|
|
734
|
+
a same-spelled clash would be a type error caught at load time, not a
|
|
735
|
+
silent misreading).
|
|
736
|
+
"""
|
|
737
|
+
return f"nom_{name}"
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def _domain_vocab_lines() -> List[str]:
|
|
741
|
+
"""``existsAt`` + the existsAt-guarded ``mforall``/``mexists`` (actualist modes only).
|
|
742
|
+
|
|
743
|
+
Ported from hol.isabelle_modal.py's ``_quant_block`` (see the module
|
|
744
|
+
docstring's "Domains" section for the one judgment call this makes): the
|
|
745
|
+
guard is typed ``i => world => bool`` (an INDIVIDUAL exists at a WORLD),
|
|
746
|
+
matching ho_modal.py's own type names rather than isabelle_modal.py's
|
|
747
|
+
(there ``i``/``e`` mean world/individual, the reverse of here). Emitted
|
|
748
|
+
only when an actualist ``mode=`` is combined with an actual individual
|
|
749
|
+
``Quantifier`` — never for the default constant/possibilist mode, which
|
|
750
|
+
keeps using the existing, unguarded ``mall``/``mex`` exactly as before.
|
|
751
|
+
"""
|
|
752
|
+
return [
|
|
753
|
+
f'consts existsAt :: "i {_FUN} world {_FUN} bool"'
|
|
754
|
+
' \\<comment> \\<open>individual x exists at world w\\<close>',
|
|
755
|
+
f'abbreviation mforall :: "(i {_FUN} sigma) {_FUN} sigma" where',
|
|
756
|
+
f' "mforall P {_EQUIV} {_LAM}v. {_ALL}x. existsAt x v {_IMP} P x v"',
|
|
757
|
+
f'abbreviation mexists :: "(i {_FUN} sigma) {_FUN} sigma" where',
|
|
758
|
+
f' "mexists P {_EQUIV} {_LAM}v. {_EX}x. existsAt x v {_AND} P x v"',
|
|
759
|
+
]
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _domain_axiom_lines(mode: str) -> List[str]:
|
|
763
|
+
"""``existsAt`` domain-regime axioms for an actualist ``mode`` — ported from
|
|
764
|
+
:func:`unicode_logic_kit.hol.isabelle_modal._domain_axioms`, over ``R`` (this
|
|
765
|
+
module's alethic relation) rather than ``r``. Never called for a constant
|
|
766
|
+
mode (see :func:`_domain_vocab_lines`): there, quantification stays plain
|
|
767
|
+
``mall``/``mex`` and no ``existsAt`` axiom is needed at all.
|
|
768
|
+
"""
|
|
769
|
+
out = [f'axiomatization where nonempty_dom: "{_EX}x. existsAt x w"']
|
|
770
|
+
if mode in ("increasing", "cumulative"):
|
|
771
|
+
out.append(f'axiomatization where cumul_dom: "existsAt x w {_IMP} '
|
|
772
|
+
f'R w v {_IMP} existsAt x v"')
|
|
773
|
+
elif mode == "decreasing":
|
|
774
|
+
out.append(f'axiomatization where decr_dom: "existsAt x v {_IMP} '
|
|
775
|
+
f'R w v {_IMP} existsAt x w"')
|
|
776
|
+
# "varying": nonempty_dom only, no monotonicity axiom.
|
|
777
|
+
return out
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _isa_arg(node: Node, bound_arity: Dict[str, int],
|
|
781
|
+
display: Dict[str, str], mode: str,
|
|
782
|
+
scope: Optional[BinderScope] = None,
|
|
783
|
+
symbols: Optional[IsabelleNames] = None) -> str:
|
|
784
|
+
"""Render a node standing in ARGUMENT position — an individual or a property.
|
|
785
|
+
|
|
786
|
+
``scope`` holds the binders that enclose the node and the names they are printed under: a
|
|
787
|
+
variable or a lambda variable bound by one of them is printed under its binder's name. A
|
|
788
|
+
free variable and a constant are not bound by any binder, whatever their spelling: each is
|
|
789
|
+
printed under the name ``symbols`` gives it, and the two have two names. Without a ``scope``
|
|
790
|
+
every binder is printed under its own name.
|
|
791
|
+
"""
|
|
792
|
+
if scope is None:
|
|
793
|
+
scope = BinderScope({})
|
|
794
|
+
if symbols is None:
|
|
795
|
+
symbols = IsabelleNames(ISABELLE_BUILT_IN)
|
|
796
|
+
if isinstance(node, (Variable, LambdaVar)):
|
|
797
|
+
# a binder's token is never empty, so "" means: no binder of this name encloses the node
|
|
798
|
+
return scope.token(VARIABLE, node.name, default="") or symbols.symbol(FREE_VARIABLE, node.name)
|
|
799
|
+
if isinstance(node, Constant):
|
|
800
|
+
return symbols.symbol(CONSTANT, node.name)
|
|
801
|
+
if isinstance(node, PredicateTerm):
|
|
802
|
+
return display.get(node.name) or symbols.symbol("predicate", node.name)
|
|
803
|
+
if isinstance(node, Lambda):
|
|
804
|
+
names, body = peel_lambdas(node)
|
|
805
|
+
binders = []
|
|
806
|
+
for name in names:
|
|
807
|
+
token, scope = scope.enter(VARIABLE, name)
|
|
808
|
+
binders.append(f"{_LAM}{token}::i.")
|
|
809
|
+
return f"({' '.join(binders)} {_isa_sigma(body, bound_arity, display, mode, scope, symbols)})"
|
|
810
|
+
if isinstance(node, Function):
|
|
811
|
+
head = symbols.symbol("function", node.name)
|
|
812
|
+
args = " ".join(_isa_arg(a, bound_arity, display, mode, scope, symbols) for a in node.args)
|
|
813
|
+
return f"({head} {args})" if args else head
|
|
814
|
+
raise UnsupportedHigherOrderNode(
|
|
815
|
+
f"ho_modal: {type(node).__name__} cannot stand in argument position; an "
|
|
816
|
+
f"argument is an individual term, a predicate name, or a λ-abstraction."
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
_BINARY_ISA = {And: "mand", Or: "mor", Implies: "mimp", Iff: "miff", Xor: "mxor"}
|
|
821
|
+
|
|
822
|
+
#: Node types this embedding EXPLICITLY refuses by name rather than folding
|
|
823
|
+
#: into the generic "no reading for ..." fallback -- each needs its own
|
|
824
|
+
#: pointed message naming where the construct DOES belong.
|
|
825
|
+
_NO_COUNTERFACTUAL = (
|
|
826
|
+
"ho_modal: the counterfactuals □→/◇→ (Would/Might) read a similarity "
|
|
827
|
+
"ordering of worlds (Lewis spheres), not an accessibility relation, and "
|
|
828
|
+
"are not shallow-embeddable the way □/◇/K_a/... are. Use "
|
|
829
|
+
"hol.isabelle_conditional / isabelle_decide_counterfactual instead."
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
#: N1: the ↓ binder is not supported by this third-order HOL embedding —
|
|
833
|
+
#: H(@,↓) validity is undecidable, and this emitter's job (a theory/theorem
|
|
834
|
+
#: for a human or ATP proof search) assumes a goal shape that search can be
|
|
835
|
+
#: expected to close, not an open research question.
|
|
836
|
+
_NO_DOWN = (
|
|
837
|
+
"ho_modal: the ↓ binder is not supported by this third-order HOL "
|
|
838
|
+
"embedding — H(@,↓) validity is undecidable. Use "
|
|
839
|
+
"unicode_logic_kit.fol.modal_translation.down_is_valid (propositional "
|
|
840
|
+
"H(@,↓), Z3, PROVED-only) or unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend "
|
|
841
|
+
"/ modal_enum_search (bounded search, REFUTED-only) instead."
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _isa_sigma(node: Node, bound_arity: Dict[str, int],
|
|
846
|
+
display: Dict[str, str], mode: str,
|
|
847
|
+
scope: Optional[BinderScope] = None,
|
|
848
|
+
symbols: Optional[IsabelleNames] = None) -> str:
|
|
849
|
+
"""Render ``node`` as an Isabelle term of type ``sigma`` (a world-indexed proposition).
|
|
850
|
+
|
|
851
|
+
``display`` gives each bound predicate variable the name it is printed under, ``scope`` each
|
|
852
|
+
enclosing object binder (see :mod:`unicode_logic_kit.hol._isabelle_binders`); without a
|
|
853
|
+
``scope`` every binder is printed under its own name. ``symbols`` names the free symbols
|
|
854
|
+
(predicates, functions, free variables, constants) and is the one the declarations were
|
|
855
|
+
written under."""
|
|
856
|
+
if scope is None:
|
|
857
|
+
scope = BinderScope({})
|
|
858
|
+
if symbols is None:
|
|
859
|
+
symbols = IsabelleNames(ISABELLE_BUILT_IN)
|
|
860
|
+
if isinstance(node, Not):
|
|
861
|
+
return f"(mnot {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
862
|
+
op = _BINARY_ISA.get(type(node))
|
|
863
|
+
if op is not None:
|
|
864
|
+
left = _isa_sigma(node.left, bound_arity, display, mode, scope, symbols)
|
|
865
|
+
right = _isa_sigma(node.right, bound_arity, display, mode, scope, symbols)
|
|
866
|
+
return f"({op} {left} {right})"
|
|
867
|
+
if isinstance(node, Box):
|
|
868
|
+
return f"(mbox {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
869
|
+
if isinstance(node, Diamond):
|
|
870
|
+
return f"(mdia {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
871
|
+
if isinstance(node, Quantifier):
|
|
872
|
+
# The one judgment call this port makes (see the module docstring):
|
|
873
|
+
# ONLY the individual-typed binder is existsAt-guarded under an
|
|
874
|
+
# actualist mode; a property binder (SecondOrderQuantifier, below)
|
|
875
|
+
# never is.
|
|
876
|
+
if mode in _ACTUALIST_MODES:
|
|
877
|
+
binder = "mforall" if node.type == "∀" else "mexists"
|
|
878
|
+
else:
|
|
879
|
+
binder = "mall" if node.type == "∀" else "mex"
|
|
880
|
+
token, inner = scope.enter(VARIABLE, node.variable.name)
|
|
881
|
+
body = _isa_sigma(node.formula, bound_arity, display, mode, inner, symbols)
|
|
882
|
+
return f"({binder} ({_LAM}{token}::i. {body}))"
|
|
883
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
884
|
+
binder = "mall" if node.type == "∀" else "mex"
|
|
885
|
+
arity = bound_arity.get(node.predicate, node.arity)
|
|
886
|
+
name = display.get(node.predicate, node.predicate)
|
|
887
|
+
body = _isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)
|
|
888
|
+
return (f"({binder} ({_LAM}{name}::{_prop_type(arity)}. {body}))")
|
|
889
|
+
if isinstance(node, (Knows, Believes, Says, Wants)):
|
|
890
|
+
macro = {Knows: "mknows", Believes: "mbelieves",
|
|
891
|
+
Says: "msays", Wants: "mwants"}[type(node)]
|
|
892
|
+
agent = _isa_arg(node.agent, bound_arity, display, mode, scope, symbols)
|
|
893
|
+
body = _isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)
|
|
894
|
+
return f"({macro} {agent} {body})"
|
|
895
|
+
if isinstance(node, Obligatory):
|
|
896
|
+
return f"(mobl {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
897
|
+
if isinstance(node, Permitted):
|
|
898
|
+
return f"(mperm {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
899
|
+
if isinstance(node, Always):
|
|
900
|
+
return f"(malways {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
901
|
+
if isinstance(node, Eventually):
|
|
902
|
+
return f"(meventually {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
903
|
+
if isinstance(node, Next):
|
|
904
|
+
return f"(mnext {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
905
|
+
if isinstance(node, Historically):
|
|
906
|
+
return f"(mhistorically {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
907
|
+
if isinstance(node, Once):
|
|
908
|
+
return f"(monce {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
909
|
+
if isinstance(node, Previous):
|
|
910
|
+
return f"(mprevious {_isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)})"
|
|
911
|
+
if isinstance(node, Until):
|
|
912
|
+
left = _isa_sigma(node.left, bound_arity, display, mode, scope, symbols)
|
|
913
|
+
right = _isa_sigma(node.right, bound_arity, display, mode, scope, symbols)
|
|
914
|
+
return f"(muntil {left} {right})"
|
|
915
|
+
if isinstance(node, Since):
|
|
916
|
+
left = _isa_sigma(node.left, bound_arity, display, mode, scope, symbols)
|
|
917
|
+
right = _isa_sigma(node.right, bound_arity, display, mode, scope, symbols)
|
|
918
|
+
return f"(msince {left} {right})"
|
|
919
|
+
if isinstance(node, Nominal):
|
|
920
|
+
return f"({_LAM}v::world. v = {_nominal_const(node.name)})"
|
|
921
|
+
if isinstance(node, At):
|
|
922
|
+
# The world binder is anonymous: ``body`` holds the caller's own symbols, and a binder
|
|
923
|
+
# named ``v`` would capture a constant, a function or a predicate called ``v`` there.
|
|
924
|
+
body = _isa_sigma(node.formula, bound_arity, display, mode, scope, symbols)
|
|
925
|
+
return f"({_LAM}_. {body} {_nominal_const(node.nominal.name)})"
|
|
926
|
+
if isinstance(node, Down):
|
|
927
|
+
raise UnsupportedHigherOrderNode(_NO_DOWN)
|
|
928
|
+
if isinstance(node, Atom):
|
|
929
|
+
constant = truth_value(node)
|
|
930
|
+
if constant is not None:
|
|
931
|
+
# `$true` / `$false`: HOL's True / False under the anonymous world binder.
|
|
932
|
+
return f"({_LAM}_. True)" if constant else f"({_LAM}_. False)"
|
|
933
|
+
if is_equality_atom(node):
|
|
934
|
+
# Rigid identity: HOL's own ``=`` under a world binder it never uses, so
|
|
935
|
+
# it cannot vary by world. The binder is the anonymous ``_`` rather than a
|
|
936
|
+
# name so a user variable called ``w`` can never be captured by it. The
|
|
937
|
+
# same text isabelle_modal emits for the same atom.
|
|
938
|
+
left, right = _identity_sides(node)
|
|
939
|
+
return (f"({_LAM}_. {_isa_arg(left, bound_arity, display, mode, scope, symbols)} = "
|
|
940
|
+
f"{_isa_arg(right, bound_arity, display, mode, scope, symbols)})")
|
|
941
|
+
if node.predicate in ORDERING:
|
|
942
|
+
name = ORDERING[node.predicate]
|
|
943
|
+
else:
|
|
944
|
+
name = display.get(node.predicate) or symbols.symbol("predicate", node.predicate)
|
|
945
|
+
if not node.args:
|
|
946
|
+
return name
|
|
947
|
+
args = " ".join(_isa_arg(a, bound_arity, display, mode, scope, symbols) for a in node.args)
|
|
948
|
+
return f"({name} {args})"
|
|
949
|
+
if type(node).__name__ in ("Would", "Might"):
|
|
950
|
+
raise UnsupportedHigherOrderNode(_NO_COUNTERFACTUAL)
|
|
951
|
+
raise UnsupportedHigherOrderNode(
|
|
952
|
+
f"ho_modal: no reading for {type(node).__name__}. This embedding covers "
|
|
953
|
+
f"alethic □/◇, agent-indexed epistemic/doxastic/assertive/bouletic "
|
|
954
|
+
f"(K_a/B_a/Say_a/Want_a), deontic O/P, temporal (Always/Eventually/Next/"
|
|
955
|
+
f"Until/Since and their past mirrors) and hybrid (nominals/@) over "
|
|
956
|
+
f"third-order syntax. The Lewis counterfactuals (Would/Might) and the "
|
|
957
|
+
f"GROUP-epistemic operators (EverybodyKnows/DistributedKnowledge/"
|
|
958
|
+
f"CommonKnowledge) are not carried by any HOL route in this kit yet."
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def _used_ordering(apart: Sequence[Node]) -> List[str]:
|
|
963
|
+
"""The names of the ordering relations (``flt``, ...) that the formulas use, sorted."""
|
|
964
|
+
return sorted({ORDERING[p] for f in apart for p in atom_predicates(f) if p in ORDERING})
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def _signature_lines(apart: Sequence[Node], signatures, symbols: IsabelleNames) -> List[str]:
|
|
968
|
+
"""Return the ``consts`` declarations for every free symbol of ``apart``.
|
|
969
|
+
|
|
970
|
+
``apart`` are the formulas with their bound predicate variables renamed apart and
|
|
971
|
+
``signatures`` is the analysis over them TOGETHER, because that is the scope on which a
|
|
972
|
+
free predicate's argument types are determined: ``Positive(G)`` in one axiom and ``G(x)``
|
|
973
|
+
in another jointly say that ``Positive`` takes a property of arity 1.
|
|
974
|
+
|
|
975
|
+
Every symbol is declared under the name ``symbols`` gives it, and ``symbols`` is the one
|
|
976
|
+
the formulas are rendered with. A theory has one namespace for its constants, so a
|
|
977
|
+
predicate, a function, a free variable and a constant that are spelled alike are four
|
|
978
|
+
constants with four names, and none takes a name of the embedding's own vocabulary.
|
|
979
|
+
"""
|
|
980
|
+
bound = set()
|
|
981
|
+
for formula in apart:
|
|
982
|
+
bound |= bound_pred_names(formula)
|
|
983
|
+
predicates = [pred for pred in sorted(signatures.slots)
|
|
984
|
+
if not (pred in bound or pred in EQUALITY_PREDICATES or pred in ORDERING
|
|
985
|
+
or pred in ORDERING.values())]
|
|
986
|
+
# Free individual symbols: a bare NAME parses to a Constant, and a variable left unbound
|
|
987
|
+
# by any quantifier denotes a particular individual too. Both are individuals of type i,
|
|
988
|
+
# but a free variable is a parameter and a constant a particular element: two symbols.
|
|
989
|
+
individuals = individual_symbols(apart)
|
|
990
|
+
functions = sorted(function_symbols(apart).items())
|
|
991
|
+
symbols.claim([("predicate", pred) for pred in predicates]
|
|
992
|
+
+ [("function", name) for name, _ in functions] + individuals)
|
|
993
|
+
|
|
994
|
+
lines: List[str] = []
|
|
995
|
+
for pred in predicates:
|
|
996
|
+
parts = []
|
|
997
|
+
for kind in signatures.slots[pred]:
|
|
998
|
+
parts.append("i" if kind == INDIVIDUAL else f"({_prop_type(kind[1])})")
|
|
999
|
+
arrow = "".join(f"{p} {_FUN} " for p in parts)
|
|
1000
|
+
lines.append(f'consts {symbols.symbol("predicate", pred)} :: "{arrow}sigma"')
|
|
1001
|
+
|
|
1002
|
+
for kind, name in individuals:
|
|
1003
|
+
lines.append(f'consts {symbols.symbol(kind, name)} :: "i"')
|
|
1004
|
+
|
|
1005
|
+
# Ordering predicates actually used, as world-relativised relations. Identity is
|
|
1006
|
+
# NOT among them: it is HOL's own ``=`` (see _rigid_identity) and is declared
|
|
1007
|
+
# nowhere.
|
|
1008
|
+
for name in _used_ordering(apart):
|
|
1009
|
+
lines.append(f'consts {name} :: "i {_FUN} i {_FUN} sigma"')
|
|
1010
|
+
|
|
1011
|
+
# Function symbols in term position.
|
|
1012
|
+
for name, arity in functions:
|
|
1013
|
+
arrow = "".join(f"i {_FUN} " for _ in range(arity))
|
|
1014
|
+
lines.append(f'consts {symbols.symbol("function", name)} :: "{arrow}i"')
|
|
1015
|
+
return lines
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
# --------------------------------------------------------------------------
|
|
1019
|
+
# Theory assembly
|
|
1020
|
+
# --------------------------------------------------------------------------
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
@dataclass(frozen=True)
|
|
1024
|
+
class HoAxiom:
|
|
1025
|
+
"""One named axiom of an emitted theory.
|
|
1026
|
+
|
|
1027
|
+
``comment`` is printed above it, so a theory can carry the argument's own
|
|
1028
|
+
numbering (``A1``, ``D2``, …) and say what each line is meant to assert.
|
|
1029
|
+
"""
|
|
1030
|
+
|
|
1031
|
+
name: str
|
|
1032
|
+
formula: Node
|
|
1033
|
+
comment: str = ""
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
@dataclass(frozen=True)
|
|
1037
|
+
class HoGoal:
|
|
1038
|
+
"""One named goal, with the proof text to attempt it with.
|
|
1039
|
+
|
|
1040
|
+
``proof`` is Isabelle proof text (``by blast``, ``using A1 A2 by metis``,
|
|
1041
|
+
``nitpick [expect = genuine]``, …). The kit does not invent proofs: what is
|
|
1042
|
+
written here is what the theory will try. ``oops`` leaves the goal
|
|
1043
|
+
explicitly unproved, which is what :func:`to_isabelle_ho_modal` emits when
|
|
1044
|
+
no proof is supplied.
|
|
1045
|
+
|
|
1046
|
+
A goal is stated EITHER as a ``formula`` of the object logic — asserted
|
|
1047
|
+
valid, i.e. ``mvalid φ`` — OR, for the one thing the object logic cannot
|
|
1048
|
+
say about itself, as a raw Isabelle ``statement`` such as ``"False"``. That
|
|
1049
|
+
second form is what lets a theory record that an axiom set is INCONSISTENT:
|
|
1050
|
+
"these axioms prove falsity" is a claim about the theory, not a formula in
|
|
1051
|
+
it. Exactly one of the two must be given.
|
|
1052
|
+
"""
|
|
1053
|
+
|
|
1054
|
+
name: str
|
|
1055
|
+
formula: Optional[Node] = None
|
|
1056
|
+
proof: str = "oops"
|
|
1057
|
+
comment: str = ""
|
|
1058
|
+
kind: str = "theorem"
|
|
1059
|
+
statement: Optional[str] = None
|
|
1060
|
+
|
|
1061
|
+
def __post_init__(self):
|
|
1062
|
+
"""Reject a goal that states nothing, or states itself twice over."""
|
|
1063
|
+
if (self.formula is None) == (self.statement is None):
|
|
1064
|
+
raise ValueError(
|
|
1065
|
+
f"HoGoal({self.name!r}): give exactly one of formula= (an object-logic "
|
|
1066
|
+
f"formula, asserted valid) or statement= (a raw Isabelle proposition)."
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def isabelle_ho_modal_theory(name: str,
|
|
1071
|
+
axioms: Sequence[HoAxiom] = (),
|
|
1072
|
+
goals: Sequence[HoGoal] = (),
|
|
1073
|
+
frame: str = "K",
|
|
1074
|
+
preamble: Sequence[str] = (),
|
|
1075
|
+
mode: str = "constant",
|
|
1076
|
+
systems: Optional[dict] = None,
|
|
1077
|
+
temporal_closure: bool = True) -> str:
|
|
1078
|
+
"""Emit a complete Isabelle theory for a third-order modal axiom set and its goals.
|
|
1079
|
+
|
|
1080
|
+
``axioms`` are asserted as ``axiomatization where <name>: "mvalid φ"`` — valid at
|
|
1081
|
+
every world, which is what an axiom of the object logic means. ``goals`` are
|
|
1082
|
+
stated the same way and followed by their own proof text.
|
|
1083
|
+
|
|
1084
|
+
``frame`` names a system from :data:`unicode_logic_kit.fol.frames.FRAMES` and
|
|
1085
|
+
constrains ``R`` accordingly; ``"K"`` leaves ``R`` arbitrary. ``preamble``
|
|
1086
|
+
lines are inserted after the vocabulary and before the axioms, for anything
|
|
1087
|
+
the caller wants to add in Isabelle's own syntax.
|
|
1088
|
+
|
|
1089
|
+
``mode`` selects the domain regime for INDIVIDUAL quantification (see the
|
|
1090
|
+
module docstring): the default ``"constant"``/``"possibilist"`` behaves
|
|
1091
|
+
exactly as before this parameter existed; ``"varying"``/``"increasing"``/
|
|
1092
|
+
``"cumulative"``/``"decreasing"`` add the ``existsAt``-guarded reading.
|
|
1093
|
+
``systems`` optionally constrains one or more of the agent-indexed
|
|
1094
|
+
families -- ``{"epistemic": "S5", "doxastic": "KD45", ...}`` -- a family
|
|
1095
|
+
named that the formula set never uses is refused, and an unnamed one
|
|
1096
|
+
stays unconstrained (plain K). ``temporal_closure`` (default ``True``)
|
|
1097
|
+
adds the reflexive+transitive axioms that make ``Rt`` the henceforth
|
|
1098
|
+
relation ``Always``/``Eventually`` need, exactly mirroring
|
|
1099
|
+
:func:`unicode_logic_kit.hol.isabelle_modal.isabelle_modal_theory`'s own
|
|
1100
|
+
parameter of the same name.
|
|
1101
|
+
|
|
1102
|
+
The signature is read off the axioms and goals together, so a symbol used in
|
|
1103
|
+
a goal but not in any axiom is still declared. Property-argument slots whose
|
|
1104
|
+
arity nothing in the theory determines are reported as a comment rather than
|
|
1105
|
+
silently defaulted — see :mod:`unicode_logic_kit.fol._ho_nodes`.
|
|
1106
|
+
|
|
1107
|
+
Identity ``=`` / ``≠`` between individuals is RIGID HOL equality (``≠`` is ``¬(=)``),
|
|
1108
|
+
declared nowhere; a ``=`` / ``≠`` atom without exactly two terms raises
|
|
1109
|
+
``ValueError`` and identity between PROPERTIES raises ``NotImplementedError`` — the
|
|
1110
|
+
module docstring's "Equality is rigid, or refused" has the reasons.
|
|
1111
|
+
"""
|
|
1112
|
+
if frame not in FRAMES:
|
|
1113
|
+
raise ValueError(
|
|
1114
|
+
f"isabelle_ho_modal_theory: unknown frame {frame!r}; "
|
|
1115
|
+
f"known systems are {sorted(FRAMES)}."
|
|
1116
|
+
)
|
|
1117
|
+
if mode not in _ACTUALIST_MODES and mode not in _CONSTANT_MODES:
|
|
1118
|
+
raise ValueError(
|
|
1119
|
+
f"isabelle_ho_modal_theory: unknown mode {mode!r} "
|
|
1120
|
+
f"(use one of {sorted(_ACTUALIST_MODES | _CONSTANT_MODES)})."
|
|
1121
|
+
)
|
|
1122
|
+
# A goal stated raw (statement=) contributes no formula to type-check.
|
|
1123
|
+
typed_goals = [g for g in goals if g.formula is not None]
|
|
1124
|
+
# Identity is read ONCE, up front, as rigid HOL ``=`` (``≠`` -> ``¬(=)``, a
|
|
1125
|
+
# non-binary atom or a property-typed one refused by name) -- before the signature
|
|
1126
|
+
# analysis, so nothing downstream ever sees a ``≠``.
|
|
1127
|
+
formulas = _rigid_identity(
|
|
1128
|
+
[a.formula for a in axioms] + [g.formula for g in typed_goals],
|
|
1129
|
+
"isabelle_ho_modal_theory")
|
|
1130
|
+
apart, display = rename_apart(formulas)
|
|
1131
|
+
signatures = analyse_signatures(apart)
|
|
1132
|
+
# Arities come from the theory-wide analysis, not from each node's own
|
|
1133
|
+
# parse-time field: a binder whose arity only the OTHER axioms determine is
|
|
1134
|
+
# exactly the case a per-formula answer gets wrong.
|
|
1135
|
+
bound_arity = signatures.arity
|
|
1136
|
+
axiom_bodies = apart[:len(axioms)]
|
|
1137
|
+
typed_bodies = dict(zip((g.name for g in typed_goals), apart[len(axioms):]))
|
|
1138
|
+
usage = _scan_family_usage(apart)
|
|
1139
|
+
resolved_systems = _validate_systems(usage, systems)
|
|
1140
|
+
nominal_consts = sorted({_nominal_const(n.name) for f in apart for n in f.walk()
|
|
1141
|
+
if isinstance(n, Nominal)})
|
|
1142
|
+
|
|
1143
|
+
lines = [
|
|
1144
|
+
"(* Third-order modal logic in HOL: shallow (Benzmuller-style) embedding. *)",
|
|
1145
|
+
f"(* Frame: {frame}. Domain mode: {mode}. *)",
|
|
1146
|
+
f"theory {name}",
|
|
1147
|
+
" imports Main",
|
|
1148
|
+
"begin",
|
|
1149
|
+
"",
|
|
1150
|
+
ho_modal_definitions(),
|
|
1151
|
+
]
|
|
1152
|
+
extra_vocab = (_family_vocab_lines(usage) + _hybrid_vocab_lines(nominal_consts))
|
|
1153
|
+
if mode in _ACTUALIST_MODES and usage.has_quant:
|
|
1154
|
+
extra_vocab = _domain_vocab_lines() + extra_vocab
|
|
1155
|
+
if extra_vocab:
|
|
1156
|
+
lines.append("")
|
|
1157
|
+
lines += extra_vocab
|
|
1158
|
+
lines.append("")
|
|
1159
|
+
# The symbols of the formulas share one namespace with the vocabulary above (R, mall,
|
|
1160
|
+
# mnot, Rk, existsAt, nom_i, ...): none of them may take one of those names, and none
|
|
1161
|
+
# takes the name of another symbol, whatever its kind.
|
|
1162
|
+
fixed = declared_names([ho_modal_definitions()] + extra_vocab)
|
|
1163
|
+
symbols = IsabelleNames((fixed - declared_names(())) | ISABELLE_BUILT_IN
|
|
1164
|
+
| set(_used_ordering(apart)))
|
|
1165
|
+
signature = _signature_lines(apart, signatures, symbols)
|
|
1166
|
+
frame_lines = _frame_axiom_lines(frame)
|
|
1167
|
+
frame_lines += _family_axiom_lines(usage, resolved_systems, temporal_closure)
|
|
1168
|
+
if mode in _ACTUALIST_MODES and usage.has_quant:
|
|
1169
|
+
frame_lines += _domain_axiom_lines(mode)
|
|
1170
|
+
if frame_lines:
|
|
1171
|
+
lines.append(f"\\<comment> \\<open>frame/domain conditions for {frame}/{mode}\\<close>")
|
|
1172
|
+
lines += frame_lines
|
|
1173
|
+
lines.append("")
|
|
1174
|
+
if signature:
|
|
1175
|
+
lines += signature
|
|
1176
|
+
lines.append("")
|
|
1177
|
+
if signatures.defaulted:
|
|
1178
|
+
pairs = ", ".join(f"{p}[{i}]" for p, i in sorted(signatures.defaulted))
|
|
1179
|
+
lines.append(
|
|
1180
|
+
f"\\<comment> \\<open>arity defaulted to 1 (nothing in the theory "
|
|
1181
|
+
f"fixes it): {pairs}\\<close>")
|
|
1182
|
+
lines.append("")
|
|
1183
|
+
lines += list(preamble)
|
|
1184
|
+
if preamble:
|
|
1185
|
+
lines.append("")
|
|
1186
|
+
|
|
1187
|
+
# A binder shadows a constant of its own spelling inside its scope, and the lifted
|
|
1188
|
+
# operators (mand, mall, ...) are constants too: every binder (object quantifier, lambda
|
|
1189
|
+
# parameter, bound predicate variable) is printed under a name that nothing declared above
|
|
1190
|
+
# has. ``display`` gives each renamed-apart predicate variable that name.
|
|
1191
|
+
tokens = binder_tokens(collect_binders(formulas), declared_names(lines))
|
|
1192
|
+
display = {fresh: tokens[(PREDICATE, original)] for fresh, original in display.items()}
|
|
1193
|
+
scope = BinderScope(tokens)
|
|
1194
|
+
|
|
1195
|
+
for axiom, renamed in zip(axioms, axiom_bodies):
|
|
1196
|
+
if axiom.comment:
|
|
1197
|
+
lines.append(f"\\<comment> \\<open>{axiom.comment}\\<close>")
|
|
1198
|
+
body = _isa_sigma(renamed, bound_arity, display, mode, scope, symbols)
|
|
1199
|
+
lines.append(f'axiomatization where {axiom.name}: "mvalid {body}"')
|
|
1200
|
+
if axioms:
|
|
1201
|
+
lines.append("")
|
|
1202
|
+
|
|
1203
|
+
for goal in goals:
|
|
1204
|
+
if goal.comment:
|
|
1205
|
+
lines.append(f"\\<comment> \\<open>{goal.comment}\\<close>")
|
|
1206
|
+
if goal.statement is not None:
|
|
1207
|
+
proposition = goal.statement
|
|
1208
|
+
else:
|
|
1209
|
+
body = _isa_sigma(typed_bodies[goal.name], bound_arity, display, mode, scope, symbols)
|
|
1210
|
+
proposition = f"mvalid {body}"
|
|
1211
|
+
lines.append(f'{goal.kind} {goal.name}: "{proposition}"')
|
|
1212
|
+
lines.append(f" {goal.proof}")
|
|
1213
|
+
lines.append("")
|
|
1214
|
+
|
|
1215
|
+
lines.append("end")
|
|
1216
|
+
return "\n".join(lines) + "\n"
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
def to_isabelle_ho_modal(formula: Node, name: str = "HO_Modal_Goal",
|
|
1220
|
+
frame: str = "K", proof: Optional[str] = None,
|
|
1221
|
+
mode: str = "constant",
|
|
1222
|
+
systems: Optional[dict] = None,
|
|
1223
|
+
temporal_closure: bool = True) -> str:
|
|
1224
|
+
"""Emit a theory whose single goal is ``formula``, valid on every world.
|
|
1225
|
+
|
|
1226
|
+
The one-formula convenience over :func:`isabelle_ho_modal_theory`. Without a
|
|
1227
|
+
``proof`` the goal is left ``oops`` — the kit states the problem, it does not
|
|
1228
|
+
invent a proof for it. ``mode``/``systems``/``temporal_closure`` pass
|
|
1229
|
+
through unchanged; see :func:`isabelle_ho_modal_theory`.
|
|
1230
|
+
"""
|
|
1231
|
+
goal = HoGoal("goal", formula, proof or "oops")
|
|
1232
|
+
return isabelle_ho_modal_theory(name, (), (goal,), frame=frame, mode=mode,
|
|
1233
|
+
systems=systems,
|
|
1234
|
+
temporal_closure=temporal_closure)
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
# --------------------------------------------------------------------------
|
|
1238
|
+
# THF
|
|
1239
|
+
# --------------------------------------------------------------------------
|
|
1240
|
+
|
|
1241
|
+
_THF_PRELUDE = [
|
|
1242
|
+
"% Third-order modal logic in THF: shallow (Benzmuller-style) embedding.",
|
|
1243
|
+
"% mu = worlds; $i = individuals; (mu > $o) = propositions;",
|
|
1244
|
+
"% ($i > mu > $o) = properties; (($i > mu > $o) > mu > $o) = predicates of properties.",
|
|
1245
|
+
"thf(mu_type, type, ( mu : $tType )).",
|
|
1246
|
+
"thf(r_type, type, ( r : mu > mu > $o )).",
|
|
1247
|
+
"thf(mnot_type, type, ( mnot : ( mu > $o ) > mu > $o )).",
|
|
1248
|
+
"thf(mnot_def, definition, ( mnot = ( ^ [P: mu > $o, W: mu] : ( ~ ( P @ W ) ) ) )).",
|
|
1249
|
+
"thf(mand_type, type, ( mand : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1250
|
+
"thf(mand_def, definition, ( mand = ( ^ [P: mu > $o, Q: mu > $o, W: mu] : "
|
|
1251
|
+
"( ( P @ W ) & ( Q @ W ) ) ) )).",
|
|
1252
|
+
"thf(mor_type, type, ( mor : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1253
|
+
"thf(mor_def, definition, ( mor = ( ^ [P: mu > $o, Q: mu > $o, W: mu] : "
|
|
1254
|
+
"( ( P @ W ) | ( Q @ W ) ) ) )).",
|
|
1255
|
+
"thf(mimp_type, type, ( mimp : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1256
|
+
"thf(mimp_def, definition, ( mimp = ( ^ [P: mu > $o, Q: mu > $o, W: mu] : "
|
|
1257
|
+
"( ( P @ W ) => ( Q @ W ) ) ) )).",
|
|
1258
|
+
"thf(miff_type, type, ( miff : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1259
|
+
"thf(miff_def, definition, ( miff = ( ^ [P: mu > $o, Q: mu > $o, W: mu] : "
|
|
1260
|
+
"( ( P @ W ) <=> ( Q @ W ) ) ) )).",
|
|
1261
|
+
"thf(mbox_type, type, ( mbox : ( mu > $o ) > mu > $o )).",
|
|
1262
|
+
"thf(mbox_def, definition, ( mbox = ( ^ [P: mu > $o, W: mu] : "
|
|
1263
|
+
"( ! [V: mu] : ( ( r @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1264
|
+
"thf(mdia_type, type, ( mdia : ( mu > $o ) > mu > $o )).",
|
|
1265
|
+
"thf(mdia_def, definition, ( mdia = ( ^ [P: mu > $o, W: mu] : "
|
|
1266
|
+
"( ? [V: mu] : ( ( r @ W @ V ) & ( P @ V ) ) ) ) )).",
|
|
1267
|
+
"thf(mvalid_type, type, ( mvalid : ( mu > $o ) > $o )).",
|
|
1268
|
+
"thf(mvalid_def, definition, ( mvalid = "
|
|
1269
|
+
"( ^ [P: mu > $o] : ( ! [W: mu] : ( P @ W ) ) ) )).",
|
|
1270
|
+
]
|
|
1271
|
+
|
|
1272
|
+
_THF_FRAME = {
|
|
1273
|
+
"refl": "( ! [W: mu] : ( r @ W @ W ) )",
|
|
1274
|
+
"sym": "( ! [W: mu, V: mu] : ( ( r @ W @ V ) => ( r @ V @ W ) ) )",
|
|
1275
|
+
"trans": "( ! [W: mu, V: mu, U: mu] : ( ( ( r @ W @ V ) & ( r @ V @ U ) ) "
|
|
1276
|
+
"=> ( r @ W @ U ) ) )",
|
|
1277
|
+
"serial": "( ! [W: mu] : ( ? [V: mu] : ( r @ W @ V ) ) )",
|
|
1278
|
+
"eucl": "( ! [W: mu, V: mu, U: mu] : ( ( ( r @ W @ V ) & ( r @ W @ U ) ) "
|
|
1279
|
+
"=> ( r @ V @ U ) ) )",
|
|
1280
|
+
"directed": "( ! [W: mu, V: mu, U: mu] : ( ( ( r @ W @ V ) & ( r @ W @ U ) ) "
|
|
1281
|
+
"=> ( ? [Z: mu] : ( ( r @ V @ Z ) & ( r @ U @ Z ) ) ) ) )",
|
|
1282
|
+
"connected": "( ! [W: mu, V: mu, U: mu] : ( ( ( r @ W @ V ) & ( r @ W @ U ) ) "
|
|
1283
|
+
"=> ( ( r @ V @ U ) | ( r @ U @ V ) ) ) )",
|
|
1284
|
+
"functional": "( ! [W: mu, V: mu, U: mu] : ( ( ( r @ W @ V ) & ( r @ W @ U ) ) "
|
|
1285
|
+
"=> ( V = U ) ) )",
|
|
1286
|
+
"dense": "( ! [W: mu, V: mu] : ( ( r @ W @ V ) "
|
|
1287
|
+
"=> ( ? [U: mu] : ( ( r @ W @ U ) & ( r @ U @ V ) ) ) ) )",
|
|
1288
|
+
"shift_refl": "( ! [W: mu, V: mu] : ( ( r @ W @ V ) => ( r @ V @ V ) ) )",
|
|
1289
|
+
"empty": "( ! [W: mu, V: mu] : ( ~ ( r @ W @ V ) ) )",
|
|
1290
|
+
# Löb / McKinsey / Grz: schemas over propositions, with the schema
|
|
1291
|
+
# variable P bound explicitly by a THF `! [P: mu > $o]`, matching the
|
|
1292
|
+
# explicit `\<forall>P::sigma.` on the Isabelle side (see the comment above
|
|
1293
|
+
# _FRAME_AXIOMS). A user symbol named P is a constant spelled `p`, so it
|
|
1294
|
+
# cannot capture the binder.
|
|
1295
|
+
"loeb": "( ! [P: mu > $o] : ( ! [W: mu] : ( ( ! [V: mu] : ( ( r @ W @ V ) => "
|
|
1296
|
+
"( ( ! [U: mu] : ( ( r @ V @ U ) => ( P @ U ) ) ) => ( P @ V ) ) ) ) "
|
|
1297
|
+
"=> ( ! [V: mu] : ( ( r @ W @ V ) => ( P @ V ) ) ) ) ) )",
|
|
1298
|
+
"mckinsey": "( ! [P: mu > $o] : ( ! [W: mu] : ( ( ! [V: mu] : ( ( r @ W @ V ) => "
|
|
1299
|
+
"( ? [U: mu] : ( ( r @ V @ U ) & ( P @ U ) ) ) ) ) "
|
|
1300
|
+
"=> ( ? [V: mu] : ( ( r @ W @ V ) & ( ! [U: mu] : ( ( r @ V @ U ) "
|
|
1301
|
+
"=> ( P @ U ) ) ) ) ) ) ) )",
|
|
1302
|
+
"grz": "( ! [P: mu > $o] : ( ! [W: mu] : ( ( ! [V: mu] : ( ( r @ W @ V ) => "
|
|
1303
|
+
"( ( ! [U: mu] : ( ( r @ V @ U ) => ( ( P @ U ) => "
|
|
1304
|
+
"( ! [Z: mu] : ( ( r @ U @ Z ) => ( P @ Z ) ) ) ) ) ) => ( P @ V ) ) ) ) "
|
|
1305
|
+
"=> ( P @ W ) ) ) )",
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
# The extended vocabulary (epistemic/doxastic/assertive/bouletic, deontic,
|
|
1309
|
+
# temporal, hybrid) -- unconditionally declared, matching thf_modal.py's own
|
|
1310
|
+
# ``_THF_DEFS_FULL`` convention (a bridge/family whose relation is never used
|
|
1311
|
+
# stays merely unconstrained, not absent; see that module's own comment on
|
|
1312
|
+
# why "declared unconditionally" is the honest, THF-idiomatic choice) rather
|
|
1313
|
+
# than this module's own conditional Isabelle side. ``existsAt``/``mforall``/
|
|
1314
|
+
# ``mexists`` are the ONE exception (see _thf_domain_lines below): they stay
|
|
1315
|
+
# conditional, so the DEFAULT constant-mode THF output is unchanged.
|
|
1316
|
+
_THF_FAMILY_PRELUDE = [
|
|
1317
|
+
"thf(rk_type, type, ( rk : $i > mu > mu > $o )).",
|
|
1318
|
+
"thf(mknows_type, type, ( mknows : $i > ( mu > $o ) > mu > $o )).",
|
|
1319
|
+
"thf(mknows_def, definition, ( mknows = ( ^ [A: $i, P: mu > $o, W: mu] : "
|
|
1320
|
+
"( ! [V: mu] : ( ( rk @ A @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1321
|
+
"thf(rb_type, type, ( rb : $i > mu > mu > $o )).",
|
|
1322
|
+
"thf(mbelieves_type, type, ( mbelieves : $i > ( mu > $o ) > mu > $o )).",
|
|
1323
|
+
"thf(mbelieves_def, definition, ( mbelieves = ( ^ [A: $i, P: mu > $o, W: mu] : "
|
|
1324
|
+
"( ! [V: mu] : ( ( rb @ A @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1325
|
+
"thf(rs_type, type, ( rs : $i > mu > mu > $o )).",
|
|
1326
|
+
"thf(msays_type, type, ( msays : $i > ( mu > $o ) > mu > $o )).",
|
|
1327
|
+
"thf(msays_def, definition, ( msays = ( ^ [A: $i, P: mu > $o, W: mu] : "
|
|
1328
|
+
"( ! [V: mu] : ( ( rs @ A @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1329
|
+
"thf(rw_type, type, ( rw : $i > mu > mu > $o )).",
|
|
1330
|
+
"thf(mwants_type, type, ( mwants : $i > ( mu > $o ) > mu > $o )).",
|
|
1331
|
+
"thf(mwants_def, definition, ( mwants = ( ^ [A: $i, P: mu > $o, W: mu] : "
|
|
1332
|
+
"( ! [V: mu] : ( ( rw @ A @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1333
|
+
"thf(d_type, type, ( d : mu > mu > $o )).",
|
|
1334
|
+
"thf(mobl_type, type, ( mobl : ( mu > $o ) > mu > $o )).",
|
|
1335
|
+
"thf(mobl_def, definition, ( mobl = ( ^ [P: mu > $o, W: mu] : "
|
|
1336
|
+
"( ! [V: mu] : ( ( d @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1337
|
+
"thf(mperm_type, type, ( mperm : ( mu > $o ) > mu > $o )).",
|
|
1338
|
+
"thf(mperm_def, definition, ( mperm = ( ^ [P: mu > $o, W: mu] : "
|
|
1339
|
+
"( ? [V: mu] : ( ( d @ W @ V ) & ( P @ V ) ) ) ) )).",
|
|
1340
|
+
"thf(t_type, type, ( t : mu > mu > $o )).",
|
|
1341
|
+
"thf(malways_type, type, ( malways : ( mu > $o ) > mu > $o )).",
|
|
1342
|
+
"thf(malways_def, definition, ( malways = ( ^ [P: mu > $o, W: mu] : "
|
|
1343
|
+
"( ! [V: mu] : ( ( t @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1344
|
+
"thf(meventually_type, type, ( meventually : ( mu > $o ) > mu > $o )).",
|
|
1345
|
+
"thf(meventually_def, definition, ( meventually = ( ^ [P: mu > $o, W: mu] : "
|
|
1346
|
+
"( ? [V: mu] : ( ( t @ W @ V ) & ( P @ V ) ) ) ) )).",
|
|
1347
|
+
"thf(mhistorically_type, type, ( mhistorically : ( mu > $o ) > mu > $o )).",
|
|
1348
|
+
"thf(mhistorically_def, definition, ( mhistorically = ( ^ [P: mu > $o, W: mu] : "
|
|
1349
|
+
"( ! [V: mu] : ( ( t @ V @ W ) => ( P @ V ) ) ) ) )).",
|
|
1350
|
+
"thf(monce_type, type, ( monce : ( mu > $o ) > mu > $o )).",
|
|
1351
|
+
"thf(monce_def, definition, ( monce = ( ^ [P: mu > $o, W: mu] : "
|
|
1352
|
+
"( ? [V: mu] : ( ( t @ V @ W ) & ( P @ V ) ) ) ) )).",
|
|
1353
|
+
"thf(tnext_type, type, ( tnext : mu > mu > $o )).",
|
|
1354
|
+
"thf(mnext_type, type, ( mnext : ( mu > $o ) > mu > $o )).",
|
|
1355
|
+
"thf(mnext_def, definition, ( mnext = ( ^ [P: mu > $o, W: mu] : "
|
|
1356
|
+
"( ! [V: mu] : ( ( tnext @ W @ V ) => ( P @ V ) ) ) ) )).",
|
|
1357
|
+
"thf(mprevious_type, type, ( mprevious : ( mu > $o ) > mu > $o )).",
|
|
1358
|
+
"thf(mprevious_def, definition, ( mprevious = ( ^ [P: mu > $o, W: mu] : "
|
|
1359
|
+
"( ! [V: mu] : ( ( tnext @ V @ W ) => ( P @ V ) ) ) ) )).",
|
|
1360
|
+
"thf(muntil_type, type, ( muntil : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1361
|
+
"thf(muntil_def, definition, ( muntil = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : "
|
|
1362
|
+
"! [S: mu>$o] : ( ( ( ! [V: mu] : ( ( Psi @ V ) => ( S @ V ) ) ) & "
|
|
1363
|
+
"( ! [V: mu, U: mu] : ( ( ( Phi @ V ) & ( tnext @ V @ U ) & ( S @ U ) ) "
|
|
1364
|
+
"=> ( S @ V ) ) ) ) => ( S @ W ) ) ) )).",
|
|
1365
|
+
"thf(msince_type, type, ( msince : ( mu > $o ) > ( mu > $o ) > mu > $o )).",
|
|
1366
|
+
"thf(msince_def, definition, ( msince = ( ^ [Phi: mu>$o, Psi: mu>$o, W: mu] : "
|
|
1367
|
+
"! [S: mu>$o] : ( ( ( ! [V: mu] : ( ( Psi @ V ) => ( S @ V ) ) ) & "
|
|
1368
|
+
"( ! [V: mu, U: mu] : ( ( ( Phi @ V ) & ( tnext @ U @ V ) & ( S @ U ) ) "
|
|
1369
|
+
"=> ( S @ V ) ) ) ) => ( S @ W ) ) ) )).",
|
|
1370
|
+
]
|
|
1371
|
+
|
|
1372
|
+
# Per-agent frame axioms for the four agent-indexed relations, keyed the same
|
|
1373
|
+
# way as _ISA_AGENT_CONDS's Isabelle counterpart (ported from the same five
|
|
1374
|
+
# Horn schemas thf_modal.py's own _agent_frame_axioms states in THF).
|
|
1375
|
+
_THF_AGENT_CONDS = {
|
|
1376
|
+
"refl": "thf({tag}_refl, axiom, ( ! [A: $i, W: mu] : ( {rel} @ A @ W @ W ) )).",
|
|
1377
|
+
"trans": ("thf({tag}_trans, axiom, ( ! [A: $i, W: mu, V: mu, U: mu] : "
|
|
1378
|
+
"( ( ( {rel} @ A @ W @ V ) & ( {rel} @ A @ V @ U ) ) "
|
|
1379
|
+
"=> ( {rel} @ A @ W @ U ) ) ))."),
|
|
1380
|
+
"sym": ("thf({tag}_sym, axiom, ( ! [A: $i, W: mu, V: mu] : "
|
|
1381
|
+
"( ( {rel} @ A @ W @ V ) => ( {rel} @ A @ V @ W ) ) ))."),
|
|
1382
|
+
"serial": ("thf({tag}_serial, axiom, ( ! [A: $i, W: mu] : "
|
|
1383
|
+
"( ? [V: mu] : ( {rel} @ A @ W @ V ) ) ))."),
|
|
1384
|
+
"eucl": ("thf({tag}_eucl, axiom, ( ! [A: $i, W: mu, V: mu, U: mu] : "
|
|
1385
|
+
"( ( ( {rel} @ A @ W @ V ) & ( {rel} @ A @ W @ U ) ) "
|
|
1386
|
+
"=> ( {rel} @ A @ V @ U ) ) )).")
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
_THF_FAMILY_REL = {"epistemic": "rk", "doxastic": "rb",
|
|
1390
|
+
"assertive": "rs", "bouletic": "rw"}
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
def _thf_agent_system_axioms(family: str, system: str) -> List[str]:
|
|
1394
|
+
"""THF counterpart of :func:`_agent_system_axioms` -- same systems, same refusals."""
|
|
1395
|
+
if system not in FRAMES:
|
|
1396
|
+
raise ValueError(
|
|
1397
|
+
f"to_thf_ho_modal: unknown system {system!r} for {family} "
|
|
1398
|
+
f"(use one of {sorted(FRAMES)})."
|
|
1399
|
+
)
|
|
1400
|
+
conditions = resolve_frame(system)
|
|
1401
|
+
unsupported = [c for c in conditions if c not in _THF_AGENT_CONDS]
|
|
1402
|
+
if unsupported:
|
|
1403
|
+
raise NotImplementedError(
|
|
1404
|
+
f"to_thf_ho_modal: system {system!r} for {family} needs the frame "
|
|
1405
|
+
f"condition(s) {unsupported}, which have no per-agent axiom schema "
|
|
1406
|
+
f"here; use frame= on the alethic relation for those systems."
|
|
1407
|
+
)
|
|
1408
|
+
rel = _THF_FAMILY_REL[family]
|
|
1409
|
+
return [_THF_AGENT_CONDS[c].format(rel=rel, tag=rel) for c in conditions]
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
def _thf_family_axiom_lines(usage: "_FamilyUsage", systems: dict,
|
|
1413
|
+
temporal_closure: bool) -> List[str]:
|
|
1414
|
+
"""THF ``axiom`` lines for every family/system ``usage``/``systems`` need.
|
|
1415
|
+
|
|
1416
|
+
Mirrors :func:`_family_axiom_lines`'s order and gating exactly, so the
|
|
1417
|
+
Isabelle and THF routes constrain the same relations under the same call.
|
|
1418
|
+
"""
|
|
1419
|
+
lines: List[str] = []
|
|
1420
|
+
for family in sorted(systems):
|
|
1421
|
+
lines += _thf_agent_system_axioms(family, systems[family])
|
|
1422
|
+
if usage.deontic:
|
|
1423
|
+
lines.append("thf(d_serial, axiom, ( ! [W: mu] : ( ? [V: mu] : "
|
|
1424
|
+
"( d @ W @ V ) ) )).")
|
|
1425
|
+
if usage.temporal and temporal_closure:
|
|
1426
|
+
lines += [
|
|
1427
|
+
"thf(t_refl, axiom, ( ! [W: mu] : ( t @ W @ W ) )).",
|
|
1428
|
+
"thf(t_trans, axiom, ( ! [W: mu, V: mu, U: mu] : "
|
|
1429
|
+
"( ( ( t @ W @ V ) & ( t @ V @ U ) ) => ( t @ W @ U ) ) )).",
|
|
1430
|
+
]
|
|
1431
|
+
if usage.temporal and usage.needs_next_rel:
|
|
1432
|
+
lines.append("thf(tnext_in_t, axiom, ( ! [W: mu, V: mu] : "
|
|
1433
|
+
"( ( tnext @ W @ V ) => ( t @ W @ V ) ) )).")
|
|
1434
|
+
return lines
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
def _thf_domain_lines() -> List[str]:
|
|
1438
|
+
"""``existsAt`` typed, unconditionally -- the ONLY family piece kept
|
|
1439
|
+
conditional in this module's THF export (see the module docstring and
|
|
1440
|
+
_domain_vocab_lines's Isabelle counterpart): emitted only for an
|
|
1441
|
+
actualist ``mode=`` combined with an actual individual quantifier, so the
|
|
1442
|
+
default constant-mode THF problem is unchanged from before this port.
|
|
1443
|
+
"""
|
|
1444
|
+
return ["thf(existsat_type, type, ( existsat : $i > mu > $o ))."]
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def _thf_domain_axiom_lines(mode: str) -> List[str]:
|
|
1448
|
+
"""THF counterpart of :func:`_domain_axiom_lines`, over the alethic ``r``."""
|
|
1449
|
+
out = ["thf(nonempty_dom, axiom, ( ! [W: mu] : "
|
|
1450
|
+
"( ? [X: $i] : ( existsat @ X @ W ) ) ))."]
|
|
1451
|
+
if mode in ("increasing", "cumulative"):
|
|
1452
|
+
out.append("thf(cumul_dom, axiom, ( ! [X: $i, W: mu, V: mu] : "
|
|
1453
|
+
"( ( ( existsat @ X @ W ) & ( r @ W @ V ) ) "
|
|
1454
|
+
"=> ( existsat @ X @ V ) ) )).")
|
|
1455
|
+
elif mode == "decreasing":
|
|
1456
|
+
out.append("thf(decr_dom, axiom, ( ! [X: $i, W: mu, V: mu] : "
|
|
1457
|
+
"( ( ( existsat @ X @ V ) & ( r @ W @ V ) ) "
|
|
1458
|
+
"=> ( existsat @ X @ W ) ) )).")
|
|
1459
|
+
return out
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
def _thf_type(kind) -> str:
|
|
1463
|
+
"""Return the THF type of an argument slot: ``$i`` or a property type."""
|
|
1464
|
+
if kind == INDIVIDUAL:
|
|
1465
|
+
return "$i"
|
|
1466
|
+
return "( " + " > ".join(["$i"] * kind[1] + ["mu", "$o"]) + " )"
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
def _thf_arg(node: Node, upper: Dict[str, str], display: Dict[str, str],
|
|
1470
|
+
depth: int, names: ThfNames, mode: str) -> str:
|
|
1471
|
+
"""Render an argument-position node in THF — an individual or a property.
|
|
1472
|
+
|
|
1473
|
+
``upper`` maps each source name bound in scope to its THF variable token;
|
|
1474
|
+
a variable that is not in it is free and spelled through ``names``. A constant
|
|
1475
|
+
is never looked up in ``upper``: no binder captures it, whatever its spelling.
|
|
1476
|
+
"""
|
|
1477
|
+
if isinstance(node, (Variable, LambdaVar)):
|
|
1478
|
+
return upper.get(node.name) or names.functor(FREE_VARIABLE, node.name)
|
|
1479
|
+
if isinstance(node, Constant):
|
|
1480
|
+
return names.functor(CONSTANT, node.name)
|
|
1481
|
+
if isinstance(node, PredicateTerm):
|
|
1482
|
+
return upper.get(node.name) or names.functor("predicate", node.name)
|
|
1483
|
+
if isinstance(node, Lambda):
|
|
1484
|
+
params, body = peel_lambdas(node)
|
|
1485
|
+
fresh = dict(upper)
|
|
1486
|
+
for n in params:
|
|
1487
|
+
fresh[n] = bound_token(n, "_V", fresh)
|
|
1488
|
+
world = f"W{depth}"
|
|
1489
|
+
binders = ", ".join(f"{fresh[n]}: $i" for n in params)
|
|
1490
|
+
return (f"( ^ [{binders}, {world}: mu] : "
|
|
1491
|
+
f"( {_thf(body, fresh, display, depth + 1, names, mode)} @ {world} ) )")
|
|
1492
|
+
if isinstance(node, Function):
|
|
1493
|
+
functor = names.functor("function", node.name)
|
|
1494
|
+
args = " @ ".join(_thf_arg(a, upper, display, depth, names, mode)
|
|
1495
|
+
for a in node.args)
|
|
1496
|
+
return f"( {functor} @ {args} )" if node.args else functor
|
|
1497
|
+
raise UnsupportedHigherOrderNode(
|
|
1498
|
+
f"ho_modal: {type(node).__name__} cannot stand in argument position.")
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
_BINARY_THF = {And: "mand", Or: "mor", Implies: "mimp", Iff: "miff"}
|
|
1502
|
+
|
|
1503
|
+
#: The embedding's own functors, pre-claimed so no user symbol can take them.
|
|
1504
|
+
_THF_RESERVED = ("mu", "r", "mnot", "mand", "mor", "mimp", "miff", "mbox", "mdia",
|
|
1505
|
+
"mvalid", "rk", "rb", "rs", "rw", "d", "t", "tnext",
|
|
1506
|
+
"mknows", "mbelieves", "msays", "mwants", "mobl", "mperm",
|
|
1507
|
+
"malways", "meventually", "mnext", "mhistorically", "monce",
|
|
1508
|
+
"mprevious", "muntil", "msince",
|
|
1509
|
+
"existsat", "mforall", "mexists", "feq", "fneq") + tuple(ORDERING.values())
|
|
1510
|
+
# ``feq`` / ``fneq`` name nothing in this embedding any more (identity is rigid: the
|
|
1511
|
+
# ``meq`` macro below, not an uninterpreted functor), but they stay claimed so a user
|
|
1512
|
+
# predicate that sanitises onto one of them is still pushed to ``feq_2`` -- the same
|
|
1513
|
+
# choice hol.thf_modal makes -- rather than reading as the old identity alias. ``meq``
|
|
1514
|
+
# is claimed only when the problem contains identity (see to_thf_ho_modal), so an
|
|
1515
|
+
# identity-free problem names its symbols exactly as before.
|
|
1516
|
+
|
|
1517
|
+
#: The identity macro (emitted only when a formula contains identity): the name and the
|
|
1518
|
+
#: DEFINITION are hol.thf_modal's, verbatim -- ``meq = ^ [A: $i, B: $i, W: mu] : ( A = B )``,
|
|
1519
|
+
#: THF's own ``=`` over the individual sort with a world binder the body never mentions
|
|
1520
|
+
#: -- plus the type line every other macro of this prelude carries.
|
|
1521
|
+
_THF_RIGID_EQ_LINES = [
|
|
1522
|
+
f"thf({_THF_RIGID_EQ}_type, type, ( {_THF_RIGID_EQ} : $i > $i > mu > $o )).",
|
|
1523
|
+
_THF_RIGID_EQ_DEF,
|
|
1524
|
+
]
|
|
1525
|
+
|
|
1526
|
+
#: Refusal text for the counterfactuals, THF side (see the module docstring).
|
|
1527
|
+
_NO_THF_COUNTERFACTUAL = (
|
|
1528
|
+
"to_thf_ho_modal: the counterfactuals □→/◇→ (Would/Might) "
|
|
1529
|
+
"read a similarity ordering of worlds (Lewis spheres), not an "
|
|
1530
|
+
"accessibility relation. Use hol.isabelle_conditional instead."
|
|
1531
|
+
)
|
|
1532
|
+
|
|
1533
|
+
#: N1: ↓, THF side (see _NO_DOWN above for the Isabelle-side twin).
|
|
1534
|
+
_NO_THF_DOWN = (
|
|
1535
|
+
"to_thf_ho_modal: the ↓ binder is not supported by this third-order THF "
|
|
1536
|
+
"embedding — H(@,↓) validity is undecidable. Use "
|
|
1537
|
+
"unicode_logic_kit.fol.modal_translation.down_is_valid (propositional "
|
|
1538
|
+
"H(@,↓), Z3, PROVED-only) or unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend "
|
|
1539
|
+
"/ modal_enum_search (bounded search, REFUTED-only) instead."
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
def _thf(node: Node, upper: Dict[str, str], display: Dict[str, str],
|
|
1544
|
+
depth: int, names: ThfNames, mode: str) -> str:
|
|
1545
|
+
"""Render ``node`` as a THF term of type ``mu > $o``.
|
|
1546
|
+
|
|
1547
|
+
``depth`` numbers the world binders this rendering introduces (``W0``,
|
|
1548
|
+
``W1``, …). Shadowing would be sound — each application picks up its own
|
|
1549
|
+
innermost binder — but a distinct name per level is what makes the emitted
|
|
1550
|
+
problem readable, and readable is what gets a mis-embedding noticed. User
|
|
1551
|
+
variable tokens always carry a ``_V``/``_P`` suffix, so they never meet a
|
|
1552
|
+
world binder.
|
|
1553
|
+
"""
|
|
1554
|
+
if isinstance(node, Not):
|
|
1555
|
+
return f"( mnot @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1556
|
+
op = _BINARY_THF.get(type(node))
|
|
1557
|
+
if op is not None:
|
|
1558
|
+
left = _thf(node.left, upper, display, depth, names, mode)
|
|
1559
|
+
right = _thf(node.right, upper, display, depth, names, mode)
|
|
1560
|
+
return f"( {op} @ {left} @ {right} )"
|
|
1561
|
+
if isinstance(node, Box):
|
|
1562
|
+
return f"( mbox @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1563
|
+
if isinstance(node, Diamond):
|
|
1564
|
+
return f"( mdia @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1565
|
+
if isinstance(node, Quantifier):
|
|
1566
|
+
var = bound_token(node.variable.name, "_V", upper)
|
|
1567
|
+
fresh = dict(upper, **{node.variable.name: var})
|
|
1568
|
+
quant = "!" if node.type == "∀" else "?"
|
|
1569
|
+
world = f"W{depth}"
|
|
1570
|
+
body = f"( {_thf(node.formula, fresh, display, depth + 1, names, mode)} @ {world} )"
|
|
1571
|
+
if mode in _ACTUALIST_MODES:
|
|
1572
|
+
# The individual binder becomes existsAt-guarded; a property
|
|
1573
|
+
# binder (below) never is -- the one judgment call this port
|
|
1574
|
+
# makes, see the module docstring.
|
|
1575
|
+
guard = f"( existsat @ {var} @ {world} )"
|
|
1576
|
+
connective = "=>" if quant == "!" else "&"
|
|
1577
|
+
inner = f"( {quant} [{var}: $i] : ( {guard} {connective} {body} ) )"
|
|
1578
|
+
else:
|
|
1579
|
+
inner = f"( {quant} [{var}: $i] : {body} )"
|
|
1580
|
+
return f"( ^ [{world}: mu] : {inner} )"
|
|
1581
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
1582
|
+
var = bound_token(display.get(node.predicate, node.predicate), "_P", upper)
|
|
1583
|
+
fresh = dict(upper, **{node.predicate: var})
|
|
1584
|
+
quant = "!" if node.type == "∀" else "?"
|
|
1585
|
+
ptype = " > ".join(["$i"] * node.arity + ["mu", "$o"])
|
|
1586
|
+
world = f"W{depth}"
|
|
1587
|
+
return (f"( ^ [{world}: mu] : ( {quant} [{var}: {ptype}] : "
|
|
1588
|
+
f"( {_thf(node.formula, fresh, display, depth + 1, names, mode)} @ {world} ) ) )")
|
|
1589
|
+
if isinstance(node, (Knows, Believes, Says, Wants)):
|
|
1590
|
+
macro = {Knows: "mknows", Believes: "mbelieves",
|
|
1591
|
+
Says: "msays", Wants: "mwants"}[type(node)]
|
|
1592
|
+
agent = _thf_arg(node.agent, upper, display, depth, names, mode)
|
|
1593
|
+
body = _thf(node.formula, upper, display, depth, names, mode)
|
|
1594
|
+
return f"( {macro} @ {agent} @ {body} )"
|
|
1595
|
+
if isinstance(node, Obligatory):
|
|
1596
|
+
return f"( mobl @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1597
|
+
if isinstance(node, Permitted):
|
|
1598
|
+
return f"( mperm @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1599
|
+
if isinstance(node, Always):
|
|
1600
|
+
return f"( malways @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1601
|
+
if isinstance(node, Eventually):
|
|
1602
|
+
return f"( meventually @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1603
|
+
if isinstance(node, Next):
|
|
1604
|
+
return f"( mnext @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1605
|
+
if isinstance(node, Historically):
|
|
1606
|
+
return f"( mhistorically @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1607
|
+
if isinstance(node, Once):
|
|
1608
|
+
return f"( monce @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1609
|
+
if isinstance(node, Previous):
|
|
1610
|
+
return f"( mprevious @ {_thf(node.formula, upper, display, depth, names, mode)} )"
|
|
1611
|
+
if isinstance(node, Until):
|
|
1612
|
+
left = _thf(node.left, upper, display, depth, names, mode)
|
|
1613
|
+
right = _thf(node.right, upper, display, depth, names, mode)
|
|
1614
|
+
return f"( muntil @ {left} @ {right} )"
|
|
1615
|
+
if isinstance(node, Since):
|
|
1616
|
+
left = _thf(node.left, upper, display, depth, names, mode)
|
|
1617
|
+
right = _thf(node.right, upper, display, depth, names, mode)
|
|
1618
|
+
return f"( msince @ {left} @ {right} )"
|
|
1619
|
+
if isinstance(node, Nominal):
|
|
1620
|
+
return f"( ^ [W: mu] : ( W = {names.functor('nominal', node.name)} ) )"
|
|
1621
|
+
if isinstance(node, At):
|
|
1622
|
+
body = _thf(node.formula, upper, display, depth, names, mode)
|
|
1623
|
+
return f"( ^ [W: mu] : ( {body} @ {names.functor('nominal', node.nominal.name)} ) )"
|
|
1624
|
+
if isinstance(node, Down):
|
|
1625
|
+
raise UnsupportedHigherOrderNode(_NO_THF_DOWN)
|
|
1626
|
+
if isinstance(node, Atom):
|
|
1627
|
+
constant = truth_value(node)
|
|
1628
|
+
if constant is not None:
|
|
1629
|
+
# `$true` / `$false`: the proposition true (false) at every world.
|
|
1630
|
+
return "( ^ [W: mu] : $true )" if constant else "( ^ [W: mu] : $false )"
|
|
1631
|
+
if is_equality_atom(node):
|
|
1632
|
+
_identity_sides(node)
|
|
1633
|
+
name = _THF_RIGID_EQ
|
|
1634
|
+
elif node.predicate in ORDERING:
|
|
1635
|
+
name = ORDERING[node.predicate]
|
|
1636
|
+
else:
|
|
1637
|
+
name = upper.get(node.predicate) or names.functor("predicate", node.predicate)
|
|
1638
|
+
if not node.args:
|
|
1639
|
+
return name
|
|
1640
|
+
args = " @ ".join(_thf_arg(a, upper, display, depth, names, mode)
|
|
1641
|
+
for a in node.args)
|
|
1642
|
+
return f"( {name} @ {args} )"
|
|
1643
|
+
if type(node).__name__ in ("Would", "Might"):
|
|
1644
|
+
raise UnsupportedHigherOrderNode(_NO_THF_COUNTERFACTUAL)
|
|
1645
|
+
raise UnsupportedHigherOrderNode(
|
|
1646
|
+
f"ho_modal: no THF reading for {type(node).__name__}; this embedding "
|
|
1647
|
+
f"covers alethic, agent-indexed epistemic/doxastic/assertive/bouletic, "
|
|
1648
|
+
f"deontic, temporal and hybrid families over third-order syntax -- not "
|
|
1649
|
+
f"the Lewis counterfactuals or the group-epistemic operators.")
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def to_thf_ho_modal(formula: Node, frame: str = "K",
|
|
1653
|
+
axioms: Sequence[HoAxiom] = (), mode: str = "constant",
|
|
1654
|
+
systems: Optional[dict] = None,
|
|
1655
|
+
temporal_closure: bool = True) -> str:
|
|
1656
|
+
"""Emit a THF (TH0) problem: ``axioms`` as hypotheses, ``formula`` as the conjecture.
|
|
1657
|
+
|
|
1658
|
+
The THF counterpart of :func:`isabelle_ho_modal_theory`, for a higher-order
|
|
1659
|
+
ATP (Leo-III, Satallax) rather than Isabelle. Same embedding, same fragment,
|
|
1660
|
+
same refusals, same ``mode=``/``systems=``/``temporal_closure=`` meaning;
|
|
1661
|
+
the toolkit emits the problem and does not run a prover.
|
|
1662
|
+
|
|
1663
|
+
Identity ``=`` / ``≠`` between individuals is the rigid ``meq`` macro (HOL's own ``=``,
|
|
1664
|
+
no world argument; ``≠`` is ``¬(=)``), emitted only when a formula contains identity;
|
|
1665
|
+
a non-binary atom raises ``ValueError`` and identity between PROPERTIES raises
|
|
1666
|
+
``NotImplementedError`` — see the module docstring's "Equality is rigid, or refused".
|
|
1667
|
+
"""
|
|
1668
|
+
if frame not in FRAMES:
|
|
1669
|
+
raise ValueError(f"to_thf_ho_modal: unknown frame {frame!r}.")
|
|
1670
|
+
if mode not in _ACTUALIST_MODES and mode not in _CONSTANT_MODES:
|
|
1671
|
+
raise ValueError(
|
|
1672
|
+
f"to_thf_ho_modal: unknown mode {mode!r} "
|
|
1673
|
+
f"(use one of {sorted(_ACTUALIST_MODES | _CONSTANT_MODES)})."
|
|
1674
|
+
)
|
|
1675
|
+
# Identity is read ONCE, up front, as rigid ``=`` (see _rigid_identity).
|
|
1676
|
+
formulas = _rigid_identity([a.formula for a in axioms] + [formula], "to_thf_ho_modal")
|
|
1677
|
+
apart, display = rename_apart(formulas)
|
|
1678
|
+
signatures = analyse_signatures(apart)
|
|
1679
|
+
bound = set()
|
|
1680
|
+
for f in apart:
|
|
1681
|
+
bound |= bound_pred_names(f)
|
|
1682
|
+
usage = _scan_family_usage(apart)
|
|
1683
|
+
resolved_systems = _validate_systems(usage, systems, caller="to_thf_ho_modal")
|
|
1684
|
+
|
|
1685
|
+
identity = any(_has_identity(f) for f in apart)
|
|
1686
|
+
names = ThfNames(reserved=_THF_RESERVED + ((_THF_RIGID_EQ,) if identity else ()))
|
|
1687
|
+
lines = list(_THF_PRELUDE) + list(_THF_FAMILY_PRELUDE)
|
|
1688
|
+
if identity:
|
|
1689
|
+
lines += _THF_RIGID_EQ_LINES
|
|
1690
|
+
if mode in _ACTUALIST_MODES and usage.has_quant:
|
|
1691
|
+
lines += _thf_domain_lines()
|
|
1692
|
+
for pred in sorted(signatures.slots):
|
|
1693
|
+
if pred in bound or pred in EQUALITY_PREDICATES or pred in ORDERING:
|
|
1694
|
+
continue
|
|
1695
|
+
parts = [_thf_type(k) for k in signatures.slots[pred]]
|
|
1696
|
+
thf_type = " > ".join(parts + ["mu", "$o"]) if parts else "mu > $o"
|
|
1697
|
+
functor = names.functor("predicate", pred)
|
|
1698
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : {thf_type} )).")
|
|
1699
|
+
for kind, name in individual_symbols(apart):
|
|
1700
|
+
functor = names.functor(kind, name)
|
|
1701
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : $i )).")
|
|
1702
|
+
# An ordering atom is a world-dependent uninterpreted relation here, as in qml.
|
|
1703
|
+
# Identity is not: it is the ``meq`` macro above, declared nowhere else.
|
|
1704
|
+
for symbol in sorted({ORDERING[p] for f in apart for p in atom_predicates(f)
|
|
1705
|
+
if p in ORDERING}):
|
|
1706
|
+
lines.append(f"thf({symbol}_type, type, ( {symbol} : $i > $i > mu > $o )).")
|
|
1707
|
+
for name, arity in sorted(function_symbols(apart).items()):
|
|
1708
|
+
functor = names.functor("function", name)
|
|
1709
|
+
ftype = " > ".join(["$i"] * (arity + 1))
|
|
1710
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : {ftype} )).")
|
|
1711
|
+
# Hybrid nominals: one `mu`-typed constant per distinct nominal NAME across
|
|
1712
|
+
# the whole problem, declared up front (like every other free symbol) so
|
|
1713
|
+
# every later reference -- in an axiom or in the goal -- resolves to the
|
|
1714
|
+
# SAME world constant.
|
|
1715
|
+
nominal_names = sorted({n.name for f in apart for n in f.walk()
|
|
1716
|
+
if isinstance(n, Nominal)})
|
|
1717
|
+
for name in nominal_names:
|
|
1718
|
+
functor = names.functor("nominal", name)
|
|
1719
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : mu )).")
|
|
1720
|
+
|
|
1721
|
+
for condition in resolve_frame(frame):
|
|
1722
|
+
if condition not in _THF_FRAME:
|
|
1723
|
+
raise UnsupportedFrameCondition(
|
|
1724
|
+
f"to_thf_ho_modal: frame condition {condition!r} (system "
|
|
1725
|
+
f"{frame!r}) has no axiom registered in this embedding's "
|
|
1726
|
+
f"_THF_FRAME.")
|
|
1727
|
+
lines.append(f"thf(frame_{condition}, axiom, {_THF_FRAME[condition]}).")
|
|
1728
|
+
lines += _thf_family_axiom_lines(usage, resolved_systems, temporal_closure)
|
|
1729
|
+
if mode in _ACTUALIST_MODES and usage.has_quant:
|
|
1730
|
+
lines += _thf_domain_axiom_lines(mode)
|
|
1731
|
+
|
|
1732
|
+
# Annotated-formula names share one namespace; an axiom called "A1" is not
|
|
1733
|
+
# a lower word, and one called "goal" or "mu_type" would clash.
|
|
1734
|
+
for line in lines:
|
|
1735
|
+
if line.startswith("thf("):
|
|
1736
|
+
names.unit(line[4:line.index(",")])
|
|
1737
|
+
for axiom, renamed in zip(axioms, apart):
|
|
1738
|
+
lines.append(f"thf({names.unit(axiom.name)}, axiom, ( mvalid @ "
|
|
1739
|
+
f"{_thf(renamed, {}, display, 0, names, mode)} )).")
|
|
1740
|
+
goal = names.unit("goal")
|
|
1741
|
+
lines.append(f"thf({goal}, conjecture, "
|
|
1742
|
+
f"( mvalid @ {_thf(apart[-1], {}, display, 0, names, mode)} )).")
|
|
1743
|
+
return "\n".join(lines) + "\n"
|