Changelog¶
All notable changes to this project are documented in this file. The format is loosely based on Keep a Changelog. Versioning is semantic, but the project is pre-1.0 (alpha): a minor release may contain breaking changes.
[0.13.1] - 2026-07-03¶
Documentation correctness fix: the higher-order guide and the hol/__init__,
isabelle_modal, and thf_modal docstrings previously described FOL as “not even
semi-decidable”. That is wrong — FOL validity is recursively enumerable (Gödel
completeness), so FOL and the standard first-order modal logics K/T/S4/S5 are
undecidable but semi-decidable; only full second-order validity is not even
semi-decidable. Corrected in the source text and in the German translation. No code
changes.
[0.13.0] - 2026-07-03¶
Deep and shallow HOL embeddings with machine-checked faithfulness proofs,
reproducing Benzmüller, Faithful Logic Embeddings in HOL — Deep and Shallow
(arXiv:2502.19311). For each of four worlds-based non-classical logics the new
unicode_fol_kit.hol.deepshallow subpackage emits one self-contained Isabelle/HOL
theory carrying all three embeddings — a deep one (object syntax as a datatype
with a recursive truthD), a maximal (heavyweight) shallow one (every semantic
parameter explicit), and a minimal (lightweight) shallow one (accessibility and
valuation fixed as consts) — together with the primrec mappings dpToMax /
dpToMin and the theorems faithful1a/faithful1b (deep ↔ maximal),
faithful2/faithful3 (↔ minimal in the fixed model) and sound_min, each closed
by a one-line induct. Unlike the existing emit-only shallow exporters, these
theories are verified end to end by the Isabelle runner (check_theory): a
green build means Isabelle’s kernel discharged every faithfulness proof.
Added:
modal_faithfulness_theory/modal_to_deep— propositional modal logic K.intuitionistic_faithfulness_theory/int_to_deep— intuitionistic propositional logic (Kripke semantics: preorder ≤, persistent valuation).conditional_faithfulness_theory/counterfactual_to_deep— Lewis counterfactual (sphere) logic, matchingsemantics.conditional.relevant_faithfulness_theory/rel_to_deep— relevant logic B (simplified Routley–Meyer semantics: normal worlds, Routley star, ternary relation), matchingsemantics.relevant.Isabelle-gated live tests that build each emitted theory and assert the kernel discharges the five faithfulness theorems, plus always-run structure tests.
The stack targets the propositional/schematic fragment (where induction over the
syntax datatype applies); the quantified decision path stays in
unicode_fol_kit.hol.isabelle_modal.
[0.12.0] - 2026-07-03¶
The four families the documentation used to list as deliberately out of scope are now first-class: relevant logic, hybrid logic, dependence / IF logic, and the substructural pair (intuitionistic linear logic and the Lambek calculus). Each ships with parser support, real semantics or a real proof system, hand-checked tests cross-checked against existing oracles, and a documentation page.
Added¶
Relevant logic B (
semantics.relevant) — the Priest–Sylvan simplified Routley–Meyer semantics for the basic affixing system B over the classical propositional syntax:RelevantModel(worlds, normal worlds, involutive Routley star, ternary accessibility at non-normal worlds),rel_satisfies, and a verified exhaustive countermodel searchrel_countermodel/rel_valid(bounded, mirroringint_valid’s contract). The headline non-theorems come out right:p → (q → p), explosion, disjunctive syllogism, Peirce, and evenp ∨ ¬pare refuted, while the B-validities hold. Random formulas are cross-checked against the classical Z3 oracle (every classical countermodel is a one-world Routley–Meyer model). B is the decidable base; full R is undecidable (Urquhart 1984) and stays out of scope.Hybrid logic H(@) — nominals and the satisfaction operator inside the modal mode:
MSFLParser(modal=True)now parses nominals (i,here) as formulas and@i φ;KripkeModeltakes anominals=assignment andsatisfies_modalevaluates both constructs; the standard translation maps a nominal to a world-equality with a reservednom_…constant, and the newhybrid_is_valid(φ, frame="K"|"T"|"S4"|"S5")decides hybrid validity via Z3. The ↓ binder is deliberately absent (it makes validity undecidable); the modal tableau rejects hybrid input with a clear error instead of guessing.Dependence / IF logic (
MSFLParser(dependence=True)+semantics.team) — Väänänen-style team semantics over the toolkit’s finiteStructures: dependence atoms=(x, y)(functional determination;=(x)constancy) and IF slashed existentials∃y/{x} φ(witness chosen uniformly in the slashed variables), with the splitting∨, strict∃, duplicating∀, and flat literals.team_satisfies/team_modelsevaluate; the fragment is honest — no→/↔, negation on atoms only, and no classical export (dependence logic is expressively second-order). Tested with flatness and downward-closure property tests against the Tarski evaluator and the classic|dom| = 1signalling facts.Substructural logics — two new sequent provers in
atp:MSFLParser(linear=True)parses propositional intuitionistic linear logic (⊗ & ⊕ ⊸ ! 𝟙) andill_prove/ill_derivable/check_ill_proofdecide it by cut-free backward search — a complete decision procedure for the !-free fragment, honestly bounded when!occurs;MSFLParser(lambek=True)parses Lambek-calculus types (• \ /over categories likeNP,S) andlambek_prove/lambek_derivableare a complete, terminating decision procedure for L (ordered, nonempty antecedents —A, A\B ⊢ Bderives,A\B, A ⊢ Bdoes not). Every found derivation is re-validated by its checker, and every derivable sequent’s classical collapse is verified Z3-valid in the test suite.The frontier constructs render, verbalize (
to_english), serialise (dict/JSON), and round-trip like every other node family;exact_match/canonicalizeα-normalise the slashed binder (slash sets follow their enclosing binders’ renames).
[0.11.0] - 2026-07-02¶
Cross-logic parity for the natural-language / CCG translation-target constructs, plus a
capture-safety fix in the canonical form. The guiding principle: anything expressible in
classical FOL should also be expressible in the richer classical logics that extend it
(modal, second-order, and many-sorted), so a construct is no longer arbitrarily confined
to the plain fol mode.
Added¶
Counting quantifier, concessive connective, and degree/cardinality terms across the classical modes.
Count(∃≥n/∃≤n/∃=n),Contrast(Ⓒ),Measure(μ), andCardinality(|{v : φ}|) — previously accepted only byMSFLParser()— now also parse in modal mode (MSFLParser(modal=True)) and second-order mode (MSFLParser(second_order=True)), with identical semantics. A CCG-derived form that nests a counting quantifier under a modal or second-order operator — e.g.B_a ∃≥3 x Pass(x)(“a believes at least three x pass”) — now parses and round-trips as a single string, not only as a hand-assembled AST.MSFOL many-sorted parity:
SortedCountandSortedCardinality. The many-sorted mode (MSFLParser(many_sorted=True)) gains sort-annotated counterparts of the counting quantifier and the set-cardinality term —∃≥n x:S φ(SortedCount) and|{v:S : φ}|(SortedCardinality) — mirroringSortedQuantifier.SortedCountreduces to plain FOL by guarding the matrix with the sort predicate and reusing the distinct-witnesses encoding (so it is Z3-checkable);SortedCardinalityis second-order and, likeCardinality, has no first-order export.Contrast, theMeasureterm, andXor(⊕, previously excluded from MSFOL) are available in MSFOL too. Both new node types are exported from the package root.The fuzzy modes (FL / MSFL) deliberately do not gain these constructs: fuzzy logic is not a conservative extension of classical FOL (it reinterprets the connectives and its evaluator rejects comparison atoms), so counting/degree/cardinality/concession have no faithful truth semantics there. This is an intentional boundary, not an omission.
Fixed¶
exact_match/canonicalizenow α-normalize the counting and cardinality binders.CountandCardinality(and their new sorted variants) bind a variable, but the canonical form previously treated onlyQuantifier/SortedQuantifier/Lambdaas binders. As a result∃≥3 x P(x)and∃≥3 y P(y)— α-equivalent — compared as unequal. They now canonicalize identically, while the op, the boundn, the sort, and the matrix stay significant.Capture-safety of the canonical bound-variable rename. The rename to canonical names
q0, q1, …did not avoid free variables that happen to be named the same way, so the logically-distinct∃x P(x)and∃x P(q0)(whereq0is free) both canonicalized to∃q0 P(q0)— a false positive inexact_matchthat violated equivalence-preservation. The rename now skips any canonical name that occurs free in the formula. The bug affected every binder (Quantifier/Lambdaincluded) and was newly reachable through the counting binders; the fix covers all of them.
[0.10.1] - 2026-06-30¶
Fixed¶
unicode_fol_kit.__version__now reports the actual package version (it lagged at"0.9.0"in the 0.10.0 release). The Sphinx / Read the Docs configuration reads this string for the documented version, so the docs version display is corrected as well.
[0.10.0] - 2026-06-30¶
Natural-language → logic front-end support: attitude operators, a counting quantifier, degree and cardinality terms, a concessive connective, a whole-file Prover9 reader, TPTP single-quoted atoms, and name sanitisation. These are translation targets and interchange aids for pipelines (e.g. CCG → logical form, or OWL → FOL) that need a determinate, round-trippable representation of cardinal determiners, degree comparatives, counting comparisons, reportative/desiderative attitudes, and concessive coordination — and that ingest external problem files whose symbol names are not native MSFLParser tokens.
Added¶
Assertive and bouletic attitude operators —
Says(Say_a φ, a asserts that φ) andWants(Want_a φ, a wants it to be that φ), modalagent_prefixoperators alongsideKnows/Believes(the agent is a β-bindable term, so∀x (Speaker(x) → Say_x φ)works).Saysis non-factive and non-doxastic;Wantsis non-veridical — each a minimal normal modality K over its own per-agent accessibility relation ("Say:"+a/"Want:"+a), with no frame conditions. Wired into the parser (MSFLParser(modal=True)),satisfies_modal, the modal tableau (is_modal_valid),to_english, and the dict/Unicode/LaTeX renderers.Counting quantifier
Count—∃≥n/∃≤n/∃=n(at least / at most / exactly n). The bound n is carried symbolically (aNumber, never expanded into single-letter variables), so an arbitrarily large n —∃≥500 x …— is represented exactly and coordinated counts compose asAnd(Count(…), Count(…)). First-order expressible:to_z3/to_prover9/to_tptplower it to the standard distinct-witnesses encoding (a balanced conjunction tree, verified against Z3; the expansion is bounded ton ≤ 500— beyond that the exporters raise a clear error, while the symbolic node still round-trips for anyn). Parses with the defaultMSFLParser().Degree term
Measure—μ(entity, dimension), a first-class measure-function term for bare quantity comparatives (μ(x, height) > μ(y, height)); exports as the uninterpreted binary functionmeasure(entity, dimension).Set-cardinality term
Cardinality—|{v : φ}|, the count of individuals satisfyingφ, for faithful counting comparisons (|{v : Votes(x, v)}| > |{v : Votes(y, v)}|). It bindsv; set cardinality is second-order, so it has no first-order export.Concessive connective
Contrast—P Ⓒ Q(whereas / although / but), truth-functionally identical to∧but kept distinct so a front-end can preserve the concession instead of flattening it.Whole-file Prover9 reader —
load_prover9(path)/parse_prover9_problem(text)read a Prover9 / LADR input file:set/clear/assigndirectives (recognised and skipped),formulas(LIST). … end_of_list.blocks, and bare top-level formulas, returningProver9Formula(role, formula)records (the list name becomes the role). Completes the file-reading trio withload_tptpandload_smtlib.TPTP single-quoted atoms — the TPTP reader now accepts single-quoted functor names (
'http___example_org_Thing'(X)), the form OWL→FOL dumps use for IRIs; the quotes are stripped and the\'/\\escapes unescaped. A 2.3 MB / 7198-formula OWL ontology dump reads end to end.Name sanitisation for round-trippable rendering —
sanitize_names(node)/sanitize_all(nodes)rewrite imported symbol names to MSFLParser-legal tokens (predicates[A-Z]…, functions multi-letter lowercase, constants kept or put in thec_…form, variables[a-z][0-9]*) so thatparse(node.to_unicode_str())round-trips. Already-legal names are unchanged; a returnedNameMappingrecovers the originals and keeps names consistent across a whole problem. (Verified: all 7198 formulas of the OWL dump round-trip after sanitisation.)
[0.9.0] - 2026-06-29¶
A broad non-classical expansion: a native modal tableau, past-tense temporal logic, more modal frames, four-valued FDE and a general matrix layer, fuzzy t-norms, first-order intuitionistic and second-order search, sorted model finding, a description-logic subpackage, and a cluster of non-classical neighbours.
Added¶
Native modal tableaux —
unicode_fol_kit.atp.modal_tableau. A labelled, install-free tableau that decides the propositional box/diamond family (alethic□/◇, epistemicK_a, doxasticB_a, deonticO/P, one-step temporalNext) over the systems K, T, D, B, K4, K45, S4, S5, KD45 plus per-family systems.is_modal_valid/modal_decide/modal_countermodelreturn valid / invalid / unknown with a Kripke counter-model verified againstsatisfies_modal. The classicalis_valid_tableau/prove_tableau/tableau_closednow route modal inputs here instead of raisingValueError.Past-tense temporal operators — Prior tense logic:
Historically(⒣),Once(⒫),Previous(⒴) and binarySince(⒮), the duals ofAlways/Eventually/Next/Untilover the converse temporal relation. Covered in the parser,satisfies_modal, the standard translation, the qml embedding, andto_english.More modal frames —
B(Brouwer),S4.2(convergent),S4.3(linear) decided by Z3 (qml_is_valid);GL(Gödel–Löb provability) via the Löb schema in the Isabelle / THF exporters (verified to discharge Löb’s theorem in real Isabelle).Finite-valued logical matrices + Belnap–Dunn FDE —
semantics.matrix:TruthMatrix.from_functionsbuilds any finite matrix; shipsK3_MATRIX,LP_MATRIX(reproducing the existing three-valued decisions) and the four-valuedFDE_MATRIX(paraconsistent and paracomplete, with no logical truths).Fuzzy t-norm selector + quantifier grounding —
fuzzy_evaluate(…, tnorm=)over Łukasiewicz / Gödel / product (newsemantics.tnorm);z3_fuzzydecides the two piecewise-linear t-norms and grounds quantifiers over a finite domain, so quantified fuzzy validity / satisfiability is now decidable.First-order intuitionistic Kripke search —
int_valid/int_countermodelsearch increasing-domain models for quantified formulas (bounded; the propositional fragment stays an exact decision).Bounded second-order search —
so_find_model/so_find_countermodel/so_is_satisfiable_finite/so_is_valid_finitecomplementsatisfies_so.Many-sorted (MSFOL) model finding —
find_model/find_countermodelenumerate sort universes and return sortedStructures.Description logic ALC —
unicode_fol_kit.dl. Concept syntax (⊤ ⊥, ¬ ⊓ ⊔, ∃r.C ∀r.C), and a tableau reasoner (concept_satisfiable,subsumes,equivalent,abox_consistent) over general TBoxes / ABoxes, with TBox internalisation and subset blocking; cross-checked against the modal tableau.Non-classical neighbours — free logic (
semantics.free_logic), public- announcement / dynamic epistemic logic (semantics.dynamic_epistemic), counterfactual conditionals (semantics.conditional, Lewis spheres), and circumscriptive non-monotonic entailment (semantics.nonmonotonic).isabelle_decide_fol— decide classical FOL / MSFOL through Isabelle. The counterpart ofisabelle_decide_modalfor the classical fragment: prove-battery →nitpickfinite counter-model → UNKNOWN (common, since FOL is only semi-decidable), overto_isabelle_fol/to_isabelle_msfol. Returns aFolVerdict. Equality stays the uninterpretedfeq/fneqof the embedding (no equality axioms assumed).Linux CI for the Isabelle-backed tests (
.github/workflows/isabelle-tests.yml). Installs a real Linux Isabelle (cached) and runs the gated live tests onubuntu-latest— the standing guarantee that the runner’s primary (Linux) path works, since the dev box is Windows. Path-scoped +workflow_dispatch.
Changed¶
to_englishparaphrases the non-classical operators (modal / temporal / epistemic / deontic / second-order / fuzzy) instead of falling back to glyphs; the fuzzy strong/weak/Łukasiewicz connectives are named so they do not read as their classical look-alikes.naminggained explicit modal / second-order mixing-error hints.isabelle_decide_modalINVALID verdicts now carry a concrete counter-model. For the propositional alethic fragment,ModalVerdict.countermodelis populated with a finite Kripke counter-model reconstructed from the toolkit’s ownsatisfies_modalevaluator (isabelle builddoes not echo nitpick’s model).Nonefor fragments the bounded search does not cover — the certified verdict is unaffected.Temporal
Always/Eventually+Nextrefutation is sharp again. The runner’s refute theory now defines the henceforth relation ast = rtranclp n(the reflexive-transitive closure) instead of axiomatising it, so nitpick can construct the closure and genuinely refute a non-theorem — e.g.Next(p) → Always(p)is nowINVALIDinstead ofUNKNOWN. The prove theory keeps the axiom form (the battery needs it); both encodet = n**. (Newtemporal_defflag onisabelle_modal_theory.)
[0.8.0] - 2026-06-28¶
Added¶
Run a local Isabelle to actually prove the modal embeddings —
unicode_fol_kit.hol.isabelle_runner. Theholexporters only emit; this opt-in module turns “emit” into “proven / refuted” when an Isabelle/HOL install is present.isabelle_decide_modal(φ, frame=…, mode=…)decides validity by a two-step procedure, read off the build’s exit code: (1) emit the lemma with a proof battery that brings the frame/domain axioms into scope (using … by (blast | force | … | meson … | metis …)) — a successfulisabelle buildmeans VALID; (2) otherwise emitnitpick[expect = genuine], whose build succeeds iff a genuine finite counter-model exists — that means INVALID; (3) otherwise UNKNOWN. Sound (Isabelle’s kernel certifies the proof; nitpick reports only genuine counter-models) and, necessarily, incomplete. The verdict is validated differentially against an independent brute-force Kripke oracle (satisfies_modal) across K/T/S4/S5 in the test suite.find_isabelle/isabelle_availablelocate an install (explicit path →UFK_ISABELLE_HOME/ISABELLE_HOME→isabelleonPATH→ a light scan);check_theory(text, name)builds any self-contained theory;ModalVerdict/BuildResult/IsabelleInstallcarry the results.Linux/macOS is the primary path —
isabelleis invoked directly. Windows is also supported: the build is additionally routed through Isabelle’s bundled Cygwin (Windows→/cygdrivepath translation, launcher exec-bit fixup,binonPATH). No install path is hard-coded — installations are discovered generically. Absent Isabelle raises a clearIsabelleNotAvailable; the live tests skip.All re-exported at the package top level.
Fixed¶
to_isabelle_modalemitted proofs could not discharge axiom-dependent validities. A bareaxiomatization where r_refl: …fact is not in Isabelle’s default claset, soby blast/by auto/by (metis …)could not see it: every validity that depends on a frame/domain axiom (T, S4, S5, KD, KD45, the temporal closure, a domain regime) failed to prove even though the formula is valid and the theory sound (only the pure-K fragment, like the K axiom, went through). Theby-style tactics now emitusing <frame/domain axioms> by …. Newmodal_axiom_names(φ, …)exposes the axioms in scope, andisabelle_modal_theory(…, proof=…)accepts an explicit proof override.to_isabelle_k3lp/to_isabelle_k3lp_entailmentemitted a non-discharging proof.by (simp add: des_def)does not close the validity lemma —simpcannot reducekneg v/kor v …while the quantified truth-valuevis abstract (e.g. the LP-validp ∨ ¬pfailed). The∀(valid) form is now discharged by exhausting each variable’s threetvconstructors (case_tac+simp_all); the∃(refutation) form by supplying the counter-valuation asrule exIwitnesses computed at emit time. Every form is verified to build in real Isabelle.
Changed¶
to_isabelle_intuitionisticnow emits a real, Isabelle-checked proof for valid formulas. The proof is verdict-dependent: an intuitionistically valid formula getsusing r_refl r_trans by (metis … | meson … | blast | auto)that Isabelle discharges; a non-valid one is leftoops(loads, claims nothing — seeint_countermodel). Previously the lemma was alwaysoops. The verdict is taken from the decidable S4 oraclegmt_is_s4_valid(Z3 on the GMT→S4 translation), not fromint_valid’s default 3-world bound — which is incomplete (IPL’s finite-model bound grows with the formula, so a non-theorem like(p→q)∨(q→r)∨(r→p)would otherwise be mis-emitted with a real proof that cannot close).
Audit hardening¶
A multi-agent adversarial soundness audit of the new subsystem confirmed five issues (no false VALID is possible — Isabelle’s kernel rejects a proof of a false goal), all fixed and re-checked against a live Isabelle:
Intuitionistic proof gated on the decidable oracle (above) — was a real proof emitted for an IPL-invalid formula (theory then failed to build).
Atom-name collisions in the intuitionistic export. An atom named
r(orw/v/u) collided with the accessibility relation / frame-axiom variables, emitting a duplicateconsts r(or an ill-typed axiom) so the theory never loaded — even for the validr → r._isa_atom_namenow reserves the structural identifiers and de-collides (r→p_r).Temporal closure now pinned faithfully. When
Always/Eventuallyco-occur withNext, the emitted henceforth relationtis now forced to equal the reflexive-transitive closure of the one-stepn(t ⊆ rtranclp n, with the existingt_refl/t_trans/n_in_tgiving the converse), matchingsatisfies_modal. Previouslytcould be any refl-trans superset ofn, so asatisfies_modal-valid temporal induction(p ∧ G(p→Xp)) → Gpwas spuriously refuted (a false INVALID); it is now never refuted (UNKNOWN — the closure fragment is a documented approximation).ModalVerdict.infra_error. AnUNKNOWNwhose build failed for an infrastructure reason (syntax / JVM / timeout / …) now carries a short signature, so a broken theory or environment is no longer indistinguishable from honest incompleteness. Never changes the verdict.
[0.7.0] - 2026-06-28¶
Added¶
Epistemic / doxastic frame systems in the first-order embedding.
qml_is_valid/qml_equivalentnow takesystems={"epistemic": "S5", "doxastic": "KD45"}, emitting per-agent frame axioms for the agent-indexedRk/Rbrelations — so e.g. factivity∀x (K_x φ → φ)is valid under a reflexive epistemic system. This makes the FO path symmetric to the THF exporter (which already hadsystems=).Quantified modal logic (QML) via shallow embeddings. Object quantifiers
∀x/∃xunder a modality, with the domain-regime semantics that decide the Barcan formulas:Semantics —
KripkeModelnow takes per-world object domains (domains={w: …}for varying,domain=[…]for constant), andsatisfies_modalinterprets∀x/∃xactualistically (at a worldwthey range overD_w). Barcan (◇∃x A → ∃x ◇A) and converse Barcan are valid/invalid exactly as the domains vary. Backward compatible (omit the domains for the propositional fragment).(A) First-order shallow embedding (
unicode_fol_kit.fol.qml):qml_translate(with the existence predicateE!relativising actualist quantifiers + world/object sort guards),qml_axioms, andqml_is_valid/qml_equivalentdecide validity / equivalence with Z3 per domain regime (constant/increasing/decreasing/varying) and frame (K/T/S4/S5/KD/KD45). Sound but bounded-incomplete (first-order modal logic is undecidable). The regime↔Barcan correspondence (BF ⇔ decreasing, CBF ⇔ increasing, constant ⇔ both) is cross-checked against exhaustive Kripke-model enumeration over every regime.(B) Higher-order shallow embedding —
to_thf_modalemits a Benzmüller-style TPTP THF problem (liftedmbox/mdia/mforall/mexists/mvalid+ frame and domain axioms) for an external higher-order prover (Leo-III, Satallax);to_isabelle_modalemits an Isabelle/HOL skeleton. Alethic □/◇ fragment.All re-exported at the package top level;
BARCAN/CONVERSE_BARCANare provided as the standard litmus formulas.
⊕L/⊕R(exclusive-or) rules in the sequent calculus (A⊕B ≡ ¬(A↔B)), closing the one connective that had no inference rule in either checker.HOL / Isabelle / THF exporters for all non-fuzzy logics (new
unicode_fol_kit.holsubpackage) — Benzmüller-style shallow semantical embeddings, emitted as complete problem files for an external prover (the toolkit emits; it does not run Leo-III / Satallax / Sledgehammer, and FOL / FO-modal / SOL are undecidable, so emission means “a sound problem a prover may discharge”, never “decided”):hol.isabelle_modal.to_isabelle_modal— a real, loadable Isabelle/HOL theory (theory … imports Main begin … end, all lifted operators, frame + domain axioms, the formula lifted into the embedding, a reallemma) for the full modal family: alethic, epistemic/doxastic over agent-indexed relations, deontic, temporal. Replaces the old alethic-only skeleton that emitted the lemma inside a comment.hol.thf_modal.to_thf_modal_full— the full-modal-family TPTP THF export (agent-indexed epistemic/doxastic, deontic, temporal), extending the alethic-onlyqml.to_thf_modal; self-contained (every relation the macro block references is declared).hol.classical(FOL + MSFOL),hol.manyvalued(K3 / LP, cross-checked against the three-valued evaluator),hol.secondorder(native HOL predicate quantification),hol.intuitionistic(Gödel–McKinsey–Tarski → S4 → HOL, cross-checked againstint_valid) — each → THF and Isabelle.
First-class agent terms for epistemic/doxastic operators. The
agentofKnows/Believesis now a term (Variable or Constant), a structural child, so it is reached byfree_variables/ substitution / β-reduction and can be a quantified variable —∀x (Student(x) → K_x φ)(a quantified subject, “every student who…”) finally works: the agent is the boundx, not a baked-in relation-name suffix.The Kripke evaluator uses per-agent accessibility relations (one agent can know what another does not); object quantifiers ground a bound agent before the modality is reached.
The first-order shallow embedding emits an agent-indexed ternary relation
Rk(agent, w, v)/Rb(agent, w, v), so a bound agent genuinely quantifies over agents (epistemic/doxastic relations are plainK— no frame axioms yet).Parser convention: a free
K_ais a named agent (→ Constant); an agent bound by an enclosing quantifier (K_xunder∀x) stays a Variable. A bare string passed to the constructor is coerced to a Constant (backward compatible).
Fixed¶
HOL/THF/Isabelle exporters: distinct symbols can no longer collapse to one name. A de-colliding resolver (
_ThfNames/_IsaNames/ the second-order and classical resolvers) guarantees each distinct(kind, name, arity)symbol gets a unique emitted functor /consts, and a predicate used at two arities is two symbols. Fixes a soundness hole where a non-valid formula could be emitted as valid — e.g.□Ab → □abcollapsing to the tautology□ab → □ab(also in the shippedqml.to_thf_modal) — and the duplicate / ill-typed declarations that madeto_isabelle_so/ the modal Isabelle theories non-loadable.to_isabelle_modalis now the real exporter everywhere. The top-levelto_isabelle_modal(andfol.qml.to_isabelle_modal) delegate to the completeunicode_fol_kit.hol.isabelle_modalimplementation (a loadable theory with a genuinelemma), instead of the old alethic-only skeleton that emitted the lemma in a comment.substituteis now capture-avoiding for a re-binding quantifier. Substituting aVariable(assatisfies_modaldoes when grounding an object quantifier) no longer leaks past an inner quantifier that re-binds the same name:substitute(∃x A(x), x, a)now correctly returns∃x A(x)unchanged. This fixes a soundness bug in the modal evaluator, where a shadowed quantifier (e.g.∀x ∃x A(x), also under modalities) evaluated to the wrong truth value. (TheLambdabranch already stopped at a rebinding binder; theQuantifier/SortedQuantifierbranches now do too.)QML rejects an unknown
mode.qml_translate/qml_is_valid/qml_equivalentraiseValueErroron an unrecognised domain regime (e.g. a mis-capitalised'Increasing') instead of silently treating it as constant-domain and returning a wrong validity verdict — matching the existingframevalidation.THF export:
possibilistnow emits theconst_domaxiom. Since the FO embedding treatspossibilistas a constant domain, the THF export does too (its actualistmforall/mexistsmacros would otherwise model a varying domain), keeping the two embeddings in agreement.THF export:
=/≠are now uninterpreted predicates, not primitive HOL identity.to_thf_modalpreviously rendered equality as rigid HOL=, diverging fromsatisfies_modaland the FO embedding (which key=/≠as ordinary world-relativized predicates) — so e.g.∀x. x=xwas a THF theorem but Kripke-falsifiable. All three embeddings now agree.
Internal¶
Retired the parser-equivalence oracle. The registry-assembled parser was pinned during migration by a byte-for-byte equivalence test against the legacy hand-written per-mode
.larkgrammars +*Transformerclasses. With that equivalence long established, the reference pipeline was removed: the six per-mode grammar files (fol/msfol/msfl/fl/modal/so.lark) and the legacy transformer classes are gone; the registry self-assembly + grammar-structure guards remain. Onlyterminals.larksurvives (imported by the generated grammar at runtime).Shared symbol-name de-collision (
fol/_symbol_names.py): thededupehelper and the THF/Isabelle resolver, previously copy-pasted across the exporters, are now oneSymbolNamesbase +dedupeused by the THF, Isabelle, classical, and second-order exporters.Agent token parsing reuses the scope pass. The epistemic/doxastic agent is parsed as a Variable and resolved to bound-Variable / free-Constant by
resolve_agent_variables(mirroringresolve_lambda_scope), instead of re-deciding variable-vs-constant with a hand-copied lexer regex.Adversarial audit of the proof checkers. A multi-agent audit ran independent oracles against every accepted proof/derivation of the Fitch and sequent checkers across all logics (~75 hand-built adversarial constructions plus >1M fuzzed cases) and found no soundness hole. Follow-up hardening from the audit’s coverage findings: added regression tests pinning the
verify_proofrobustness guards (a cleanProofResult(ok=False)instead of a crash on a non-Linepremise or subproof assumption) and the mixed quantifier-spelling normalisation ('forall'vs∀); and extended the sequent test corpus so the randomised mutation / Z3 audit now also exercisesCut, weakening, contraction,∨L,↔L/↔R,∃L, and⊕L/⊕R.Independent differential test harnesses promoted into the committed suite, so the checkers are cross-checked against oracles other than the ones they use internally:
the alethic modal Fitch checker against brute-force Kripke-frame enumeration (
tests/test_modal_differential.py) — independent of its standard-translation/Z3 path, covering the K/T/S4/S5 frame-sensitivity facts;the second-order sequent rules against
satisfies_so(so_valid_tiny) under a randomised mutation audit (Z3 cannot evaluate second-order nodes);the object-level eigenvariable freshness condition (
∀R/∃L) under randomised fresh/non-fresh fuzzing.
[0.6.0] - 2026-06-27¶
A large reasoning-and-interoperability release: a Fitch natural-deduction checker and backtracking prover, a Gentzen LK sequent calculus (with second-order rules) and an intuitionistic LJ calculus, analytic tableaux, a finite model finder, truth tables, reverse importers for TPTP / Prover9 / Z3-SMT-LIB, intuitionistic Kripke semantics, and formula verbalization. All additive.
Added¶
Fitch-style natural-deduction proof checker (
unicode_fol_kit.atp.fitch) —Proof/Subproof/Line/Justificationproof objects (frozen, hashable, JSON-serialisable) pluscheck_proof/verify_proof, all re-exported at the package top level. The checker is sound: it returnsTrueonly when every line genuinely follows by the cited rule and the proof’s premises entail its conclusion;verify_proofreports the certified sequent and the first failing line with a reason.Classical FOL / MSFOL (
logic="fol"/"msfol") is checked by a syntactic rule table: the connective rules (∧I/∧E,∨I/∨E,→I/→E,↔I/↔E,¬I,⊥I/⊥E,¬Edouble-negation,RAA,Reit), the quantifier rules (∀I/∀E,∃I/∃E) with the eigenvariable side-conditions enforced via a capture-avoiding substitution, and equality (=I/=E, certified against Z3). Citation accessibility is enforced (no reaching into a closed sibling subproof) and discharge rules are checked against the proof’s open assumptions.⊥is the reserved logical constantFALSUM.Three-valued K3 / LP (
logic="K3"/"LP") certify each step against the many-valued decision procedure (semantics.manyvalued.entails), so the paraconsistency facts hold: LP rejects modus ponens, the disjunctive syllogism, and explosion; K3 has no zero-premise theorems. Propositional fragment.Modal family (
logic="K"/"T"/"S4"/"S5") certifies each step by the standard translation to FOL plus the frame axioms, decided by Z3. Knowledge (Knows, S5) is factive; belief (Believes, KD45) and obligation (Obligatory, KD) are not. Propositional fragment; temporal and quantified modal input are rejected.Rendering —
render_fitch(Unicode/ASCII scope bars, line-number gutter, justification column; alsoproof.to_fitch()) andrender_latex_fitch(self-contained LaTeXarray; alsoproof.to_latex_fitch()).Tested with hand-derived proofs per rule, soundness guards for the broken cases, and a randomised audit that checks every accepted proof line-by-line against the Z3 / resolution oracles.
Gentzen sequent-calculus checker (
unicode_fol_kit.atp.sequent) — a two-sided LK derivation checker re-exported at the package top level:Sequent/Derivation/Comprehension/SequentResult, the helperssequent/derive/axiom, andcheck_sequent_proof/verify_sequent_proof/render_sequent_proof. A sequentΓ ⊢ Δ(multisets, read⋀Γ → ⋁Δ) is derived by a tree of rules; the checker verifies each step.Rules:
Ax, structuralWL/WR/CL/CR/Cut, the connective rules (¬,∧,∨,→,↔, each L and R), the first-order quantifier rules (∀L/∀R,∃L/∃R, with the eigenvariable condition on∀R/∃L), and the second-order rules∀²L/∀²R,∃²L/∃²R.∀²L/∃²Rinstantiate a bound predicate variable with a comprehension termλx̄.ψ(aComprehension, arity-checked, capture-avoiding);∀²R/∃²Luse a fresh predicate eigenvariable. This reaches the second-order fragment (second_order=True), which has no first-order / SMT encoding.Sound but, for full second-order logic, necessarily not a complete prover (second-order validity is not r.e.). Tested with hand derivations per rule, soundness guards, a randomised mutation audit that re-checks every accepted derivation node-by-node against Z3 (first-order fragment), and
satisfies_sospot-checks over small finite models (second-order fragment).
Analytic tableaux (
unicode_fol_kit.atp.tableau) —is_valid_tableau,prove_tableau,tableau_closed, andtableau_model, re-exported at the top level. A fourth proof method (beside resolution, Fitch, and the sequent calculus): the signed-free α/β/γ/δ rules, a branch closing onφ/¬φ. Decidable and complete for the propositional fragment; first-order γ-instantiation is bounded (max_terms/max_steps). An open branch is returned as a countermodel bytableau_model.Finite model finder (
unicode_fol_kit.semantics.modelfinder) —find_model,find_countermodel,is_satisfiable_finite, andis_valid_finite. Brute-force enumeration of finiteStructures (domain1..max_size) checked with the Tarskian evaluator — the Mace4-style partner of the provers (a valid entailment has no countermodel; an invalid one usually a small finite one). Bounded bymax_candidates.Truth tables (
unicode_fol_kit.semantics.truthtable) —truth_tablereturning aTruthTable(Markdownrender,is_tautology/is_contradiction/is_satisfiable), plusis_tautology/is_contradiction/is_satisfiable_tt, over classical, Kleene K3, and Priest LP value sets (cross-checked against Z3 for classical).Intuitionistic propositional logic (
unicode_fol_kit.semantics.intuitionistic) —IntKripkeModelwith monotone Kripkeforces, andint_valid/int_countermodelthat decide intuitionistic validity by Kripke-model search (the logic has the finite-model property). Excluded middle, double-negation elimination, and Peirce’s law are reported invalid with explicit countermodels; every intuitionistic validity is also classically valid (cross-checked).Intuitionistic sequent calculus LJ (
unicode_fol_kit.atp.lj) —check_lj_proof/verify_lj_proof, re-exported at the top level. Gentzen LJ is the LK calculus (it reuses the sameSequent/Derivationdata model) restricted to at most one succedent formula — the change that makes excluded middle / double-negation elimination / Peirce’s law underivable. Rules:Ax, structuralWL/WR/CL/Cut,¬/∧/→/↔(L and R), the split disjunction-right∨R1/∨R2and∨L, and the quantifier rules∀L/∀R,∃L/∃R. Accepted derivations are cross-checked against the intuitionistic Kripke decision procedure and classical Z3 validity.Verbalization (
unicode_fol_kit.fol.verbalize) —to_english, an English paraphrase of a formula (a readability aid, not a parse inverse).Fitch proof searcher (
unicode_fol_kit.atp.fitch_search) —find_fitch_proof,fitch_prove, andis_valid_fitch, re-exported at the package top level. A goal-directed, iterative-deepening backtracking search over the classical propositional + first-order natural-deduction rules (introduction rules, ∨/∃ elimination by case split, backward chaining, ex falso, and reductio/RAA — which makes it complete for the propositional fragment). It builds an actualProofthat is re-validated bycheck_proofbefore being returned, so it is sound by construction: a search/assembly bug can only make it fail to find a proof, never return an unsound one. Like the resolution prover it is sound but, under its depth bound, incomplete (None/False= “not found withinmax_depth”). Tested with curated theorems/non-theorems and a randomised cross-check that every found proof is Z3-valid.Reverse importers for TPTP, Prover9, and Z3/SMT-LIB — the inverses of
to_tptp/to_prover9/to_z3, all re-exported at the package top level:TPTP (
unicode_fol_kit.fol.tptp_input):parse_tptp_formula(one bare FOF/CNF formula →Node),parse_tptp(a whole problem → a list ofTptpFormula(name, role, formula)), andload_tptp(a.p/.tptpfile), via a dedicated Lark grammar. Round-tripsto_tptp;%and/* */comments are ignored; predicates are re-capitalised (TPTP lowercases them); typedtff/thfandincludeare out of scope.Prover9/LADR (
unicode_fol_kit.fol.prover9_input):parse_prover9, followingset(prolog_style_variables)to matchto_prover9’s output (a trailing.is accepted).Xorround-trips to its(a|b) & -(a&b)desugaring.Z3 (
unicode_fol_kit.atp.z3_input):from_z3(az3.ExprRef→Node) andparse_smtlib/load_smtlib(SMT-LIB2 via Z3’s own parser). Conversion is meaning-preserving (Z3 collapses variables/constants/numbers onto one uninterpreted sort, so a free variable returns as aConstant).Tested by round-trip over random formulas (
parse(node.to_X()) == node) for TPTP/Prover9 and by logical equivalence (is_valid(Iff(node, from_z3(node.to_z3())))) for Z3, plus curated problem-file and SMT-LIB cases.
[0.5.2] - 2026-06-26¶
Added¶
Predicate-aligned string match (
unicode_fol_kit.eval.predicate_match) —match_predicates,formulas_are_matched_identical, andformulas_are_identical, re-exported at the package top level. A lexical (string-level) evaluation notion for NL→FOL:match_predicatesgreedily renames each predicate/function symbol in a predicted formula to the lexically-closest symbol in the reference (by normalised Levenshtein distance, accepting matches at or below amax_norm_distancethreshold, default0.6), so a structurally-correct answer that merely chose different predicate names is not penalised.formulas_are_identicalis the plain whitespace- and case-insensitive string equality;formulas_are_matched_identicalcombines the two (realign predicates, then compare). This is complementary to the AST-levelexact_match: the canonical match quotients out α-renaming / commutativity / associativity / double negation but treats different predicate names as a mismatch, whereas this matcher quotients out predicate-name (and whitespace/case) differences but not the structural rewrites — the two are typically reported as separate metrics. The Levenshtein distance is computed in pure Python, so no new dependency is introduced; the matcher is parser-independent and also applies to raw, not-yet-parseable model output.
[0.5.1] - 2026-06-24¶
Added¶
check_logical_entailment_vampire— entailment checking via the Vampire theorem prover, a TPTP-based companion to the existing Prover9 backend. Premises are emitted as TPTPaxioms and the conclusion as aconjecture; the path to the Vampire executable is passed as thevampire_pathargument, and aSZS status Theoremresult means the entailment holds. Classical FOL only (the same fragmentto_tptpsupports). Passuse_wsl=Trueto drive a Linux Vampire installed in WSL from a Windows host (Vampire is launched viawsl.exe, with automaticwslpathtranslation of the temp-file path).
[0.5.0] - 2026-06-24¶
Adds an NL→FOL evaluation toolkit and broad non-classical logic coverage — modal/temporal/epistemic/deontic logic with Kripke semantics, three-valued (Kleene/Priest) logic, and second-order quantification with finite-model semantics. All additive; no breaking changes.
Added¶
unicode_fol_kit.eval—canonicalize/exact_match(a fair “canonical exact match” that quotients out bound-variable renaming, commutativity/ associativity, operand duplication, and double negation while staying logically equivalent), andvalidate/is_wellformed/validate_text/ValidationReport(free variables, inconsistent predicate/function arity, leftover lambda nodes, parseability of raw model output).Modal / temporal / epistemic / deontic logic (
MSFLParser(modal=True)): node classesBox,Diamond,Knows,Believes,Always,Eventually,Next,Until,Obligatory,Permittedwith surface syntax□ ◇,K_a/B_a,Ⓖ Ⓕ Ⓝ Ⓤ,Ⓞ Ⓟ. Kripke-model semantics (KripkeModel,satisfies_modal,reflexive_transitive_closure) and a relationalstandard_translation()to classical FOL so Z3/resolution can decide modal validity. Propositional/ground (v1).Many-valued logic (
unicode_fol_kit.semantics.manyvalued): three-valued strong-Kleene evaluationkleene_valueover {0, ½, 1}, andis_valid/is_satisfiable/entailswith selectable designated values for Kleene K3 ({1}) and Priest LP ({½, 1}, paraconsistent).kleene_value/DESIGNATEDare also re-exported at the package top level.Second-order / monadic-second-order quantification (
MSFLParser(second_order=True)):SecondOrderQuantifier(∀P/∃P, arity inferred from the body) with finite-model semantics (satisfies_so) that enumerates relations over a finite domain. Higher-order terms remain available via the existing lambda layer; full HOL types are out of scope.LaTeX import —
parse_latex()reads a LaTeX-math formula (the inverse ofto_latex()) andlatex_to_unicode()does the LaTeX→Unicode translation alone; accepts the exactto_latex()output (round-trips) and common hand-written synonyms.
Internal¶
Operator registry — operators are now fully self-describing, decoupling rendering and parsing from the central modules:
Rendering: each operator registers its glyph, LaTeX markup, precedence, and fixity via
register_operator(); the Unicode and LaTeX renderers are driven generically from the registry (no per-operator branches, no hand-maintained dispatch tables).Parsing: each operator also registers its grammar fragment + transform via
register_parser_op().MSFLParsernow assembles BOTH the Lark grammar and the transformer for every mode (FOL/MSFOL/MSFL/FL/modal/second-order) from the registry — there is no longer a hand-written per-mode transformer or a hand-loaded.larkgrammar on the runtime path.Output and parsed ASTs are byte-identical to before (guarded by a legacy-vs-registry equivalence test across a 190-formula × 6-mode corpus). Adding an operator — or a whole new logic — is now a self-contained registry entry in the operator’s own module, with no edit to the renderers, the parser, or any shared grammar file.
Hardening of the new evaluators.
The three-valued enumeration (
is_valid/is_satisfiable/entails) now scores each assignment with a compiled evaluator built once from the formula (no per-assignment AST walk or atom re-rendering), and refuses to start an enumeration abovemanyvalued.MAX_MODELSrather than hanging.Second-order
satisfies_sorefuses a∀P/∃Pwhose2 ** (n ** k)relation space exceedssecondorder.MAX_RELATIONS, with a clear error.Added seeded, randomized cross-checks: the compiled three-valued path against the reference
kleene_valueon every assignment; strong-Kleene algebraic identities and the K3-vs-LP headline facts; second-order∀P φ ≡ ¬∃P ¬φduality and the agreement ofsatisfies_so’s classical core with the first-order Tarski evaluator; render→parse round-trips over random FOL, modal, Łukasiewicz, and second-order formulas; and a whole-treetree_str/to_dotcoverage check over every node type.Łukasiewicz-algebra cross-checks for the fuzzy evaluator (strong/weak De Morgan, double negation, the residuum
a → b ≡ ¬a ⊕ b, and the defining adjunctiona ⊗ b ≤ c ⟺ a ≤ b → c) over random + boundary-grid valuations.Eval cross-checks against the independent Z3 oracle:
canonicalizeis equivalence-preserving,exact_matchabsorbs the rewrites it should and never merges Z3-inequivalent formulas, andvalidateflags free variables, arity clashes, and leftover lambdas.A README example runner executes every
pythonblock in the docs (cumulative namespace) so the documentation stays in lock-step with the code.
[0.4.0] - 2026-06-23¶
A large feature release adding model-theoretic and many-valued semantics, an in-process theorem prover, more solver back-ends, and lambda/normal-form tooling, plus a set of correctness fixes. Includes one breaking change (see Changed).
Added¶
Tarskian model theory (
unicode_fol_kit.semantics.tarski): define aStructure(a “world” with a domain of individuals and interpretations of constants, functions, predicates, and — for MSFOL — sorts) and compute a formula’s truth value withsatisfies()/models()/term_value(). Equality is built in; sorted quantifiers range over their sort universe.Łukasiewicz fuzzy evaluator (
fuzzy_evaluate): the truth degree in [0, 1] of an FL/MSFL formula under a valuation (∀= inf,∃= sup).Fuzzy satisfiability / validity via Z3 reals:
fuzzy_is_satisfiable,fuzzy_is_valid,fuzzy_get_model,degree_expr.Arithmetic-aware Z3 translation:
to_z3_arith,is_satisfiable_arith,is_valid_arith,get_model_arithinterpret+ - * /and the comparisons over Z3 reals/integers (the defaultto_z3keeps them uninterpreted).Built-in first-order resolution prover (
unicode_fol_kit.atp.resolution):prove,is_valid_resolution,to_clauses,refute— sound entailment and validity checking in-process, without an external prover. Deliberately incomplete under a step bound (never reports a non-theorem as proved);=is treated as an uninterpreted predicate.Lambda tooling:
eliminate_lambdas(beta-eta normalise and verify lambda-free),reduce_trace,beta_reduce_step,has_lambdas.Normal forms:
to_dnf(equivalence-preserving) andto_tseitin_cnf(equisatisfiable, avoids the distributive blow-up).Robinson unification:
unify(most general unifier with occurs-check) andapply_subst.Command-line interface:
python -m unicode_fol_kit "<formula>" --mode … --to ….Typing: a
py.typedmarker (PEP 561).AST helper:
Node.map_children, the single structural-recursion engine.
Changed¶
BREAKING — AST nodes are now frozen dataclasses. Every node is immutable and hashable, so nodes can be put in sets, used as dict keys, and deduplicated.
BREAKING —
Function.argsandAtom.argsare nowtuples, notlists. Construction stays lenient: a list passed to the constructor is coerced to a tuple, soAtom("P", [x])still works andnode == nodecomparisons are unaffected. Code that relied on.argsbeing a list (in-place mutation,isinstance(node.args, list), or comparingnode.args == [...]) must switch to tuples.
Fixed¶
Xor.to_tptpemitted~|(TPTP NOR); now emits<~>(correct XOR / non-equivalence).TPTP arithmetic comparisons (
<,>,≤,≥) are now emitted as prefix dollar-word predicates ($less(a, b)), not as invalid infix expressions.Prover9 export: quantified variables are uppercased to match the emitted
set(prolog_style_variables); nullary predicates render as bare propositional atoms instead of the invalidP().to_latexescapes the underscore inc_-prefixed constants (otherwise read as a LaTeX subscript).Prover9 entailment: the temporary input file is no longer leaked when the
prover9_pathis invalid (now cleaned up in afinally).Several README inaccuracies (clone URL, “three” vs “four” parser modes, the exception class raised on mixing same-level connectives, the
QuantifierAST-table annotation), and theformulas_are_equivalent/is_validdocstrings.
Documentation¶
Clarified that
to_fol/to_msfolis a classical Boolean projection (the strong and weak Łukasiewicz connectives both collapse toAnd/Or), not a fuzzy-preserving translation — usefuzzy_evaluate/ the fuzzy Z3 solver for many-valued degrees.
Internal¶
Refactored the duplicated structural recursions (
free_variables, substitution, beta/eta reduction, scope resolution,to_msfol/_relativize, term substitution) onto the sharedNode.map_children/_child_nodeshelpers, removing the per-nodeisinstancechains while preserving the binder-aware special cases and the publicTypeErrorcontracts.
[0.3.1] - earlier¶
LaTeX export, normal forms, Horn check, Z3 models, traversal API, Graphviz export.
[0.3.0] - earlier¶
to_unicode_str()with parser round-trip.
[0.2.1] - earlier¶
README patch release.