unicode-fol-kit 0.5.2__tar.gz → 0.7.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.
Files changed (71) hide show
  1. unicode_fol_kit-0.7.0/CHANGELOG.md +472 -0
  2. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/PKG-INFO +354 -7
  3. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/README.md +353 -6
  4. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/pyproject.toml +1 -1
  5. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/__init__.py +39 -1
  6. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/__init__.py +47 -0
  7. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/fitch.py +1426 -0
  8. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/fitch_search.py +486 -0
  9. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/lj.py +352 -0
  10. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/sequent.py +948 -0
  11. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/tableau.py +255 -0
  12. unicode_fol_kit-0.7.0/unicode_fol_kit/atp/z3_input.py +181 -0
  13. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/__init__.py +12 -0
  14. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/_fol_nodes.py +3 -3
  15. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/_modal_nodes.py +100 -31
  16. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/_msfl_nodes.py +25 -9
  17. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/_so_nodes.py +6 -6
  18. unicode_fol_kit-0.7.0/unicode_fol_kit/fol/_symbol_names.py +67 -0
  19. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/modal_translation.py +8 -2
  20. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/msflparser.py +16 -174
  21. unicode_fol_kit-0.7.0/unicode_fol_kit/fol/prover9_input.py +210 -0
  22. unicode_fol_kit-0.7.0/unicode_fol_kit/fol/qml.py +575 -0
  23. unicode_fol_kit-0.7.0/unicode_fol_kit/fol/tptp_input.py +314 -0
  24. unicode_fol_kit-0.7.0/unicode_fol_kit/fol/verbalize.py +106 -0
  25. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/__init__.py +49 -0
  26. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/classical.py +494 -0
  27. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/intuitionistic.py +295 -0
  28. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/isabelle_modal.py +712 -0
  29. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/manyvalued.py +643 -0
  30. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/secondorder.py +529 -0
  31. unicode_fol_kit-0.7.0/unicode_fol_kit/hol/thf_modal.py +449 -0
  32. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/__init__.py +13 -0
  33. unicode_fol_kit-0.7.0/unicode_fol_kit/semantics/intuitionistic.py +153 -0
  34. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/kripke.py +73 -6
  35. unicode_fol_kit-0.7.0/unicode_fol_kit/semantics/modelfinder.py +192 -0
  36. unicode_fol_kit-0.7.0/unicode_fol_kit/semantics/truthtable.py +158 -0
  37. unicode_fol_kit-0.5.2/CHANGELOG.md +0 -213
  38. unicode_fol_kit-0.5.2/unicode_fol_kit/atp/__init__.py +0 -21
  39. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/fl.lark +0 -68
  40. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/fol.lark +0 -66
  41. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/modal.lark +0 -97
  42. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/msfl.lark +0 -68
  43. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/msfol.lark +0 -64
  44. unicode_fol_kit-0.5.2/unicode_fol_kit/fol/grammars/so.lark +0 -74
  45. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/.gitignore +0 -0
  46. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/LICENSE +0 -0
  47. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/__main__.py +0 -0
  48. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/prover9_entailment.py +0 -0
  49. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/resolution.py +0 -0
  50. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/vampire_entailment.py +0 -0
  51. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/z3_arith.py +0 -0
  52. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/z3_equivalence.py +0 -0
  53. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/z3_fuzzy.py +0 -0
  54. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/atp/z3_models.py +0 -0
  55. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/eval/__init__.py +0 -0
  56. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/eval/canonical.py +0 -0
  57. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/eval/predicate_match.py +0 -0
  58. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/eval/validate.py +0 -0
  59. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/grammars/terminals.lark +0 -0
  60. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/lambda_tools.py +0 -0
  61. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/latex_input.py +0 -0
  62. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/naming.py +0 -0
  63. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/nodes.py +0 -0
  64. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/normalforms.py +0 -0
  65. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/fol/unification.py +0 -0
  66. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/py.typed +0 -0
  67. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/_modal_reject.py +0 -0
  68. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/fuzzy.py +0 -0
  69. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/manyvalued.py +0 -0
  70. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/secondorder.py +0 -0
  71. {unicode_fol_kit-0.5.2 → unicode_fol_kit-0.7.0}/unicode_fol_kit/semantics/tarski.py +0 -0
@@ -0,0 +1,472 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file. The format is
4
+ loosely based on [Keep a Changelog](https://keepachangelog.com/). Versioning is
5
+ semantic, but the project is pre-1.0 (alpha): a **minor** release may contain
6
+ breaking changes.
7
+
8
+ ## [0.7.0] - 2026-06-28
9
+
10
+ ### Added
11
+
12
+ - **Epistemic / doxastic frame systems in the first-order embedding.**
13
+ `qml_is_valid` / `qml_equivalent` now take `systems={"epistemic": "S5", "doxastic":
14
+ "KD45"}`, emitting per-agent frame axioms for the agent-indexed `Rk` / `Rb` relations
15
+ — so e.g. factivity `∀x (K_x φ → φ)` is valid under a reflexive epistemic system. This
16
+ makes the FO path symmetric to the THF exporter (which already had `systems=`).
17
+ - **Quantified modal logic (QML) via shallow embeddings.** Object quantifiers `∀x` /
18
+ `∃x` under a modality, with the domain-regime semantics that decide the Barcan
19
+ formulas:
20
+ - **Semantics** — `KripkeModel` now takes per-world object domains (`domains={w: …}`
21
+ for varying, `domain=[…]` for constant), and `satisfies_modal` interprets `∀x` /
22
+ `∃x` *actualistically* (at a world `w` they range over `D_w`). Barcan
23
+ (`◇∃x A → ∃x ◇A`) and converse Barcan are valid/invalid exactly as the domains
24
+ vary. Backward compatible (omit the domains for the propositional fragment).
25
+ - **(A) First-order shallow embedding** (`unicode_fol_kit.fol.qml`): `qml_translate`
26
+ (with the existence predicate `E!` relativising actualist quantifiers + world/object
27
+ sort guards), `qml_axioms`, and `qml_is_valid` / `qml_equivalent` decide validity /
28
+ equivalence with Z3 per domain regime (`constant` / `increasing` / `decreasing` /
29
+ `varying`) and frame (K/T/S4/S5/KD/KD45). Sound but bounded-incomplete (first-order
30
+ modal logic is undecidable). The regime↔Barcan correspondence (BF ⇔ decreasing,
31
+ CBF ⇔ increasing, constant ⇔ both) is cross-checked against exhaustive Kripke-model
32
+ enumeration over every regime.
33
+ - **(B) Higher-order shallow embedding** — `to_thf_modal` emits a Benzmüller-style
34
+ TPTP **THF** problem (lifted `mbox`/`mdia`/`mforall`/`mexists`/`mvalid` + frame and
35
+ domain axioms) for an external higher-order prover (Leo-III, Satallax);
36
+ `to_isabelle_modal` emits an Isabelle/HOL skeleton. Alethic □/◇ fragment.
37
+ - All re-exported at the package top level; `BARCAN` / `CONVERSE_BARCAN` are provided
38
+ as the standard litmus formulas.
39
+ - **`⊕L` / `⊕R` (exclusive-or) rules in the sequent calculus** (`A⊕B ≡ ¬(A↔B)`),
40
+ closing the one connective that had no inference rule in either checker.
41
+ - **HOL / Isabelle / THF exporters for all non-fuzzy logics** (new
42
+ `unicode_fol_kit.hol` subpackage) — Benzmüller-style shallow semantical embeddings,
43
+ emitted as complete problem files for an external prover (the toolkit emits; it does
44
+ not run Leo-III / Satallax / Sledgehammer, and FOL / FO-modal / SOL are undecidable, so
45
+ emission means "a sound problem a prover *may* discharge", never "decided"):
46
+ - `hol.isabelle_modal.to_isabelle_modal` — a **real, loadable** Isabelle/HOL theory
47
+ (`theory … imports Main begin … end`, all lifted operators, frame + domain axioms,
48
+ the formula lifted into the embedding, a real `lemma`) for the **full modal family**:
49
+ alethic, epistemic/doxastic over **agent-indexed** relations, deontic, temporal.
50
+ Replaces the old alethic-only skeleton that emitted the lemma inside a comment.
51
+ - `hol.thf_modal.to_thf_modal_full` — the full-modal-family TPTP **THF** export
52
+ (agent-indexed epistemic/doxastic, deontic, temporal), extending the alethic-only
53
+ `qml.to_thf_modal`; self-contained (every relation the macro block references is declared).
54
+ - `hol.classical` (FOL + MSFOL), `hol.manyvalued` (K3 / LP, cross-checked against the
55
+ three-valued evaluator), `hol.secondorder` (native HOL predicate quantification),
56
+ `hol.intuitionistic` (Gödel–McKinsey–Tarski → S4 → HOL, cross-checked against
57
+ `int_valid`) — each → THF and Isabelle.
58
+ - **First-class agent terms for epistemic/doxastic operators.** The `agent` of
59
+ `Knows` / `Believes` is now a **term** (Variable or Constant), a structural child, so
60
+ it is reached by `free_variables` / substitution / β-reduction and can be a quantified
61
+ variable — `∀x (Student(x) → K_x φ)` (a quantified subject, "every student who…")
62
+ finally works: the agent is the bound `x`, not a baked-in relation-name suffix.
63
+ - The Kripke evaluator uses **per-agent** accessibility relations (one agent can know
64
+ what another does not); object quantifiers ground a bound agent before the modality
65
+ is reached.
66
+ - The first-order shallow embedding emits an **agent-indexed ternary** relation
67
+ `Rk(agent, w, v)` / `Rb(agent, w, v)`, so a bound agent genuinely quantifies over
68
+ agents (epistemic/doxastic relations are plain `K` — no frame axioms yet).
69
+ - Parser convention: a free `K_a` is a *named* agent (→ Constant); an agent bound by an
70
+ enclosing quantifier (`K_x` under `∀x`) stays a Variable. A bare string passed to the
71
+ constructor is coerced to a Constant (backward compatible).
72
+
73
+ ### Fixed
74
+
75
+ - **HOL/THF/Isabelle exporters: distinct symbols can no longer collapse to one name.**
76
+ A de-colliding resolver (`_ThfNames` / `_IsaNames` / the second-order and classical
77
+ resolvers) guarantees each distinct `(kind, name, arity)` symbol gets a unique emitted
78
+ functor / `consts`, and a predicate used at two arities is two symbols. Fixes a
79
+ **soundness hole** where a non-valid formula could be emitted as valid — e.g. `□Ab →
80
+ □ab` collapsing to the tautology `□ab → □ab` (also in the shipped `qml.to_thf_modal`)
81
+ — and the duplicate / ill-typed declarations that made `to_isabelle_so` / the modal
82
+ Isabelle theories non-loadable.
83
+ - **`to_isabelle_modal` is now the real exporter everywhere.** The top-level
84
+ `to_isabelle_modal` (and `fol.qml.to_isabelle_modal`) delegate to the complete
85
+ `unicode_fol_kit.hol.isabelle_modal` implementation (a loadable theory with a genuine
86
+ `lemma`), instead of the old alethic-only skeleton that emitted the lemma in a comment.
87
+ - **`substitute` is now capture-avoiding for a re-binding quantifier.** Substituting a
88
+ `Variable` (as `satisfies_modal` does when grounding an object quantifier) no longer
89
+ leaks past an inner quantifier that re-binds the same name: `substitute(∃x A(x), x, a)`
90
+ now correctly returns `∃x A(x)` unchanged. This fixes a **soundness bug in the modal
91
+ evaluator**, where a shadowed quantifier (e.g. `∀x ∃x A(x)`, also under modalities)
92
+ evaluated to the wrong truth value. (The `Lambda` branch already stopped at a rebinding
93
+ binder; the `Quantifier` / `SortedQuantifier` branches now do too.)
94
+ - **QML rejects an unknown `mode`.** `qml_translate` / `qml_is_valid` / `qml_equivalent`
95
+ raise `ValueError` on an unrecognised domain regime (e.g. a mis-capitalised
96
+ `'Increasing'`) instead of silently treating it as constant-domain and returning a
97
+ wrong validity verdict — matching the existing `frame` validation.
98
+ - **THF export: `possibilist` now emits the `const_dom` axiom.** Since the FO embedding
99
+ treats `possibilist` as a constant domain, the THF export does too (its actualist
100
+ `mforall`/`mexists` macros would otherwise model a varying domain), keeping the two
101
+ embeddings in agreement.
102
+ - **THF export: `=` / `≠` are now uninterpreted predicates, not primitive HOL identity.**
103
+ `to_thf_modal` previously rendered equality as rigid HOL `=`, diverging from
104
+ `satisfies_modal` and the FO embedding (which key `=` / `≠` as ordinary
105
+ world-relativized predicates) — so e.g. `∀x. x=x` was a THF theorem but
106
+ Kripke-falsifiable. All three embeddings now agree.
107
+
108
+ ### Internal
109
+
110
+ - **Retired the parser-equivalence oracle.** The registry-assembled parser was pinned
111
+ during migration by a byte-for-byte equivalence test against the legacy hand-written
112
+ per-mode `.lark` grammars + `*Transformer` classes. With that equivalence long
113
+ established, the reference pipeline was removed: the six per-mode grammar files
114
+ (`fol`/`msfol`/`msfl`/`fl`/`modal`/`so.lark`) and the legacy transformer classes are
115
+ gone; the registry self-assembly + grammar-structure guards remain. Only
116
+ `terminals.lark` survives (imported by the generated grammar at runtime).
117
+ - **Shared symbol-name de-collision** (`fol/_symbol_names.py`): the `dedupe` helper and
118
+ the THF/Isabelle resolver, previously copy-pasted across the exporters, are now one
119
+ `SymbolNames` base + `dedupe` used by the THF, Isabelle, classical, and second-order
120
+ exporters.
121
+ - **Agent token parsing reuses the scope pass.** The epistemic/doxastic agent is parsed
122
+ as a Variable and resolved to bound-Variable / free-Constant by `resolve_agent_variables`
123
+ (mirroring `resolve_lambda_scope`), instead of re-deciding variable-vs-constant with a
124
+ hand-copied lexer regex.
125
+ - **Adversarial audit of the proof checkers.** A multi-agent audit ran independent
126
+ oracles against every accepted proof/derivation of the Fitch and sequent checkers
127
+ across all logics (~75 hand-built adversarial constructions plus >1M fuzzed cases)
128
+ and found **no soundness hole**. Follow-up hardening from the audit's coverage
129
+ findings: added regression tests pinning the `verify_proof` robustness guards (a
130
+ clean `ProofResult(ok=False)` instead of a crash on a non-`Line` premise or subproof
131
+ assumption) and the mixed quantifier-spelling normalisation (`'forall'` vs `∀`); and
132
+ extended the sequent test corpus so the randomised mutation / Z3 audit now also
133
+ exercises `Cut`, weakening, contraction, `∨L`, `↔L`/`↔R`, `∃L`, and `⊕L`/`⊕R`.
134
+ - **Independent differential test harnesses promoted into the committed suite**, so the
135
+ checkers are cross-checked against oracles *other* than the ones they use internally:
136
+ - the alethic modal Fitch checker against brute-force Kripke-frame enumeration
137
+ (`tests/test_modal_differential.py`) — independent of its standard-translation/Z3
138
+ path, covering the K/T/S4/S5 frame-sensitivity facts;
139
+ - the second-order sequent rules against `satisfies_so` (`so_valid_tiny`) under a
140
+ randomised mutation audit (Z3 cannot evaluate second-order nodes);
141
+ - the object-level eigenvariable freshness condition (`∀R`/`∃L`) under randomised
142
+ fresh/non-fresh fuzzing.
143
+
144
+ ## [0.6.0] - 2026-06-27
145
+
146
+ A large reasoning-and-interoperability release: a Fitch natural-deduction checker and
147
+ backtracking prover, a Gentzen **LK** sequent calculus (with second-order rules) and an
148
+ intuitionistic **LJ** calculus, analytic tableaux, a finite model finder, truth tables,
149
+ reverse importers for TPTP / Prover9 / Z3-SMT-LIB, intuitionistic Kripke semantics, and
150
+ formula verbalization. All additive.
151
+
152
+ ### Added
153
+
154
+ - **Fitch-style natural-deduction proof checker** (`unicode_fol_kit.atp.fitch`) —
155
+ `Proof` / `Subproof` / `Line` / `Justification` proof objects (frozen, hashable,
156
+ JSON-serialisable) plus `check_proof` / `verify_proof`, all re-exported at the
157
+ package top level. The checker is *sound*: it returns `True` only when every
158
+ line genuinely follows by the cited rule and the proof's premises entail its
159
+ conclusion; `verify_proof` reports the certified sequent and the first failing
160
+ line with a reason.
161
+ - **Classical FOL / MSFOL** (`logic="fol"`/`"msfol"`) is checked by a syntactic
162
+ rule table: the connective rules (`∧I`/`∧E`, `∨I`/`∨E`, `→I`/`→E`, `↔I`/`↔E`,
163
+ `¬I`, `⊥I`/`⊥E`, `¬E` double-negation, `RAA`, `Reit`), the quantifier rules
164
+ (`∀I`/`∀E`, `∃I`/`∃E`) with the eigenvariable side-conditions enforced via a
165
+ capture-avoiding substitution, and equality (`=I`/`=E`, certified against Z3).
166
+ Citation accessibility is enforced (no reaching into a closed sibling
167
+ subproof) and discharge rules are checked against the proof's *open
168
+ assumptions*. `⊥` is the reserved logical constant `FALSUM`.
169
+ - **Three-valued K3 / LP** (`logic="K3"`/`"LP"`) certify each step against the
170
+ many-valued decision procedure (`semantics.manyvalued.entails`), so the
171
+ paraconsistency facts hold: LP rejects modus ponens, the disjunctive
172
+ syllogism, and explosion; K3 has no zero-premise theorems. Propositional
173
+ fragment.
174
+ - **Modal family** (`logic="K"`/`"T"`/`"S4"`/`"S5"`) certifies each step by the
175
+ standard translation to FOL plus the frame axioms, decided by Z3. Knowledge
176
+ (`Knows`, S5) is factive; belief (`Believes`, KD45) and obligation
177
+ (`Obligatory`, KD) are not. Propositional fragment; temporal and quantified
178
+ modal input are rejected.
179
+ - **Rendering** — `render_fitch` (Unicode/ASCII scope bars, line-number gutter,
180
+ justification column; also `proof.to_fitch()`) and `render_latex_fitch`
181
+ (self-contained LaTeX `array`; also `proof.to_latex_fitch()`).
182
+ - Tested with hand-derived proofs per rule, soundness guards for the broken
183
+ cases, and a randomised audit that checks every accepted proof line-by-line
184
+ against the Z3 / resolution oracles.
185
+ - **Gentzen sequent-calculus checker** (`unicode_fol_kit.atp.sequent`) — a
186
+ two-sided **LK** derivation checker re-exported at the package top level:
187
+ `Sequent` / `Derivation` / `Comprehension` / `SequentResult`, the helpers
188
+ `sequent` / `derive` / `axiom`, and `check_sequent_proof` / `verify_sequent_proof`
189
+ / `render_sequent_proof`. A sequent `Γ ⊢ Δ` (multisets, read `⋀Γ → ⋁Δ`) is
190
+ derived by a tree of rules; the checker verifies each step.
191
+ - Rules: `Ax`, structural `WL`/`WR`/`CL`/`CR`/`Cut`, the connective rules
192
+ (`¬`, `∧`, `∨`, `→`, `↔`, each L and R), the first-order quantifier rules
193
+ (`∀L`/`∀R`, `∃L`/`∃R`, with the eigenvariable condition on `∀R`/`∃L`), and the
194
+ **second-order** rules `∀²L`/`∀²R`, `∃²L`/`∃²R`. `∀²L`/`∃²R` instantiate a bound
195
+ predicate variable with a comprehension term `λx̄.ψ` (a `Comprehension`,
196
+ arity-checked, capture-avoiding); `∀²R`/`∃²L` use a fresh predicate
197
+ eigenvariable. This reaches the second-order fragment (`second_order=True`),
198
+ which has no first-order / SMT encoding.
199
+ - Sound but, for full second-order logic, necessarily **not a complete prover**
200
+ (second-order validity is not r.e.). Tested with hand derivations per rule,
201
+ soundness guards, a randomised mutation audit that re-checks every accepted
202
+ derivation node-by-node against Z3 (first-order fragment), and `satisfies_so`
203
+ spot-checks over small finite models (second-order fragment).
204
+ - **Analytic tableaux** (`unicode_fol_kit.atp.tableau`) — `is_valid_tableau`,
205
+ `prove_tableau`, `tableau_closed`, and `tableau_model`, re-exported at the top
206
+ level. A fourth proof method (beside resolution, Fitch, and the sequent calculus):
207
+ the signed-free α/β/γ/δ rules, a branch closing on `φ`/`¬φ`. Decidable and complete
208
+ for the propositional fragment; first-order γ-instantiation is bounded (`max_terms`
209
+ / `max_steps`). An *open* branch is returned as a countermodel by `tableau_model`.
210
+ - **Finite model finder** (`unicode_fol_kit.semantics.modelfinder`) — `find_model`,
211
+ `find_countermodel`, `is_satisfiable_finite`, and `is_valid_finite`. Brute-force
212
+ enumeration of finite `Structure`s (domain `1..max_size`) checked with the Tarskian
213
+ evaluator — the Mace4-style partner of the provers (a valid entailment has no
214
+ countermodel; an invalid one usually a small finite one). Bounded by
215
+ `max_candidates`.
216
+ - **Truth tables** (`unicode_fol_kit.semantics.truthtable`) — `truth_table` returning
217
+ a `TruthTable` (Markdown `render`, `is_tautology`/`is_contradiction`/`is_satisfiable`),
218
+ plus `is_tautology` / `is_contradiction` / `is_satisfiable_tt`, over **classical**,
219
+ Kleene **K3**, and Priest **LP** value sets (cross-checked against Z3 for classical).
220
+ - **Intuitionistic propositional logic** (`unicode_fol_kit.semantics.intuitionistic`) —
221
+ `IntKripkeModel` with monotone Kripke `forces`, and `int_valid` / `int_countermodel`
222
+ that decide intuitionistic validity by Kripke-model search (the logic has the
223
+ finite-model property). Excluded middle, double-negation elimination, and Peirce's
224
+ law are reported invalid with explicit countermodels; every intuitionistic validity
225
+ is also classically valid (cross-checked).
226
+ - **Intuitionistic sequent calculus LJ** (`unicode_fol_kit.atp.lj`) — `check_lj_proof`
227
+ / `verify_lj_proof`, re-exported at the top level. Gentzen **LJ** is the LK calculus
228
+ (it reuses the same `Sequent` / `Derivation` data model) restricted to **at most one
229
+ succedent formula** — the change that makes excluded middle / double-negation
230
+ elimination / Peirce's law underivable. Rules: `Ax`, structural `WL`/`WR`/`CL`/`Cut`,
231
+ `¬`/`∧`/`→`/`↔` (L and R), the split disjunction-right `∨R1`/`∨R2` and `∨L`, and the
232
+ quantifier rules `∀L`/`∀R`, `∃L`/`∃R`. Accepted derivations are cross-checked against
233
+ the intuitionistic Kripke decision procedure and classical Z3 validity.
234
+ - **Verbalization** (`unicode_fol_kit.fol.verbalize`) — `to_english`, an English
235
+ paraphrase of a formula (a readability aid, not a parse inverse).
236
+ - **Fitch proof *searcher*** (`unicode_fol_kit.atp.fitch_search`) — `find_fitch_proof`,
237
+ `fitch_prove`, and `is_valid_fitch`, re-exported at the package top level. A
238
+ goal-directed, **iterative-deepening backtracking** search over the classical
239
+ propositional + first-order natural-deduction rules (introduction rules, ∨/∃
240
+ elimination by case split, backward chaining, ex falso, and reductio/RAA — which
241
+ makes it complete for the propositional fragment). It builds an actual `Proof`
242
+ that is re-validated by `check_proof` before being returned, so it is **sound by
243
+ construction**: a search/assembly bug can only make it fail to find a proof, never
244
+ return an unsound one. Like the resolution prover it is sound but, under its depth
245
+ bound, incomplete (`None`/`False` = "not found within `max_depth`"). Tested with
246
+ curated theorems/non-theorems and a randomised cross-check that every found proof
247
+ is Z3-valid.
248
+ - **Reverse importers for TPTP, Prover9, and Z3/SMT-LIB** — the inverses of
249
+ `to_tptp` / `to_prover9` / `to_z3`, all re-exported at the package top level:
250
+ - **TPTP** (`unicode_fol_kit.fol.tptp_input`): `parse_tptp_formula` (one bare
251
+ FOF/CNF formula → `Node`), `parse_tptp` (a whole problem → a list of
252
+ `TptpFormula(name, role, formula)`), and `load_tptp` (a `.p`/`.tptp` file), via
253
+ a dedicated Lark grammar. Round-trips `to_tptp`; `%` and `/* */` comments are
254
+ ignored; predicates are re-capitalised (TPTP lowercases them); typed
255
+ `tff`/`thf` and `include` are out of scope.
256
+ - **Prover9/LADR** (`unicode_fol_kit.fol.prover9_input`): `parse_prover9`,
257
+ following `set(prolog_style_variables)` to match `to_prover9`'s output (a
258
+ trailing `.` is accepted). `Xor` round-trips to its `(a|b) & -(a&b)` desugaring.
259
+ - **Z3** (`unicode_fol_kit.atp.z3_input`): `from_z3` (a `z3.ExprRef` → `Node`)
260
+ and `parse_smtlib` / `load_smtlib` (SMT-LIB2 via Z3's own parser). Conversion is
261
+ meaning-preserving (Z3 collapses variables/constants/numbers onto one
262
+ uninterpreted sort, so a free variable returns as a `Constant`).
263
+ - Tested by round-trip over random formulas (`parse(node.to_X()) == node`) for
264
+ TPTP/Prover9 and by logical equivalence (`is_valid(Iff(node, from_z3(node.to_z3())))`)
265
+ for Z3, plus curated problem-file and SMT-LIB cases.
266
+
267
+ ## [0.5.2] - 2026-06-26
268
+
269
+ ### Added
270
+
271
+ - **Predicate-aligned string match** (`unicode_fol_kit.eval.predicate_match`) —
272
+ `match_predicates`, `formulas_are_matched_identical`, and
273
+ `formulas_are_identical`, re-exported at the package top level. A lexical
274
+ (string-level) evaluation notion for NL→FOL: `match_predicates` greedily
275
+ renames each predicate/function symbol in a predicted formula to the
276
+ lexically-closest symbol in the reference (by **normalised Levenshtein
277
+ distance**, accepting matches at or below a `max_norm_distance` threshold,
278
+ default `0.6`), so a structurally-correct answer that merely chose different
279
+ predicate names is not penalised. `formulas_are_identical` is the plain
280
+ whitespace- and case-insensitive string equality; `formulas_are_matched_identical`
281
+ combines the two (realign predicates, then compare). This is **complementary**
282
+ to the AST-level `exact_match`: the canonical match quotients out α-renaming /
283
+ commutativity / associativity / double negation but treats different predicate
284
+ names as a mismatch, whereas this matcher quotients out predicate-name (and
285
+ whitespace/case) differences but not the structural rewrites — the two are
286
+ typically reported as separate metrics. The Levenshtein distance is computed in
287
+ pure Python, so **no new dependency** is introduced; the matcher is
288
+ parser-independent and also applies to raw, not-yet-parseable model output.
289
+
290
+ ## [0.5.1] - 2026-06-24
291
+
292
+ ### Added
293
+
294
+ - **`check_logical_entailment_vampire`** — entailment checking via the
295
+ [Vampire](https://vprover.github.io/) theorem prover, a TPTP-based companion to
296
+ the existing Prover9 backend. Premises are emitted as TPTP `axiom`s and the
297
+ conclusion as a `conjecture`; the path to the Vampire executable is passed as
298
+ the `vampire_path` argument, and a `SZS status Theorem` result means the
299
+ entailment holds. Classical FOL only (the same fragment `to_tptp` supports).
300
+ Pass `use_wsl=True` to drive a Linux Vampire installed in WSL from a Windows
301
+ host (Vampire is launched via `wsl.exe`, with automatic `wslpath` translation of
302
+ the temp-file path).
303
+
304
+ ## [0.5.0] - 2026-06-24
305
+
306
+ Adds an NL→FOL **evaluation** toolkit and broad **non-classical logic** coverage —
307
+ modal/temporal/epistemic/deontic logic with Kripke semantics, three-valued
308
+ (Kleene/Priest) logic, and second-order quantification with finite-model
309
+ semantics. All additive; no breaking changes.
310
+
311
+ ### Added
312
+
313
+ - **`unicode_fol_kit.eval`** — `canonicalize` / `exact_match` (a fair "canonical
314
+ exact match" that quotients out bound-variable renaming, commutativity/
315
+ associativity, operand duplication, and double negation while staying logically
316
+ equivalent), and `validate` / `is_wellformed` / `validate_text` /
317
+ `ValidationReport` (free variables, inconsistent predicate/function arity,
318
+ leftover lambda nodes, parseability of raw model output).
319
+ - **Modal / temporal / epistemic / deontic logic** (`MSFLParser(modal=True)`):
320
+ node classes `Box`, `Diamond`, `Knows`, `Believes`, `Always`, `Eventually`,
321
+ `Next`, `Until`, `Obligatory`, `Permitted` with surface syntax `□ ◇`, `K_a` /
322
+ `B_a`, `Ⓖ Ⓕ Ⓝ Ⓤ`, `Ⓞ Ⓟ`. Kripke-model semantics (`KripkeModel`,
323
+ `satisfies_modal`, `reflexive_transitive_closure`) and a relational
324
+ `standard_translation()` to classical FOL so Z3/resolution can decide modal
325
+ validity. Propositional/ground (v1).
326
+ - **Many-valued logic** (`unicode_fol_kit.semantics.manyvalued`): three-valued
327
+ strong-Kleene evaluation `kleene_value` over {0, ½, 1}, and `is_valid` /
328
+ `is_satisfiable` / `entails` with selectable designated values for Kleene
329
+ **K3** (`{1}`) and Priest **LP** (`{½, 1}`, paraconsistent). `kleene_value` /
330
+ `DESIGNATED` are also re-exported at the package top level.
331
+ - **Second-order / monadic-second-order quantification** (`MSFLParser(second_order=True)`):
332
+ `SecondOrderQuantifier` (`∀P` / `∃P`, arity inferred from the body) with
333
+ finite-model semantics (`satisfies_so`) that enumerates relations over a finite
334
+ domain. Higher-order *terms* remain available via the existing lambda layer;
335
+ full HOL types are out of scope.
336
+ - **LaTeX import** — `parse_latex()` reads a LaTeX-math formula (the inverse of
337
+ `to_latex()`) and `latex_to_unicode()` does the LaTeX→Unicode translation alone;
338
+ accepts the exact `to_latex()` output (round-trips) and common hand-written synonyms.
339
+
340
+ ### Internal
341
+
342
+ - **Operator registry** — operators are now fully self-describing, decoupling
343
+ rendering *and* parsing from the central modules:
344
+ - *Rendering:* each operator registers its glyph, LaTeX markup, precedence, and
345
+ fixity via `register_operator()`; the Unicode and LaTeX renderers are driven
346
+ generically from the registry (no per-operator branches, no hand-maintained
347
+ dispatch tables).
348
+ - *Parsing:* each operator also registers its grammar fragment + transform via
349
+ `register_parser_op()`. `MSFLParser` now assembles BOTH the Lark grammar and
350
+ the transformer for every mode (FOL/MSFOL/MSFL/FL/modal/second-order) from the
351
+ registry — there is no longer a hand-written per-mode transformer or a
352
+ hand-loaded `.lark` grammar on the runtime path.
353
+ - Output and parsed ASTs are byte-identical to before (guarded by a
354
+ legacy-vs-registry equivalence test across a 190-formula × 6-mode corpus).
355
+ Adding an operator — or a whole new logic — is now a self-contained registry
356
+ entry in the operator's own module, with no edit to the renderers, the parser,
357
+ or any shared grammar file.
358
+ - **Hardening of the new evaluators.**
359
+ - The three-valued enumeration (`is_valid` / `is_satisfiable` / `entails`) now
360
+ scores each assignment with a compiled evaluator built once from the formula
361
+ (no per-assignment AST walk or atom re-rendering), and refuses to start an
362
+ enumeration above `manyvalued.MAX_MODELS` rather than hanging.
363
+ - Second-order `satisfies_so` refuses a `∀P` / `∃P` whose `2 ** (n ** k)`
364
+ relation space exceeds `secondorder.MAX_RELATIONS`, with a clear error.
365
+ - Added seeded, randomized cross-checks: the compiled three-valued path against
366
+ the reference `kleene_value` on every assignment; strong-Kleene algebraic
367
+ identities and the K3-vs-LP headline facts; second-order `∀P φ ≡ ¬∃P ¬φ`
368
+ duality and the agreement of `satisfies_so`'s classical core with the
369
+ first-order Tarski evaluator; render→parse round-trips over random FOL, modal,
370
+ Łukasiewicz, and second-order formulas; and a whole-tree `tree_str` / `to_dot`
371
+ coverage check over every node type.
372
+ - Łukasiewicz-algebra cross-checks for the fuzzy evaluator (strong/weak
373
+ De Morgan, double negation, the residuum `a → b ≡ ¬a ⊕ b`, and the defining
374
+ adjunction `a ⊗ b ≤ c ⟺ a ≤ b → c`) over random + boundary-grid valuations.
375
+ - Eval cross-checks against the independent Z3 oracle: `canonicalize` is
376
+ equivalence-preserving, `exact_match` absorbs the rewrites it should and never
377
+ merges Z3-inequivalent formulas, and `validate` flags free variables, arity
378
+ clashes, and leftover lambdas.
379
+ - A README example runner executes every `python` block in the docs (cumulative
380
+ namespace) so the documentation stays in lock-step with the code.
381
+
382
+ ## [0.4.0] - 2026-06-23
383
+
384
+ A large feature release adding model-theoretic and many-valued semantics, an
385
+ in-process theorem prover, more solver back-ends, and lambda/normal-form tooling,
386
+ plus a set of correctness fixes. **Includes one breaking change** (see *Changed*).
387
+
388
+ ### Added
389
+
390
+ - **Tarskian model theory** (`unicode_fol_kit.semantics.tarski`): define a
391
+ `Structure` (a "world" with a domain of individuals and interpretations of
392
+ constants, functions, predicates, and — for MSFOL — sorts) and compute a
393
+ formula's truth value with `satisfies()` / `models()` / `term_value()`.
394
+ Equality is built in; sorted quantifiers range over their sort universe.
395
+ - **Łukasiewicz fuzzy evaluator** (`fuzzy_evaluate`): the truth degree in [0, 1]
396
+ of an FL/MSFL formula under a valuation (`∀` = inf, `∃` = sup).
397
+ - **Fuzzy satisfiability / validity** via Z3 reals: `fuzzy_is_satisfiable`,
398
+ `fuzzy_is_valid`, `fuzzy_get_model`, `degree_expr`.
399
+ - **Arithmetic-aware Z3 translation**: `to_z3_arith`, `is_satisfiable_arith`,
400
+ `is_valid_arith`, `get_model_arith` interpret `+ - * /` and the comparisons
401
+ over Z3 reals/integers (the default `to_z3` keeps them uninterpreted).
402
+ - **Built-in first-order resolution prover** (`unicode_fol_kit.atp.resolution`):
403
+ `prove`, `is_valid_resolution`, `to_clauses`, `refute` — sound entailment and
404
+ validity checking in-process, without an external prover. Deliberately
405
+ incomplete under a step bound (never reports a non-theorem as proved);
406
+ `=` is treated as an uninterpreted predicate.
407
+ - **Lambda tooling**: `eliminate_lambdas` (beta-eta normalise and verify
408
+ lambda-free), `reduce_trace`, `beta_reduce_step`, `has_lambdas`.
409
+ - **Normal forms**: `to_dnf` (equivalence-preserving) and `to_tseitin_cnf`
410
+ (equisatisfiable, avoids the distributive blow-up).
411
+ - **Robinson unification**: `unify` (most general unifier with occurs-check) and
412
+ `apply_subst`.
413
+ - **Command-line interface**: `python -m unicode_fol_kit "<formula>" --mode … --to …`.
414
+ - **Typing**: a `py.typed` marker (PEP 561).
415
+ - **AST helper**: `Node.map_children`, the single structural-recursion engine.
416
+
417
+ ### Changed
418
+
419
+ - **BREAKING — AST nodes are now frozen dataclasses.** Every node is immutable
420
+ and **hashable**, so nodes can be put in sets, used as dict keys, and
421
+ deduplicated.
422
+ - **BREAKING — `Function.args` and `Atom.args` are now `tuple`s, not `list`s.**
423
+ Construction stays lenient: a list passed to the constructor is coerced to a
424
+ tuple, so `Atom("P", [x])` still works and `node == node` comparisons are
425
+ unaffected. Code that relied on `.args` being a *list* (in-place mutation,
426
+ `isinstance(node.args, list)`, or comparing `node.args == [...]`) must switch
427
+ to tuples.
428
+
429
+ ### Fixed
430
+
431
+ - `Xor.to_tptp` emitted `~|` (TPTP **NOR**); now emits `<~>` (correct XOR /
432
+ non-equivalence).
433
+ - TPTP arithmetic comparisons (`<`, `>`, `≤`, `≥`) are now emitted as prefix
434
+ dollar-word predicates (`$less(a, b)`), not as invalid infix expressions.
435
+ - Prover9 export: quantified variables are uppercased to match the emitted
436
+ `set(prolog_style_variables)`; nullary predicates render as bare propositional
437
+ atoms instead of the invalid `P()`.
438
+ - `to_latex` escapes the underscore in `c_`-prefixed constants (otherwise read as
439
+ a LaTeX subscript).
440
+ - Prover9 entailment: the temporary input file is no longer leaked when the
441
+ `prover9_path` is invalid (now cleaned up in a `finally`).
442
+ - Several README inaccuracies (clone URL, "three" vs "four" parser modes, the
443
+ exception class raised on mixing same-level connectives, the `Quantifier`
444
+ AST-table annotation), and the `formulas_are_equivalent` / `is_valid`
445
+ docstrings.
446
+
447
+ ### Documentation
448
+
449
+ - Clarified that `to_fol` / `to_msfol` is a classical **Boolean projection**
450
+ (the strong and weak Łukasiewicz connectives both collapse to `And`/`Or`), not
451
+ a fuzzy-preserving translation — use `fuzzy_evaluate` / the fuzzy Z3 solver for
452
+ many-valued degrees.
453
+
454
+ ### Internal
455
+
456
+ - Refactored the duplicated structural recursions (`free_variables`,
457
+ substitution, beta/eta reduction, scope resolution, `to_msfol`/`_relativize`,
458
+ term substitution) onto the shared `Node.map_children` / `_child_nodes`
459
+ helpers, removing the per-node `isinstance` chains while preserving the
460
+ binder-aware special cases and the public `TypeError` contracts.
461
+
462
+ ## [0.3.1] - earlier
463
+
464
+ - LaTeX export, normal forms, Horn check, Z3 models, traversal API, Graphviz export.
465
+
466
+ ## [0.3.0] - earlier
467
+
468
+ - `to_unicode_str()` with parser round-trip.
469
+
470
+ ## [0.2.1] - earlier
471
+
472
+ - README patch release.