Prove2Me mission category: Textbook.
Reusable fast matrix multiplication
Matrix multiplication is often a component of a larger computation. A useful
verified routine must return the right product, preserve its caller's data,
and bound the work performed by the same returning call. Strassen's algorithm
reduces the number of recursive block products from eight to seven, obtaining
a subcubic arithmetic bound. This mission connects that algorithm to a mutable
array implementation and a reusable Lean contract.
The reference is Introduction to Algorithms (CLRS), fourth edition,
Chapter 4, §4.2: Strassen's algorithm for matrix multiplication.
CLRS, fourth edition.
The underlying algorithm is an established result originating in
Strassen's 1969 paper.
Matrices, returned product, and measured work
A matrix is represented by an array of row arrays, written
Seq[Seq[Int]]. Each row contains unbounded mathematical integers. Let
k∈N be the recursion depth and n=2k the matrix dimension.
The mathematical input values are functions
A,B:Z→Z→Z; only their entries inside the
n×n square enter the product.
A heap h stores mutable rows, outer arrays, objects, and named
natural-number counters. The predicate MatrixHeap.Stored h a A connects
an allocated outer-array reference a to its row contents A.
Both input arrays must satisfy this representation predicate. Input matrices
and their rows may share references, including sharing between the two inputs.
A Call contract supplies a finite execution in the generated semantics:
for every suspended caller stack, strassen.Call returns the same result
reference r and final heap h′. All correctness, preservation, and work
conclusions below concern that same h′ and r.
The counted work T sums the increments of four source clocks:
products for scalar leaf products, calls for all Strassen invocations
(including the initial call),
linear_cells for entrywise combinations, and copied_cells for entries
copied during quadrant extraction and joining. Each increment has unit weight.
Initial counters may be nonzero; the bound concerns additional work.
Formalization targets
The main correctness-and-running-time theorem
already has an accepted proof on Prove2Me under Lean 4.33.1. Its name is
CLRS.Chapter4Publication.Strassen.correctness_and_running_time.
For every represented input of dimension n=2k, the contract supplies a
finite returning call whose output matrix R satisfies
Rij=t=0∑n−1AitBtj(0≤i,j<n),Rij=0outside this square.
Every previously allocated row, outer array, and object is preserved. The
result's outer array is fresh, and its n in-square rows are fresh and
pairwise distinct. One positive natural constant is chosen before all depths,
heaps, and matrix inputs:
∃C>0,∀ represented inputs with n=2k,∃ a correct returning call with 7k≤T≤C7k.
Thus the main proposition leaves the upper constant available for improvement.
The accepted proof supplies C=13. On the permitted dimensions this gives
the conventional Θ(nlog27) counted-work bound. The checked
headline states natural-power inequalities, rather than a real-logarithm
asymptotic relation over arbitrary dimensions.
The exact counter schedule is stronger than the displayed upper bound.
Writing Q0=1, Qk+1=1+7Qk, H0=0, and
Hk+1=4k+7Hk, the four increments are
Δproducts=7k,Δcalls=Qk,Δlinear_cells=18Hk,Δcopied_cells=12Hk.
All other clocks remain unchanged. Consequently the total satisfies
T(0)=2 and T(k+1)=7T(k)+30⋅4k+1.
These are properties of the existing combined contract and its supporting
definitions, rather than additional open targets.
What callers can reuse
A later algorithm can invoke the multiplication routine with its existing
heap and counters and use the established product, preservation, freshness,
and work guarantees. Fresh independent output rows permit subsequent row
updates without aliasing another in-square output row or an input row.
The surrounding algorithm still proves its own input representation and any
new conclusions; it can reuse this multiplication analysis unchanged.
The source development
contains the matrix algebra, mutable execution analysis, and exact clock
summaries. The contribution is their connection in one importable contract.
The mission reuses the published theorem and its existing dependencies.
Alternative checked proofs, improved constants, and verified client examples
can build on that result.
Where the formalization is demanding
The seven-product identity alone does not establish that a mutable program
returns the product. Quadrant copies, intermediate combinations, allocations,
and possible input sharing must agree with the mathematical matrices.
Equal row contents do not imply independent row references.
Counting only scalar leaf products also omits copying and combination work.
A numerical recurrence by itself does not supply a returning program execution.
The contract must connect those charges and the returned values to the same
finite call while preserving caller storage.
Formalization scope and source
The .algo implementation follows the seven recursive products and block
recombination of CLRS §4.2, making quadrant copies and result allocation
explicit. The complete source file is shown below.
Sequences are total over integer indices. Stored requires an allocated row
at every integer row index, including indices outside the finite square.
In a fixed heap only finitely many distinct whole row sequences can be
represented; out-of-square rows may share storage. The contract does not
assume the desired product, an existing transcript, or an unverified runtime
bound. Its execution conclusion is an existential returning-call guarantee;
the headline alone does not quantify over every possible execution.
Depth zero gives dimension one and T=2. Dimension zero, other dimensions,
rectangular multiplication, and automatic padding are outside this contract.
Freshness and pairwise distinctness of output rows apply to the finite square.
The cost model excludes allocation, uncharged bookkeeping, total generated
transition counts, elapsed time, and integer bit complexity. Multiplication
by sign in a linear combination is charged to linear_cells, not products.
The exact compiler/source snapshot is
fd33cf959b39f9e4a9fcfda4ef31d5a36b784d41,
an Algo2Lean development snapshot distinct from public v0.1.4. The repository
pins Lean 4.26.0; the accepted platform declarations use Lean 4.33.1 with
Mathlib 0df444a360eaa60ab8c11dca51a86af692955474. The
publication record
records the source and adaptation checks. Platform packaging uses the CLRS
namespace and adapted imports. Source generation, declaration comparison,
and Lean proof checking are separate checks; the compiler itself has no
formal semantics-preservation proof.
examples/matrix_strassen.algo
# Strassen multiplication with correctness and Θ(n^(log₂ 7)) counted work for n = 2^k (CLRS 4.2).
# Preconditions: n = 2^depth for a natural depth; inputs have arbitrary Int entries.
# Every finite output row is fresh. Input matrices and their rows are never modified.
def matrix_linear_row(a: Seq[Int], b: Seq[Int], count: Int, sign: Int) -> Seq[Int]:
c = const(0)
i = 0
# @loop
while i < count:
c[i] = a[i] + sign * b[i]
tick("linear_cells")
i += 1
return c
def matrix_linear(a: Seq[Seq[Int]], b: Seq[Seq[Int]], count: Int, sign: Int) -> Seq[Seq[Int]]:
c: Seq[Seq[Int]] = const(const(0))
i = 0
# @loop
while i < count:
c[i] = matrix_linear_row(a[i], b[i], count, sign)
i += 1
return c
def matrix_quadrant_row(a: Seq[Int], h: Int, column: Int) -> Seq[Int]:
c = const(0)
i = 0
# @loop
while i < h:
c[i] = a[column + i]
tick("copied_cells")
i += 1
return c
def matrix_quadrant(a: Seq[Seq[Int]], n: Int, h: Int, row: Int, column: Int) -> Seq[Seq[Int]]:
c: Seq[Seq[Int]] = const(const(0))
i = 0
# @loop
while i < h:
c[i] = matrix_quadrant_row(a[row + i], h, column)
i += 1
return c
def matrix_join_row(a: Seq[Int], b: Seq[Int], h: Int) -> Seq[Int]:
result = const(0)
i = 0
# @loop
while i < h:
result[i] = a[i]
result[i + h] = b[i]
tick("copied_cells", 2)
i += 1
return result
def matrix_join(a: Seq[Seq[Int]], b: Seq[Seq[Int]], c: Seq[Seq[Int]], d: Seq[Seq[Int]], h: Int) -> Seq[Seq[Int]]:
result: Seq[Seq[Int]] = const(const(0))
i = 0
# @loop
while i < h:
result[i] = matrix_join_row(a[i], b[i], h)
result[i + h] = matrix_join_row(c[i], d[i], h)
i += 1
return result
def strassen(a: Seq[Seq[Int]], b: Seq[Seq[Int]], n: Int) -> Seq[Seq[Int]]:
tick("calls")
if n <= 1:
c: Seq[Seq[Int]] = const(const(0))
c[0] = const(0)
c[0][0] = a[0][0] * b[0][0]
tick("products")
return c
else:
h = n // 2
a11 = matrix_quadrant(a, n, h, 0, 0)
a12 = matrix_quadrant(a, n, h, 0, h)
a21 = matrix_quadrant(a, n, h, h, 0)
a22 = matrix_quadrant(a, n, h, h, h)
b11 = matrix_quadrant(b, n, h, 0, 0)
b12 = matrix_quadrant(b, n, h, 0, h)
b21 = matrix_quadrant(b, n, h, h, 0)
b22 = matrix_quadrant(b, n, h, h, h)
# Seven recursive products replace the ordinary eight.
p1 = strassen(a11, matrix_linear(b12, b22, h, -1), h)
p2 = strassen(matrix_linear(a11, a12, h, 1), b22, h)
p3 = strassen(matrix_linear(a21, a22, h, 1), b11, h)
p4 = strassen(a22, matrix_linear(b21, b11, h, -1), h)
p5 = strassen(matrix_linear(a11, a22, h, 1), matrix_linear(b11, b22, h, 1), h)
p6 = strassen(matrix_linear(a12, a22, h, -1), matrix_linear(b21, b22, h, 1), h)
p7 = strassen(matrix_linear(a11, a21, h, -1), matrix_linear(b11, b12, h, 1), h)
c11 = matrix_linear(matrix_linear(matrix_linear(p5, p4, h, 1), p2, h, -1), p6, h, 1)
c12 = matrix_linear(p1, p2, h, 1)
c21 = matrix_linear(p3, p4, h, 1)
c22 = matrix_linear(matrix_linear(matrix_linear(p5, p1, h, 1), p3, h, -1), p7, h, -1)
return matrix_join(c11, c12, c21, c22, h)
Selected references
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein,
Introduction to Algorithms, fourth edition, MIT Press, 2022, Chapter 4,
§4.2. Publisher.
- Volker Strassen, “Gaussian Elimination is Not Optimal,” Numerische
Mathematik 13, 354–356, 1969. DOI.
- Algo2Lean, Chapter 4 source and proof development, 2026.
Source snapshot.
Algo2Lean was created by Lunjia Hu; Chutong Yang is a contributor, and both
are software citation authors.