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,1908 @@
|
|
|
1
|
+
r"""Isabelle/HOL shallow semantical embedding (SSE) of quantified modal logic.
|
|
2
|
+
|
|
3
|
+
This module *emits* a complete, loadable Isabelle/HOL theory (``theory NAME
|
|
4
|
+
imports Main begin ... end``) that shallow-embeds a modal formula in the
|
|
5
|
+
Benzmüller style and states it as a **real** ``lemma`` (not a comment). It is the
|
|
6
|
+
faithful Isabelle counterpart of the THF export
|
|
7
|
+
:func:`unicode_logic_kit.fol.qml.to_thf_modal` and is kept consistent with the
|
|
8
|
+
ground-truth Kripke evaluator
|
|
9
|
+
:func:`unicode_logic_kit.semantics.kripke.satisfies_modal`.
|
|
10
|
+
|
|
11
|
+
What it covers
|
|
12
|
+
--------------
|
|
13
|
+
The full modal family handled by the AST:
|
|
14
|
+
|
|
15
|
+
- **alethic** ``Box`` □ / ``Diamond`` ◇ over an accessibility relation ``r``;
|
|
16
|
+
- **epistemic** ``Knows`` and **doxastic** ``Believes`` over **agent-indexed**
|
|
17
|
+
relations ``rk`` / ``rb`` of type ``'a \<Rightarrow> i \<Rightarrow> i \<Rightarrow> bool`` — the
|
|
18
|
+
agent is a first-class *term* (``Variable`` or ``Constant``), so a bound object
|
|
19
|
+
variable in agent position (``∀x (Student(x) → K_x φ)``) quantifies over agents,
|
|
20
|
+
exactly as in :func:`satisfies_modal`;
|
|
21
|
+
- **deontic** ``Obligatory`` / ``Permitted`` over a (serial) relation ``d``;
|
|
22
|
+
- **temporal** ``Always`` / ``Eventually`` over the henceforth relation ``t`` and
|
|
23
|
+
``Next`` over the one-step relation ``n``, plus the binary interval operators
|
|
24
|
+
``Until`` / ``Since`` as inductive least fixpoints over ``n`` (see below).
|
|
25
|
+
|
|
26
|
+
The embedding is **faithful to** :func:`satisfies_modal` on the overlapping
|
|
27
|
+
fragment: every modality is read as a box/diamond over the corresponding
|
|
28
|
+
relation and object quantifiers are ``existsAt``-guarded (actualist).
|
|
29
|
+
|
|
30
|
+
Identity is **rigid** (see "Equality" below), matching
|
|
31
|
+
:func:`unicode_logic_kit.fol.qml.qml_is_valid`.
|
|
32
|
+
|
|
33
|
+
Equality
|
|
34
|
+
--------
|
|
35
|
+
An identity atom ``t₁ = t₂`` is Isabelle's own polymorphic ``=`` over the entity
|
|
36
|
+
type ``e``, lifted with a world binder that is never used —
|
|
37
|
+
``(\<lambda>_. t₁ = t₂)`` — so it takes **no world argument** and cannot vary by
|
|
38
|
+
world. ``t₁ ≠ t₂`` is lowered to ``¬(t₁ = t₂)`` first, exactly as
|
|
39
|
+
:func:`unicode_logic_kit.fol.qml.qml_translate` does (the lowering is
|
|
40
|
+
:func:`unicode_logic_kit.fol.qml._st_equality` itself, imported rather than
|
|
41
|
+
copied). Consequences, each one a theorem of the emitted theory and the same
|
|
42
|
+
verdict ``qml_is_valid`` gives: ``a = a``, ``a = b → b = a``, transitivity, the
|
|
43
|
+
necessity of identity ``a = b → □(a = b)`` and of distinctness
|
|
44
|
+
``a ≠ b → □(a ≠ b)`` (valid in every frame, under every ``mode``),
|
|
45
|
+
``◇(a = b) → a = b``, and Leibniz's law ``a = b → (P(a) ↔ P(b))`` /
|
|
46
|
+
``a = b → (□P(a) ↔ □P(b))`` and congruence over function symbols.
|
|
47
|
+
``□(a = b) → a = b`` is **not** valid in ``K`` (a dead-end world makes the box
|
|
48
|
+
vacuously true) and is valid exactly when the frame guarantees a
|
|
49
|
+
successor-or-self (``T``, ``S4``, ``S5``, ``KD``, ``KD45``).
|
|
50
|
+
|
|
51
|
+
Identity ranges over the whole entity type ``e`` and is **not** existence-guarded
|
|
52
|
+
under a varying domain (``qml``'s documented choice): ``a = a`` holds at a world
|
|
53
|
+
where ``a`` does not exist, and ``∃x (x = c)`` — which *does* go through the
|
|
54
|
+
``existsAt``-guarded ``mexists`` — is valid under ``constant`` / ``possibilist``
|
|
55
|
+
and not under the actualist modes. The ordering atoms ``<`` ``>`` ``≤`` ``≥`` are
|
|
56
|
+
still ordinary uninterpreted world-relativized predicates (``flt`` …), as in
|
|
57
|
+
``qml``. An ``=`` / ``≠`` atom that is not binary raises ``ValueError``, again as
|
|
58
|
+
in ``qml``.
|
|
59
|
+
|
|
60
|
+
Temporal caveat (henceforth / transitive closure)
|
|
61
|
+
-------------------------------------------------
|
|
62
|
+
``satisfies_modal`` reads ``Always``/``Eventually`` over the **reflexive-transitive
|
|
63
|
+
closure** of the one-step ``"temporal"`` relation, whereas a plain shallow
|
|
64
|
+
abbreviation can only quantify over the relation it is given. This module emits
|
|
65
|
+
the **box/diamond-over-``t`` reading** and, when ``temporal_closure=True`` (the
|
|
66
|
+
default), additionally constrains ``t`` to be a *reflexive + transitive* relation
|
|
67
|
+
via frame axioms, so ``t`` denotes the henceforth relation directly and the
|
|
68
|
+
embedding matches the closure semantics. ``Next`` is the genuine one-step box over
|
|
69
|
+
the one-step successor relation ``n``. In ``satisfies_modal`` ``Always`` /
|
|
70
|
+
``Eventually`` close over the **same** one-step ``"temporal"`` relation that
|
|
71
|
+
``Next`` reads, so every one-step successor is henceforth-reachable and
|
|
72
|
+
``Always(P) → Next(P)`` is valid. To keep the embedding faithful, when ``Next``
|
|
73
|
+
co-occurs with ``Always`` / ``Eventually`` (both ``n`` and ``t`` declared) this
|
|
74
|
+
module emits the linking axiom ``n_in_t : "⋀w v. n w v ⟹ t w v"`` (one-step
|
|
75
|
+
successor ⟹ henceforth-reachable), so ``Always(P) → Next(P)`` is likewise a
|
|
76
|
+
theorem of the emitted theory.
|
|
77
|
+
|
|
78
|
+
Binary interval operators ``Until`` / ``Since``
|
|
79
|
+
-----------------------------------------------
|
|
80
|
+
``Until`` and ``Since`` are emitted as **inductive least-fixpoint predicates**
|
|
81
|
+
``muntil`` / ``msince`` over the one-step relation ``n`` (not as box/diamond
|
|
82
|
+
abbreviations). ``muntil phi psi`` is the least predicate closed under
|
|
83
|
+
``psi w ⟹ muntil w`` and ``phi w ∧ n w v ∧ muntil v ⟹ muntil w``, so it denotes
|
|
84
|
+
exactly the **finite forward ``n``-paths** with ``psi`` at the endpoint and
|
|
85
|
+
``phi`` at every earlier point — the faithful counterpart of the depth-first path
|
|
86
|
+
search in :func:`satisfies_modal` (``_until_holds``); ``msince`` is the backward
|
|
87
|
+
mirror over the converse of ``n`` (``_since_holds``). A plain interval reading over
|
|
88
|
+
the closure ``t`` would *not* be faithful on branching / short-cut frames (it asks
|
|
89
|
+
``phi`` at every ``t``-intermediate world, the path search only along one path); the
|
|
90
|
+
least fixpoint is faithful on **every** frame. Because these read the one-step
|
|
91
|
+
relation ``n``, using ``Until`` / ``Since`` declares ``n`` (as ``Next`` does) and,
|
|
92
|
+
when ``Always`` / ``Eventually`` also occur, links ``n`` into the henceforth ``t``
|
|
93
|
+
so the two agree. The Isabelle-verified characterization (the strong-Until /
|
|
94
|
+
Since fixpoint equation and reachability) lives in ``tests/test_hol_isabelle_modal``
|
|
95
|
+
and the live ``check_theory`` gate.
|
|
96
|
+
|
|
97
|
+
Cross-family bridges (``bridges=``)
|
|
98
|
+
-----------------------------------
|
|
99
|
+
``frame=`` and ``systems=`` each constrain exactly **one** relation. A *bridge*
|
|
100
|
+
relates **two** relations belonging to different modal families, which is why it
|
|
101
|
+
is a separate, **opt-in** option and not another ``systems=`` entry. Three are
|
|
102
|
+
available (see :data:`BRIDGES`):
|
|
103
|
+
|
|
104
|
+
====================================== ==================== ==========================
|
|
105
|
+
name schema exact frame correspondent
|
|
106
|
+
====================================== ==================== ==========================
|
|
107
|
+
``knowledge_implies_belief`` ``K_a φ → B_a φ`` ``rb ⊆ rk``
|
|
108
|
+
``sincerity`` ``Say_a φ → B_a φ`` ``rb ⊆ rs``
|
|
109
|
+
``ought_implies_can`` ``Oφ → ◇φ`` ``∀w. ∃v. d w v ∧ r w v``
|
|
110
|
+
====================================== ==================== ==========================
|
|
111
|
+
|
|
112
|
+
Each condition is the **exact** correspondent of its schema — necessary as well
|
|
113
|
+
as sufficient — established by brute force over every frame on ≤ 2 worlds against
|
|
114
|
+
:func:`satisfies_modal`, not quoted from the literature. In particular
|
|
115
|
+
``ought_implies_can`` is deliberately **not** the frequently quoted inclusion
|
|
116
|
+
``d ⊆ r``: measured, ``d ⊆ r`` alone does not validate ``Oφ → ◇φ`` at all (a world
|
|
117
|
+
with no ``d``-successor makes ``Oφ`` vacuously true while ``◇φ`` is false), and
|
|
118
|
+
``d ⊆ r`` *together with* ``d``-seriality validates the strictly stronger
|
|
119
|
+
``□φ → Oφ`` ("whatever is necessary is obligatory") that the caller never asked
|
|
120
|
+
for. The emitted "meet" condition subsumes ``d``-seriality and does *not*
|
|
121
|
+
validate ``□φ → Oφ``. ``d_serial`` is still emitted alongside it — redundant but
|
|
122
|
+
harmless, and dropping it would change :func:`modal_axiom_names`' documented
|
|
123
|
+
output.
|
|
124
|
+
|
|
125
|
+
Bridges are OFF by default. A bridge whose partner family does not occur in the
|
|
126
|
+
formula **raises** ``ValueError``: silently skipping it would emit a weaker logic
|
|
127
|
+
than requested, and declaring the missing relation anyway is not conservative
|
|
128
|
+
(``d_meets_r`` entails seriality of the alethic ``r``, measurably turning
|
|
129
|
+
``□P → ◇P`` from invalid into valid under ``frame='K'``). See
|
|
130
|
+
:func:`_bridge_axioms` for the full argument.
|
|
131
|
+
|
|
132
|
+
Honesty
|
|
133
|
+
-------
|
|
134
|
+
The toolkit *emits* a theory; it does **not** run Isabelle, Sledgehammer, or any
|
|
135
|
+
prover. First-order modal logic, like FOL, is **undecidable but semi-decidable**
|
|
136
|
+
(validity is recursively enumerable; full second-order logic is *not even
|
|
137
|
+
semi-decidable*), so a successful emission means "here is a sound problem an
|
|
138
|
+
external prover *may* discharge", never "the formula is decided". Propositional
|
|
139
|
+
modal K/T/S4/S5 *are* decidable, but this emitter does not itself decide them.
|
|
140
|
+
The default proof tactic is a Sledgehammer hook followed by an ``oops`` fallback
|
|
141
|
+
so the emitted theory always loads even when no automatic proof is found.
|
|
142
|
+
|
|
143
|
+
Public API
|
|
144
|
+
----------
|
|
145
|
+
:func:`to_isabelle_modal` (the real ``to_isabelle_modal`` replacement),
|
|
146
|
+
:func:`isabelle_modal_theory` (alias with an explicit ``theory_name``),
|
|
147
|
+
and the constants :data:`ISABELLE_TACTICS` and :data:`BRIDGES`.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
import re
|
|
151
|
+
from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TypedDict
|
|
152
|
+
|
|
153
|
+
from unicode_logic_kit.fol.nodes import (
|
|
154
|
+
Node, Variable, Constant, Number, Function,
|
|
155
|
+
Atom, Not, And, Or, Xor, Implies, Iff, Quantifier, Contrast,
|
|
156
|
+
Box, Diamond, Knows, Believes, Says, Wants,
|
|
157
|
+
Always, Eventually, Next, Until, Since,
|
|
158
|
+
Historically, Once, Previous, Nominal, At,
|
|
159
|
+
Obligatory, Permitted, SortedQuantifier,
|
|
160
|
+
)
|
|
161
|
+
from unicode_logic_kit.fol._fol_nodes import constant_name_to_ascii
|
|
162
|
+
from unicode_logic_kit.fol._msfl_nodes import nonempty_sort_axioms, sort_membership_axioms
|
|
163
|
+
from unicode_logic_kit.fol._numeral_symbols import numerals_as_constants, prefixed_numeral_name
|
|
164
|
+
from unicode_logic_kit.fol._symbol_names import SymbolNames, dedupe
|
|
165
|
+
from unicode_logic_kit.fol._truth_constants import truth_value
|
|
166
|
+
# Down (the ↓ binder, N1) is not yet re-exported through fol.nodes / fol's
|
|
167
|
+
# public __init__ / the top-level unicode_logic_kit package (that three-file
|
|
168
|
+
# edit is outside this change's file ownership — see the change's own
|
|
169
|
+
# report); imported directly from its defining module in the meantime, the
|
|
170
|
+
# same class object either import path would give.
|
|
171
|
+
from unicode_logic_kit.fol._hybrid_nodes import Down
|
|
172
|
+
from unicode_logic_kit.fol.frames import FRAMES as _SHARED_FRAMES
|
|
173
|
+
# Identity is RIGID here exactly as in the first-order embedding: the lowering rule
|
|
174
|
+
# (``t1 != t2`` -> ``not (t1 = t2)``, binary-only) is qml's own, imported rather than
|
|
175
|
+
# copied so the two routes cannot drift apart on what counts as an identity atom.
|
|
176
|
+
from unicode_logic_kit.fol.qml import _st_equality, _EQUALITY_PREDICATES
|
|
177
|
+
|
|
178
|
+
# --------------------------------------------------------------------------- #
|
|
179
|
+
# Configuration tables (kept in sync with qml.py / satisfies_modal).
|
|
180
|
+
# --------------------------------------------------------------------------- #
|
|
181
|
+
|
|
182
|
+
# Frame systems for the ALETHIC relation r — the shared registry
|
|
183
|
+
# (unicode_logic_kit.fol.frames), so this route understands the same systems as
|
|
184
|
+
# every other one. Being higher-order, it is the route that CAN carry the
|
|
185
|
+
# three conditions with no first-order frame condition (Löb, McKinsey, Grz):
|
|
186
|
+
# it asserts the schema itself, with the proposition universally generalised.
|
|
187
|
+
_FRAMES = _SHARED_FRAMES
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_ACTUALIST_MODES = frozenset({"varying", "increasing", "cumulative", "decreasing"})
|
|
191
|
+
_CONSTANT_MODES = frozenset({"constant", "possibilist"})
|
|
192
|
+
|
|
193
|
+
_FORALL = ("∀", "forall")
|
|
194
|
+
_EXISTS = ("∃", "exists")
|
|
195
|
+
|
|
196
|
+
# Available proof-tactic presets for the emitted lemma. Each maps to the Isabelle
|
|
197
|
+
# proof text placed after the lemma statement. ``sledgehammer`` is the honest
|
|
198
|
+
# default: it inserts a Sledgehammer invitation and an ``oops`` so the theory
|
|
199
|
+
# always loads (Isabelle treats ``oops`` as "abandon this goal" — syntactically
|
|
200
|
+
# valid, nothing claimed proved).
|
|
201
|
+
#: Proof-tactic presets for the emitted lemma, keyed by name; the value is the
|
|
202
|
+
#: Isabelle proof text placed after the lemma statement. ``"sledgehammer"`` is
|
|
203
|
+
#: the honest default — see the comment above for why a theory that always
|
|
204
|
+
#: loads matters more here than one that claims a proof.
|
|
205
|
+
ISABELLE_TACTICS = {
|
|
206
|
+
"sledgehammer": " sledgehammer\n oops",
|
|
207
|
+
"auto": " by auto",
|
|
208
|
+
"blast": " by blast",
|
|
209
|
+
"metis": " by (metis (full_types))",
|
|
210
|
+
"smt": " by (smt (verit))",
|
|
211
|
+
"simp": " by simp",
|
|
212
|
+
"force": " by force",
|
|
213
|
+
"oops": " oops",
|
|
214
|
+
"sorry": " sorry",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Tactics whose ``by ...`` proof needs the frame / domain axioms brought into the
|
|
218
|
+
# proof context: a bare ``axiomatization where r_refl: ...`` fact is NOT in the
|
|
219
|
+
# default claset/simpset, so ``by blast`` / ``by auto`` / ``by (metis ...)`` cannot
|
|
220
|
+
# see it and any axiom-dependent validity (T/S4/S5/KD/KD45, temporal closure, a
|
|
221
|
+
# domain regime) fails to discharge even though the formula is valid and the theory
|
|
222
|
+
# sound. For these tactics we emit ``using <axioms> by <tactic>``. ``sledgehammer``
|
|
223
|
+
# finds its own facts; ``oops`` / ``sorry`` prove nothing, so they are excluded.
|
|
224
|
+
_BY_TACTICS = frozenset({"auto", "blast", "metis", "smt", "simp", "force"})
|
|
225
|
+
|
|
226
|
+
_AXIOM_NAME_RE = re.compile(r"axiomatization where (\w+):")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _axiom_names(axiom_lines: List[str]) -> List[str]:
|
|
230
|
+
"""Read the fact names out of emitted ``axiomatization where NAME: ...`` lines."""
|
|
231
|
+
out: List[str] = []
|
|
232
|
+
for ln in axiom_lines:
|
|
233
|
+
m = _AXIOM_NAME_RE.search(ln)
|
|
234
|
+
if m:
|
|
235
|
+
out.append(m.group(1))
|
|
236
|
+
return out
|
|
237
|
+
|
|
238
|
+
# Names for the symbolic predicates that are STILL ordinary uninterpreted world-
|
|
239
|
+
# relativized predicates here (the orderings; same convention as fol.qml). ``=`` / ``≠``
|
|
240
|
+
# are NOT among the predicates this table names any more: identity is rigid, lowered by
|
|
241
|
+
# :func:`_lower_identity` to HOL's own ``=`` and never declared as a constant. The two
|
|
242
|
+
# entries are kept only so that, in a formula that contains identity, they keep claiming
|
|
243
|
+
# ``feq`` / ``fneq`` first (see :class:`_IsaNames`), as they do in the THF resolver --
|
|
244
|
+
# a user predicate literally spelled ``feq`` is then pushed to ``feq_2`` in both.
|
|
245
|
+
_PRED_ALIAS = {"=": "feq", "≠": "fneq", "<": "flt", ">": "fgt", "≤": "fle", "≥": "fge"}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# --------------------------------------------------------------------------- #
|
|
249
|
+
# Rigid identity.
|
|
250
|
+
# --------------------------------------------------------------------------- #
|
|
251
|
+
|
|
252
|
+
def _has_identity(formula: Node) -> bool:
|
|
253
|
+
"""Whether ``formula`` contains an ``=`` / ``≠`` atom (of ANY arity)."""
|
|
254
|
+
return any(isinstance(n, Atom) and n.predicate in _EQUALITY_PREDICATES
|
|
255
|
+
for n in formula.walk())
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _lower_identity(formula: Node, who: str) -> Node:
|
|
259
|
+
"""Lower every ``≠`` to ``¬(=)`` and refuse a non-binary identity atom.
|
|
260
|
+
|
|
261
|
+
This is :func:`unicode_logic_kit.fol.qml._st_equality` applied to each ``=`` / ``≠``
|
|
262
|
+
atom -- the very rule ``qml_translate`` uses, so the HOL routes and the
|
|
263
|
+
first-order route read the same atoms the same way. ``who`` names the calling
|
|
264
|
+
exporter in the error message (qml's own message names qml). A formula with no
|
|
265
|
+
identity atom is returned UNCHANGED (the same object), so every equality-free
|
|
266
|
+
formula is emitted byte-for-byte as before.
|
|
267
|
+
"""
|
|
268
|
+
if not _has_identity(formula):
|
|
269
|
+
return formula
|
|
270
|
+
|
|
271
|
+
def lower(node: Node) -> Node:
|
|
272
|
+
if isinstance(node, Atom) and node.predicate in _EQUALITY_PREDICATES:
|
|
273
|
+
try:
|
|
274
|
+
return _st_equality(node)
|
|
275
|
+
except ValueError as exc:
|
|
276
|
+
msg = str(exc)
|
|
277
|
+
raise ValueError(who + ": " + (msg[5:] if msg.startswith("qml: ")
|
|
278
|
+
else msg)) from None
|
|
279
|
+
return node.map_children(lower)
|
|
280
|
+
|
|
281
|
+
return lower(formula)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# --------------------------------------------------------------------------- #
|
|
285
|
+
# Name sanitising.
|
|
286
|
+
# --------------------------------------------------------------------------- #
|
|
287
|
+
|
|
288
|
+
# Built-in identifiers of the emitted theory — the world type, the accessibility
|
|
289
|
+
# relations / existence predicate, and every lifted-operator abbreviation. A user
|
|
290
|
+
# predicate / constant / function whose sanitised name lands here would shadow the
|
|
291
|
+
# embedding and break loadability, so _safe_name disambiguates it with a trailing "_".
|
|
292
|
+
# NOTE the axiom / bridge FACT names (r_refl, n_in_t, d_serial, rb_in_rk, ...) are
|
|
293
|
+
# deliberately absent: they live in Isabelle's fact namespace, not the consts
|
|
294
|
+
# namespace, so a user predicate sanitising onto one of them cannot shadow it.
|
|
295
|
+
_RESERVED = frozenset({
|
|
296
|
+
"i", "e", "existsAt",
|
|
297
|
+
"r", "rk", "rb", "rs", "rw", "d", "t", "n",
|
|
298
|
+
"mnot", "mand", "mor", "mimp", "miff", "mvalid", "mbox", "mdia",
|
|
299
|
+
"knows", "believes", "says", "wants", "obl", "perm",
|
|
300
|
+
"malways", "meventually", "mnext",
|
|
301
|
+
"mhistorically", "monce", "mprevious", "muntil", "msince",
|
|
302
|
+
"mforall", "mexists",
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
#: The names of the lifted operators (the long entries of :data:`_RESERVED`): a binder spelled
|
|
306
|
+
#: like one would shadow the operator in its own scope, so no bound variable gets one of them.
|
|
307
|
+
_OPERATOR_NAMES = frozenset(name for name in _RESERVED if len(name) > 2)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _safe_name(name: str) -> str:
|
|
311
|
+
"""Turn a predicate/constant/function name into a legal lowercase ASCII Isabelle id.
|
|
312
|
+
|
|
313
|
+
Disambiguates names that would collide with a built-in embedding identifier
|
|
314
|
+
(a relation, the existence predicate, the world type, or a lifted operator).
|
|
315
|
+
|
|
316
|
+
Non-ASCII characters are transliterated FIRST via
|
|
317
|
+
:func:`~unicode_logic_kit.fol._fol_nodes.constant_name_to_ascii` (ASCII passthrough;
|
|
318
|
+
Greek -> conventional name; anything else -> a reversible ``uXXXX`` escape) — an
|
|
319
|
+
Isabelle ``consts`` identifier is ASCII-only, so a raw Unicode letter reaching this
|
|
320
|
+
far (``str.isalnum()`` is ``True`` for almost every one of them, so the old
|
|
321
|
+
alnum-or-underscore filter alone let them straight through) would be silent
|
|
322
|
+
corruption of the loaded theory, not sanitisation. ``constant_name_to_ascii`` is
|
|
323
|
+
the identity on an already-ASCII string, so this changes nothing for a name that
|
|
324
|
+
was already legal — the digit-lead (``c_`` prefix) and reserved-word handling below
|
|
325
|
+
are exactly what they were before.
|
|
326
|
+
"""
|
|
327
|
+
if name in _PRED_ALIAS:
|
|
328
|
+
return _PRED_ALIAS[name]
|
|
329
|
+
out = "".join(c if (c.isalnum() or c == "_") else "_"
|
|
330
|
+
for c in constant_name_to_ascii(str(name)))
|
|
331
|
+
if not out:
|
|
332
|
+
out = "p"
|
|
333
|
+
if not (out[0].isalpha() or out[0] == "_"):
|
|
334
|
+
out = "c_" + out
|
|
335
|
+
# Lower-case the leading char so it cannot collide with a bound type/var.
|
|
336
|
+
out = out[0].lower() + out[1:]
|
|
337
|
+
while out in _RESERVED:
|
|
338
|
+
out = out + "_"
|
|
339
|
+
return out
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _var_name(name: str) -> str:
|
|
343
|
+
"""Isabelle term-variable name (object variables stay lowercase, legal).
|
|
344
|
+
|
|
345
|
+
Same ASCII-transliteration fix as :func:`_safe_name`, for the same reason: a raw
|
|
346
|
+
non-ASCII variable name previously reached the Isabelle text verbatim (only
|
|
347
|
+
non-alnum/underscore ASCII punctuation was replaced), which is not a legal
|
|
348
|
+
Isabelle identifier. Use :meth:`_IsaNames.variable` for a per-formula unique
|
|
349
|
+
token — ``_var_name`` alone can map two distinct source names onto the same
|
|
350
|
+
token once they are both transliterated (the same class of gap ``_safe_name``
|
|
351
|
+
has, closed there by :class:`_IsaNames`'s ``dedupe``-backed maps).
|
|
352
|
+
"""
|
|
353
|
+
out = "".join(c if (c.isalnum() or c == "_") else "_"
|
|
354
|
+
for c in constant_name_to_ascii(str(name)))
|
|
355
|
+
if not out or not (out[0].isalpha() or out[0] == "_"):
|
|
356
|
+
out = "x_" + out
|
|
357
|
+
return out
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class _IsaNames(SymbolNames):
|
|
361
|
+
"""Per-formula Isabelle constant-name resolver (the shared :class:`SymbolNames` over
|
|
362
|
+
``_safe_name`` + the equality aliases), so distinct source symbols that sanitise
|
|
363
|
+
alike (``Ab`` / ``ab``) — or a predicate used at two arities — get DISTINCT
|
|
364
|
+
``consts``. Without it ``□Ab → □ab`` could collapse to a tautology and emit duplicate
|
|
365
|
+
``consts`` (which Isabelle rejects).
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
def __init__(self, formula: Node, reserved=frozenset()):
|
|
369
|
+
# ``reserved`` holds the free variables of the axioms of this theory
|
|
370
|
+
# (:func:`_axiom_variable_names`): a symbol of the user never has one of those
|
|
371
|
+
# spellings, it gets a numbered variant instead.
|
|
372
|
+
super().__init__(formula, _safe_name, _PRED_ALIAS, reserved=reserved)
|
|
373
|
+
# Identity is rigid HOL ``=`` (see _atom), not a predicate: it gets no
|
|
374
|
+
# ``consts`` line and no entry here. (SymbolNames collected it with every other
|
|
375
|
+
# atom, and let it claim ``feq`` / ``fneq`` -- which only matters to a user
|
|
376
|
+
# predicate literally spelled that way, pushed to ``feq_2``: still unique.)
|
|
377
|
+
for key in [k for k in self.pred if k[0] in _EQUALITY_PREDICATES]:
|
|
378
|
+
del self.pred[key]
|
|
379
|
+
# Nominals become world constants ``nom_<name> :: i`` — the same reserved
|
|
380
|
+
# ``nom_`` prefix the standard translation uses, deduped against every
|
|
381
|
+
# other emitted constant. _safe_name may append "_" to dodge reserved
|
|
382
|
+
# single letters (e.g. the world type "i"), but Isabelle REJECTS
|
|
383
|
+
# identifiers with a trailing underscore — the "nom_" prefix already
|
|
384
|
+
# dodges those names, so strip it; dedupe with digit suffixes (legal).
|
|
385
|
+
taken = (set(self.pred.values()) | set(self.const.values())
|
|
386
|
+
| set(self.func.values()))
|
|
387
|
+
self.nom: Dict[str, str] = {}
|
|
388
|
+
for n in formula.walk():
|
|
389
|
+
if isinstance(n, Nominal) and n.name not in self.nom:
|
|
390
|
+
cand = "nom_" + (_safe_name(n.name).rstrip("_") or "x")
|
|
391
|
+
while cand in taken:
|
|
392
|
+
cand += "0"
|
|
393
|
+
taken.add(cand)
|
|
394
|
+
self.nom[n.name] = cand
|
|
395
|
+
self._var_map: Dict[str, str] = {}
|
|
396
|
+
# A binder shadows every constant of its own name inside its scope, so a bound
|
|
397
|
+
# variable never has the spelling of a constant, predicate, function or nominal
|
|
398
|
+
# constant of the theory, nor of a lifted operator.
|
|
399
|
+
self._var_used: set = taken | _OPERATOR_NAMES
|
|
400
|
+
|
|
401
|
+
def nominal(self, name: str) -> str:
|
|
402
|
+
"""The world constant naming nominal ``name``."""
|
|
403
|
+
return self.nom[name]
|
|
404
|
+
|
|
405
|
+
def variable(self, name: str) -> str:
|
|
406
|
+
"""Unique Isabelle bound-variable token for object-variable ``name``.
|
|
407
|
+
|
|
408
|
+
Isabelle variables are not case-folded the way THF's are, so the pre-existing
|
|
409
|
+
`x`/`X` case collision THF's variable resolver preserves does not arise here.
|
|
410
|
+
What DOES need de-collision is the same ASCII-transliteration gap
|
|
411
|
+
:func:`_var_name` closed for individual names: two distinct source names could
|
|
412
|
+
still coincide AFTER transliteration (e.g. a literal ``theta`` and the Greek
|
|
413
|
+
``θ``, both -> ``theta``). Routed through
|
|
414
|
+
:func:`~unicode_logic_kit.fol._symbol_names.dedupe` against a token pool of this
|
|
415
|
+
resolver instance that STARTS with every token of the theory's own constants,
|
|
416
|
+
predicates, functions and nominal constants and with the names of the lifted
|
|
417
|
+
operators. That is not optional: a binder shadows a constant of its own name
|
|
418
|
+
inside its scope, so ``∀x P(x, c)`` with a constant spelled ``x`` would be written
|
|
419
|
+
``(\\<lambda>x. (p x x))`` -- the constant captured, a different sentence. A variable
|
|
420
|
+
whose natural token is taken gets a numbered variant (``x_2``).
|
|
421
|
+
"""
|
|
422
|
+
if name in self._var_map:
|
|
423
|
+
return self._var_map[name]
|
|
424
|
+
cand = dedupe(_var_name(name), self._var_used)
|
|
425
|
+
self._var_map[name] = cand
|
|
426
|
+
return cand
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# --------------------------------------------------------------------------- #
|
|
430
|
+
# Term rendering.
|
|
431
|
+
# --------------------------------------------------------------------------- #
|
|
432
|
+
|
|
433
|
+
def _term(node: Node, names: "_IsaNames") -> str:
|
|
434
|
+
"""Render an individual term (used as an argument to a lifted predicate)."""
|
|
435
|
+
if isinstance(node, Variable):
|
|
436
|
+
return names.variable(node.name)
|
|
437
|
+
if isinstance(node, Constant):
|
|
438
|
+
return names.constant(node.name)
|
|
439
|
+
if isinstance(node, Number):
|
|
440
|
+
return names.constant(prefixed_numeral_name(node.value))
|
|
441
|
+
if isinstance(node, Function):
|
|
442
|
+
head = names.function(node)
|
|
443
|
+
if not node.args:
|
|
444
|
+
return head
|
|
445
|
+
return "(" + head + " " + " ".join(_term(a, names) for a in node.args) + ")"
|
|
446
|
+
raise NotImplementedError(
|
|
447
|
+
f"to_isabelle_modal: unsupported term {type(node).__name__}.")
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# --------------------------------------------------------------------------- #
|
|
451
|
+
# Formula lifting: Node -> Isabelle term of type i => bool.
|
|
452
|
+
# --------------------------------------------------------------------------- #
|
|
453
|
+
|
|
454
|
+
def _atom(node: Atom, names: "_IsaNames") -> str:
|
|
455
|
+
r"""Lift an atom to ``(pred a1 ... an)`` — a value of type ``i => bool``.
|
|
456
|
+
|
|
457
|
+
An identity atom (``=``; ``≠`` was lowered to ``¬(=)`` by :func:`_lower_identity`
|
|
458
|
+
before any atom is lifted) is HOL's own ``=`` over the entity terms, under a
|
|
459
|
+
world binder it never uses: ``(\<lambda>_. a = b)``. No world argument means it
|
|
460
|
+
cannot vary by world (rigid identity, qml's reading), and the binder is the
|
|
461
|
+
anonymous ``_`` rather than a name so a user variable called ``w`` can never be
|
|
462
|
+
captured by it.
|
|
463
|
+
"""
|
|
464
|
+
constant = truth_value(node)
|
|
465
|
+
if constant is not None:
|
|
466
|
+
# `$true` / `$false`: HOL's own True / False under the anonymous world binder,
|
|
467
|
+
# so they cannot vary by world.
|
|
468
|
+
return "(\\<lambda>_. True)" if constant else "(\\<lambda>_. False)"
|
|
469
|
+
if node.predicate == "=":
|
|
470
|
+
left, right = node.args
|
|
471
|
+
return f"(\\<lambda>_. {_term(left, names)} = {_term(right, names)})"
|
|
472
|
+
head = names.atom(node)
|
|
473
|
+
if not node.args:
|
|
474
|
+
return head
|
|
475
|
+
return "(" + head + " " + " ".join(_term(a, names) for a in node.args) + ")"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _lift(node: Node, names: "_IsaNames") -> str:
|
|
479
|
+
r"""Lift a modal formula to an Isabelle term of type ``i \<Rightarrow> bool``.
|
|
480
|
+
|
|
481
|
+
Mirrors the THF ``_thf_lift`` and stays faithful to ``satisfies_modal``: every
|
|
482
|
+
modality becomes a box/diamond over the matching relation, object quantifiers
|
|
483
|
+
are existsAt-guarded, and the agent of Knows/Believes is carried as a real term.
|
|
484
|
+
"""
|
|
485
|
+
if isinstance(node, Atom):
|
|
486
|
+
return _atom(node, names)
|
|
487
|
+
if isinstance(node, Not):
|
|
488
|
+
return f"(mnot {_lift(node.formula, names)})"
|
|
489
|
+
if isinstance(node, And):
|
|
490
|
+
return f"(mand {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
491
|
+
if isinstance(node, Or):
|
|
492
|
+
return f"(mor {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
493
|
+
if isinstance(node, Xor):
|
|
494
|
+
# Xor ≡ ¬(φ ↔ ψ); satisfies_modal reads Xor as truth-value inequality.
|
|
495
|
+
return f"(mnot (miff {_lift(node.left, names)} {_lift(node.right, names)}))"
|
|
496
|
+
if isinstance(node, Implies):
|
|
497
|
+
return f"(mimp {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
498
|
+
if isinstance(node, Iff):
|
|
499
|
+
return f"(miff {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
500
|
+
|
|
501
|
+
if isinstance(node, Box):
|
|
502
|
+
return f"(mbox {_lift(node.formula, names)})"
|
|
503
|
+
if isinstance(node, Diamond):
|
|
504
|
+
return f"(mdia {_lift(node.formula, names)})"
|
|
505
|
+
|
|
506
|
+
if isinstance(node, Contrast):
|
|
507
|
+
# Truth-functionally conjunction (Contrast's own contract).
|
|
508
|
+
return f"(mand {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
509
|
+
if isinstance(node, Knows):
|
|
510
|
+
return f"(knows {_term(node.agent, names)} {_lift(node.formula, names)})"
|
|
511
|
+
if isinstance(node, Believes):
|
|
512
|
+
return f"(believes {_term(node.agent, names)} {_lift(node.formula, names)})"
|
|
513
|
+
if isinstance(node, Says):
|
|
514
|
+
return f"(says {_term(node.agent, names)} {_lift(node.formula, names)})"
|
|
515
|
+
if isinstance(node, Wants):
|
|
516
|
+
return f"(wants {_term(node.agent, names)} {_lift(node.formula, names)})"
|
|
517
|
+
|
|
518
|
+
if isinstance(node, Obligatory):
|
|
519
|
+
return f"(obl {_lift(node.formula, names)})"
|
|
520
|
+
if isinstance(node, Permitted):
|
|
521
|
+
return f"(perm {_lift(node.formula, names)})"
|
|
522
|
+
|
|
523
|
+
if isinstance(node, Always):
|
|
524
|
+
return f"(malways {_lift(node.formula, names)})"
|
|
525
|
+
if isinstance(node, Eventually):
|
|
526
|
+
return f"(meventually {_lift(node.formula, names)})"
|
|
527
|
+
if isinstance(node, Next):
|
|
528
|
+
return f"(mnext {_lift(node.formula, names)})"
|
|
529
|
+
if isinstance(node, Historically):
|
|
530
|
+
# "now and at every past point": box over the CONVERSE of the henceforth
|
|
531
|
+
# closure t — matching satisfies_modal's converse-relation reading.
|
|
532
|
+
return f"(mhistorically {_lift(node.formula, names)})"
|
|
533
|
+
if isinstance(node, Once):
|
|
534
|
+
# "now or at some past point": diamond over the converse of t.
|
|
535
|
+
return f"(monce {_lift(node.formula, names)})"
|
|
536
|
+
if isinstance(node, Previous):
|
|
537
|
+
# "at every immediate past point": box over the converse of one-step n.
|
|
538
|
+
return f"(mprevious {_lift(node.formula, names)})"
|
|
539
|
+
|
|
540
|
+
if isinstance(node, Nominal):
|
|
541
|
+
# A nominal is true at exactly the world it names: the world constant
|
|
542
|
+
# nom_<name>, mirroring standard_translation's reserved nom_ prefix and
|
|
543
|
+
# KripkeModel's nominals= assignment.
|
|
544
|
+
return f"(\\<lambda>w. w = {names.nominal(node.name)})"
|
|
545
|
+
if isinstance(node, At):
|
|
546
|
+
# @i φ: evaluate φ AT the named world, regardless of the current one. The world
|
|
547
|
+
# binder is anonymous: it is not used, and a named one would capture an object
|
|
548
|
+
# variable of the body spelled ``w`` (``∀w @i P(w)``).
|
|
549
|
+
return (f"(\\<lambda>_. {_lift(node.formula, names)} "
|
|
550
|
+
f"{names.nominal(node.nominal.name)})")
|
|
551
|
+
if isinstance(node, Down):
|
|
552
|
+
raise NotImplementedError(
|
|
553
|
+
"to_isabelle_modal: the ↓ binder is not supported by this HOL "
|
|
554
|
+
"embedding — H(@,↓) validity is undecidable, and this emitter's "
|
|
555
|
+
"job (a lemma statement for Sledgehammer/a tactic) assumes a goal "
|
|
556
|
+
"shape a human or ATP proof search can be expected to close, not "
|
|
557
|
+
"an open research question. Use "
|
|
558
|
+
"unicode_logic_kit.fol.modal_translation.down_is_valid (Z3, "
|
|
559
|
+
"PROVED-only) or unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend "
|
|
560
|
+
"/ modal_enum_search (bounded search, REFUTED-only) instead.")
|
|
561
|
+
|
|
562
|
+
if isinstance(node, Quantifier):
|
|
563
|
+
x = names.variable(node.variable.name)
|
|
564
|
+
binder = "mforall" if node.type in _FORALL else "mexists"
|
|
565
|
+
return f"({binder} (\\<lambda>{x}. {_lift(node.formula, names)}))"
|
|
566
|
+
|
|
567
|
+
if isinstance(node, Until):
|
|
568
|
+
# Strong "left until right" as the inductive predicate ``muntil`` (least
|
|
569
|
+
# fixpoint over the one-step relation n), matching satisfies_modal's finite
|
|
570
|
+
# forward-path search exactly. See _until_block.
|
|
571
|
+
return f"(muntil {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
572
|
+
if isinstance(node, Since):
|
|
573
|
+
# Past-tense "left since right" as ``msince`` (least fixpoint over the CONVERSE
|
|
574
|
+
# of n), the backward mirror of muntil — matching satisfies_modal's backward
|
|
575
|
+
# path search. See _since_block.
|
|
576
|
+
return f"(msince {_lift(node.left, names)} {_lift(node.right, names)})"
|
|
577
|
+
if isinstance(node, SortedQuantifier):
|
|
578
|
+
raise NotImplementedError(
|
|
579
|
+
"to_isabelle_modal: SortedQuantifier is not supported; use a plain ∀x/∃x.")
|
|
580
|
+
if type(node).__name__ in ("Would", "Might"):
|
|
581
|
+
raise NotImplementedError(
|
|
582
|
+
"to_isabelle_modal: the counterfactuals □→/◇→ read a similarity "
|
|
583
|
+
"ordering (Lewis spheres), not an accessibility relation — use "
|
|
584
|
+
"hol.isabelle_conditional / isabelle_decide_counterfactual.")
|
|
585
|
+
raise NotImplementedError(
|
|
586
|
+
f"to_isabelle_modal: unsupported node type {type(node).__name__}.")
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
# --------------------------------------------------------------------------- #
|
|
590
|
+
# Signature discovery (which operators / relations / typed consts are needed).
|
|
591
|
+
# --------------------------------------------------------------------------- #
|
|
592
|
+
|
|
593
|
+
class _Sig:
|
|
594
|
+
"""Collected signature: predicates, constants, functions, and used modalities."""
|
|
595
|
+
|
|
596
|
+
def __init__(self):
|
|
597
|
+
self.preds = {} # safe_name -> arity (object args)
|
|
598
|
+
self.consts = set() # safe_name
|
|
599
|
+
self.funcs = {} # safe_name -> arity
|
|
600
|
+
self.uses_alethic = False
|
|
601
|
+
self.uses_epistemic = False
|
|
602
|
+
self.uses_doxastic = False
|
|
603
|
+
self.uses_assertive = False # Says (agent-indexed relation rs, plain K)
|
|
604
|
+
self.uses_bouletic = False # Wants (agent-indexed relation rw, plain K)
|
|
605
|
+
self.uses_deontic = False
|
|
606
|
+
self.uses_temporal = False # Always / Eventually / Historically / Once (t)
|
|
607
|
+
self.uses_past = False # Historically / Once (converse-of-t readers)
|
|
608
|
+
self.uses_next = False # Next (relation n)
|
|
609
|
+
self.uses_previous = False # Previous (converse-of-n reader)
|
|
610
|
+
self.uses_until = False # Until (inductive muntil over n)
|
|
611
|
+
self.uses_since = False # Since (inductive msince over converse n)
|
|
612
|
+
self.uses_hybrid = False # Nominal / At (world constants nom_*)
|
|
613
|
+
self.has_quant = False
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def needs_next_rel(self) -> bool:
|
|
617
|
+
"""Whether the one-step relation ``n`` must be declared.
|
|
618
|
+
|
|
619
|
+
``n`` is needed by ``Next`` (mnext), ``Previous`` (mprevious — its
|
|
620
|
+
converse), and the inductive ``muntil`` / ``msince`` (one-step forward /
|
|
621
|
+
backward path search).
|
|
622
|
+
"""
|
|
623
|
+
return (self.uses_next or self.uses_previous
|
|
624
|
+
or self.uses_until or self.uses_since)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _scan_term(node: Node, sig: _Sig) -> None:
|
|
628
|
+
if isinstance(node, Constant):
|
|
629
|
+
sig.consts.add(_safe_name(node.name))
|
|
630
|
+
elif isinstance(node, Number):
|
|
631
|
+
sig.consts.add(_safe_name(prefixed_numeral_name(node.value)))
|
|
632
|
+
elif isinstance(node, Function):
|
|
633
|
+
sig.funcs[_safe_name(node.name)] = len(node.args)
|
|
634
|
+
for a in node.args:
|
|
635
|
+
_scan_term(a, sig)
|
|
636
|
+
# Variables contribute nothing to the signature.
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _scan(node: Node, sig: _Sig) -> None:
|
|
640
|
+
"""Walk the formula collecting the signature and which modalities are used."""
|
|
641
|
+
if isinstance(node, Atom):
|
|
642
|
+
if node.predicate not in _EQUALITY_PREDICATES and truth_value(node) is None:
|
|
643
|
+
sig.preds[_safe_name(node.predicate)] = len(node.args)
|
|
644
|
+
for a in node.args:
|
|
645
|
+
_scan_term(a, sig)
|
|
646
|
+
return
|
|
647
|
+
if isinstance(node, (Not,)):
|
|
648
|
+
_scan(node.formula, sig)
|
|
649
|
+
return
|
|
650
|
+
if isinstance(node, (And, Or, Xor, Implies, Iff, Contrast)):
|
|
651
|
+
_scan(node.left, sig)
|
|
652
|
+
_scan(node.right, sig)
|
|
653
|
+
return
|
|
654
|
+
if isinstance(node, Box):
|
|
655
|
+
sig.uses_alethic = True
|
|
656
|
+
_scan(node.formula, sig)
|
|
657
|
+
return
|
|
658
|
+
if isinstance(node, Diamond):
|
|
659
|
+
sig.uses_alethic = True
|
|
660
|
+
_scan(node.formula, sig)
|
|
661
|
+
return
|
|
662
|
+
if isinstance(node, Knows):
|
|
663
|
+
sig.uses_epistemic = True
|
|
664
|
+
_scan_term(node.agent, sig)
|
|
665
|
+
_scan(node.formula, sig)
|
|
666
|
+
return
|
|
667
|
+
if isinstance(node, Believes):
|
|
668
|
+
sig.uses_doxastic = True
|
|
669
|
+
_scan_term(node.agent, sig)
|
|
670
|
+
_scan(node.formula, sig)
|
|
671
|
+
return
|
|
672
|
+
if isinstance(node, Says):
|
|
673
|
+
sig.uses_assertive = True
|
|
674
|
+
_scan_term(node.agent, sig)
|
|
675
|
+
_scan(node.formula, sig)
|
|
676
|
+
return
|
|
677
|
+
if isinstance(node, Wants):
|
|
678
|
+
sig.uses_bouletic = True
|
|
679
|
+
_scan_term(node.agent, sig)
|
|
680
|
+
_scan(node.formula, sig)
|
|
681
|
+
return
|
|
682
|
+
if isinstance(node, (Obligatory, Permitted)):
|
|
683
|
+
sig.uses_deontic = True
|
|
684
|
+
_scan(node.formula, sig)
|
|
685
|
+
return
|
|
686
|
+
if isinstance(node, (Always, Eventually)):
|
|
687
|
+
sig.uses_temporal = True
|
|
688
|
+
_scan(node.formula, sig)
|
|
689
|
+
return
|
|
690
|
+
if isinstance(node, (Historically, Once)):
|
|
691
|
+
# Past closure operators read the CONVERSE of the same henceforth t.
|
|
692
|
+
sig.uses_temporal = True
|
|
693
|
+
sig.uses_past = True
|
|
694
|
+
_scan(node.formula, sig)
|
|
695
|
+
return
|
|
696
|
+
if isinstance(node, Next):
|
|
697
|
+
sig.uses_next = True
|
|
698
|
+
_scan(node.formula, sig)
|
|
699
|
+
return
|
|
700
|
+
if isinstance(node, Previous):
|
|
701
|
+
sig.uses_previous = True
|
|
702
|
+
_scan(node.formula, sig)
|
|
703
|
+
return
|
|
704
|
+
if isinstance(node, Nominal):
|
|
705
|
+
sig.uses_hybrid = True
|
|
706
|
+
return
|
|
707
|
+
if isinstance(node, At):
|
|
708
|
+
sig.uses_hybrid = True
|
|
709
|
+
_scan(node.formula, sig)
|
|
710
|
+
return
|
|
711
|
+
if isinstance(node, Down):
|
|
712
|
+
raise NotImplementedError(
|
|
713
|
+
"to_isabelle_modal: the ↓ binder is not supported by this HOL "
|
|
714
|
+
"embedding — H(@,↓) validity is undecidable, and this emitter's "
|
|
715
|
+
"job (a lemma statement for Sledgehammer/a tactic) assumes a goal "
|
|
716
|
+
"shape a human or ATP proof search can be expected to close, not "
|
|
717
|
+
"an open research question. Use "
|
|
718
|
+
"unicode_logic_kit.fol.modal_translation.down_is_valid (Z3, "
|
|
719
|
+
"PROVED-only) or unicode_logic_kit.atp.kripke_enum.KripkeEnumBackend "
|
|
720
|
+
"/ modal_enum_search (bounded search, REFUTED-only) instead.")
|
|
721
|
+
if isinstance(node, Quantifier):
|
|
722
|
+
sig.has_quant = True
|
|
723
|
+
_scan(node.formula, sig)
|
|
724
|
+
return
|
|
725
|
+
if isinstance(node, Until):
|
|
726
|
+
sig.uses_until = True
|
|
727
|
+
_scan(node.left, sig)
|
|
728
|
+
_scan(node.right, sig)
|
|
729
|
+
return
|
|
730
|
+
if isinstance(node, Since):
|
|
731
|
+
sig.uses_since = True
|
|
732
|
+
_scan(node.left, sig)
|
|
733
|
+
_scan(node.right, sig)
|
|
734
|
+
return
|
|
735
|
+
if isinstance(node, SortedQuantifier):
|
|
736
|
+
raise NotImplementedError(
|
|
737
|
+
"to_isabelle_modal: SortedQuantifier is not supported; use a plain ∀x/∃x.")
|
|
738
|
+
if type(node).__name__ in ("Would", "Might"):
|
|
739
|
+
raise NotImplementedError(
|
|
740
|
+
"to_isabelle_modal: the counterfactuals □→/◇→ read a similarity "
|
|
741
|
+
"ordering (Lewis spheres), not an accessibility relation — use "
|
|
742
|
+
"hol.isabelle_conditional / isabelle_decide_counterfactual.")
|
|
743
|
+
raise NotImplementedError(
|
|
744
|
+
f"to_isabelle_modal: unsupported node type {type(node).__name__}.")
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
# --------------------------------------------------------------------------- #
|
|
748
|
+
# Fixed building blocks of the theory.
|
|
749
|
+
# --------------------------------------------------------------------------- #
|
|
750
|
+
|
|
751
|
+
# World type and the propositional/connective abbreviations are ALWAYS emitted.
|
|
752
|
+
_CORE_ABBREVS = [
|
|
753
|
+
'abbreviation mnot :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
754
|
+
' "mnot \\<phi> \\<equiv> \\<lambda>w. \\<not> \\<phi> w"',
|
|
755
|
+
'abbreviation mand :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
756
|
+
' "mand \\<phi> \\<psi> \\<equiv> \\<lambda>w. \\<phi> w \\<and> \\<psi> w"',
|
|
757
|
+
'abbreviation mor :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
758
|
+
' "mor \\<phi> \\<psi> \\<equiv> \\<lambda>w. \\<phi> w \\<or> \\<psi> w"',
|
|
759
|
+
'abbreviation mimp :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
760
|
+
' "mimp \\<phi> \\<psi> \\<equiv> \\<lambda>w. \\<phi> w \\<longrightarrow> \\<psi> w"',
|
|
761
|
+
'abbreviation miff :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
762
|
+
' "miff \\<phi> \\<psi> \\<equiv> \\<lambda>w. \\<phi> w \\<longleftrightarrow> \\<psi> w"',
|
|
763
|
+
]
|
|
764
|
+
|
|
765
|
+
# validity: truth at every world.
|
|
766
|
+
_VALID_ABBREV = [
|
|
767
|
+
'abbreviation mvalid :: "(i \\<Rightarrow> bool) \\<Rightarrow> bool" ("\\<lfloor>_\\<rfloor>") where',
|
|
768
|
+
' "\\<lfloor>\\<phi>\\<rfloor> \\<equiv> \\<forall>w. \\<phi> w"',
|
|
769
|
+
]
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def _alethic_block() -> List[str]:
|
|
773
|
+
return [
|
|
774
|
+
'consts r :: "i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>alethic accessibility\\<close>',
|
|
775
|
+
'abbreviation mbox :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
776
|
+
' "mbox \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. r w v \\<longrightarrow> \\<phi> v"',
|
|
777
|
+
'abbreviation mdia :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
778
|
+
' "mdia \\<phi> \\<equiv> \\<lambda>w. \\<exists>v. r w v \\<and> \\<phi> v"',
|
|
779
|
+
]
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def _epistemic_block() -> List[str]:
|
|
783
|
+
return [
|
|
784
|
+
'consts rk :: "e \\<Rightarrow> i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>agent-indexed epistemic accessibility\\<close>',
|
|
785
|
+
'abbreviation knows :: "e \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
786
|
+
' "knows a \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. rk a w v \\<longrightarrow> \\<phi> v"',
|
|
787
|
+
]
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def _doxastic_block() -> List[str]:
|
|
791
|
+
return [
|
|
792
|
+
'consts rb :: "e \\<Rightarrow> i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>agent-indexed doxastic accessibility\\<close>',
|
|
793
|
+
'abbreviation believes :: "e \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
794
|
+
' "believes a \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. rb a w v \\<longrightarrow> \\<phi> v"',
|
|
795
|
+
]
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _assertive_block() -> List[str]:
|
|
799
|
+
return [
|
|
800
|
+
'consts rs :: "e \\<Rightarrow> i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>agent-indexed assertive accessibility (Says)\\<close>',
|
|
801
|
+
'abbreviation says :: "e \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
802
|
+
' "says a \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. rs a w v \\<longrightarrow> \\<phi> v"',
|
|
803
|
+
]
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def _bouletic_block() -> List[str]:
|
|
807
|
+
return [
|
|
808
|
+
'consts rw :: "e \\<Rightarrow> i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>agent-indexed bouletic accessibility (Wants)\\<close>',
|
|
809
|
+
'abbreviation wants :: "e \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
810
|
+
' "wants a \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. rw a w v \\<longrightarrow> \\<phi> v"',
|
|
811
|
+
]
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def _deontic_block() -> List[str]:
|
|
815
|
+
return [
|
|
816
|
+
'consts d :: "i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>deontic accessibility (serial)\\<close>',
|
|
817
|
+
'abbreviation obl :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
818
|
+
' "obl \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. d w v \\<longrightarrow> \\<phi> v"',
|
|
819
|
+
'abbreviation perm :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
820
|
+
' "perm \\<phi> \\<equiv> \\<lambda>w. \\<exists>v. d w v \\<and> \\<phi> v"',
|
|
821
|
+
]
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _temporal_block() -> List[str]:
|
|
825
|
+
return [
|
|
826
|
+
'consts t :: "i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>temporal henceforth relation (refl+trans closure)\\<close>',
|
|
827
|
+
'abbreviation malways :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
828
|
+
' "malways \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. t w v \\<longrightarrow> \\<phi> v"',
|
|
829
|
+
'abbreviation meventually :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
830
|
+
' "meventually \\<phi> \\<equiv> \\<lambda>w. \\<exists>v. t w v \\<and> \\<phi> v"',
|
|
831
|
+
]
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _temporal_def_block() -> List[str]:
|
|
835
|
+
r"""Temporal block with ``t`` **defined** as ``rtranclp n`` (the refl-trans closure
|
|
836
|
+
of the one-step relation), not a ``consts`` constrained by axioms.
|
|
837
|
+
|
|
838
|
+
Used by the runner's refute step: with ``t`` determined by ``n``, nitpick searches
|
|
839
|
+
a candidate ``n`` and computes ``t`` itself, so it can actually find a finite
|
|
840
|
+
counter-model to a non-theorem of the Always/Next closure fragment (a ``consts t``
|
|
841
|
+
with an ``rtranclp`` *axiom* leaves nitpick unable to construct the closure).
|
|
842
|
+
Requires the next block (``consts n``) to be emitted first.
|
|
843
|
+
"""
|
|
844
|
+
return [
|
|
845
|
+
'definition t :: "i \\<Rightarrow> i \\<Rightarrow> bool" where "t = rtranclp n" '
|
|
846
|
+
'\\<comment> \\<open>henceforth = reflexive-transitive closure of the one-step n\\<close>',
|
|
847
|
+
'abbreviation malways :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
848
|
+
' "malways \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. t w v \\<longrightarrow> \\<phi> v"',
|
|
849
|
+
'abbreviation meventually :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
850
|
+
' "meventually \\<phi> \\<equiv> \\<lambda>w. \\<exists>v. t w v \\<and> \\<phi> v"',
|
|
851
|
+
]
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _next_consts_block() -> List[str]:
|
|
855
|
+
"""Declare the one-step successor relation ``n`` (needed by Next / Until / Since)."""
|
|
856
|
+
return [
|
|
857
|
+
'consts n :: "i \\<Rightarrow> i \\<Rightarrow> bool" \\<comment> \\<open>one-step temporal successor\\<close>',
|
|
858
|
+
]
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _next_abbrev_block() -> List[str]:
|
|
862
|
+
"""The ``mnext`` box-over-``n`` abbreviation (emitted only when Next occurs)."""
|
|
863
|
+
return [
|
|
864
|
+
'abbreviation mnext :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
865
|
+
' "mnext \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. n w v \\<longrightarrow> \\<phi> v"',
|
|
866
|
+
]
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _previous_abbrev_block() -> List[str]:
|
|
870
|
+
r"""``mprevious`` — box over the CONVERSE of the one-step ``n`` (⒴).
|
|
871
|
+
|
|
872
|
+
Faithful to :func:`satisfies_modal`'s Previous: "at every immediate PAST
|
|
873
|
+
point", i.e. every ``v`` with ``n v w``. Requires ``n`` to be declared.
|
|
874
|
+
"""
|
|
875
|
+
return [
|
|
876
|
+
'abbreviation mprevious :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
877
|
+
' "mprevious \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. n v w \\<longrightarrow> \\<phi> v"',
|
|
878
|
+
]
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def _past_temporal_block() -> List[str]:
|
|
882
|
+
r"""``mhistorically`` / ``monce`` — box/diamond over the CONVERSE of ``t``.
|
|
883
|
+
|
|
884
|
+
Faithful to :func:`satisfies_modal`'s converse-closure readings: ``t`` is
|
|
885
|
+
reflexive, so ``mhistorically`` covers "now and every past point" and
|
|
886
|
+
``monce`` "now or some past point". The converse of a reflexive+transitive
|
|
887
|
+
relation is reflexive+transitive, so the ``t_refl`` / ``t_trans`` axioms
|
|
888
|
+
constrain the past readings exactly as the future ones — no extra axioms.
|
|
889
|
+
Requires ``t`` to be declared.
|
|
890
|
+
"""
|
|
891
|
+
return [
|
|
892
|
+
'abbreviation mhistorically :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
893
|
+
' "mhistorically \\<phi> \\<equiv> \\<lambda>w. \\<forall>v. t v w \\<longrightarrow> \\<phi> v"',
|
|
894
|
+
'abbreviation monce :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
895
|
+
' "monce \\<phi> \\<equiv> \\<lambda>w. \\<exists>v. t v w \\<and> \\<phi> v"',
|
|
896
|
+
]
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _hybrid_block(names: "_IsaNames") -> List[str]:
|
|
900
|
+
"""``consts nom_<name> :: i`` — one world constant per nominal (H(@))."""
|
|
901
|
+
out = []
|
|
902
|
+
for name, c in sorted(names.nom.items(), key=lambda kv: kv[1]):
|
|
903
|
+
out.append(f'consts {c} :: "i" \\<comment> \\<open>the world named by nominal {name}\\<close>')
|
|
904
|
+
return out
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _until_block() -> List[str]:
|
|
908
|
+
r"""``muntil`` — strong "left until right" as a least fixpoint over ``n``.
|
|
909
|
+
|
|
910
|
+
Defined inductively so it denotes exactly the FINITE forward paths
|
|
911
|
+
``w = w0 \<rightarrow> ... \<rightarrow> wn`` (one-step ``n`` edges, n \<ge> 0) with ``psi`` at the
|
|
912
|
+
endpoint and ``phi`` at every earlier point — the faithful counterpart of
|
|
913
|
+
:func:`unicode_logic_kit.semantics.kripke._until_holds` (its depth-first path
|
|
914
|
+
search). A plain box/diamond-over-``t`` abbreviation could NOT capture "phi all
|
|
915
|
+
the way along the path" (on a branching/short-cut frame the interval reading
|
|
916
|
+
over the closure ``t`` differs from the path search); the least fixpoint does,
|
|
917
|
+
so this is faithful to ``satisfies_modal`` on every frame, not just linear ones.
|
|
918
|
+
``n`` (the one-step relation) must be declared first.
|
|
919
|
+
"""
|
|
920
|
+
return [
|
|
921
|
+
'inductive muntil :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> i \\<Rightarrow> bool"',
|
|
922
|
+
' for phi :: "i \\<Rightarrow> bool" and psi :: "i \\<Rightarrow> bool" where',
|
|
923
|
+
' muntil_base: "psi w \\<Longrightarrow> muntil phi psi w"',
|
|
924
|
+
'| muntil_step: "phi w \\<Longrightarrow> n w v \\<Longrightarrow> muntil phi psi v '
|
|
925
|
+
'\\<Longrightarrow> muntil phi psi w"',
|
|
926
|
+
]
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _since_block() -> List[str]:
|
|
930
|
+
r"""``msince`` — past-tense "left since right", the backward mirror of ``muntil``.
|
|
931
|
+
|
|
932
|
+
Same least fixpoint but stepping along the CONVERSE of ``n`` (``n v w``: ``v`` is a
|
|
933
|
+
one-step predecessor of ``w``), so it denotes the finite BACKWARD paths matching
|
|
934
|
+
:func:`unicode_logic_kit.semantics.kripke._since_holds`. ``n`` must be declared first.
|
|
935
|
+
"""
|
|
936
|
+
return [
|
|
937
|
+
'inductive msince :: "(i \\<Rightarrow> bool) \\<Rightarrow> (i \\<Rightarrow> bool) \\<Rightarrow> i \\<Rightarrow> bool"',
|
|
938
|
+
' for phi :: "i \\<Rightarrow> bool" and psi :: "i \\<Rightarrow> bool" where',
|
|
939
|
+
' msince_base: "psi w \\<Longrightarrow> msince phi psi w"',
|
|
940
|
+
'| msince_step: "phi w \\<Longrightarrow> n v w \\<Longrightarrow> msince phi psi v '
|
|
941
|
+
'\\<Longrightarrow> msince phi psi w"',
|
|
942
|
+
]
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
#: The declaration of the existence predicate: ``existsAt x w`` says object ``x`` is in
|
|
946
|
+
#: the domain of world ``w``. Written once, for :func:`_quant_block` and for the
|
|
947
|
+
#: quantifier-free formula whose sort witness (an actualist mode) names it.
|
|
948
|
+
_EXISTS_AT_DECL = ('consts existsAt :: "e \\<Rightarrow> i \\<Rightarrow> bool" '
|
|
949
|
+
'\\<comment> \\<open>object x exists at world w\\<close>')
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _quant_block(mode: str) -> List[str]:
|
|
953
|
+
"""existsAt + actualist mforall/mexists (constant mode makes existsAt total)."""
|
|
954
|
+
return [
|
|
955
|
+
_EXISTS_AT_DECL,
|
|
956
|
+
'abbreviation mforall :: "(e \\<Rightarrow> (i \\<Rightarrow> bool)) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
957
|
+
' "mforall \\<Phi> \\<equiv> \\<lambda>w. \\<forall>x. existsAt x w \\<longrightarrow> \\<Phi> x w"',
|
|
958
|
+
'abbreviation mexists :: "(e \\<Rightarrow> (i \\<Rightarrow> bool)) \\<Rightarrow> (i \\<Rightarrow> bool)" where',
|
|
959
|
+
' "mexists \\<Phi> \\<equiv> \\<lambda>w. \\<exists>x. existsAt x w \\<and> \\<Phi> x w"',
|
|
960
|
+
]
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
# --------------------------------------------------------------------------- #
|
|
964
|
+
# Frame / domain axioms.
|
|
965
|
+
# --------------------------------------------------------------------------- #
|
|
966
|
+
|
|
967
|
+
def _frame_axioms(frame: str) -> List[str]:
|
|
968
|
+
"""Alethic-frame axioms on r for the chosen frame system."""
|
|
969
|
+
conds = _FRAMES[frame]
|
|
970
|
+
out = []
|
|
971
|
+
if "refl" in conds:
|
|
972
|
+
out.append('axiomatization where r_refl: "r w w"')
|
|
973
|
+
if "trans" in conds:
|
|
974
|
+
out.append('axiomatization where r_trans: "r w v \\<Longrightarrow> r v u \\<Longrightarrow> r w u"')
|
|
975
|
+
if "sym" in conds:
|
|
976
|
+
out.append('axiomatization where r_sym: "r w v \\<Longrightarrow> r v w"')
|
|
977
|
+
if "serial" in conds:
|
|
978
|
+
out.append('axiomatization where r_serial: "\\<exists>v. r w v"')
|
|
979
|
+
if "eucl" in conds:
|
|
980
|
+
out.append('axiomatization where r_eucl: "r w v \\<Longrightarrow> r w u \\<Longrightarrow> r v u"')
|
|
981
|
+
if "directed" in conds:
|
|
982
|
+
out.append('axiomatization where r_directed: '
|
|
983
|
+
'"r w v \\<Longrightarrow> r w u \\<Longrightarrow> \\<exists>z. r v z \\<and> r u z"')
|
|
984
|
+
if "connected" in conds:
|
|
985
|
+
out.append('axiomatization where r_conn: '
|
|
986
|
+
'"r w v \\<Longrightarrow> r w u \\<Longrightarrow> r v u \\<or> r u v"')
|
|
987
|
+
if "functional" in conds:
|
|
988
|
+
# CD: at most one successor.
|
|
989
|
+
out.append('axiomatization where r_func: '
|
|
990
|
+
'"r w v \\<Longrightarrow> r w u \\<Longrightarrow> v = u"')
|
|
991
|
+
if "dense" in conds:
|
|
992
|
+
out.append('axiomatization where r_dense: '
|
|
993
|
+
'"r w v \\<Longrightarrow> \\<exists>u. r w u \\<and> r u v"')
|
|
994
|
+
if "shift_refl" in conds:
|
|
995
|
+
out.append('axiomatization where r_shift_refl: '
|
|
996
|
+
'"r w v \\<Longrightarrow> r v v"')
|
|
997
|
+
if "empty" in conds:
|
|
998
|
+
out.append('axiomatization where r_empty: "\\<not> r w v"')
|
|
999
|
+
if "mckinsey" in conds:
|
|
1000
|
+
# McKinsey (.1): the schema □◇P → ◇□P, P and w schematic. There is NO
|
|
1001
|
+
# first-order frame condition for it, so the schema IS the axiom —
|
|
1002
|
+
# which is precisely what a higher-order route can state.
|
|
1003
|
+
out.append('axiomatization where r_mckinsey: '
|
|
1004
|
+
'"(\\<forall>v. r w v \\<longrightarrow> (\\<exists>u. r v u \\<and> P u)) \\<Longrightarrow> '
|
|
1005
|
+
'(\\<exists>v. r w v \\<and> (\\<forall>u. r v u \\<longrightarrow> P u))"')
|
|
1006
|
+
if "grz" in conds:
|
|
1007
|
+
# Grzegorczyk: □(□(P → □P) → P) → P, P and w schematic; likewise not
|
|
1008
|
+
# first-order definable.
|
|
1009
|
+
out.append('axiomatization where r_grz: '
|
|
1010
|
+
'"(\\<forall>v. r w v \\<longrightarrow> ((\\<forall>u. r v u \\<longrightarrow> (P u \\<longrightarrow> '
|
|
1011
|
+
'(\\<forall>z. r u z \\<longrightarrow> P z))) \\<longrightarrow> P v)) \\<Longrightarrow> P w"')
|
|
1012
|
+
if "loeb" in conds:
|
|
1013
|
+
# Gödel–Löb provability: the Löb schema □(□P → P) → □P (P, w free, i.e.
|
|
1014
|
+
# universally generalised). Stated over r directly so it needs no abbreviation;
|
|
1015
|
+
# transitivity (4) is derivable from it but emitted too for a faithful frame.
|
|
1016
|
+
out.append('axiomatization where r_loeb: '
|
|
1017
|
+
'"(\\<forall>v. r w v \\<longrightarrow> '
|
|
1018
|
+
'(\\<forall>u. r v u \\<longrightarrow> P u) \\<longrightarrow> P v) '
|
|
1019
|
+
'\\<Longrightarrow> (\\<forall>v. r w v \\<longrightarrow> P v)"')
|
|
1020
|
+
return out
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
# Conditions expressible as per-agent axiomatization schemas (free vars are
|
|
1024
|
+
# schematic, i.e. universally generalised — including the agent a).
|
|
1025
|
+
_AGENT_CONDS = {
|
|
1026
|
+
"refl": '{tag}_refl: "{rel} a w w"',
|
|
1027
|
+
"trans": '{tag}_trans: "{rel} a w v \\<Longrightarrow> {rel} a v u \\<Longrightarrow> {rel} a w u"',
|
|
1028
|
+
"sym": '{tag}_sym: "{rel} a w v \\<Longrightarrow> {rel} a v w"',
|
|
1029
|
+
"serial": '{tag}_serial: "\\<exists>v. {rel} a w v"',
|
|
1030
|
+
"eucl": '{tag}_eucl: "{rel} a w v \\<Longrightarrow> {rel} a w u \\<Longrightarrow> {rel} a v u"',
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
# The agent-indexed families systems= may constrain: family → (relation const, _Sig flag).
|
|
1034
|
+
_AGENT_SYSTEM_FAMILIES = {
|
|
1035
|
+
"epistemic": ("rk", "uses_epistemic"),
|
|
1036
|
+
"doxastic": ("rb", "uses_doxastic"),
|
|
1037
|
+
"assertive": ("rs", "uses_assertive"),
|
|
1038
|
+
"bouletic": ("rw", "uses_bouletic"),
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
def _agent_system_axioms(rel: str, system: str, family: str) -> List[str]:
|
|
1043
|
+
"""Per-agent frame axioms constraining an agent-indexed relation (rk/rb/rs/rw).
|
|
1044
|
+
|
|
1045
|
+
Each axiom leaves the agent ``a`` schematic, so the property holds for EVERY
|
|
1046
|
+
agent — matching the ``∀A``-quantified axioms of ``to_thf_modal_full``'s
|
|
1047
|
+
``systems=`` and the per-agent relations of ``satisfies_modal``. Only systems
|
|
1048
|
+
whose conditions are plain Horn frame properties are accepted; ``GL`` /
|
|
1049
|
+
``S4.2`` / ``S4.3`` need conditions (Löb / directedness / connectedness) that
|
|
1050
|
+
this schema set does not express, and silently dropping one would emit a
|
|
1051
|
+
WEAKER logic than requested.
|
|
1052
|
+
"""
|
|
1053
|
+
if system not in _FRAMES:
|
|
1054
|
+
raise ValueError(
|
|
1055
|
+
f"to_isabelle_modal: unknown system {system!r} for {family} "
|
|
1056
|
+
f"(use one of {sorted(_FRAMES)}).")
|
|
1057
|
+
conds = _FRAMES[system]
|
|
1058
|
+
unsupported = [c for c in conds if c not in _AGENT_CONDS]
|
|
1059
|
+
if unsupported:
|
|
1060
|
+
raise NotImplementedError(
|
|
1061
|
+
f"to_isabelle_modal: system {system!r} for {family} needs the frame "
|
|
1062
|
+
f"condition(s) {unsupported}, which have no per-agent axiom schema "
|
|
1063
|
+
"here; use frame= on the alethic relation for those systems.")
|
|
1064
|
+
return ["axiomatization where "
|
|
1065
|
+
+ _AGENT_CONDS[c].format(rel=rel, tag=rel) for c in conds]
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
# --------------------------------------------------------------------------- #
|
|
1069
|
+
# Cross-family bridge axioms (opt-in, ``bridges=``).
|
|
1070
|
+
# --------------------------------------------------------------------------- #
|
|
1071
|
+
#
|
|
1072
|
+
# WHY A SEPARATE OPTION. ``frame=`` constrains the alethic r and ``systems=``
|
|
1073
|
+
# constrains ONE agent-indexed relation each; both are single-relation frame
|
|
1074
|
+
# properties. A bridge is a principle relating TWO relations of DIFFERENT modal
|
|
1075
|
+
# families (knowledge to belief, assertion to belief, obligation to possibility),
|
|
1076
|
+
# so it cannot be phrased as a ``systems=`` entry — hence its own opt-in option,
|
|
1077
|
+
# defaulting to off.
|
|
1078
|
+
#
|
|
1079
|
+
# WHY THESE CONDITIONS. Each condition below is the EXACT frame correspondent of
|
|
1080
|
+
# its schema — necessary as well as sufficient — measured by brute force over
|
|
1081
|
+
# every frame on <= 2 worlds against semantics.kripke.satisfies_modal, not quoted
|
|
1082
|
+
# from the literature:
|
|
1083
|
+
#
|
|
1084
|
+
# rb subset rk |= K_a phi -> B_a phi and rb subset rk does NOT
|
|
1085
|
+
# validate B_a phi -> K_a phi,
|
|
1086
|
+
# while no condition at all fails
|
|
1087
|
+
# to validate K -> B.
|
|
1088
|
+
# rb subset rs |= Say_a phi -> B_a phi (same two-sided check)
|
|
1089
|
+
# d meets r |= O phi -> <>phi
|
|
1090
|
+
#
|
|
1091
|
+
# THE ``d subset r`` TRAP — do NOT "simplify" d_meets_r into an inclusion. The
|
|
1092
|
+
# frequently quoted ought-implies-can condition ``d subset r`` is measurably the
|
|
1093
|
+
# WRONG axiom: on its own it does not validate O phi -> <>phi at all (a world with
|
|
1094
|
+
# no d-successor makes O phi vacuously true while <>phi is false), and ``d subset
|
|
1095
|
+
# r`` TOGETHER WITH d-seriality validates the strictly stronger [] phi -> O phi
|
|
1096
|
+
# ("whatever is necessary is obligatory"), which the caller never requested. The
|
|
1097
|
+
# existential "meet" condition emitted here is exactly right: it subsumes
|
|
1098
|
+
# d-seriality (every world gets a d-successor) and does not validate [] phi -> O
|
|
1099
|
+
# phi. ``d_serial`` is still emitted alongside it whenever the deontic family
|
|
1100
|
+
# occurs — redundant but harmless, and suppressing it would change the documented
|
|
1101
|
+
# output of :func:`modal_axiom_names`.
|
|
1102
|
+
#
|
|
1103
|
+
# ALL ROUTES AGREE, and that is asserted rather than assumed. The guarded
|
|
1104
|
+
# first-order route ``fol.qml`` emits the same three conditions under the same three
|
|
1105
|
+
# names, including this meet condition for ``ought_implies_can`` (it realised that
|
|
1106
|
+
# one as the inclusion ``D subset R`` in an earlier draft, which validated the
|
|
1107
|
+
# unrequested ``[] phi -> O phi`` and ``P phi -> <>phi``). ``fol.QML_BRIDGES`` and
|
|
1108
|
+
# ``hol.BRIDGES`` stay SEPARATE registries — ``fol`` must not import ``hol`` — so
|
|
1109
|
+
# tests/test_hol_bridges.py and tests/test_qml_bridges.py cross-check the two tables
|
|
1110
|
+
# against each other and against the Kripke oracle, rather than trusting either.
|
|
1111
|
+
#
|
|
1112
|
+
# WHY THE VARIABLES ARE BOUND EXPLICITLY (\<And>a w v. ...) rather than left free
|
|
1113
|
+
# and schematic like _frame_axioms / _AGENT_CONDS. A free variable in an
|
|
1114
|
+
# axiomatization is generalised ONLY if the name is not already a declared
|
|
1115
|
+
# constant. The agent slot is the dangerous one: an agent named ``a`` (the
|
|
1116
|
+
# canonical test agent) emits ``consts a :: "e"``, and a free ``a`` in the axiom
|
|
1117
|
+
# then resolves to THAT CONSTANT and typechecks, so the inclusion would silently
|
|
1118
|
+
# hold for one agent instead of all — a weaker logic than requested, with no
|
|
1119
|
+
# error. A Pure binder shadows the constant inside its scope, so the schema is
|
|
1120
|
+
# universal whatever the formula's symbols are named. (The pre-existing
|
|
1121
|
+
# _AGENT_CONDS schemas have the same exposure; changing them is a separate
|
|
1122
|
+
# change, since their emitted text is pinned by existing tests.)
|
|
1123
|
+
|
|
1124
|
+
# Canonical family name -> the _Sig flag that says its relation is declared.
|
|
1125
|
+
_FAMILY_FLAG = {
|
|
1126
|
+
"alethic": "uses_alethic",
|
|
1127
|
+
"epistemic": "uses_epistemic",
|
|
1128
|
+
"doxastic": "uses_doxastic",
|
|
1129
|
+
"assertive": "uses_assertive",
|
|
1130
|
+
"bouletic": "uses_bouletic",
|
|
1131
|
+
"deontic": "uses_deontic",
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
# Bridge name -> the families it relates (with the operator label to name in an
|
|
1135
|
+
# error message), a description of the relations, and the Isabelle axiom lines.
|
|
1136
|
+
# The fact names ``rb_in_rk`` / ``rb_in_rs`` / ``d_meets_r`` deliberately follow
|
|
1137
|
+
# the existing relation-shaped naming (``n_in_t``, ``t_in_nstar``, ``d_serial``)
|
|
1138
|
+
# and are reused VERBATIM as the THF formula names in hol.thf_modal, so a single
|
|
1139
|
+
# grep for a fact name finds both routes.
|
|
1140
|
+
class _BridgeSpec(TypedDict):
|
|
1141
|
+
"""One entry of :data:`_BRIDGES`."""
|
|
1142
|
+
|
|
1143
|
+
needs: Tuple[Tuple[str, str], ...]
|
|
1144
|
+
schema: str
|
|
1145
|
+
rels: str
|
|
1146
|
+
lines: List[str]
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
_BRIDGES: Dict[str, _BridgeSpec] = {
|
|
1150
|
+
"knowledge_implies_belief": {
|
|
1151
|
+
"needs": (("epistemic", "Knows"), ("doxastic", "Believes")),
|
|
1152
|
+
"schema": "K_a phi -> B_a phi",
|
|
1153
|
+
"rels": "the doxastic relation rb to the epistemic relation rk",
|
|
1154
|
+
"lines": ['axiomatization where rb_in_rk: '
|
|
1155
|
+
'"\\<And>a w v. rb a w v \\<Longrightarrow> rk a w v"'],
|
|
1156
|
+
},
|
|
1157
|
+
"sincerity": {
|
|
1158
|
+
"needs": (("assertive", "Says"), ("doxastic", "Believes")),
|
|
1159
|
+
"schema": "Say_a phi -> B_a phi",
|
|
1160
|
+
"rels": "the doxastic relation rb to the assertive relation rs",
|
|
1161
|
+
"lines": ['axiomatization where rb_in_rs: '
|
|
1162
|
+
'"\\<And>a w v. rb a w v \\<Longrightarrow> rs a w v"'],
|
|
1163
|
+
},
|
|
1164
|
+
"ought_implies_can": {
|
|
1165
|
+
"needs": (("deontic", "Obligatory/Permitted"), ("alethic", "□/◇")),
|
|
1166
|
+
"schema": "O phi -> <>phi",
|
|
1167
|
+
"rels": "the deontic relation d to the alethic relation r",
|
|
1168
|
+
"lines": ['axiomatization where d_meets_r: '
|
|
1169
|
+
'"\\<And>w. \\<exists>v. d w v \\<and> r w v"'],
|
|
1170
|
+
},
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
#: The cross-family bridge names accepted by ``bridges=`` (see :func:`_bridge_axioms`).
|
|
1174
|
+
BRIDGES = tuple(_BRIDGES)
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
def _validate_bridges(bridges, func: str) -> frozenset:
|
|
1178
|
+
"""Normalise and validate a ``bridges=`` request; return it as a frozen set.
|
|
1179
|
+
|
|
1180
|
+
``None`` / empty means "no bridges" (the default). A bare string is rejected
|
|
1181
|
+
rather than iterated character-wise — ``bridges="sincerity"`` would otherwise
|
|
1182
|
+
silently degrade into a set of unknown one-character names.
|
|
1183
|
+
"""
|
|
1184
|
+
if not bridges:
|
|
1185
|
+
return frozenset()
|
|
1186
|
+
if isinstance(bridges, str):
|
|
1187
|
+
raise ValueError(
|
|
1188
|
+
f"{func}: bridges= takes a collection of names, not a single string; "
|
|
1189
|
+
f"use bridges=['{bridges}'].")
|
|
1190
|
+
requested = frozenset(bridges)
|
|
1191
|
+
unknown = sorted(requested - set(_BRIDGES))
|
|
1192
|
+
if unknown:
|
|
1193
|
+
raise ValueError(
|
|
1194
|
+
f"{func}: unknown bridge(s) {unknown} (use one of {sorted(_BRIDGES)}).")
|
|
1195
|
+
return requested
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def _bridge_axioms(sig: "_Sig", bridges) -> List[str]:
|
|
1199
|
+
"""Axioms for the requested cross-family bridges, in table order.
|
|
1200
|
+
|
|
1201
|
+
Each bridge adds ONE frame condition relating two relations of different
|
|
1202
|
+
modal families; the emitted axiom is the exact correspondent of the schema
|
|
1203
|
+
the option is named after (``knowledge_implies_belief`` → ``rb ⊆ rk``,
|
|
1204
|
+
``sincerity`` → ``rb ⊆ rs``, ``ought_implies_can`` → ``∀w. ∃v. d w v ∧ r w v``
|
|
1205
|
+
— never the folklore ``d ⊆ r``, see the section comment above). Emission
|
|
1206
|
+
follows :data:`_BRIDGES` order, not the caller's set-iteration order, so the
|
|
1207
|
+
output is deterministic.
|
|
1208
|
+
|
|
1209
|
+
Honest contract — what a request for a bridge whose partner family does NOT
|
|
1210
|
+
occur in the formula does, and why. Such a bridge names a relation that this
|
|
1211
|
+
emitter never declares (every relation block is conditional on its operators
|
|
1212
|
+
occurring), so the axiom would not even load. There are three possible
|
|
1213
|
+
policies and only one of them is honest:
|
|
1214
|
+
|
|
1215
|
+
- *silently skip it*: the caller asked for a logic and would receive a
|
|
1216
|
+
strictly WEAKER one with no signal — the exact "silently weakened logic"
|
|
1217
|
+
failure this module refuses everywhere else;
|
|
1218
|
+
- *declare the relation anyway*: not uniformly conservative, and it fails in
|
|
1219
|
+
the STRENGTHENING direction. Measured: ``d_meets_r`` entails
|
|
1220
|
+
``∃v. r w v``, i.e. seriality of the ALETHIC relation, so emitting
|
|
1221
|
+
``ought_implies_can`` for a formula with no ``O``/``P`` but with ``□``/``◇``
|
|
1222
|
+
would turn ``□P → ◇P`` from invalid into valid under ``frame='K'`` — a
|
|
1223
|
+
silent change to the alethic logic the caller *did* select. (The two
|
|
1224
|
+
inclusion bridges happen to be conservative when their partner family is
|
|
1225
|
+
absent, but a rule that sometimes emits and sometimes refuses depending on
|
|
1226
|
+
the axiom's shape is not predictable from the option name.)
|
|
1227
|
+
- *raise*: one uniform rule for every bridge, present and future; the repo's
|
|
1228
|
+
established idiom for "this cannot be expressed here as asked" (see the GL
|
|
1229
|
+
guard in ``fol.qml.qml_axioms`` and :func:`_agent_system_axioms`); and the
|
|
1230
|
+
only policy under which the relation-declaration blocks stay untouched, so
|
|
1231
|
+
a bridge is emitted exactly when both relation blocks already are.
|
|
1232
|
+
|
|
1233
|
+
``ValueError`` — not ``NotImplementedError`` — because the route *can* express
|
|
1234
|
+
the bridge; the caller's formula simply does not mention one of the families.
|
|
1235
|
+
``NotImplementedError`` stays reserved for "this route cannot express it".
|
|
1236
|
+
"""
|
|
1237
|
+
requested = _validate_bridges(bridges, "to_isabelle_modal")
|
|
1238
|
+
if not requested:
|
|
1239
|
+
return []
|
|
1240
|
+
out: List[str] = []
|
|
1241
|
+
for name, spec in _BRIDGES.items():
|
|
1242
|
+
if name not in requested:
|
|
1243
|
+
continue
|
|
1244
|
+
missing = [op for fam, op in spec["needs"]
|
|
1245
|
+
if not getattr(sig, _FAMILY_FLAG[fam])]
|
|
1246
|
+
if missing:
|
|
1247
|
+
raise ValueError(
|
|
1248
|
+
f"to_isabelle_modal: the bridge {name!r} relates {spec['rels']}, "
|
|
1249
|
+
f"but the formula contains no {' / '.join(missing)} operator, so "
|
|
1250
|
+
"that relation is never declared. Emitting the axiom would not "
|
|
1251
|
+
"load; declaring the relation anyway is not conservative "
|
|
1252
|
+
"(d_meets_r entails seriality of the alethic r, which would "
|
|
1253
|
+
"silently make []P -> <>P valid under frame='K'); and skipping it "
|
|
1254
|
+
"would emit a weaker logic than requested. Drop the bridge, or "
|
|
1255
|
+
"state the formula in both families.")
|
|
1256
|
+
out += spec["lines"]
|
|
1257
|
+
return out
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def _temporal_axioms() -> List[str]:
|
|
1261
|
+
"""t is the refl+trans (henceforth) relation, matching satisfies_modal's closure."""
|
|
1262
|
+
return [
|
|
1263
|
+
'axiomatization where t_refl: "t w w"',
|
|
1264
|
+
'axiomatization where t_trans: "t w v \\<Longrightarrow> t v u \\<Longrightarrow> t w u"',
|
|
1265
|
+
]
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
def _next_in_temporal_axiom() -> List[str]:
|
|
1269
|
+
"""Link the one-step successor n to the henceforth relation t.
|
|
1270
|
+
|
|
1271
|
+
In ``satisfies_modal`` ``Always`` / ``Eventually`` close over the *same*
|
|
1272
|
+
one-step ``"temporal"`` relation that ``Next`` reads, so every one-step
|
|
1273
|
+
successor is henceforth-reachable and ``Always(P) → Next(P)`` is valid.
|
|
1274
|
+
Without this axiom the emitted ``n`` and ``t`` are decoupled and that
|
|
1275
|
+
entailment fails, breaking faithfulness; the axiom restores it.
|
|
1276
|
+
"""
|
|
1277
|
+
return ['axiomatization where n_in_t: "n w v \\<Longrightarrow> t w v"']
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
def _temporal_next_closure_axiom() -> List[str]:
|
|
1281
|
+
r"""Pin the henceforth relation ``t`` to the reflexive-transitive CLOSURE of ``n``.
|
|
1282
|
+
|
|
1283
|
+
``n_in_t`` gives ``n ⊆ t`` and the temporal axioms make ``t`` reflexive+transitive,
|
|
1284
|
+
so ``n** ⊆ t``. This adds the converse ``t ⊆ n**`` (``rtranclp n``), so ``t = n**``
|
|
1285
|
+
EXACTLY — matching :func:`satisfies_modal`, which reads ``Always`` / ``Eventually``
|
|
1286
|
+
over the reflexive-transitive *closure* of the one-step ``"temporal"`` relation.
|
|
1287
|
+
Without it ``t`` may be any refl-trans superset of ``n``, and a formula valid only
|
|
1288
|
+
because ``t = closure(n)`` (e.g. the temporal induction ``(p ∧ G(p→Xp)) → Gp``) is
|
|
1289
|
+
spuriously refutable in the embedding — a **false INVALID** under the runner.
|
|
1290
|
+
"""
|
|
1291
|
+
return ['axiomatization where t_in_nstar: "t w v \\<Longrightarrow> rtranclp n w v"']
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _deontic_axioms() -> List[str]:
|
|
1295
|
+
"""Standard Deontic Logic KD: the deontic relation is serial."""
|
|
1296
|
+
return ['axiomatization where d_serial: "\\<exists>v. d w v"']
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
def _domain_axioms(mode: str) -> List[str]:
|
|
1300
|
+
"""existsAt domain-regime axioms (only meaningful when quantifiers occur)."""
|
|
1301
|
+
out = []
|
|
1302
|
+
if mode in _CONSTANT_MODES:
|
|
1303
|
+
out.append('axiomatization where const_dom: "existsAt x w"')
|
|
1304
|
+
else:
|
|
1305
|
+
# actualist: keep every world's domain non-empty (matches qml nonempty_dom).
|
|
1306
|
+
out.append('axiomatization where nonempty_dom: "\\<exists>x. existsAt x w"')
|
|
1307
|
+
if mode in ("increasing", "cumulative"):
|
|
1308
|
+
out.append('axiomatization where cumul_dom: "existsAt x w \\<Longrightarrow> r w v \\<Longrightarrow> existsAt x v"')
|
|
1309
|
+
elif mode == "decreasing":
|
|
1310
|
+
out.append('axiomatization where decr_dom: "existsAt x v \\<Longrightarrow> r w v \\<Longrightarrow> existsAt x w"')
|
|
1311
|
+
# "varying": no monotonicity axiom.
|
|
1312
|
+
return out
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def _sort_consts(original: Node, names: "_IsaNames") -> List[str]:
|
|
1316
|
+
"""Isabelle constant names of the sorts ``original`` quantifies over.
|
|
1317
|
+
|
|
1318
|
+
Takes the UNrelativized formula, because after ``_relativize`` a sort is no
|
|
1319
|
+
longer distinguishable from any other unary predicate. Reuses
|
|
1320
|
+
:func:`~unicode_logic_kit.fol._msfl_nodes.nonempty_sort_axioms`'s own scan (as
|
|
1321
|
+
:func:`~unicode_logic_kit.fol.qml._sort_names_used` does) so this route and the
|
|
1322
|
+
classical one can never disagree about which sorts a formula uses, then maps
|
|
1323
|
+
each through the de-colliding resolver so the axiom names the same ``consts``
|
|
1324
|
+
the lifted goal does. A sort the relativized formula never mentions as a
|
|
1325
|
+
unary atom — it has no ``consts`` line — is skipped rather than referenced.
|
|
1326
|
+
"""
|
|
1327
|
+
out: List[str] = []
|
|
1328
|
+
for axiom in nonempty_sort_axioms(original):
|
|
1329
|
+
assert isinstance(axiom, Quantifier) and isinstance(axiom.formula, Atom) # ∃x S(x)
|
|
1330
|
+
const = names.pred.get((axiom.formula.predicate, 1))
|
|
1331
|
+
if const is not None and const not in out:
|
|
1332
|
+
out.append(const)
|
|
1333
|
+
return out
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
def _names_with_sort_facts(relativized: Node, original: Node,
|
|
1337
|
+
reserved=frozenset()) -> "_IsaNames":
|
|
1338
|
+
"""The constant-name resolver of ``relativized``, extended by the sort facts of ``original``.
|
|
1339
|
+
|
|
1340
|
+
``relativized`` is the formula the theory is lifted from; in it a sorted
|
|
1341
|
+
constant ``c:S`` has become the plain ``c`` and the guard ``S`` is mentioned only
|
|
1342
|
+
if some sorted QUANTIFIER used it. A sort that occurs only through a sorted
|
|
1343
|
+
constant would therefore get neither a ``consts`` line nor a name for its
|
|
1344
|
+
axioms. Resolving over ``relativized`` together with the membership atom
|
|
1345
|
+
``S(c)`` of every sorted constant declares that guard too — the very name the
|
|
1346
|
+
membership axiom and the non-emptiness axiom then use. A formula without a sorted
|
|
1347
|
+
constant is resolved over ``relativized`` itself, so its names and declarations
|
|
1348
|
+
are exactly what they were. ``reserved`` is the set of names no symbol of the formula
|
|
1349
|
+
may take (see :func:`_axiom_variable_names`).
|
|
1350
|
+
"""
|
|
1351
|
+
scope = relativized
|
|
1352
|
+
for atom in sort_membership_axioms(original):
|
|
1353
|
+
scope = And(scope, atom)
|
|
1354
|
+
return _IsaNames(scope, reserved)
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def _sort_members(original: Node, names: "_IsaNames") -> List[Tuple[str, str]]:
|
|
1358
|
+
"""``(guard constant, individual constant)`` of every sorted constant of ``original``.
|
|
1359
|
+
|
|
1360
|
+
One pair per distinct ``c:S``, in first-occurrence order (the order of
|
|
1361
|
+
:func:`~unicode_logic_kit.fol._msfl_nodes.sort_membership_axioms`), each name
|
|
1362
|
+
taken from ``names`` — which must come from :func:`_names_with_sort_facts`, so
|
|
1363
|
+
both are declared and agree with the ones the lifted goal uses.
|
|
1364
|
+
"""
|
|
1365
|
+
out: List[Tuple[str, str]] = []
|
|
1366
|
+
for atom in sort_membership_axioms(original):
|
|
1367
|
+
assert isinstance(atom, Atom) and isinstance(atom.args[0], Constant) # ``S(c)``
|
|
1368
|
+
pair = (names.pred[(atom.predicate, 1)], names.constant(atom.args[0].name))
|
|
1369
|
+
if pair not in out:
|
|
1370
|
+
out.append(pair)
|
|
1371
|
+
return out
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def _sort_member_axioms(members: Sequence[Tuple[str, str]]) -> List[str]:
|
|
1375
|
+
r"""Membership of every sorted constant in its sort, at EVERY world and unguarded.
|
|
1376
|
+
|
|
1377
|
+
``c:S`` denotes an element of ``S`` (the many-sorted reading every route of the
|
|
1378
|
+
kit shares, :func:`~unicode_logic_kit.fol._msfl_nodes.sort_membership_axioms`), and
|
|
1379
|
+
a constant is a rigid designator, so ``S c w`` holds at every world ``w`` —
|
|
1380
|
+
``w`` is free, hence universally quantified by ``axiomatization``, exactly as in
|
|
1381
|
+
the ``nonempty_sort`` lines. It is NOT guarded by ``existsAt c w``, in any
|
|
1382
|
+
mode: a constant may lie outside the local domain (the route's reading of a
|
|
1383
|
+
constant, see :mod:`unicode_logic_kit.fol.qml`), and the guarded form would never
|
|
1384
|
+
fire in the modes where that matters. Without the line a countermodel with
|
|
1385
|
+
``socrates`` outside ``human`` refutes
|
|
1386
|
+
``∀x:Human Mortal(x) → Mortal(socrates:Human)`` in the constant-domain mode,
|
|
1387
|
+
which the plain-FOL routes call valid.
|
|
1388
|
+
|
|
1389
|
+
The facts are named ``sort_member<i>``, a family distinct from
|
|
1390
|
+
``nonempty_sort<i>``, so :func:`modal_axiom_names` lists them separately.
|
|
1391
|
+
"""
|
|
1392
|
+
return [f'axiomatization where sort_member{i}: "{sort} {const} w"'
|
|
1393
|
+
for i, (sort, const) in enumerate(members)]
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def _nonempty_sort_axioms(sorts: Sequence[str], mode: str) -> List[str]:
|
|
1397
|
+
r"""Per-world non-emptiness for every sort the source formula quantifies over.
|
|
1398
|
+
|
|
1399
|
+
``_relativize`` turns ``∀x:S φ`` into ``∀x (S(x) → φ)``, which leaves the sort
|
|
1400
|
+
guard an ordinary WORLD-RELATIVE predicate — so without this axiom nothing
|
|
1401
|
+
forces ``S`` to hold of anything at a world, and ``∀x:S P(x) → ∃x:S P(x)``
|
|
1402
|
+
comes out unprovable here while the classical many-sorted routes call it
|
|
1403
|
+
valid (``fol._msfl_nodes.nonempty_sort_axioms`` adds the same convention
|
|
1404
|
+
there, and :func:`~unicode_logic_kit.fol.qml.qml_axioms` the same per-world
|
|
1405
|
+
version). ``w`` is free, i.e. implicitly universally quantified over worlds
|
|
1406
|
+
by ``axiomatization``, so the sort is non-empty at EVERY world, matching the
|
|
1407
|
+
qml route rather than merely fixing the verdict at one.
|
|
1408
|
+
|
|
1409
|
+
Under an actualist ``mode`` the witness must also EXIST at the world: a
|
|
1410
|
+
witness outside the local domain cannot instantiate the ``existsAt``-guarded
|
|
1411
|
+
``mexists``, so the bare version would not restore the entailment there.
|
|
1412
|
+
"""
|
|
1413
|
+
out: List[str] = []
|
|
1414
|
+
for i, sort in enumerate(sorts):
|
|
1415
|
+
body = (f"\\<exists>x. existsAt x w \\<and> {sort} x w"
|
|
1416
|
+
if mode not in _CONSTANT_MODES else f"\\<exists>x. {sort} x w")
|
|
1417
|
+
out.append(f'axiomatization where nonempty_sort{i}: "{body}"')
|
|
1418
|
+
return out
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def _collect_axioms(sig: "_Sig", frame: str, mode: str,
|
|
1422
|
+
temporal_closure: bool, temporal_def: bool = False,
|
|
1423
|
+
systems: Optional[dict] = None,
|
|
1424
|
+
bridges: Optional[Iterable[str]] = None,
|
|
1425
|
+
sorts: Sequence[str] = (),
|
|
1426
|
+
members: Sequence[Tuple[str, str]] = ()) -> List[str]:
|
|
1427
|
+
"""All ``axiomatization where ...`` lines the theory emits, in emission order.
|
|
1428
|
+
|
|
1429
|
+
Centralised so the proof emitter and :func:`modal_axiom_names` agree on exactly
|
|
1430
|
+
which axioms are in scope (the proof must ``using`` precisely these to discharge
|
|
1431
|
+
an axiom-dependent validity). When ``temporal_def`` makes ``t = rtranclp n`` a
|
|
1432
|
+
definition (Always/Eventually + Next), the temporal/next closure axioms become
|
|
1433
|
+
theorems and are omitted. ``systems`` optionally constrains the agent-indexed
|
|
1434
|
+
relations (see :func:`isabelle_modal_theory`); a family whose operators do not
|
|
1435
|
+
occur in the formula is skipped (its relation is not even declared).
|
|
1436
|
+
|
|
1437
|
+
``bridges`` optionally adds the opt-in CROSS-family axioms (see
|
|
1438
|
+
:func:`_bridge_axioms`). Unlike a ``systems=`` family, a bridge whose partner
|
|
1439
|
+
family is absent is NOT skipped — it raises, because a bridge constrains two
|
|
1440
|
+
relations and skipping it would silently emit a weaker logic. The bridge block
|
|
1441
|
+
is APPENDED after the temporal axioms, so every pre-existing
|
|
1442
|
+
:func:`modal_axiom_names` result keeps its exact content *and* order.
|
|
1443
|
+
"""
|
|
1444
|
+
# t = rtranclp n needs only t to be IN USE: for a formula without Next/
|
|
1445
|
+
# Until/Since the one-step n is simply unconstrained and nitpick constructs
|
|
1446
|
+
# the closure itself — exactly what lets it refute non-theorems of the
|
|
1447
|
+
# pure closure fragment (④/Ⓘ included) instead of quasi_genuine failure.
|
|
1448
|
+
use_temporal_def = temporal_def and sig.uses_temporal
|
|
1449
|
+
axioms: List[str] = []
|
|
1450
|
+
if sig.uses_alethic:
|
|
1451
|
+
axioms += _frame_axioms(frame)
|
|
1452
|
+
for family, system in sorted((systems or {}).items()):
|
|
1453
|
+
if family not in _AGENT_SYSTEM_FAMILIES:
|
|
1454
|
+
raise ValueError(
|
|
1455
|
+
f"to_isabelle_modal: unknown systems family {family!r} "
|
|
1456
|
+
f"(use one of {sorted(_AGENT_SYSTEM_FAMILIES)}).")
|
|
1457
|
+
rel, flag = _AGENT_SYSTEM_FAMILIES[family]
|
|
1458
|
+
if getattr(sig, flag):
|
|
1459
|
+
axioms += _agent_system_axioms(rel, system, family)
|
|
1460
|
+
if sig.uses_deontic:
|
|
1461
|
+
axioms += _deontic_axioms()
|
|
1462
|
+
if not use_temporal_def:
|
|
1463
|
+
if sig.uses_temporal and temporal_closure:
|
|
1464
|
+
axioms += _temporal_axioms()
|
|
1465
|
+
# When the one-step relation n co-occurs with Always/Eventually (t) — because
|
|
1466
|
+
# Next, Until, or Since is present — link n into t so t and the n-based
|
|
1467
|
+
# operators agree (t denotes the closure of the SAME one-step relation the
|
|
1468
|
+
# path-search Until/Since and Next read), keeping the oracle-valid
|
|
1469
|
+
# Always(P) -> Next(P) and the Always/Until interplay theorems of the theory...
|
|
1470
|
+
if sig.uses_temporal and sig.needs_next_rel:
|
|
1471
|
+
axioms += _next_in_temporal_axiom()
|
|
1472
|
+
# ...and, when t is the closure relation, pin t = n** exactly (not merely a
|
|
1473
|
+
# refl-trans superset of n), so satisfies_modal-valid temporal-induction
|
|
1474
|
+
# formulas are not spuriously refuted (false INVALID).
|
|
1475
|
+
if temporal_closure:
|
|
1476
|
+
axioms += _temporal_next_closure_axiom()
|
|
1477
|
+
axioms += _bridge_axioms(sig, bridges)
|
|
1478
|
+
if sig.has_quant:
|
|
1479
|
+
axioms += _domain_axioms(mode)
|
|
1480
|
+
# After the domain axioms, so `existsAt` is already constrained when an
|
|
1481
|
+
# actualist sort witness refers to it, and so every pre-existing
|
|
1482
|
+
# modal_axiom_names result keeps its exact content and order (a formula
|
|
1483
|
+
# with no many-sorted node yields no sorts and no extra line).
|
|
1484
|
+
axioms += _nonempty_sort_axioms(sorts, mode)
|
|
1485
|
+
# Then the sorted constants' membership (a formula without a sorted constant
|
|
1486
|
+
# has none, so its axiom list is unchanged).
|
|
1487
|
+
axioms += _sort_member_axioms(members)
|
|
1488
|
+
return axioms
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
_ISABELLE_MACRO = re.compile(r"\\<[A-Za-z^]+>")
|
|
1492
|
+
_ONE_LETTER_NAME = re.compile(r"\b[a-z]\b")
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
def _axiom_variable_names(sig: "_Sig", original: Node, frame: str, mode: str,
|
|
1496
|
+
temporal_closure: bool, temporal_def: bool = False,
|
|
1497
|
+
systems: Optional[dict] = None,
|
|
1498
|
+
bridges: Optional[Iterable[str]] = None) -> frozenset:
|
|
1499
|
+
r"""The one-letter names the axioms and inductive definitions of THIS theory mention.
|
|
1500
|
+
|
|
1501
|
+
The fixed lines of a theory write their variables free and one letter long
|
|
1502
|
+
(``r_eucl: "r w v ⟹ r w u ⟹ r v u"``, ``const_dom: "existsAt x w"``,
|
|
1503
|
+
``rk_refl: "rk a w w"``, the world ``w`` of the sort facts, the ``w`` and ``v`` of the
|
|
1504
|
+
introduction rules of ``muntil``). ``axiomatization`` generalises a free name only if no
|
|
1505
|
+
constant of that name is declared, so a constant, predicate, function or sort of the
|
|
1506
|
+
user spelled like one of them would be read INSTEAD of the variable: ``consts x ::
|
|
1507
|
+
"e"`` turns ``existsAt x w`` into a statement about that one constant, and the regime it
|
|
1508
|
+
was meant to state (every object exists at every world) is gone. The theory therefore
|
|
1509
|
+
never gives such a name to a symbol of the user.
|
|
1510
|
+
|
|
1511
|
+
The names are read off the text of the axioms the theory is going to emit (the sort
|
|
1512
|
+
facts with placeholders for the user's names), so a line added to the module is
|
|
1513
|
+
covered without being listed here. Only the lines that are in the theory count: a
|
|
1514
|
+
formula without a modality, a quantifier or a sort has no axiom, and its symbols keep
|
|
1515
|
+
their natural spelling.
|
|
1516
|
+
"""
|
|
1517
|
+
sorts = ("SORT",) if nonempty_sort_axioms(original) else ()
|
|
1518
|
+
members = (("SORT", "CONST"),) if sort_membership_axioms(original) else ()
|
|
1519
|
+
lines = list(_collect_axioms(sig, frame, mode, temporal_closure, temporal_def, systems,
|
|
1520
|
+
bridges, sorts, members))
|
|
1521
|
+
if sig.uses_until:
|
|
1522
|
+
lines += _until_block()
|
|
1523
|
+
if sig.uses_since:
|
|
1524
|
+
lines += _since_block()
|
|
1525
|
+
found: set = set()
|
|
1526
|
+
for line in lines:
|
|
1527
|
+
found.update(_ONE_LETTER_NAME.findall(_ISABELLE_MACRO.sub(" ", line)))
|
|
1528
|
+
return frozenset(found)
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
def _proof_lines(tactic: str, axiom_ids: List[str]) -> List[str]:
|
|
1532
|
+
"""The proof block for ``tactic``, bringing the frame/domain axioms into scope.
|
|
1533
|
+
|
|
1534
|
+
For a ``by``-style tactic (:data:`_BY_TACTICS`) we prepend ``using <axioms>`` so
|
|
1535
|
+
``blast`` / ``auto`` / ``metis`` can actually use them; ``sledgehammer`` / ``oops``
|
|
1536
|
+
/ ``sorry`` are emitted verbatim.
|
|
1537
|
+
"""
|
|
1538
|
+
body = ISABELLE_TACTICS[tactic]
|
|
1539
|
+
if tactic in _BY_TACTICS and axiom_ids:
|
|
1540
|
+
return [" using " + " ".join(axiom_ids), body]
|
|
1541
|
+
return [body]
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
# --------------------------------------------------------------------------- #
|
|
1545
|
+
# Type declarations for the discovered signature.
|
|
1546
|
+
# --------------------------------------------------------------------------- #
|
|
1547
|
+
|
|
1548
|
+
def _signature_decls(names: "_IsaNames") -> List[str]:
|
|
1549
|
+
"""Emit ``consts`` declarations for predicates, object constants, functions.
|
|
1550
|
+
|
|
1551
|
+
Draws each constant's name from the de-colliding resolver, so distinct source
|
|
1552
|
+
symbols never share a ``consts`` declaration (Isabelle rejects duplicate consts).
|
|
1553
|
+
"""
|
|
1554
|
+
out = []
|
|
1555
|
+
for (name, arity), c in sorted(names.pred.items(), key=lambda kv: kv[1]):
|
|
1556
|
+
typ = " \\<Rightarrow> ".join(["e"] * arity + ["i \\<Rightarrow> bool"])
|
|
1557
|
+
out.append(f'consts {c} :: "{typ}"')
|
|
1558
|
+
for name, c in sorted(names.const.items(), key=lambda kv: kv[1]):
|
|
1559
|
+
out.append(f'consts {c} :: "e"')
|
|
1560
|
+
for (name, arity), c in sorted(names.func.items(), key=lambda kv: kv[1]):
|
|
1561
|
+
typ = " \\<Rightarrow> ".join(["e"] * (arity + 1))
|
|
1562
|
+
out.append(f'consts {c} :: "{typ}"')
|
|
1563
|
+
return out
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
# --------------------------------------------------------------------------- #
|
|
1567
|
+
# Public entry points.
|
|
1568
|
+
# --------------------------------------------------------------------------- #
|
|
1569
|
+
|
|
1570
|
+
def _validate(mode: str, frame: str, tactic: str) -> None:
|
|
1571
|
+
if frame not in _FRAMES:
|
|
1572
|
+
raise ValueError(
|
|
1573
|
+
f"to_isabelle_modal: unknown frame {frame!r} (use one of {sorted(_FRAMES)}).")
|
|
1574
|
+
if mode not in _ACTUALIST_MODES and mode not in _CONSTANT_MODES:
|
|
1575
|
+
raise ValueError(
|
|
1576
|
+
f"to_isabelle_modal: unknown mode {mode!r} "
|
|
1577
|
+
f"(use one of {sorted(_ACTUALIST_MODES | _CONSTANT_MODES)}).")
|
|
1578
|
+
if tactic not in ISABELLE_TACTICS:
|
|
1579
|
+
raise ValueError(
|
|
1580
|
+
f"to_isabelle_modal: unknown tactic {tactic!r} "
|
|
1581
|
+
f"(use one of {sorted(ISABELLE_TACTICS)}).")
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
def isabelle_modal_theory(
|
|
1585
|
+
formula: Node,
|
|
1586
|
+
mode: str = "constant",
|
|
1587
|
+
frame: str = "K",
|
|
1588
|
+
tactic: str = "sledgehammer",
|
|
1589
|
+
theory_name: str = "ModalEmbedding",
|
|
1590
|
+
temporal_closure: bool = True,
|
|
1591
|
+
proof: Optional[str] = None,
|
|
1592
|
+
temporal_def: bool = False,
|
|
1593
|
+
systems: Optional[dict] = None,
|
|
1594
|
+
bridges: Optional[Iterable[str]] = None,
|
|
1595
|
+
) -> str:
|
|
1596
|
+
"""Emit a complete, loadable Isabelle/HOL theory shallow-embedding ``formula``.
|
|
1597
|
+
|
|
1598
|
+
The returned string is a full ``theory <theory_name> imports Main begin ... end``
|
|
1599
|
+
that:
|
|
1600
|
+
|
|
1601
|
+
- declares the world type ``i`` and the accessibility relations actually used
|
|
1602
|
+
(alethic ``r``; **agent-indexed** epistemic ``rk`` / doxastic ``rb`` of type
|
|
1603
|
+
``'a \\<Rightarrow> i \\<Rightarrow> i \\<Rightarrow> bool``; deontic ``d``; temporal henceforth ``t`` and
|
|
1604
|
+
one-step ``n``);
|
|
1605
|
+
- defines every lifted operator (``mnot mand mor mimp miff mbox mdia``, the
|
|
1606
|
+
agent-indexed ``knows`` / ``believes``, ``obl`` / ``perm``, ``malways`` /
|
|
1607
|
+
``meventually`` / ``mnext``, the ``existsAt``-guarded ``mforall`` / ``mexists``,
|
|
1608
|
+
and ``mvalid``) as Isabelle abbreviations;
|
|
1609
|
+
- emits the frame axioms for ``frame`` (K/T/S4/S5/KD/KD45), the domain-regime
|
|
1610
|
+
axioms for ``mode``, and (when ``temporal_closure``) the refl+trans axioms on
|
|
1611
|
+
the temporal relation ``t`` so it denotes the henceforth (closure) relation,
|
|
1612
|
+
faithfully to :func:`satisfies_modal`;
|
|
1613
|
+
- states the formula as a **real** ``lemma "\\<lfloor> ... \\<rfloor>"`` whose body is the
|
|
1614
|
+
*lifted* embedding (not a ``to_unicode_str`` dump in a comment), followed by
|
|
1615
|
+
the proof text for ``tactic``;
|
|
1616
|
+
- for a many-sorted formula, states that every sort the formula uses is
|
|
1617
|
+
non-empty (``nonempty_sort<i>``) and that every sorted constant ``c:S`` is an
|
|
1618
|
+
element of ``S`` at EVERY world (``sort_member<i>``: ``S c w``, unguarded by
|
|
1619
|
+
``existsAt``, because a constant is rigid and may lie outside the domain of a
|
|
1620
|
+
world). A sort that occurs only through a sorted constant gets its guard
|
|
1621
|
+
declared too. A formula without a sorted constant gets no ``sort_member`` line
|
|
1622
|
+
and its text is unchanged.
|
|
1623
|
+
|
|
1624
|
+
Args:
|
|
1625
|
+
formula: the modal AST node to embed.
|
|
1626
|
+
mode: object-quantifier domain regime — ``constant`` / ``possibilist`` or
|
|
1627
|
+
``varying`` / ``increasing`` / ``cumulative`` / ``decreasing``.
|
|
1628
|
+
frame: alethic frame system in {K, T, S4, S5, KD, KD45}.
|
|
1629
|
+
tactic: a key of :data:`ISABELLE_TACTICS`. The default ``"sledgehammer"``
|
|
1630
|
+
emits a Sledgehammer hook plus ``oops`` so the theory loads without
|
|
1631
|
+
claiming a proof; ``"metis"`` / ``"auto"`` / ``"blast"`` / ``"smt"`` emit a
|
|
1632
|
+
``by ...`` proof. For these ``by``-style tactics the proof is prefixed
|
|
1633
|
+
with ``using <frame/domain axioms>`` so an axiom-dependent validity
|
|
1634
|
+
(T/S4/S5/KD/KD45, temporal closure, domain regime) can actually
|
|
1635
|
+
discharge — a bare ``axiomatization`` fact is not in the default claset.
|
|
1636
|
+
``"sorry"`` admits the goal.
|
|
1637
|
+
theory_name: the Isabelle theory name (must match the .thy filename).
|
|
1638
|
+
proof: an explicit proof block to emit verbatim in place of the ``tactic``
|
|
1639
|
+
preset (used by the Isabelle runner to splice in a prove-battery or a
|
|
1640
|
+
``nitpick`` invocation). When ``None`` the ``tactic`` preset is used.
|
|
1641
|
+
temporal_closure: when True, constrain ``t`` to be reflexive+transitive so
|
|
1642
|
+
``Always`` / ``Eventually`` denote the henceforth (refl-trans-closure)
|
|
1643
|
+
reading of :func:`satisfies_modal`. ``Next`` always uses the one-step
|
|
1644
|
+
relation ``n`` regardless.
|
|
1645
|
+
systems: optional per-family frame systems for the AGENT-INDEXED relations,
|
|
1646
|
+
e.g. ``{"epistemic": "S5", "doxastic": "KD45"}`` (families:
|
|
1647
|
+
``epistemic`` rk, ``doxastic`` rb, ``assertive`` rs, ``bouletic`` rw;
|
|
1648
|
+
values from {K, T, B, S4, S5, KD, KD45}). Each axiom leaves the agent
|
|
1649
|
+
schematic, so the property holds per agent — mirroring
|
|
1650
|
+
``to_thf_modal_full``'s parameter of the same name. A family whose
|
|
1651
|
+
operators do not occur in the formula is skipped.
|
|
1652
|
+
bridges: optional collection of CROSS-family bridge names (see
|
|
1653
|
+
:data:`BRIDGES`), **off by default**:
|
|
1654
|
+
|
|
1655
|
+
- ``"knowledge_implies_belief"`` — schema ``K_a φ → B_a φ``, frame
|
|
1656
|
+
correspondent ``rb ⊆ rk`` (axiom ``rb_in_rk``);
|
|
1657
|
+
- ``"sincerity"`` — schema ``Say_a φ → B_a φ``, correspondent
|
|
1658
|
+
``rb ⊆ rs`` (axiom ``rb_in_rs``);
|
|
1659
|
+
- ``"ought_implies_can"`` — schema ``Oφ → ◇φ``, correspondent
|
|
1660
|
+
``∀w. ∃v. d w v ∧ r w v`` (axiom ``d_meets_r``). Deliberately NOT
|
|
1661
|
+
the folklore ``d ⊆ r``, which measurably fails to validate
|
|
1662
|
+
``Oφ → ◇φ`` on its own and, with seriality, over-validates
|
|
1663
|
+
``□φ → Oφ``.
|
|
1664
|
+
|
|
1665
|
+
Each condition is the exact correspondent of its schema, so the
|
|
1666
|
+
bridge adds precisely that principle and nothing stronger. Requesting
|
|
1667
|
+
a bridge whose partner family does not occur in the formula raises
|
|
1668
|
+
``ValueError`` (its relation is never declared; skipping would emit a
|
|
1669
|
+
weaker logic and declaring it anyway is not conservative) — see
|
|
1670
|
+
:func:`_bridge_axioms`.
|
|
1671
|
+
|
|
1672
|
+
Returns:
|
|
1673
|
+
The theory text (newline-terminated).
|
|
1674
|
+
|
|
1675
|
+
Note:
|
|
1676
|
+
The toolkit only *emits* this theory — it does not run Isabelle or
|
|
1677
|
+
Sledgehammer. First-order modal logic is undecidable, so emission proves
|
|
1678
|
+
nothing; an external prover must discharge the lemma.
|
|
1679
|
+
"""
|
|
1680
|
+
_validate(mode, frame, tactic)
|
|
1681
|
+
# Reject a bare string / an unknown bridge name BEFORE any emission work, so a
|
|
1682
|
+
# typo fails fast rather than after the formula has been scanned and lifted.
|
|
1683
|
+
requested_bridges = _validate_bridges(bridges, "to_isabelle_modal")
|
|
1684
|
+
|
|
1685
|
+
# A numeral is a constant identified by its value (1 and 1.0 are one), named ``n1``:
|
|
1686
|
+
# a user constant spelled like it is refused, not merged with it.
|
|
1687
|
+
[formula], _ = numerals_as_constants([formula], where="to_isabelle_modal",
|
|
1688
|
+
spell=prefixed_numeral_name)
|
|
1689
|
+
|
|
1690
|
+
# A many-sorted formula (SortedQuantifier / SortedConstant) is relativized
|
|
1691
|
+
# ONCE, here, into the guarded plain FOL fol.to_fol also builds (∀x:S φ ->
|
|
1692
|
+
# ∀x (S(x) -> φ), etc.) — before _scan/_lift ever see it, rather than
|
|
1693
|
+
# adding a recursive case to either: the resulting sort-guard atom is an
|
|
1694
|
+
# ordinary unary predicate, which _scan/_lift already handle like any
|
|
1695
|
+
# other atom (see fol.qml's module docstring for the identical choice and
|
|
1696
|
+
# why "once, up front" also catches a SortedConstant anywhere in the
|
|
1697
|
+
# formula, not only directly under a SortedQuantifier).
|
|
1698
|
+
original = formula
|
|
1699
|
+
formula = formula._relativize([])
|
|
1700
|
+
# Rigid identity (qml's reading): `≠` -> `¬(=)`, a non-binary `=` refused. A formula
|
|
1701
|
+
# without an identity atom is returned unchanged.
|
|
1702
|
+
formula = _lower_identity(formula, "to_isabelle_modal")
|
|
1703
|
+
|
|
1704
|
+
sig = _Sig()
|
|
1705
|
+
_scan(formula, sig) # which modalities occur (relation/operator blocks)
|
|
1706
|
+
# de-colliding constant names (decls + usages agree); extended by the guard of a
|
|
1707
|
+
# sort that occurs only through a sorted constant, so that guard is declared.
|
|
1708
|
+
names = _names_with_sort_facts(
|
|
1709
|
+
formula, original,
|
|
1710
|
+
_axiom_variable_names(sig, original, frame, mode, temporal_closure, temporal_def,
|
|
1711
|
+
systems, bridges))
|
|
1712
|
+
sort_consts = _sort_consts(original, names)
|
|
1713
|
+
body = _lift(formula, names) # may raise on an unsupported node — do before emitting.
|
|
1714
|
+
|
|
1715
|
+
lines: List[str] = []
|
|
1716
|
+
lines.append(f"theory {theory_name}")
|
|
1717
|
+
lines.append(" imports Main")
|
|
1718
|
+
lines.append("begin")
|
|
1719
|
+
lines.append("")
|
|
1720
|
+
lines.append("(* Shallow semantical embedding (Benzmüller-style) of a quantified modal *)")
|
|
1721
|
+
lines.append(f"(* formula. mode={mode}, frame={frame}, tactic={tactic}. *)")
|
|
1722
|
+
lines.append("(* The toolkit EMITS this theory; it does not run Isabelle/Sledgehammer. *)")
|
|
1723
|
+
lines.append("(* First-order modal logic is undecidable: an external prover must close *)")
|
|
1724
|
+
lines.append("(* the lemma. Equality =/<> is RIGID identity: HOL's own =, no world *)")
|
|
1725
|
+
lines.append("(* argument, as in the first-order embedding (fol.qml). *)")
|
|
1726
|
+
if requested_bridges:
|
|
1727
|
+
# Comment only — the bridge AXIOMS are emitted with the other axioms below.
|
|
1728
|
+
named = ", ".join(n for n in _BRIDGES if n in requested_bridges)
|
|
1729
|
+
lines.append(f"(* cross-family bridges: {named} *)")
|
|
1730
|
+
lines.append("")
|
|
1731
|
+
lines.append("typedecl i \\<comment> \\<open>the type of worlds\\<close>")
|
|
1732
|
+
lines.append("typedecl e \\<comment> \\<open>the type of entities (objects and agents)\\<close>")
|
|
1733
|
+
# NOTE the fixed entity type: with a POLYMORPHIC 'a, Isabelle gives every
|
|
1734
|
+
# occurrence of an agent constant its own type instance, so the two
|
|
1735
|
+
# ``says a`` in ``Say_a(P→Q) → (Say_a P → Say_a Q)`` would read two
|
|
1736
|
+
# INDEPENDENT relation instances and nitpick could "genuinely" refute the
|
|
1737
|
+
# K axiom — a false INVALID observed live. One monomorphic e removes it.
|
|
1738
|
+
lines.append("")
|
|
1739
|
+
|
|
1740
|
+
# Relations + lifted operators for the modalities that actually occur.
|
|
1741
|
+
if sig.uses_alethic:
|
|
1742
|
+
lines += _alethic_block()
|
|
1743
|
+
lines.append("")
|
|
1744
|
+
if sig.uses_epistemic:
|
|
1745
|
+
lines += _epistemic_block()
|
|
1746
|
+
lines.append("")
|
|
1747
|
+
if sig.uses_doxastic:
|
|
1748
|
+
lines += _doxastic_block()
|
|
1749
|
+
lines.append("")
|
|
1750
|
+
if sig.uses_assertive:
|
|
1751
|
+
lines += _assertive_block()
|
|
1752
|
+
lines.append("")
|
|
1753
|
+
if sig.uses_bouletic:
|
|
1754
|
+
lines += _bouletic_block()
|
|
1755
|
+
lines.append("")
|
|
1756
|
+
if sig.uses_deontic:
|
|
1757
|
+
lines += _deontic_block()
|
|
1758
|
+
lines.append("")
|
|
1759
|
+
# When ``temporal_def`` and BOTH Always/Eventually (t) and Next (n) occur, the
|
|
1760
|
+
# henceforth relation is DEFINED as the reflexive-transitive closure of n
|
|
1761
|
+
# (``t = rtranclp n``) rather than a ``consts`` constrained by axioms — so nitpick
|
|
1762
|
+
# can determine t from a candidate n and actually refute non-theorems of the
|
|
1763
|
+
# closure fragment. n must be declared before t's definition references it.
|
|
1764
|
+
use_temporal_def = temporal_def and sig.uses_temporal
|
|
1765
|
+
# The one-step relation n is declared once when Next/Previous/Until/Since
|
|
1766
|
+
# needs it — or when the temporal definition t = rtranclp n references it;
|
|
1767
|
+
# the mnext/mprevious abbreviations only when their operator occurs.
|
|
1768
|
+
if sig.needs_next_rel or use_temporal_def:
|
|
1769
|
+
lines += _next_consts_block()
|
|
1770
|
+
if sig.uses_next:
|
|
1771
|
+
lines += _next_abbrev_block()
|
|
1772
|
+
if sig.uses_previous:
|
|
1773
|
+
lines += _previous_abbrev_block()
|
|
1774
|
+
lines.append("")
|
|
1775
|
+
if sig.uses_temporal:
|
|
1776
|
+
lines += _temporal_def_block() if use_temporal_def else _temporal_block()
|
|
1777
|
+
if sig.uses_past:
|
|
1778
|
+
lines += _past_temporal_block()
|
|
1779
|
+
lines.append("")
|
|
1780
|
+
if sig.uses_hybrid:
|
|
1781
|
+
lines += _hybrid_block(names)
|
|
1782
|
+
lines.append("")
|
|
1783
|
+
# Inductive least-fixpoint definitions for the binary interval operators; they
|
|
1784
|
+
# step over n (declared above), so they follow it.
|
|
1785
|
+
if sig.uses_until:
|
|
1786
|
+
lines += _until_block()
|
|
1787
|
+
lines.append("")
|
|
1788
|
+
if sig.uses_since:
|
|
1789
|
+
lines += _since_block()
|
|
1790
|
+
lines.append("")
|
|
1791
|
+
|
|
1792
|
+
# Connectives + validity (always present).
|
|
1793
|
+
lines += _CORE_ABBREVS
|
|
1794
|
+
lines.append("")
|
|
1795
|
+
if sig.has_quant:
|
|
1796
|
+
lines += _quant_block(mode)
|
|
1797
|
+
lines.append("")
|
|
1798
|
+
elif sort_consts and mode in _ACTUALIST_MODES:
|
|
1799
|
+
# An actualist sort witness (``_nonempty_sort_axioms``) names ``existsAt``;
|
|
1800
|
+
# a formula with no quantifier has no ``_quant_block`` to declare it.
|
|
1801
|
+
lines.append(_EXISTS_AT_DECL)
|
|
1802
|
+
lines.append("")
|
|
1803
|
+
lines += _VALID_ABBREV
|
|
1804
|
+
lines.append("")
|
|
1805
|
+
|
|
1806
|
+
# Signature of the user's predicates / constants / functions.
|
|
1807
|
+
decls = _signature_decls(names)
|
|
1808
|
+
if decls:
|
|
1809
|
+
lines += decls
|
|
1810
|
+
lines.append("")
|
|
1811
|
+
|
|
1812
|
+
# Axioms.
|
|
1813
|
+
axioms = _collect_axioms(sig, frame, mode, temporal_closure, temporal_def,
|
|
1814
|
+
systems, bridges, sort_consts,
|
|
1815
|
+
_sort_members(original, names))
|
|
1816
|
+
if axioms:
|
|
1817
|
+
lines += axioms
|
|
1818
|
+
lines.append("")
|
|
1819
|
+
|
|
1820
|
+
# The real lemma. Its proof brings the frame/domain axioms into scope (see
|
|
1821
|
+
# _proof_lines) so an axiom-dependent validity actually discharges; an explicit
|
|
1822
|
+
# ``proof`` override (used by the runner's prove-battery / nitpick) wins.
|
|
1823
|
+
lines.append(f'lemma modal_goal: "\\<lfloor> {body} \\<rfloor>"')
|
|
1824
|
+
if proof is not None:
|
|
1825
|
+
lines.append(proof.rstrip("\n"))
|
|
1826
|
+
else:
|
|
1827
|
+
lines += _proof_lines(tactic, _axiom_names(axioms))
|
|
1828
|
+
lines.append("")
|
|
1829
|
+
lines.append("end")
|
|
1830
|
+
return "\n".join(lines) + "\n"
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def modal_axiom_names(
|
|
1834
|
+
formula: Node,
|
|
1835
|
+
mode: str = "constant",
|
|
1836
|
+
frame: str = "K",
|
|
1837
|
+
temporal_closure: bool = True,
|
|
1838
|
+
systems: Optional[dict] = None,
|
|
1839
|
+
bridges: Optional[Iterable[str]] = None,
|
|
1840
|
+
) -> List[str]:
|
|
1841
|
+
"""The names of the ``axiomatization`` facts the emitted theory would declare.
|
|
1842
|
+
|
|
1843
|
+
These are exactly the frame / domain / temporal-link / agent-system / bridge
|
|
1844
|
+
axioms in scope for the proof of ``modal_goal``. The Isabelle runner needs them
|
|
1845
|
+
to build a ``using <axioms> by <method>`` proof (or to know none are required),
|
|
1846
|
+
so this exposes the same computation :func:`isabelle_modal_theory` uses
|
|
1847
|
+
internally.
|
|
1848
|
+
|
|
1849
|
+
Passing ``bridges`` here is load-bearing, not cosmetic: an ``axiomatization``
|
|
1850
|
+
fact is not in the default claset/simpset, so a validity that holds only
|
|
1851
|
+
because of a bridge (``K_a P → B_a P`` under ``knowledge_implies_belief``, say)
|
|
1852
|
+
cannot be discharged by a ``by blast`` that does not ``using rb_in_rk`` — the
|
|
1853
|
+
same failure mode the :data:`_BY_TACTICS` comment documents for the frame
|
|
1854
|
+
axioms.
|
|
1855
|
+
"""
|
|
1856
|
+
_validate(mode, frame, "oops")
|
|
1857
|
+
# The same numeral rule as isabelle_modal_theory: a numeral spelled like a constant is refused.
|
|
1858
|
+
[formula], _ = numerals_as_constants([formula], where="modal_axiom_names",
|
|
1859
|
+
spell=prefixed_numeral_name)
|
|
1860
|
+
sig = _Sig()
|
|
1861
|
+
# Relativize first, same as isabelle_modal_theory — _scan's has_quant flag
|
|
1862
|
+
# only needs to know a quantifier occurs, sorted or not, and a sort guard
|
|
1863
|
+
# is an ordinary atom to it either way; see that function's comment.
|
|
1864
|
+
relativized = _lower_identity(formula._relativize([]), "modal_axiom_names")
|
|
1865
|
+
_scan(relativized, sig)
|
|
1866
|
+
names = _names_with_sort_facts(
|
|
1867
|
+
relativized, formula,
|
|
1868
|
+
_axiom_variable_names(sig, formula, frame, mode, temporal_closure, False,
|
|
1869
|
+
systems, bridges))
|
|
1870
|
+
return _axiom_names(_collect_axioms(
|
|
1871
|
+
sig, frame, mode, temporal_closure, systems=systems, bridges=bridges,
|
|
1872
|
+
sorts=_sort_consts(formula, names),
|
|
1873
|
+
members=_sort_members(formula, names)))
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
def to_isabelle_modal(
|
|
1877
|
+
formula: Node,
|
|
1878
|
+
mode: str = "constant",
|
|
1879
|
+
frame: str = "K",
|
|
1880
|
+
tactic: str = "sledgehammer",
|
|
1881
|
+
temporal_closure: bool = True,
|
|
1882
|
+
proof: Optional[str] = None,
|
|
1883
|
+
systems: Optional[dict] = None,
|
|
1884
|
+
bridges: Optional[Iterable[str]] = None,
|
|
1885
|
+
) -> str:
|
|
1886
|
+
"""Emit a complete, loadable Isabelle/HOL theory embedding ``formula`` (real lemma).
|
|
1887
|
+
|
|
1888
|
+
This is the genuine replacement for the old skeleton stub: it returns a full
|
|
1889
|
+
``theory ModalEmbedding imports Main begin ... end`` with all lifted operators
|
|
1890
|
+
defined as abbreviations and the formula stated as a **real** ``lemma`` (not a
|
|
1891
|
+
comment). See :func:`isabelle_modal_theory` for the parameters; this thin
|
|
1892
|
+
wrapper fixes ``theory_name="ModalEmbedding"`` to match the historical
|
|
1893
|
+
signature ``to_isabelle_modal(formula, mode, frame)``.
|
|
1894
|
+
|
|
1895
|
+
``bridges`` is the opt-in cross-family option (``knowledge_implies_belief`` /
|
|
1896
|
+
``sincerity`` / ``ought_implies_can``, see :data:`BRIDGES`); it is off by
|
|
1897
|
+
default and raises ``ValueError`` when the formula does not mention both of a
|
|
1898
|
+
requested bridge's families.
|
|
1899
|
+
|
|
1900
|
+
Honesty: the toolkit emits the theory but never runs Isabelle/Sledgehammer.
|
|
1901
|
+
First-order modal logic is undecidable, so emission proves nothing — an
|
|
1902
|
+
external prover must discharge the lemma. Propositional modal K/T/S4/S5 are
|
|
1903
|
+
decidable, but this emitter does not itself decide them.
|
|
1904
|
+
"""
|
|
1905
|
+
return isabelle_modal_theory(
|
|
1906
|
+
formula, mode=mode, frame=frame, tactic=tactic,
|
|
1907
|
+
theory_name="ModalEmbedding", temporal_closure=temporal_closure, proof=proof,
|
|
1908
|
+
systems=systems, bridges=bridges)
|