FixedPoint

Difficulty: optional — 7 definitions, 0 abbreviations, 7 lemmas, 3 theorems.

definition lemma theorem
legend

Some rules can meaningfully be run on their own output, feeding what comes out straight back in as the next input. Doing this over and over settles down to a fixed value — one where running the rule again changes nothing at all — exactly when the rule never “changes its mind”: once a wire has turned on, feeding the rule more on-wires only ever keeps that wire on or turns more on, never back off. Every rule like this, run enough times starting from every wire off, reaches its own fixed value, and always the same way: by simply running it long enough.

One bit sequence lines up with another wherever it says a wire is on: every wire the first sequence turns on, the second does too (the second may turn on more).

def BitSequence.Dominates {n : Nat} (a b : BitSequence n) : Prop :=
   i, a i = true  b i = true

Outer dependencies: BitSequence

Inner dependencies: Bit

Lean core dependencies: Eq, Fin, Nat

The sequence with every wire off: it lines up with every other sequence, no matter what that other sequence says.

theorem BitSequence.Dominates.bot {n : Nat} (a : BitSequence n) :
    BitSequence.Dominates (fun _ => false) a :=

Proof dependencies: Bit

A rule never changes its mind: feeding it a sequence that lines up with another always produces an output that lines up the same way.

def Component.Func.Monotone {n : Nat} (f : BitFunction n n) : Prop :=
   a b, BitSequence.Dominates a b  BitSequence.Dominates (f a) (f b)

Outer dependencies: BitFunction

Inner dependencies: BitSequence, BitSequence.Dominates

Lean core dependencies: Nat

How many wires are on in a bit sequence.

def BitSequence.trueCount {n : Nat} (x : BitSequence n) : Nat :=
  (Finset.univ.filter (fun i => x i = true)).card

Outer dependencies: BitSequence

Inner dependencies: Bit

Mathlib dependencies: Finset.card, Finset.filter, Finset.univ

Lean core dependencies: Eq, Fin, Nat

No sequence has more than n wires on — there are only n wires to begin with.

theorem BitSequence.trueCount_le {n : Nat} (x : BitSequence n) : x.trueCount  n := by

Proof dependencies: Bit

Lean core dependencies: Eq, Eq.trans, Fin, Nat, True, congrArg, congrFun', eq_self, of_eq_true

If domination is genuine (the two sequences actually differ), strictly more wires are on afterwards: some wire had to turn on somewhere, and domination never turns one back off.

theorem BitSequence.trueCount_strict {n : Nat} {a b : BitSequence n}
    (h : BitSequence.Dominates a b) (hne : a  b) : a.trueCount < b.trueCount := by

Proof dependencies: Bit

Run f on its own output, k times over, starting from every wire off.

def Component.Func.iterate {n : Nat} (f : BitFunction n n) (k : Nat) : BitSequence n :=
  f^[k] (fun _ => false)

Outer dependencies: BitFunction, BitSequence

Inner dependencies: Bit

Mathlib dependencies: Nat.iterate

Lean core dependencies: Fin, Nat

Iterating a monotone rule never turns a wire back off from one step to the next.

theorem Component.Func.iterate_dominates_succ {n : Nat} {f : BitFunction n n}
    (hf : Component.Func.Monotone f) (k : Nat) :
    BitSequence.Dominates (Component.Func.iterate f k) (Component.Func.iterate f (k + 1)) := by

Proof dependencies: Bit, BitSequence.Dominates.bot

Mathlib dependencies: Function.iterate_succ_apply', Nat.iterate

Lean core dependencies: Eq, Eq.mpr, Fin, Nat, Nat.recAux, congrArg, id

Once iterating a rule agrees with itself one step later, it agrees with that same value at every step after — running a rule on its own fixed value never leaves it.

theorem Component.Func.iterate_stays {n : Nat} (f : BitFunction n n) {k : Nat}
    (hk : Component.Func.iterate f (k + 1) = Component.Func.iterate f k) :
     j, k  j  Component.Func.iterate f j = Component.Func.iterate f k := by

Proof dependencies: Bit

Lean core dependencies: Eq, Eq.mp, Eq.mpr, Eq.trans, Fin, Nat, True, congrArg, congrFun', eq_self, id, of_eq_true

Among the first n + 1 steps of iterating a monotone rule, at least one already agrees with the step right after it: the number of on-wires only ever climbs (never falls) with each step, and there are only n wires to turn on, so it cannot climb strictly more than n times running.

theorem Component.Func.exists_settle {n : Nat} {f : BitFunction n n}
    (hf : Component.Func.Monotone f) :
     k  n, Component.Func.iterate f (k + 1) = Component.Func.iterate f k := by

Mathlib dependencies: Mathlib.Tactic.Push.not_and_eq, le_refl

Running a monotone rule long enough (n + 1 times, where n is the number of wires) always settles down: it reaches a value it agrees with even one more step later. This is the whole point of requiring Component.Func.Monotone — the number of on-wires can only ever climb, never fall, so it cannot climb strictly for more than n steps in a row before running out of wires to turn on, at which point it has nowhere left to go but to repeat itself.

theorem Component.Func.iterate_settles {n : Nat} {f : BitFunction n n}
    (hf : Component.Func.Monotone f) :
    Component.Func.iterate f (n + 1) = Component.Func.iterate f n := by

Lean core dependencies: And, Eq, Eq.mpr, Exists, Nat, Nat.le_succ_of_le, congrArg, id

The fixed value a monotone rule settles into, run enough times from every wire off.

def Component.Func.fixedPoint {n : Nat} (f : BitFunction n n) (_ : Component.Func.Monotone f) :
    BitSequence n :=
  Component.Func.iterate f n

Inner dependencies: Component.Func.iterate

Lean core dependencies: Nat

Running f on its own fixed value changes nothing at all: Component.Func.fixedPoint really is a fixed point, and f is enough on its own to hand it back — nothing further needs to be run to find it, just f applied to itself’s own eventual output, enough times over.

theorem Component.Func.fixedPoint_isFixed {n : Nat} {f : BitFunction n n}
    (hf : Component.Func.Monotone f) :
    f (Component.Func.fixedPoint f hf) = Component.Func.fixedPoint f hf := by

Mathlib dependencies: Function.iterate_succ_apply', Nat.iterate

Lean core dependencies: Eq, Eq.mpr, Eq.trans, Fin, Nat, True, congrArg, eq_self, id, of_eq_true

The same equation, stated as a constraint instead of built

Component.Func.fixedPoint only exists for a monotone rule, and only because it’s actually built, by running f enough times over. A Component.Spec can state the very same equation — what a fixed point of f would have to satisfy — for any rule at all, monotone or not, with no promise that anything ever satisfies it. Whether it does is a separate question, answered by Component.Spec.Realizable, not baked into the statement itself.

The defining equation of a fixed point, as a bare constraint: an output counts exactly when running f on it gives that same output back. Nothing here says one exists.

def Component.Spec.fixedPoint {n : Nat} (f : BitFunction n n) : Component.Spec Bit Bit 0 n :=
  fun _ o => f o = o

Outer dependencies: Bit, BitFunction, Component.Spec

Lean core dependencies: Eq, Fin, Nat

Whenever f is monotone, its Component.Spec.fixedPoint is realizable after all — witnessed by Component.Func.fixedPoint, the same value found by actually running f.

theorem Component.Spec.fixedPoint_realizable {n : Nat} {f : BitFunction n n}
    (hf : Component.Func.Monotone f) : (Component.Spec.fixedPoint f).Realizable :=

Lean core dependencies: Fin, Nat

Used by: (none)

Example.

The rule that flips a single bit has no fixed point at all: fed true it hands back false, fed false it hands back true, never agreeing with its own input either way.

def notRule : BitFunction 1 1 := fun iv _ => !(iv 0)

Outer dependencies: BitFunction

Inner dependencies: Bit

Lean core dependencies: Bool.not, Fin, Nat

Example.

Unlike a monotone rule, notRule’s own Component.Spec.fixedPoint is genuinely unrealizable — a bare constraint, unlike an actually-built rule, is free to have nothing satisfy it at all.

theorem notRule_fixedPoint_unrealizable :
    ¬ (Component.Spec.fixedPoint notRule).Realizable := by

Used by: (none)

Dependency diagram

Drag to pan, Ctrl+scroll (or Cmd+scroll) to zoom, click a node to jump to it.

definitionlemmatheoremexampledeclared elsewheredependencyproof dependency
legend