5.5. Constructing Formulas from Predicates
Cslib.Logic.FO.Operator.Definable asserts that one well-formed formula defines a predicate
throughout an indexed family of structures. The formula is chosen before the family index and
the variable assignment. A certificate therefore cannot select a different formula for each size.
The fo_definable tactic composes certified construction rules. Here it builds a formula from an
ordinary predicate using existential quantification, conjunction, and equality:
example {L : Language} {ι : Type} {M : ι → Type}
[∀ i, L.Structure (M i)] {α : Type} (a b : α) :
Operator.Definable (L := L) LFP.policy M
(fun _ => LFP.interpretation) (fun _ => Operator.RelEnv.empty)
(fun i v => ∃ x : M i, x = v a ∧ x = v b) := L:Languageι:TypeM:ι → Typeinst✝:(i : ι) → L.Structure (M i)α:Typea:αb:α⊢ Operator.Definable LFP.policy M (fun x => LFP.interpretation) (fun x {k} => Operator.RelEnv.empty) fun i v =>
∃ x, x = v a ∧ x = v b
All goals completed! 🐙
The LFP rule also starts from an ordinary Lean relation transformer. This step includes the
starting vertex and extends the current relation by one edge. The final fo_definable constructs
the fixed-point formula and certifies its positive, existential body:
def exampleReachStep {L : Language} {A : Type} [L.Structure A]
(edge : L.Relations 2) (start : A) :
Set (Fin 1 → A) →o Set (Fin 1 → A) where
toFun R := {ys | ys 0 = start ∨ ∃ x,
(fun _ : Fin 1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}
monotone' := L:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:A⊢ Monotone fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}
L:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:AR:Set (Fin 1 → A)S:Set (Fin 1 → A)h:R ⊆ Sys:Fin 1 → Ahy:ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) R⊢ ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) S
L:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:AR:Set (Fin 1 → A)S:Set (Fin 1 → A)h:R ⊆ Sys:Fin 1 → Ahstart:ys 0 = start⊢ ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) SL:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:AR:Set (Fin 1 → A)S:Set (Fin 1 → A)h:R ⊆ Sys:Fin 1 → Ax:Ahx:(fun x_1 => x) ∈ Rhedge:Structure.RelMap edge ![x, ys 0]⊢ ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) S
L:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:AR:Set (Fin 1 → A)S:Set (Fin 1 → A)h:R ⊆ Sys:Fin 1 → Ahstart:ys 0 = start⊢ ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) S All goals completed! 🐙
L:LanguageA:Typeinst✝:L.Structure Aedge:L.Relations 2start:AR:Set (Fin 1 → A)S:Set (Fin 1 → A)h:R ⊆ Sys:Fin 1 → Ax:Ahx:(fun x_1 => x) ∈ Rhedge:Structure.RelMap edge ![x, ys 0]⊢ ys ∈ (fun R => {ys | ys 0 = start ∨ ∃ x, (fun x_1 => x) ∈ R ∧ Structure.RelMap edge ![x, ys 0]}) S All goals completed! 🐙
example {L : Language} {ι : Type} {M : ι → Type}
[∀ i, L.Structure (M i)] {α : Type}
(edge : L.Relations 2) (a b : α) :
Operator.Definable (L := L) LFP.policy M
(fun _ => LFP.interpretation) (fun _ => Operator.RelEnv.empty)
(fun _ v => ![v b] ∈ (exampleReachStep edge (v a)).lfp) := L:Languageι:TypeM:ι → Typeinst✝:(i : ι) → L.Structure (M i)α:Typeedge:L.Relations 2a:αb:α⊢ Operator.Definable LFP.policy M (fun x => LFP.interpretation) (fun x {k} => Operator.RelEnv.empty) fun x v =>
![v b] ∈ OrderHom.lfp (exampleReachStep edge (v a))
All goals completed! 🐙
Importing the TC synthesis module registers the closure rule as well. This example starts with semantic reachability for an arbitrary binary relation symbol; the tactic constructs the closure formula and its correctness proof:
example {L : Language} {ι : Type} {M : ι → Type}
[∀ i, L.Structure (M i)] {α : Type}
(r : L.Relations 2) (xs ys : Fin 1 → L.Term α) :
Operator.Definable (L := L) TC.policy M
(fun _ => TC.interpretation) (fun _ => Operator.RelEnv.empty)
(fun i v => Relation.ReflTransGen
(fun us vs : Fin 1 → M i => Structure.RelMap r ![us 0, vs 0])
(fun j => (xs j).realize v) (fun j => (ys j).realize v)) := L:Languageι:TypeM:ι → Typeinst✝:(i : ι) → L.Structure (M i)α:Typer:L.Relations 2xs:Fin 1 → L.Term αys:Fin 1 → L.Term α⊢ Operator.Definable TC.policy M (fun x => TC.interpretation) (fun x {k} => Operator.RelEnv.empty) fun i v =>
Relation.ReflTransGen (fun us vs => Structure.RelMap r ![us 0, vs 0]) (fun j => Term.realize v (xs j)) fun j =>
Term.realize v (ys j)
All goals completed! 🐙
An extension rule concludes Operator.Definable, supplies a well-formed formula witness, and
proves its semantics uniformly across the family. Register it with
@[aesop safe apply (rule_sets := [FODefinable])]. Rules for recursive bodies must also preserve
their syntactic polarity certificate. The current LFP synthesis rules cover first-order bodies;
nested LFP syntax is supported, but an automatic rule for nested fixed-point bodies is not supplied.
Finite-choice rules also let fo_definable combine a fixed finite family of alternatives. The
native tableau compiler uses these rules for transition cases, with the machine's finite state
and symbol sets fixed before the input varies. Model-checker certificates are registered with
fun_prop, so later native programs can compose a fixed FO(LFP) query with other polynomial-time
computations without unfolding its evaluator.