unicode-fol-kit 0.7.0__tar.gz → 0.8.0__tar.gz
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_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/CHANGELOG.md +73 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/PKG-INFO +29 -2
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/README.md +28 -1
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/pyproject.toml +1 -1
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/__init__.py +9 -1
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/__init__.py +20 -4
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/intuitionistic.py +53 -8
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/isabelle_modal.py +117 -18
- unicode_fol_kit-0.8.0/unicode_fol_kit/hol/isabelle_runner.py +538 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/manyvalued.py +72 -8
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/.gitignore +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/LICENSE +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/__main__.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/__init__.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/fitch.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/fitch_search.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/lj.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/prover9_entailment.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/resolution.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/sequent.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/tableau.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/vampire_entailment.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/z3_arith.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/z3_equivalence.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/z3_fuzzy.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/z3_input.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/atp/z3_models.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/eval/__init__.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/eval/canonical.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/eval/predicate_match.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/eval/validate.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/__init__.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/_fol_nodes.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/_modal_nodes.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/_msfl_nodes.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/_so_nodes.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/_symbol_names.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/grammars/terminals.lark +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/lambda_tools.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/latex_input.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/modal_translation.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/msflparser.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/naming.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/nodes.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/normalforms.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/prover9_input.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/qml.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/tptp_input.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/unification.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/fol/verbalize.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/classical.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/secondorder.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/hol/thf_modal.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/py.typed +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/__init__.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/_modal_reject.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/fuzzy.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/intuitionistic.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/kripke.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/manyvalued.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/modelfinder.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/secondorder.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/tarski.py +0 -0
- {unicode_fol_kit-0.7.0 → unicode_fol_kit-0.8.0}/unicode_fol_kit/semantics/truthtable.py +0 -0
|
@@ -5,6 +5,79 @@ loosely based on [Keep a Changelog](https://keepachangelog.com/). Versioning is
|
|
|
5
5
|
semantic, but the project is pre-1.0 (alpha): a **minor** release may contain
|
|
6
6
|
breaking changes.
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-06-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Run a local Isabelle to actually *prove* the modal embeddings — `unicode_fol_kit.hol.isabelle_runner`.**
|
|
13
|
+
The `hol` exporters only *emit*; this opt-in module turns "emit" into "proven / refuted" when an
|
|
14
|
+
Isabelle/HOL install is present. `isabelle_decide_modal(φ, frame=…, mode=…)` decides validity by a
|
|
15
|
+
two-step procedure, read off the build's exit code: (1) emit the lemma with a proof battery that
|
|
16
|
+
brings the frame/domain axioms into scope (`using … by (blast | force | … | meson … | metis …)`) — a
|
|
17
|
+
successful `isabelle build` means **VALID**; (2) otherwise emit `nitpick[expect = genuine]`, whose
|
|
18
|
+
build succeeds **iff** a genuine finite counter-model exists — that means **INVALID**; (3) otherwise
|
|
19
|
+
**UNKNOWN**. Sound (Isabelle's kernel certifies the proof; nitpick reports only genuine
|
|
20
|
+
counter-models) and, necessarily, incomplete. The verdict is validated *differentially* against an
|
|
21
|
+
independent brute-force Kripke oracle (`satisfies_modal`) across K/T/S4/S5 in the test suite.
|
|
22
|
+
- `find_isabelle` / `isabelle_available` locate an install (explicit path → `UFK_ISABELLE_HOME` /
|
|
23
|
+
`ISABELLE_HOME` → `isabelle` on `PATH` → a light scan); `check_theory(text, name)` builds any
|
|
24
|
+
self-contained theory; `ModalVerdict` / `BuildResult` / `IsabelleInstall` carry the results.
|
|
25
|
+
- **Linux/macOS is the primary path** — `isabelle` is invoked directly. **Windows** is also
|
|
26
|
+
supported: the build is additionally routed through Isabelle's bundled Cygwin (Windows→`/cygdrive`
|
|
27
|
+
path translation, launcher exec-bit fixup, `bin` on `PATH`). No install path is hard-coded —
|
|
28
|
+
installations are discovered generically. Absent Isabelle raises a clear `IsabelleNotAvailable`;
|
|
29
|
+
the live tests skip.
|
|
30
|
+
- All re-exported at the package top level.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`to_isabelle_modal` emitted proofs could not discharge axiom-dependent validities.** A bare
|
|
35
|
+
`axiomatization where r_refl: …` fact is not in Isabelle's default claset, so `by blast` / `by auto`
|
|
36
|
+
/ `by (metis …)` could not see it: every validity that *depends* on a frame/domain axiom (T, S4, S5,
|
|
37
|
+
KD, KD45, the temporal closure, a domain regime) failed to prove even though the formula is valid and
|
|
38
|
+
the theory sound (only the pure-K fragment, like the K axiom, went through). The `by`-style tactics
|
|
39
|
+
now emit `using <frame/domain axioms> by …`. New `modal_axiom_names(φ, …)` exposes the axioms in
|
|
40
|
+
scope, and `isabelle_modal_theory(…, proof=…)` accepts an explicit proof override.
|
|
41
|
+
- **`to_isabelle_k3lp` / `to_isabelle_k3lp_entailment` emitted a non-discharging proof.**
|
|
42
|
+
`by (simp add: des_def)` does **not** close the validity lemma — `simp` cannot reduce `kneg v` /
|
|
43
|
+
`kor v …` while the quantified truth-value `v` is abstract (e.g. the LP-valid `p ∨ ¬p` failed). The
|
|
44
|
+
`∀` (valid) form is now discharged by exhausting each variable's three `tv` constructors
|
|
45
|
+
(`case_tac` + `simp_all`); the `∃` (refutation) form by supplying the counter-valuation as `rule exI`
|
|
46
|
+
witnesses computed at emit time. Every form is verified to build in real Isabelle.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **`to_isabelle_intuitionistic` now emits a real, Isabelle-checked proof for valid formulas.** The
|
|
51
|
+
proof is verdict-dependent: an intuitionistically valid formula gets `using r_refl r_trans by
|
|
52
|
+
(metis … | meson … | blast | auto)` that Isabelle discharges; a non-valid one is left `oops` (loads,
|
|
53
|
+
claims nothing — see `int_countermodel`). Previously the lemma was always `oops`. The verdict is
|
|
54
|
+
taken from the **decidable** S4 oracle `gmt_is_s4_valid` (Z3 on the GMT→S4 translation), *not* from
|
|
55
|
+
`int_valid`'s default 3-world bound — which is incomplete (IPL's finite-model bound grows with the
|
|
56
|
+
formula, so a non-theorem like `(p→q)∨(q→r)∨(r→p)` would otherwise be mis-emitted with a real
|
|
57
|
+
proof that cannot close).
|
|
58
|
+
|
|
59
|
+
### Audit hardening
|
|
60
|
+
|
|
61
|
+
A multi-agent adversarial soundness audit of the new subsystem confirmed five issues (no false
|
|
62
|
+
*VALID* is possible — Isabelle's kernel rejects a proof of a false goal), all fixed and re-checked
|
|
63
|
+
against a live Isabelle:
|
|
64
|
+
|
|
65
|
+
- **Intuitionistic proof gated on the decidable oracle** (above) — was a real proof emitted for an
|
|
66
|
+
IPL-*invalid* formula (theory then failed to build).
|
|
67
|
+
- **Atom-name collisions in the intuitionistic export.** An atom named `r` (or `w`/`v`/`u`) collided
|
|
68
|
+
with the accessibility relation / frame-axiom variables, emitting a duplicate `consts r` (or an
|
|
69
|
+
ill-typed axiom) so the theory never loaded — even for the valid `r → r`. `_isa_atom_name` now
|
|
70
|
+
reserves the structural identifiers and de-collides (`r` → `p_r`).
|
|
71
|
+
- **Temporal closure now pinned faithfully.** When `Always`/`Eventually` co-occur with `Next`, the
|
|
72
|
+
emitted henceforth relation `t` is now forced to equal the reflexive-transitive closure of the
|
|
73
|
+
one-step `n` (`t ⊆ rtranclp n`, with the existing `t_refl`/`t_trans`/`n_in_t` giving the converse),
|
|
74
|
+
matching `satisfies_modal`. Previously `t` could be any refl-trans superset of `n`, so a
|
|
75
|
+
`satisfies_modal`-valid temporal induction `(p ∧ G(p→Xp)) → Gp` was spuriously refuted (a false
|
|
76
|
+
*INVALID*); it is now never refuted (UNKNOWN — the closure fragment is a documented approximation).
|
|
77
|
+
- **`ModalVerdict.infra_error`.** An `UNKNOWN` whose build failed for an infrastructure reason
|
|
78
|
+
(syntax / JVM / timeout / …) now carries a short signature, so a broken theory or environment is no
|
|
79
|
+
longer indistinguishable from honest incompleteness. Never changes the verdict.
|
|
80
|
+
|
|
8
81
|
## [0.7.0] - 2026-06-28
|
|
9
82
|
|
|
10
83
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unicode-fol-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Parser and toolkit for first-order logic formulas using Unicode operators
|
|
5
5
|
Project-URL: Repository, https://github.com/fvossel/unicode-fol-kit
|
|
6
6
|
Project-URL: Issues, https://github.com/fvossel/unicode-fol-kit/issues
|
|
@@ -1095,7 +1095,7 @@ thf = to_thf_modal(BARCAN, mode="constant", frame="S5")
|
|
|
1095
1095
|
|
|
1096
1096
|
## Higher-order proving: Isabelle / THF exporters (the `hol` subpackage)
|
|
1097
1097
|
|
|
1098
|
-
Beyond the first-order embedding above, `unicode_fol_kit.hol` emits **Benzmüller-style shallow embeddings into higher-order logic** for *all* the non-fuzzy logics — as complete, self-contained problem files for an external prover (Leo-III / Satallax on TPTP **THF**, or Isabelle/HOL theories for Sledgehammer).
|
|
1098
|
+
Beyond the first-order embedding above, `unicode_fol_kit.hol` emits **Benzmüller-style shallow embeddings into higher-order logic** for *all* the non-fuzzy logics — as complete, self-contained problem files for an external prover (Leo-III / Satallax on TPTP **THF**, or Isabelle/HOL theories for Sledgehammer). The exporters **emit**; they do not themselves run a prover — and cannot decide everything: first-order modal logic, FOL, and SOL are undecidable / not even semi-decidable, so a successful emission means *"here is a sound problem a prover may discharge"*, never *"decided"*. (The propositional fragments — K3/LP, modal K/T/S4/S5 — are decidable, but these exporters target the general case.) If you *do* have Isabelle installed, the opt-in [`isabelle_runner`](#actually-running-it-the-isabelle-runner) runs it for you and reads back a real verdict.
|
|
1099
1099
|
|
|
1100
1100
|
```python
|
|
1101
1101
|
from unicode_fol_kit import MSFLParser
|
|
@@ -1120,6 +1120,33 @@ print(to_thf_modal_full(f)) # a complete TPTP THF problem; rk is AGENT-indexe
|
|
|
1120
1120
|
|
|
1121
1121
|
Each embedding is faithful to its in-toolkit ground-truth oracle (`satisfies_modal`, `kleene_value`, `satisfies_so`, `int_valid`), verified by an adversarial differential audit. Equality `=` / `≠` is an **uninterpreted, world-relativized** predicate throughout (not primitive HOL identity), consistently across every exporter.
|
|
1122
1122
|
|
|
1123
|
+
### Actually running it: the Isabelle runner
|
|
1124
|
+
|
|
1125
|
+
If a local **Isabelle/HOL** is installed, `unicode_fol_kit.hol.isabelle_runner` turns *emit* into *proven / refuted* — the toolkit writes the embedding to a scratch session, runs `isabelle build`, and reads the verdict off the build. It is **opt-in**: with no Isabelle present everything else still works and these calls raise a clear `IsabelleNotAvailable` (the live tests skip).
|
|
1126
|
+
|
|
1127
|
+
```python
|
|
1128
|
+
# doctest: +SKIP — needs a local Isabelle install
|
|
1129
|
+
from unicode_fol_kit import MSFLParser, isabelle_available, isabelle_decide_modal
|
|
1130
|
+
|
|
1131
|
+
parse = MSFLParser(modal=True).parse
|
|
1132
|
+
print(isabelle_available()) # True if Isabelle was located
|
|
1133
|
+
|
|
1134
|
+
print(isabelle_decide_modal(parse("□P → P"), frame="K")) # ModalVerdict[invalid, frame=K, …]
|
|
1135
|
+
print(isabelle_decide_modal(parse("□P → P"), frame="T")) # ModalVerdict[valid (by prove-battery), …]
|
|
1136
|
+
print(isabelle_decide_modal(parse("□P → □□P"), frame="S4")) # ModalVerdict[valid …]
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
`isabelle_decide_modal` decides validity (for the chosen `frame` / `mode`) in two steps, read off the build's exit code:
|
|
1140
|
+
|
|
1141
|
+
1. **Prove** — emit the lemma with a proof battery that brings the frame/domain axioms into scope (`using <axioms> by (blast | force | fastforce | auto | meson … | metis …)`). A successful `isabelle build` ⇒ **VALID**.
|
|
1142
|
+
2. **Refute** — otherwise emit `nitpick[expect = genuine]`, whose build succeeds **iff** Isabelle finds a genuine finite counter-model ⇒ **INVALID**.
|
|
1143
|
+
3. Otherwise ⇒ **UNKNOWN** (expected — first-order modal logic is undecidable).
|
|
1144
|
+
|
|
1145
|
+
This is **sound** (Isabelle's kernel certifies the proof; nitpick reports only *genuine* counter-models) and necessarily **incomplete**; `UNKNOWN` is a real outcome, not a failure. The verdict is validated *differentially* against an independent brute-force Kripke oracle (`satisfies_modal`) across K/T/S4/S5 in the test suite.
|
|
1146
|
+
|
|
1147
|
+
- **Locating Isabelle.** `find_isabelle()` looks at an explicit path, then `UFK_ISABELLE_HOME` / `ISABELLE_HOME`, then `isabelle` on `PATH`, then a light scan of standard install locations (no path is hard-coded); `isabelle_available()` is the cheap predicate. **Linux/macOS is the primary path** — `isabelle` is invoked directly; **Windows** is also supported, with the build routed through Isabelle's bundled Cygwin automatically (path translation + launcher exec-bit fixup).
|
|
1148
|
+
- **Any theory.** `check_theory(theory_text, theory_name)` builds an arbitrary self-contained theory and returns a `BuildResult` — used internally, and handy for the non-modal exporters (`to_isabelle_fol`, `to_isabelle_k3lp`, `to_isabelle_intuitionistic` …), whose emitted proofs are themselves built against real Isabelle in the test suite.
|
|
1149
|
+
|
|
1123
1150
|
## Many-valued logic (Kleene K3 / Priest LP)
|
|
1124
1151
|
|
|
1125
1152
|
`kleene_value()` evaluates a classical formula over the three truth values **0 (false)**, **½ (undefined / both)**, and **1 (true)** using the strong Kleene tables (`¬x = 1−x`, `∧ = min`, `∨ = max`, `→ = max(1−x, y)`). Validity, satisfiability, and entailment are decided by enumeration under a chosen set of **designated** values — Kleene's **K3** designates `{1}`, Priest's paraconsistent **LP** designates `{½, 1}`:
|
|
@@ -1071,7 +1071,7 @@ thf = to_thf_modal(BARCAN, mode="constant", frame="S5")
|
|
|
1071
1071
|
|
|
1072
1072
|
## Higher-order proving: Isabelle / THF exporters (the `hol` subpackage)
|
|
1073
1073
|
|
|
1074
|
-
Beyond the first-order embedding above, `unicode_fol_kit.hol` emits **Benzmüller-style shallow embeddings into higher-order logic** for *all* the non-fuzzy logics — as complete, self-contained problem files for an external prover (Leo-III / Satallax on TPTP **THF**, or Isabelle/HOL theories for Sledgehammer).
|
|
1074
|
+
Beyond the first-order embedding above, `unicode_fol_kit.hol` emits **Benzmüller-style shallow embeddings into higher-order logic** for *all* the non-fuzzy logics — as complete, self-contained problem files for an external prover (Leo-III / Satallax on TPTP **THF**, or Isabelle/HOL theories for Sledgehammer). The exporters **emit**; they do not themselves run a prover — and cannot decide everything: first-order modal logic, FOL, and SOL are undecidable / not even semi-decidable, so a successful emission means *"here is a sound problem a prover may discharge"*, never *"decided"*. (The propositional fragments — K3/LP, modal K/T/S4/S5 — are decidable, but these exporters target the general case.) If you *do* have Isabelle installed, the opt-in [`isabelle_runner`](#actually-running-it-the-isabelle-runner) runs it for you and reads back a real verdict.
|
|
1075
1075
|
|
|
1076
1076
|
```python
|
|
1077
1077
|
from unicode_fol_kit import MSFLParser
|
|
@@ -1096,6 +1096,33 @@ print(to_thf_modal_full(f)) # a complete TPTP THF problem; rk is AGENT-indexe
|
|
|
1096
1096
|
|
|
1097
1097
|
Each embedding is faithful to its in-toolkit ground-truth oracle (`satisfies_modal`, `kleene_value`, `satisfies_so`, `int_valid`), verified by an adversarial differential audit. Equality `=` / `≠` is an **uninterpreted, world-relativized** predicate throughout (not primitive HOL identity), consistently across every exporter.
|
|
1098
1098
|
|
|
1099
|
+
### Actually running it: the Isabelle runner
|
|
1100
|
+
|
|
1101
|
+
If a local **Isabelle/HOL** is installed, `unicode_fol_kit.hol.isabelle_runner` turns *emit* into *proven / refuted* — the toolkit writes the embedding to a scratch session, runs `isabelle build`, and reads the verdict off the build. It is **opt-in**: with no Isabelle present everything else still works and these calls raise a clear `IsabelleNotAvailable` (the live tests skip).
|
|
1102
|
+
|
|
1103
|
+
```python
|
|
1104
|
+
# doctest: +SKIP — needs a local Isabelle install
|
|
1105
|
+
from unicode_fol_kit import MSFLParser, isabelle_available, isabelle_decide_modal
|
|
1106
|
+
|
|
1107
|
+
parse = MSFLParser(modal=True).parse
|
|
1108
|
+
print(isabelle_available()) # True if Isabelle was located
|
|
1109
|
+
|
|
1110
|
+
print(isabelle_decide_modal(parse("□P → P"), frame="K")) # ModalVerdict[invalid, frame=K, …]
|
|
1111
|
+
print(isabelle_decide_modal(parse("□P → P"), frame="T")) # ModalVerdict[valid (by prove-battery), …]
|
|
1112
|
+
print(isabelle_decide_modal(parse("□P → □□P"), frame="S4")) # ModalVerdict[valid …]
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
`isabelle_decide_modal` decides validity (for the chosen `frame` / `mode`) in two steps, read off the build's exit code:
|
|
1116
|
+
|
|
1117
|
+
1. **Prove** — emit the lemma with a proof battery that brings the frame/domain axioms into scope (`using <axioms> by (blast | force | fastforce | auto | meson … | metis …)`). A successful `isabelle build` ⇒ **VALID**.
|
|
1118
|
+
2. **Refute** — otherwise emit `nitpick[expect = genuine]`, whose build succeeds **iff** Isabelle finds a genuine finite counter-model ⇒ **INVALID**.
|
|
1119
|
+
3. Otherwise ⇒ **UNKNOWN** (expected — first-order modal logic is undecidable).
|
|
1120
|
+
|
|
1121
|
+
This is **sound** (Isabelle's kernel certifies the proof; nitpick reports only *genuine* counter-models) and necessarily **incomplete**; `UNKNOWN` is a real outcome, not a failure. The verdict is validated *differentially* against an independent brute-force Kripke oracle (`satisfies_modal`) across K/T/S4/S5 in the test suite.
|
|
1122
|
+
|
|
1123
|
+
- **Locating Isabelle.** `find_isabelle()` looks at an explicit path, then `UFK_ISABELLE_HOME` / `ISABELLE_HOME`, then `isabelle` on `PATH`, then a light scan of standard install locations (no path is hard-coded); `isabelle_available()` is the cheap predicate. **Linux/macOS is the primary path** — `isabelle` is invoked directly; **Windows** is also supported, with the build routed through Isabelle's bundled Cygwin automatically (path translation + launcher exec-bit fixup).
|
|
1124
|
+
- **Any theory.** `check_theory(theory_text, theory_name)` builds an arbitrary self-contained theory and returns a `BuildResult` — used internally, and handy for the non-modal exporters (`to_isabelle_fol`, `to_isabelle_k3lp`, `to_isabelle_intuitionistic` …), whose emitted proofs are themselves built against real Isabelle in the test suite.
|
|
1125
|
+
|
|
1099
1126
|
## Many-valued logic (Kleene K3 / Priest LP)
|
|
1100
1127
|
|
|
1101
1128
|
`kleene_value()` evaluates a classical formula over the three truth values **0 (false)**, **½ (undefined / both)**, and **1 (true)** using the strong Kleene tables (`¬x = 1−x`, `∧ = min`, `∨ = max`, `→ = max(1−x, y)`). Validity, satisfiability, and entailment are decided by enumeration under a chosen set of **designated** values — Kleene's **K3** designates `{1}`, Priest's paraconsistent **LP** designates `{½, 1}`:
|
|
@@ -63,8 +63,13 @@ from .eval import (
|
|
|
63
63
|
validate, is_wellformed, validate_text, ValidationReport,
|
|
64
64
|
formulas_are_identical, match_predicates, formulas_are_matched_identical,
|
|
65
65
|
)
|
|
66
|
+
from .hol import (
|
|
67
|
+
find_isabelle, isabelle_available, isabelle_decide_modal, check_theory,
|
|
68
|
+
IsabelleInstall, IsabelleNotAvailable, BuildResult, ModalVerdict,
|
|
69
|
+
modal_axiom_names,
|
|
70
|
+
)
|
|
66
71
|
|
|
67
|
-
__version__ = "0.
|
|
72
|
+
__version__ = "0.8.0"
|
|
68
73
|
|
|
69
74
|
__all__ = [
|
|
70
75
|
"MSFLParser",
|
|
@@ -97,6 +102,9 @@ __all__ = [
|
|
|
97
102
|
"parse_prover9",
|
|
98
103
|
"qml_translate", "qml_is_valid", "qml_equivalent",
|
|
99
104
|
"to_thf_modal", "to_isabelle_modal", "BARCAN", "CONVERSE_BARCAN",
|
|
105
|
+
"find_isabelle", "isabelle_available", "isabelle_decide_modal", "check_theory",
|
|
106
|
+
"IsabelleInstall", "IsabelleNotAvailable", "BuildResult", "ModalVerdict",
|
|
107
|
+
"modal_axiom_names",
|
|
100
108
|
"from_z3", "parse_smtlib", "load_smtlib",
|
|
101
109
|
"find_fitch_proof", "fitch_prove", "is_valid_fitch",
|
|
102
110
|
"tableau_closed", "is_valid_tableau", "prove_tableau", "tableau_model",
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
This subpackage *emits* Benzmüller-style shallow embeddings of the toolkit's non-fuzzy
|
|
4
4
|
logics into higher-order logic — as complete, self-contained problem files for an
|
|
5
5
|
external prover (Leo-III / Satallax on TPTP **THF**, or Isabelle/HOL theories for
|
|
6
|
-
Sledgehammer). The
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Sledgehammer). The exporters only emit; a successful emission means "here is a sound
|
|
7
|
+
problem an external prover *may* discharge", never "decided" — first-order modal logic,
|
|
8
|
+
FOL and SOL are undecidable / not semi-decidable.
|
|
9
|
+
|
|
10
|
+
The optional :mod:`~unicode_fol_kit.hol.isabelle_runner` is the other half: if a local
|
|
11
|
+
Isabelle/HOL is installed, it actually *runs* it on the emitted modal theories
|
|
12
|
+
(prove-battery + nitpick) and reads a VALID / INVALID / UNKNOWN verdict off the build —
|
|
13
|
+
turning "emit" into "proven / refuted". It is sound (Isabelle's kernel certifies the
|
|
14
|
+
proof; nitpick reports only genuine counter-models) and, necessarily, incomplete.
|
|
9
15
|
|
|
10
16
|
Modules:
|
|
11
17
|
|
|
@@ -24,7 +30,13 @@ Modules:
|
|
|
24
30
|
via the Gödel–McKinsey–Tarski translation into S4 then the alethic SSE.
|
|
25
31
|
"""
|
|
26
32
|
|
|
27
|
-
from .isabelle_modal import
|
|
33
|
+
from .isabelle_modal import (
|
|
34
|
+
to_isabelle_modal, isabelle_modal_theory, ISABELLE_TACTICS, modal_axiom_names,
|
|
35
|
+
)
|
|
36
|
+
from .isabelle_runner import (
|
|
37
|
+
find_isabelle, isabelle_available, isabelle_decide_modal, check_theory,
|
|
38
|
+
IsabelleInstall, IsabelleNotAvailable, BuildResult, ModalVerdict, DEFAULT_METHODS,
|
|
39
|
+
)
|
|
28
40
|
from .thf_modal import to_thf_modal_full, thf_full_definitions, thf_full_frame_axioms
|
|
29
41
|
from .classical import to_thf_fol, to_isabelle_fol, to_thf_msfol, to_isabelle_msfol
|
|
30
42
|
from .manyvalued import (
|
|
@@ -39,6 +51,10 @@ from .intuitionistic import (
|
|
|
39
51
|
|
|
40
52
|
__all__ = [
|
|
41
53
|
"to_isabelle_modal", "isabelle_modal_theory", "ISABELLE_TACTICS",
|
|
54
|
+
"modal_axiom_names",
|
|
55
|
+
"find_isabelle", "isabelle_available", "isabelle_decide_modal", "check_theory",
|
|
56
|
+
"IsabelleInstall", "IsabelleNotAvailable", "BuildResult", "ModalVerdict",
|
|
57
|
+
"DEFAULT_METHODS",
|
|
42
58
|
"to_thf_modal_full", "thf_full_definitions", "thf_full_frame_axioms",
|
|
43
59
|
"to_thf_fol", "to_isabelle_fol", "to_thf_msfol", "to_isabelle_msfol",
|
|
44
60
|
"to_thf_k3lp", "to_isabelle_k3lp",
|
|
@@ -153,21 +153,40 @@ _ISA_BOX = "\\<^bold>\\<box>"
|
|
|
153
153
|
# a distinct, valid lowercase identifier. (The THF path aliases these separately.)
|
|
154
154
|
_ISA_ATOM_ALIASES = {"⊥": "bottom", "⊤": "top", "=": "feq", "≠": "fneq"}
|
|
155
155
|
|
|
156
|
+
# Identifiers the emitted theory already uses structurally: the world type ``i``, the
|
|
157
|
+
# accessibility relation ``r`` and the free variables ``w``/``v``/``u`` of its frame
|
|
158
|
+
# axioms (``r_refl: "r w w"``, ``r_trans: "r w v ⟹ r v u ⟹ r w u"``), and the lifted
|
|
159
|
+
# operator names. An atom predicate that sanitised to one of these would emit a SECOND
|
|
160
|
+
# ``consts`` with that name (a duplicate-constant clash that Isabelle rejects — so the
|
|
161
|
+
# theory would not load even for a valid formula, e.g. the atom ``r`` in ``r → r``), or
|
|
162
|
+
# make the frame axioms ill-typed (an atom ``w`` shadowing the type-``i`` bound var).
|
|
163
|
+
# A colliding atom is de-collided with the ``p_`` prefix (a trailing ``_`` would be a
|
|
164
|
+
# *bad* Isabelle identifier).
|
|
165
|
+
_ISA_RESERVED = frozenset({
|
|
166
|
+
"i", "r", "w", "v", "u",
|
|
167
|
+
"mnot", "mand", "mor", "mimp", "mbox", "mvalid",
|
|
168
|
+
})
|
|
169
|
+
|
|
156
170
|
|
|
157
171
|
def _isa_atom_name(name: str) -> str:
|
|
158
172
|
"""A safe, *distinct* Isabelle constant name for an atom predicate.
|
|
159
173
|
|
|
160
174
|
Distinct source atoms must map to distinct legal identifiers. Reserved/symbolic
|
|
161
175
|
atoms (⊥, ⊤, =, ≠) are aliased to dedicated lowercase ids; everything else is
|
|
162
|
-
sanitised char-by-char (alnum/underscore),
|
|
163
|
-
|
|
164
|
-
|
|
176
|
+
sanitised char-by-char (alnum/underscore), a ``p_`` prefix is prepended when the
|
|
177
|
+
result is empty, is the bare reserved ``_`` token, or starts with ``_`` (all of
|
|
178
|
+
which are illegal or reserved as a bare Isabelle constant name), and a name that
|
|
179
|
+
would collide with a structural identifier of the theory (the relation ``r``, an
|
|
180
|
+
axiom variable ``w``/``v``/``u``, the world type ``i``, a lifted operator) is
|
|
181
|
+
de-collided with the ``p_`` prefix (``r`` → ``p_r``).
|
|
165
182
|
"""
|
|
166
183
|
if name in _ISA_ATOM_ALIASES:
|
|
167
184
|
return _ISA_ATOM_ALIASES[name]
|
|
168
185
|
safe = "".join(c if (c.isalnum() or c == "_") else "_" for c in name)
|
|
169
186
|
if not safe or safe[0] == "_" or not safe[0].isalpha():
|
|
170
187
|
safe = "p_" + safe
|
|
188
|
+
if safe in _ISA_RESERVED:
|
|
189
|
+
safe = "p_" + safe # p_r / p_w / … (a trailing '_' is a bad Isabelle id)
|
|
171
190
|
return safe
|
|
172
191
|
|
|
173
192
|
|
|
@@ -209,9 +228,19 @@ def to_isabelle_intuitionistic(formula: Node, theory_name: str = "IPL_GMT") -> s
|
|
|
209
228
|
theory: worlds as a type ``i``; an accessibility relation ``r`` fixed to be
|
|
210
229
|
reflexive + transitive (the **S4** frame); the lifted modal operators as
|
|
211
230
|
abbreviations; the atoms as ``consts``; and the embedded formula as a genuine
|
|
212
|
-
``lemma`` (``\\<lfloor> T(formula) \\<rfloor>``)
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
``lemma`` (``\\<lfloor> T(formula) \\<rfloor>``). The lemma is a theorem iff
|
|
232
|
+
``formula`` is intuitionistically valid (Gödel–McKinsey–Tarski).
|
|
233
|
+
|
|
234
|
+
The proof is emitted **verdict-dependently** from the module's *decidable* S4
|
|
235
|
+
oracle :func:`gmt_is_s4_valid` (Z3 on the GMT→S4 translation, decisive on this
|
|
236
|
+
propositional fragment): when the formula is valid the theory carries a real,
|
|
237
|
+
Isabelle-checked proof (``using r_refl r_trans by (metis … | meson … | blast |
|
|
238
|
+
auto)`` — the S4 frame facts must be in scope, a bare ``axiomatization`` fact is
|
|
239
|
+
not in the default claset); when it is not valid the lemma is left ``oops`` (no
|
|
240
|
+
proof exists; see
|
|
241
|
+
:func:`~unicode_fol_kit.semantics.intuitionistic.int_countermodel`). The decision
|
|
242
|
+
deliberately does **not** use ``int_valid``'s default 3-world bound, which is
|
|
243
|
+
incomplete (IPL's finite-model bound grows with the formula).
|
|
215
244
|
|
|
216
245
|
Unlike :func:`unicode_fol_kit.fol.qml.to_isabelle_modal` (an alethic skeleton
|
|
217
246
|
that puts the lemma in a comment and defines only ``mnot``/``mbox``/``mvalid``),
|
|
@@ -219,6 +248,14 @@ def to_isabelle_intuitionistic(formula: Node, theory_name: str = "IPL_GMT") -> s
|
|
|
219
248
|
The toolkit only *emits* the theory; it does not run Isabelle.
|
|
220
249
|
"""
|
|
221
250
|
s4 = gmt_translate(formula)
|
|
251
|
+
# Decide validity with the module's own DECIDABLE S4 oracle (Z3 on the GMT→S4
|
|
252
|
+
# translation), NOT int_valid's bounded Kripke search: int_valid defaults to a
|
|
253
|
+
# 3-world bound, but IPL's finite-model-property bound grows with the formula, so
|
|
254
|
+
# int_valid can wrongly call a non-theorem valid — e.g. (p→q)∨(q→r)∨(r→p) is
|
|
255
|
+
# IPL-INVALID but needs 4 worlds to refute — which would emit a real proof for a
|
|
256
|
+
# NON-theorem that then fails to build. gmt_is_s4_valid is decisive on this
|
|
257
|
+
# propositional S4 fragment, so the emitted proof is sound by construction.
|
|
258
|
+
valid = gmt_is_s4_valid(formula)
|
|
222
259
|
atoms = _isa_atoms(s4)
|
|
223
260
|
R = "\\<Rightarrow>"
|
|
224
261
|
lines = [
|
|
@@ -259,8 +296,16 @@ def to_isabelle_intuitionistic(formula: Node, theory_name: str = "IPL_GMT") -> s
|
|
|
259
296
|
lines.append(f"consts {_isa_atom_name(a)} :: \"\\<sigma>\"")
|
|
260
297
|
lines.append("")
|
|
261
298
|
lines.append("lemma gmt_goal: \"\\<lfloor> " + _isa_render(s4) + " \\<rfloor>\"")
|
|
262
|
-
|
|
263
|
-
|
|
299
|
+
if valid:
|
|
300
|
+
# A real, Isabelle-checked proof. The S4 frame facts must be brought into
|
|
301
|
+
# scope (a bare `axiomatization` fact is not in the default claset), then a
|
|
302
|
+
# small method battery closes the GMT goal.
|
|
303
|
+
lines.append(" using r_refl r_trans")
|
|
304
|
+
lines.append(" by (metis r_refl r_trans | meson r_refl r_trans | blast | auto)")
|
|
305
|
+
else:
|
|
306
|
+
lines.append(" \\<comment> \\<open>NOT intuitionistically valid: no proof exists "
|
|
307
|
+
"(see int_countermodel for a Kripke counter-model)\\<close>")
|
|
308
|
+
lines.append(" oops")
|
|
264
309
|
lines.append("")
|
|
265
310
|
lines.append("end")
|
|
266
311
|
return "\n".join(lines) + "\n"
|
|
@@ -64,7 +64,8 @@ Public API
|
|
|
64
64
|
and the constant :data:`ISABELLE_TACTICS`.
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
import re
|
|
68
|
+
from typing import List, Optional
|
|
68
69
|
|
|
69
70
|
from unicode_fol_kit.fol.nodes import (
|
|
70
71
|
Node, Variable, Constant, Number, Function,
|
|
@@ -112,6 +113,27 @@ ISABELLE_TACTICS = {
|
|
|
112
113
|
"sorry": " sorry",
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
# Tactics whose ``by ...`` proof needs the frame / domain axioms brought into the
|
|
117
|
+
# proof context: a bare ``axiomatization where r_refl: ...`` fact is NOT in the
|
|
118
|
+
# default claset/simpset, so ``by blast`` / ``by auto`` / ``by (metis ...)`` cannot
|
|
119
|
+
# see it and any axiom-dependent validity (T/S4/S5/KD/KD45, temporal closure, a
|
|
120
|
+
# domain regime) fails to discharge even though the formula is valid and the theory
|
|
121
|
+
# sound. For these tactics we emit ``using <axioms> by <tactic>``. ``sledgehammer``
|
|
122
|
+
# finds its own facts; ``oops`` / ``sorry`` prove nothing, so they are excluded.
|
|
123
|
+
_BY_TACTICS = frozenset({"auto", "blast", "metis", "smt", "simp", "force"})
|
|
124
|
+
|
|
125
|
+
_AXIOM_NAME_RE = re.compile(r"axiomatization where (\w+):")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _axiom_names(axiom_lines: List[str]) -> List[str]:
|
|
129
|
+
"""Read the fact names out of emitted ``axiomatization where NAME: ...`` lines."""
|
|
130
|
+
out: List[str] = []
|
|
131
|
+
for ln in axiom_lines:
|
|
132
|
+
m = _AXIOM_NAME_RE.search(ln)
|
|
133
|
+
if m:
|
|
134
|
+
out.append(m.group(1))
|
|
135
|
+
return out
|
|
136
|
+
|
|
115
137
|
# Equality / inequality are uninterpreted, world-relativized predicates here (NOT
|
|
116
138
|
# primitive HOL `=`), matching satisfies_modal and the THF export. These aliases
|
|
117
139
|
# give them valid, distinct Isabelle constant names.
|
|
@@ -490,6 +512,20 @@ def _next_in_temporal_axiom() -> List[str]:
|
|
|
490
512
|
return ['axiomatization where n_in_t: "n w v \\<Longrightarrow> t w v"']
|
|
491
513
|
|
|
492
514
|
|
|
515
|
+
def _temporal_next_closure_axiom() -> List[str]:
|
|
516
|
+
r"""Pin the henceforth relation ``t`` to the reflexive-transitive CLOSURE of ``n``.
|
|
517
|
+
|
|
518
|
+
``n_in_t`` gives ``n ⊆ t`` and the temporal axioms make ``t`` reflexive+transitive,
|
|
519
|
+
so ``n** ⊆ t``. This adds the converse ``t ⊆ n**`` (``rtranclp n``), so ``t = n**``
|
|
520
|
+
EXACTLY — matching :func:`satisfies_modal`, which reads ``Always`` / ``Eventually``
|
|
521
|
+
over the reflexive-transitive *closure* of the one-step ``"temporal"`` relation.
|
|
522
|
+
Without it ``t`` may be any refl-trans superset of ``n``, and a formula valid only
|
|
523
|
+
because ``t = closure(n)`` (e.g. the temporal induction ``(p ∧ G(p→Xp)) → Gp``) is
|
|
524
|
+
spuriously refutable in the embedding — a **false INVALID** under the runner.
|
|
525
|
+
"""
|
|
526
|
+
return ['axiomatization where t_in_nstar: "t w v \\<Longrightarrow> rtranclp n w v"']
|
|
527
|
+
|
|
528
|
+
|
|
493
529
|
def _deontic_axioms() -> List[str]:
|
|
494
530
|
"""Standard Deontic Logic KD: the deontic relation is serial."""
|
|
495
531
|
return ['axiomatization where d_serial: "\\<exists>v. d w v"']
|
|
@@ -511,6 +547,48 @@ def _domain_axioms(mode: str) -> List[str]:
|
|
|
511
547
|
return out
|
|
512
548
|
|
|
513
549
|
|
|
550
|
+
def _collect_axioms(sig: "_Sig", frame: str, mode: str,
|
|
551
|
+
temporal_closure: bool) -> List[str]:
|
|
552
|
+
"""All ``axiomatization where ...`` lines the theory emits, in emission order.
|
|
553
|
+
|
|
554
|
+
Centralised so the proof emitter and :func:`modal_axiom_names` agree on exactly
|
|
555
|
+
which axioms are in scope (the proof must ``using`` precisely these to discharge
|
|
556
|
+
an axiom-dependent validity).
|
|
557
|
+
"""
|
|
558
|
+
axioms: List[str] = []
|
|
559
|
+
if sig.uses_alethic:
|
|
560
|
+
axioms += _frame_axioms(frame)
|
|
561
|
+
if sig.uses_deontic:
|
|
562
|
+
axioms += _deontic_axioms()
|
|
563
|
+
if sig.uses_temporal and temporal_closure:
|
|
564
|
+
axioms += _temporal_axioms()
|
|
565
|
+
# When Next (n) co-occurs with Always/Eventually (t), link n into t so the
|
|
566
|
+
# oracle-valid Always(P) -> Next(P) is a theorem of the emitted theory too...
|
|
567
|
+
if sig.uses_temporal and sig.uses_next:
|
|
568
|
+
axioms += _next_in_temporal_axiom()
|
|
569
|
+
# ...and, when t is the closure relation, pin t = n** exactly (not merely a
|
|
570
|
+
# refl-trans superset of n), so satisfies_modal-valid temporal-induction
|
|
571
|
+
# formulas are not spuriously refuted (false INVALID).
|
|
572
|
+
if temporal_closure:
|
|
573
|
+
axioms += _temporal_next_closure_axiom()
|
|
574
|
+
if sig.has_quant:
|
|
575
|
+
axioms += _domain_axioms(mode)
|
|
576
|
+
return axioms
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _proof_lines(tactic: str, axiom_ids: List[str]) -> List[str]:
|
|
580
|
+
"""The proof block for ``tactic``, bringing the frame/domain axioms into scope.
|
|
581
|
+
|
|
582
|
+
For a ``by``-style tactic (:data:`_BY_TACTICS`) we prepend ``using <axioms>`` so
|
|
583
|
+
``blast`` / ``auto`` / ``metis`` can actually use them; ``sledgehammer`` / ``oops``
|
|
584
|
+
/ ``sorry`` are emitted verbatim.
|
|
585
|
+
"""
|
|
586
|
+
body = ISABELLE_TACTICS[tactic]
|
|
587
|
+
if tactic in _BY_TACTICS and axiom_ids:
|
|
588
|
+
return [" using " + " ".join(axiom_ids), body]
|
|
589
|
+
return [body]
|
|
590
|
+
|
|
591
|
+
|
|
514
592
|
# --------------------------------------------------------------------------- #
|
|
515
593
|
# Type declarations for the discovered signature.
|
|
516
594
|
# --------------------------------------------------------------------------- #
|
|
@@ -558,6 +636,7 @@ def isabelle_modal_theory(
|
|
|
558
636
|
tactic: str = "sledgehammer",
|
|
559
637
|
theory_name: str = "ModalEmbedding",
|
|
560
638
|
temporal_closure: bool = True,
|
|
639
|
+
proof: Optional[str] = None,
|
|
561
640
|
) -> str:
|
|
562
641
|
"""Emit a complete, loadable Isabelle/HOL theory shallow-embedding ``formula``.
|
|
563
642
|
|
|
@@ -588,8 +667,15 @@ def isabelle_modal_theory(
|
|
|
588
667
|
tactic: a key of :data:`ISABELLE_TACTICS`. The default ``"sledgehammer"``
|
|
589
668
|
emits a Sledgehammer hook plus ``oops`` so the theory loads without
|
|
590
669
|
claiming a proof; ``"metis"`` / ``"auto"`` / ``"blast"`` / ``"smt"`` emit a
|
|
591
|
-
``by ...`` proof
|
|
670
|
+
``by ...`` proof. For these ``by``-style tactics the proof is prefixed
|
|
671
|
+
with ``using <frame/domain axioms>`` so an axiom-dependent validity
|
|
672
|
+
(T/S4/S5/KD/KD45, temporal closure, domain regime) can actually
|
|
673
|
+
discharge — a bare ``axiomatization`` fact is not in the default claset.
|
|
674
|
+
``"sorry"`` admits the goal.
|
|
592
675
|
theory_name: the Isabelle theory name (must match the .thy filename).
|
|
676
|
+
proof: an explicit proof block to emit verbatim in place of the ``tactic``
|
|
677
|
+
preset (used by the Isabelle runner to splice in a prove-battery or a
|
|
678
|
+
``nitpick`` invocation). When ``None`` the ``tactic`` preset is used.
|
|
593
679
|
temporal_closure: when True, constrain ``t`` to be reflexive+transitive so
|
|
594
680
|
``Always`` / ``Eventually`` denote the henceforth (refl-trans-closure)
|
|
595
681
|
reading of :func:`satisfies_modal`. ``Next`` always uses the one-step
|
|
@@ -661,37 +747,50 @@ def isabelle_modal_theory(
|
|
|
661
747
|
lines.append("")
|
|
662
748
|
|
|
663
749
|
# Axioms.
|
|
664
|
-
axioms
|
|
665
|
-
if sig.uses_alethic:
|
|
666
|
-
axioms += _frame_axioms(frame)
|
|
667
|
-
if sig.uses_deontic:
|
|
668
|
-
axioms += _deontic_axioms()
|
|
669
|
-
if sig.uses_temporal and temporal_closure:
|
|
670
|
-
axioms += _temporal_axioms()
|
|
671
|
-
# When Next (n) co-occurs with Always/Eventually (t), link n into t so the
|
|
672
|
-
# oracle-valid Always(P) -> Next(P) is a theorem of the emitted theory too.
|
|
673
|
-
if sig.uses_temporal and sig.uses_next:
|
|
674
|
-
axioms += _next_in_temporal_axiom()
|
|
675
|
-
if sig.has_quant:
|
|
676
|
-
axioms += _domain_axioms(mode)
|
|
750
|
+
axioms = _collect_axioms(sig, frame, mode, temporal_closure)
|
|
677
751
|
if axioms:
|
|
678
752
|
lines += axioms
|
|
679
753
|
lines.append("")
|
|
680
754
|
|
|
681
|
-
# The real lemma.
|
|
755
|
+
# The real lemma. Its proof brings the frame/domain axioms into scope (see
|
|
756
|
+
# _proof_lines) so an axiom-dependent validity actually discharges; an explicit
|
|
757
|
+
# ``proof`` override (used by the runner's prove-battery / nitpick) wins.
|
|
682
758
|
lines.append(f'lemma modal_goal: "\\<lfloor> {body} \\<rfloor>"')
|
|
683
|
-
|
|
759
|
+
if proof is not None:
|
|
760
|
+
lines.append(proof.rstrip("\n"))
|
|
761
|
+
else:
|
|
762
|
+
lines += _proof_lines(tactic, _axiom_names(axioms))
|
|
684
763
|
lines.append("")
|
|
685
764
|
lines.append("end")
|
|
686
765
|
return "\n".join(lines) + "\n"
|
|
687
766
|
|
|
688
767
|
|
|
768
|
+
def modal_axiom_names(
|
|
769
|
+
formula: Node,
|
|
770
|
+
mode: str = "constant",
|
|
771
|
+
frame: str = "K",
|
|
772
|
+
temporal_closure: bool = True,
|
|
773
|
+
) -> List[str]:
|
|
774
|
+
"""The names of the ``axiomatization`` facts the emitted theory would declare.
|
|
775
|
+
|
|
776
|
+
These are exactly the frame / domain / temporal-link axioms in scope for the
|
|
777
|
+
proof of ``modal_goal``. The Isabelle runner needs them to build a ``using
|
|
778
|
+
<axioms> by <method>`` proof (or to know none are required), so this exposes the
|
|
779
|
+
same computation :func:`isabelle_modal_theory` uses internally.
|
|
780
|
+
"""
|
|
781
|
+
_validate(mode, frame, "oops")
|
|
782
|
+
sig = _Sig()
|
|
783
|
+
_scan(formula, sig)
|
|
784
|
+
return _axiom_names(_collect_axioms(sig, frame, mode, temporal_closure))
|
|
785
|
+
|
|
786
|
+
|
|
689
787
|
def to_isabelle_modal(
|
|
690
788
|
formula: Node,
|
|
691
789
|
mode: str = "constant",
|
|
692
790
|
frame: str = "K",
|
|
693
791
|
tactic: str = "sledgehammer",
|
|
694
792
|
temporal_closure: bool = True,
|
|
793
|
+
proof: Optional[str] = None,
|
|
695
794
|
) -> str:
|
|
696
795
|
"""Emit a complete, loadable Isabelle/HOL theory embedding ``formula`` (real lemma).
|
|
697
796
|
|
|
@@ -709,4 +808,4 @@ def to_isabelle_modal(
|
|
|
709
808
|
"""
|
|
710
809
|
return isabelle_modal_theory(
|
|
711
810
|
formula, mode=mode, frame=frame, tactic=tactic,
|
|
712
|
-
theory_name="ModalEmbedding", temporal_closure=temporal_closure)
|
|
811
|
+
theory_name="ModalEmbedding", temporal_closure=temporal_closure, proof=proof)
|