An append-only echelon rank routine (usable rank readings on wide matrices) #
Why it is needed #
rowReduce (GF2/RowReduce.lean) returns a mutually reduced set of pivot rows: every
time a new pivot is inserted, the new pivot column is back-substituted into all previous
rows (the D.map of insertPivot). The consequence is that the large new pivot row
w is copied into every previous row element, and the next call to reduceAgainst
traverses those copies one by one, so the term size grows multiplicatively with each
round. On width 24, 13×13 foldl steps over 24 coordinates should take well under a
second, but the measured cost falls off a cliff at about 7 pivots (more than 200 s without
converging, peak working set above 200 GB); width 18 is only just feasible and width 24 is
unusable.
This module provides an append-only, non-back-substituting companion routine, in which
previous rows are left untouched when a new row joins. Mathematically it is still a correct
echelon form: the new row is 0 on every earlier pivot column (guaranteed by the
fold inside reduceAgainst, see reduceAgainst_get_piv_eq_zero), so the pivot columns are
pairwise distinct, and taking the least index then proves the rows linearly
independent.
Relation to rowReduce #
rankEchelon_eq_length_rowReduce: the two routines return the same number and the same row
space, so the rank is rankEchelon. Downstream rank and k claims can therefore switch
backends without any change to the mathematics, while rowReduce and all of its
downstream theorems (canonical-form uniqueness in GF2/Canonical.lean relies on the RREF
produced by back-substitution) are left untouched.
A discipline for the implementation (read before editing) #
stepA must be a separate def, and the main loop must be written as
List.foldl stepA []. Inlining if hw : reduceAgainst D v = 0 then … else … into the
structural recursion makes the elaborator hang (measured: more than 3 minutes with no
output, a 3.2 GB working set on a single core), whereas the same logic split into a
separate def goes through at once.
Main results #
spanL_rowList_echelonFrom: the row space is unchanged (the code is not modified).linearIndependent_get_of_echelon: the rows of the echelon form are linearly independent.finrank_spanL_eq_length_echelonFrom/rankEchelon_eq_length_rowReduce: the rank readings.
Append-only pivot insertion and the main loop #
The echelon form obtained by appending row by row (the main loop of this module).
Equations
Instances For
The rank routine: the number of rows of the append-only echelon form.
Equations
Instances For
Invariance of the row space #
Invariance of the row space: the append-only echelon form spans the same row space as the original list of rows, and so defines the same code.
The echelon invariants #
The append-only echelon form satisfies two properties. These are weaker than IsReduced:
they do not require an earlier row to be 0 on a later pivot column, and that is exactly the
price of dropping back-substitution.
EchSelf: every row is 1 on its own pivot columnEchPair: a later row is 0 on the pivot columns of all earlier rows.
EchPair is a product of the fold inside reduceAgainst: while the j-th row is being
processed, every already processed row is cleared one at a time on its own pivot
column, and adding further rows cannot reintroduce anything, because every row added is 0
on all earlier pivot columns (see reduceAgainst_get_piv_eq_zero).
The key step: once the fold inside reduceAgainst has finished, the accumulator
is 0 on every pivot column of that state.
The induction separates the head row from the rest. For the remaining rows it uses the induction hypothesis, which holds for an arbitrary starting vector; for the pivot column of the head row it uses the fact that the remaining rows are all 0 on that column, so the fold does not change it, together with the starting vector already being zero there.
Linear independence of the echelon rows #
The rows of an echelon form are linearly independent.
Take the least index j whose coefficient in the linear combination is nonzero and
evaluate on the pivot column of row j: terms with index below j have coefficient 0, and
rows with index above j are 0 on that column by EchPair, so the combination takes the
value g j ≠ 0 there, contradicting the combination being zero.
The i-th element of (D ++ [x]), for i within D, is the i-th element of
D.
For a variable D, (D ++ [x]).length is not defeq to D.length + 1, so the index is
written out explicitly as ⟨i.val, _⟩ rather than with Fin.cast, which avoids moving
proofs around at the level of Fin.
The append-only echelon form satisfies the two invariants.
Rank readings #
The rank equals the number of rows of the append-only echelon form.
The bridge theorem: the append-only echelon form and rowReduce give the same
rank.
Both are outputs of the same greedy pivot count, with the same row space, and in both cases
the number of rows is the rank. Downstream rank and k claims can therefore switch
backends without any change to the mathematics; rowReduce and its downstream users
(canonical-form uniqueness in GF2/Canonical.lean relies on the RREF produced by
back-substitution) are untouched.
A fast backend for the membership test (echelonFrom in place of rowReduce) #
inSpanB in GF2/Membership.lean decides row-space membership from the output of
rowReduce. Where the test is recomputed for every candidate, as in the weight-bounded
enumeration of lightCand, this is a difference of orders of magnitude: on width 24,
rowReduce takes more than 200 s from the 7th row onwards, whereas echelonFrom takes
milliseconds.
The key observation: the membership test does not need the full reduction (RREF)
produced by rowReduce; the append-only echelon form suffices. The elimination argument
inside reduceAgainst uses only EchPair, that is, a later row being 0 on an earlier row's
pivot column, and the step that the RREF used to carry (a vector that lies in the row space
and is 0 on every pivot column must be zero) is supplied instead by taking the least index
in the coefficient representation.
In echelon form, all pivot columns zero implies zero: if w lies in the span of
rowList D and is 0 on every pivot column of D, then w = 0.
The argument: take a coefficient representation w = Σ cᵢ rᵢ and let j be the least
index with cⱼ ≠ 0. Evaluating on pⱼ, the terms with i > j vanish by EchPair and
those with i < j have coefficient 0, so w pⱼ = cⱼ ≠ 0, contradicting the hypothesis.
This is exactly the part that the symmetry condition (R2) of IsReduced used to carry.
The reduction vanishes exactly on the row space (echelon backend): the output of
echelonFrom annihilates the span of the original list of rows.
This is parallel to reduceAgainst_eq_zero_of_mem_spanL in GF2/Membership.lean, the
IsReduced version, but the hypotheses here use only EchSelf and EchPair; the step that
the RREF used to carry is supplied by eq_zero_of_mem_spanL_of_piv_eq_zero.
Computable membership test: echelon backend #
The same mathematical content and the same interface shape as inSpanB in
GF2/Membership.lean, with only the reduction backend changed. Where the test is run once
per candidate, as in the weight-bounded enumeration of lightCand, which has 2325
candidates for $[[24,3,4]]$, this is a difference of orders of magnitude: on width 24,
rowReduce fails to converge after the 7th row and takes more than 200 s, whereas
echelonFrom takes milliseconds.
Computable row-space membership test (echelon backend).
Equations
Instances For
Explicit rank certificates #
The cost of row reduction rises steeply with width. Where the width is large enough that
rowReduce and echelonFrom no longer finish, the rank can still be obtained by checking
a certificate. There is a cheap criterion on each side:
- Lower bound: produce a list
Dof candidate rows with pairwise distinct pivots, each of them lying inspanL L. If those rows are defined as linear combinations of rows ofL, assembled term by term withSubmodule.add_mem, membership is algebraic and independent of the width, while distinctness of the pivots is a finite check thatdecidecan perform. HenceD.length ≤ dim spanL L(length_le_finrank_spanL_of_certificate). - Upper bound: produce redundant generators, that is, write
LasA ++ Band prove that every row ofBlies inspanL A; thendim spanL L ≤ A.length(finrank_spanL_append_le_of_mem). Each redundancy is a single equality check of the form "this row is a sum of rows", whichdecidecan perform.
Both are row-by-row or relation-by-relation checks, so their cost is linear in the width. Concrete upstream rank certificates are outside this import.
The dimension of spanL A is at most the length of A: A is a generating
set.
A rank upper bound from redundant generators (set form): if every row of L
lies in spanL A, then dim spanL L ≤ A.length. The list A need not be a prefix of L,
since spanL sees only the underlying set, so picking head rows of L that span the whole
list and having the whole list generated by those head generators come to the same thing.
This form also applies when a spanning subset is not a prefix of the row list.
A rank lower bound from a certificate: if every row of D lies in spanL L and
D satisfies the echelon invariants, then D.length ≤ dim spanL L.