Zulip Chat Archive

Stream: new members

Topic: Working with basis and linear transforms


David Chanin (Jul 18 2022 at 12:35):

I'm going through the book "Linear Algebra Done Right", and thought I'd try to do some of the proof exercises from the book in Lean. I'm really struggling to understand how to deal with linear transforms and basis of vector spaces in lean. The proof I'm trying to write is of the following statement:

Suppose V and W are finite-dimensional and T is a linear transform from V to W.
Show that with respect to each choice of bases of V and W, the matrix of
T has at least dim range T nonzero entries.

I'm able to express V and W in Lean as the following, based on what I found in a Xena lecture on Youtube:

import linear_algebra.finite_dimensional

variables {K: Type} [field K]
variables {V: Type} [add_comm_group V] [module K V] [finite_dimensional K V]
variables {W: Type} [add_comm_group W] [module K W] [finite_dimensional K W]

What I'm struggling to express is the idea of a Linear Transform T which maps from V to W, and how to write the basis of V and W. And then how to express the idea of the range of T in Lean. I see there's linear_algebra/basis.lean, and there's linear_algebra/basic.lean which mentions linear maps, but in a syntax I can't follow. I can't figure out how to use the concepts in these files though - are there examples somewhere of how to work with Linear Transforms and Basis of vector spaces in Lean?

Anne Baanen (Jul 18 2022 at 12:40):

A linear transform is called docs#linear_map in Mathlib, and a K-linear transform is written (T : V →ₗ[K] W). You have to indicate which field (ring) it's linear over, because V and W could also be vector spaces over other fields.

Anne Baanen (Jul 18 2022 at 12:43):

An arbitrary basis of V is written as {ι : Type*} (b : basis ι K V). To choose a fixed, but unkown, basis for a vector space, use docs#basis.of_vector_space.

Anne Baanen (Jul 18 2022 at 12:48):

More specifically, basis ι K V is the type of all -dimensional bases for the K-vector space V, where is the cardinality of ι. So basis (fin 4) K V is the type of all 4-dimensional bases.

David Chanin (Jul 18 2022 at 12:50):

Aah thank you so much! I tried adding the following 2 lines:

variables (T : V →ₗ[K] W)
variables {bV: Type} [basis.of_vector_space K V]

The T definition seems to not give an error, but variables {bV: Type} [basis.of_vector_space K V] gives the following error:

invalid type ascription, term has type
  basis (basis.of_vector_space_index K V) K V : Type
but is expected to have type
  Sort ? : Type ?

I don't understand what this means. Apologies I'm sure this is an extremely basic misunderstanding on my part!

Riccardo Brasca (Jul 18 2022 at 12:52):

What do you want to say with variables {bV: Type} [basis.of_vector_space K V]? Do you want to fix a basis?

David Chanin (Jul 18 2022 at 12:52):

I want to say "bv is a basis of V"

Riccardo Brasca (Jul 18 2022 at 12:53):

So you need to say variables (bv : basis (basis.of_vector_space_index K V) K V))

Eric Wieser (Jul 18 2022 at 12:54):

or just (bv : basis I K V)

Eric Wieser (Jul 18 2022 at 12:54):

No need to specify that it's one particular arbitrary index set

Riccardo Brasca (Jul 18 2022 at 12:55):

Oh sure, that's even better. basis I K V is the type (read set) of K-basis of V indexed by I (you need to introduce (I : Type*) before naming it).

Riccardo Brasca (Jul 18 2022 at 12:57):

Note that in mathlib basis have this index set, that is usually irrelevant in math. You can have a basis indexed the set {1,2,3} and a basis indexed by {a,b,c} (this is not precise, basis are indexed by types, but still), and even if the two basis "choose" the same elements of V, they're different.

David Chanin (Jul 18 2022 at 12:58):

Amazing! Thank you so much! That worked! I don't think I would have ever come up with that on my own :sweat_smile:

Riccardo Brasca (Jul 18 2022 at 13:00):

This is because we usually think that a basis is a set, but this is incorrect, since then we order the elements to build the matrix. Also, at least in principle, we allow repetitions (as in the exercise "the set {v, v}" is not linear independent since v-v = 0 is a non trivial linear combination equals to 0). The way this idea is formalized is that a basis is a function b : ι → V for some index type ι. Usually only its cardinality matters, but technically it is there.

Kevin Buzzard (Jul 18 2022 at 13:07):

David Chanin said:

Amazing! Thank you so much! That worked! I don't think I would have ever come up with that on my own :sweat_smile:

This is the way of things at the beginning. Even after I'd done 50 undergraduate problem sheet questions there were still plenty of problems where I couldn't even write down the question properly. Everything works, you just have to get the hang of the way mathlib thinks about mathematics.

David Chanin (Jul 18 2022 at 16:33):

Kevin Buzzard said:

David Chanin said:

Amazing! Thank you so much! That worked! I don't think I would have ever come up with that on my own :sweat_smile:

This is the way of things at the beginning. Even after I'd done 50 undergraduate problem sheet questions there were still plenty of problems where I couldn't even write down the question properly. Everything works, you just have to get the hang of the way mathlib thinks about mathematics.

That's encouraging to hear! I went through the natural number game and can do some basic proofs, but trying to prove things in the wild still feels really challenging

David Chanin (Jul 18 2022 at 16:41):

I'm struggling now to express the idea of "the matrix of T has at least dim range T nonzero entries". I think this really boils down to the idea that T(x) can be expressed as a sum of the T(coeffients * basis_of_v), and there should be at least dim range T non-zero coefficients in that equation. Is there a way I can express that T can be expressed be as ∑ a_i* T(basis_of_v_i), so that at least dim range T of those a_i must be non-zero?

Yakov Pechersky (Jul 18 2022 at 16:58):

Do you a linear_map? If so, you can make a matrix out of it, given a basis. Then you can form a multiset of the values in the matrix. And then you can say that dim range T <= multiset.card s - multiset.count s 0.

David Chanin (Jul 18 2022 at 17:00):

I have the following setup so far:

variables {K: Type} [field K]
variables {V: Type} [add_comm_group V] [module K V] [finite_dimensional K V]
variables {W: Type} [add_comm_group W] [module K W] [finite_dimensional K W]
variables {I : Type*}

variables (T : V →ₗ[K] W)
variables (bv : basis I K V)
variables (bw : basis I K W)

So, T is a linear map (I think), and bv and bw are bases. How can I form a matrix from T given bv?

Yakov Pechersky (Jul 18 2022 at 17:01):

The guessable name docs#linear_map.to_matrix

David Chanin (Jul 18 2022 at 17:09):

Apologies this is probably really obvious, but I can't seem to get that function to work. I'm trying the following:

variable T_matrix : linear_map.to_matrix bv bw T

David Chanin (Jul 18 2022 at 17:10):

In the docs it seems like it takes 2 bases and the linear map, but this seems like the wrong inputs

Yakov Pechersky (Jul 18 2022 at 17:11):

At this point, linear_map.to_matrix bv bw is a term, not a type

David Chanin (Jul 18 2022 at 17:12):

Does that mean I can't use variable to name this?

Yakov Pechersky (Jul 18 2022 at 17:12):

so you wouldn't say you have a variable of this type. you could have:

example (T_matrix : matrix V W) (h : T_matrix = linear_map.to_matrix bv bw T) : T_matrix  = T.to_matrix bv bw := h

There, I am using dot-notation to have a cleaner statement

David Chanin (Jul 18 2022 at 17:16):

ooh interesting. Hrm this is giving an error as well,

type expected at
  matrix V W
term has type
  Type ?  Type ?

David Chanin (Jul 18 2022 at 17:17):

The full code is:

import tactic
import linear_algebra.finite_dimensional
import linear_algebra.matrix.to_lin
import linear_algebra.matrix

variables {K: Type} [field K]
variables {V: Type} [add_comm_group V] [module K V] [finite_dimensional K V]
variables {W: Type} [add_comm_group W] [module K W] [finite_dimensional K W]
variables {I : Type*}

variables (T : V →ₗ[K] W)
variables (bv : basis I K V)
variables (bw : basis I K W)

example
  (T_matrix : matrix V W)
  (h : T_matrix = linear_map.to_matrix bv bw T)
:
  T_matrix = T.to_matrix bv bw
:=
  h

Yakov Pechersky (Jul 18 2022 at 17:19):

Ah, sorry. I was wrong about the syntax here.

Yakov Pechersky (Jul 18 2022 at 17:19):

This works

import tactic
import linear_algebra.finite_dimensional
import linear_algebra.matrix.to_lin
import linear_algebra.matrix

variables {K: Type} [field K]
variables {V: Type} [add_comm_group V] [module K V] [finite_dimensional K V]
variables {W: Type} [add_comm_group W] [module K W] [finite_dimensional K W]
variables {I : Type*}

variables (T : V →ₗ[K] W)
variables (bv : basis I K V)
variables (bw : basis I K W)

example
  [decidable_eq I]
  [fintype I]
  (T_matrix : matrix I I K)
  (h : T_matrix = linear_map.to_matrix bv bw T)
:
  T_matrix = linear_map.to_matrix bv bw T
:=
  h

Yakov Pechersky (Jul 18 2022 at 17:20):

So I couldn't get dot-notation working here. And I was missing the base ring argument of the matrix term, that was the error. And also I was confused about how to specify it -- I needed to give the indexing type. Then, I got errors about missing instances -- linear_map.to_matrix only really works on indexing types that can be finitely and distinctly enumerated, which is what [decidable_eq I] [fintype I] do.

David Chanin (Jul 18 2022 at 17:22):

I see so I refers to the length of the basis, so if I use the same I that means that both V and W are the same size space

David Chanin (Jul 18 2022 at 17:23):

How did you figure all of that out?

Yakov Pechersky (Jul 18 2022 at 17:23):

Going back to your original question about why variables T_matrix : linear_map.to_matrix ..., variables just means "let us have an x \mem X" in regular math speak

Yakov Pechersky (Jul 18 2022 at 17:24):

You are the one that said that both V and W are the same size space here:

variables (bv : basis I K V)
variables (bw : basis I K W)

Yakov Pechersky (Jul 18 2022 at 17:24):

You're saying there "I have this data package I will call bv, which is a set of vectors indexed by I that is a basis for the vector space V over the base field K"

Yakov Pechersky (Jul 18 2022 at 17:25):

Similarly for bw, W. So you can have two different I, I_prime to generalize.

David Chanin (Jul 18 2022 at 17:26):

That makes sense - I just updated the code to the following to fix that:

variables {Vi : Type*} [fintype Vi] [decidable_eq Vi]
variables {Wi : Type*} [fintype Wi] [decidable_eq Wi]

variables (T : V →ₗ[K] W)
variables (bv : basis Vi K V)
variables (bw : basis Wi K W)

example
  (T_matrix : matrix Wi Vi K)
  (h : T_matrix = linear_map.to_matrix bv bw T)
:
  T_matrix = linear_map.to_matrix bv bw T
:=
  h

David Chanin (Jul 18 2022 at 17:38):

hrm maybe this proof is too advanced for me. I'm still struggle to even write out the thing that I'm trying to prove :face_palm: . I think I want something like the following:

example
  (T_matrix : matrix Wi Vi K)
  (h : T_matrix = linear_map.to_matrix bv bw T)
:
  /- number of non-zero entries in T_matrix -/  finrank K T.range
:=
  sorry

Yakov Pechersky (Jul 18 2022 at 17:38):

If you were programming, how you would get the entries of a matrix?

David Chanin (Jul 18 2022 at 17:43):

I'd loop over the indices of the matrix

Yakov Pechersky (Jul 18 2022 at 17:43):

Great, so how would it look like explicitly here? If you can write it out, we can translate it to lean

David Chanin (Jul 18 2022 at 17:44):

or do something like (mat != 0).sum()

David Chanin (Jul 18 2022 at 17:45):

Using nested for-loops, it would be something like:

total_non_zero = 0
for i in len(mat):
  row = mat[i]
  for j in len(row[i]):
    if row[j] != 0:
      total_non_zero += 1

Yakov Pechersky (Jul 18 2022 at 17:46):

OK, so here, we're going to express it as a map. Really, a map of maps

David Chanin (Jul 18 2022 at 17:51):

I see a matrix.map function. Is that the correct thing to use?

David Chanin (Jul 18 2022 at 17:52):

Something like mat.map(elm -> elm == 0 ? 0 : 1) and then sum it somehow?

Yakov Pechersky (Jul 18 2022 at 17:53):

import tactic
import linear_algebra.finite_dimensional
import linear_algebra.fin
import linear_algebra.matrix.to_lin
import linear_algebra.matrix

variables {K: Type} [field K]
variables {V: Type} [add_comm_group V] [module K V] [finite_dimensional K V]
variables {W: Type} [add_comm_group W] [module K W] [finite_dimensional K W]

variables {Vi : Type*} [fintype Vi] [decidable_eq Vi]
variables {Wi : Type*} [fintype Wi] [decidable_eq Wi]

variables (T : V →ₗ[K] W)
variables (bv : basis Vi K V)
variables (bw : basis Wi K W)

noncomputable def values : multiset K :=
(finset.product (finset.univ : finset Vi) (finset.univ : finset Wi)).val.map
  (λ p, linear_map.to_matrix bv bw T p.2 p.1)

lemma target [decidable_eq K] :
  set.finrank K (T.range : set W)  (values T bv bw).card - ((values T bv bw).count 0) :=
sorry

Yakov Pechersky (Jul 18 2022 at 17:54):

Yeah you could also do that

David Chanin (Jul 18 2022 at 17:54):

what does it mean to be noncomputable?

Yakov Pechersky (Jul 18 2022 at 17:55):

We don't have a matrix.sum, so even if we mapped the matrix, the count (which would be the sum), would require something similar

David Chanin (Jul 18 2022 at 17:55):

aah so I just need to prove the target lemma then!

Yakov Pechersky (Jul 18 2022 at 17:55):

noncomputable here just means that it does not generate VM code to execute this definition. That is because the linear_map.to_matrix construction is a axiom-of-choice-based construction

David Chanin (Jul 18 2022 at 17:55):

OK, at least that's something for me to start with

David Chanin (Jul 18 2022 at 17:57):

I see, if I delete "noncomputable" it says I need to add it back , so at least I can't ever forget to write that if it's needed

Yakov Pechersky (Jul 18 2022 at 17:57):

It's axiom-of-choice, because, consider an arbitrary distinct finite set that indexes our matrix. Sure, but in what order? The order of the indexing doesn't matter for it being a linear map. But you have to choose _some_ order for the matrix to actually exist.

Yakov Pechersky (Jul 18 2022 at 17:57):

That's not the full story, but it's close enough

David Chanin (Jul 18 2022 at 17:58):

Does that mean you can write theorems about matrices, but can never actually instantiate a specific matrix and run a computation with it?

David Chanin (Jul 18 2022 at 17:59):

like, I could never create the matrix [1 2; 3 4] and then compute the determinant or something?

Yakov Pechersky (Jul 18 2022 at 18:01):

Oh, you definitely can:

import data.matrix.notation
def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M 1 1 -- 4

Yakov Pechersky (Jul 18 2022 at 18:02):

But in lean, there are different modes of computation. There is the kernel, the VM, and also tactic based. Like this is tactic based from test/matrix.lean.

example {α : Type*} [comm_ring α] {a b c d e f g h i : α} :
  matrix.det !![a, b, c; d, e, f; g, h, i] =
    a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g :=
begin
  simp [matrix.det_succ_row_zero, fin.sum_univ_succ],
  /-
  Try this: simp only [det_succ_row_zero, fin.sum_univ_succ, neg_mul, cons_append,
  mul_one, fin.default_eq_zero, fin.coe_zero, cons_vec_bit0_eq_alt0, one_mul, cons_val_one,
  cons_vec_alt0, fin.succ_succ_above_one, fin.coe_succ, univ_unique, minor_apply, pow_one,
  fin.zero_succ_above, fin.succ_zero_eq_one, fin.succ_succ_above_zero, nat.neg_one_sq,
  finset.sum_singleton, cons_val_zero, cons_val_succ, det_fin_zero, head_cons, pow_zero]
   -/
  ring
end

Yakov Pechersky (Jul 18 2022 at 18:02):

Note, the #eval example had concrete explicit types. In the det example, I "computed" the determinant on an arbitrary type of values.

Yakov Pechersky (Jul 18 2022 at 18:03):

And yes, one can compute the det:

import data.matrix.notation
import linear_algebra.matrix.determinant
def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- -2

David Chanin (Jul 18 2022 at 18:03):

Seeing this is really helpful

David Chanin (Jul 18 2022 at 18:04):

Thank you so much for taking the time to walk me through all of this stuff!

Yakov Pechersky (Jul 18 2022 at 18:04):

Sure thing! My first foray into mathlib was also trying to prove stuff about matrices and determinants

Yakov Pechersky (Jul 18 2022 at 18:05):

That 3x3 determinant example was one of my first pushes to get all the lemmas in place and make simp; ring be able to solve it

David Chanin (Jul 18 2022 at 18:05):

ooh excellent - is any of that online? I didn't see a lot of examples of proving linear algebra stuff online

Yakov Pechersky (Jul 18 2022 at 18:05):

I kinda abandoned it, but you could look at https://github.com/pechersky/e222/tree/master/src

Yakov Pechersky (Jul 18 2022 at 18:06):

There are other people working on formalizing Axler

Yakov Pechersky (Jul 18 2022 at 18:07):

Here's another example of "computation": https://github.com/pechersky/e222/blob/master/src/problems02.lean#L49-L58

Yakov Pechersky (Jul 18 2022 at 18:08):

It's not only computing that these two matrices are the same, but also computing a proof certificate of that, that's via the norm_num tactic.

David Chanin (Jul 18 2022 at 18:09):

ah great, I'll take a look at these!

Yakov Pechersky (Jul 18 2022 at 18:09):

And here's the "proof-by-brute-force version":

import data.matrix.notation
import linear_algebra.matrix.determinant
def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- -2

example : M.det = -2 := by dec_trivial

Yakov Pechersky (Jul 18 2022 at 18:10):

Watch how a tiny change breaks the "proof-by-force":

import data.matrix.notation
import linear_algebra.matrix.determinant
def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- -2

example : M.det = -2 := by dec_trivial -- no longer works

Yakov Pechersky (Jul 18 2022 at 18:12):

So instead, "proof-by-tactic":

import data.matrix.notation
import linear_algebra.matrix.determinant
def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- -2

example : M.det = -2 := by norm_num [matrix.det_fin_two, M]

Yakov Pechersky (Jul 18 2022 at 18:14):

And yet another change:

import data.matrix.notation
import linear_algebra.matrix.determinant
import data.real.basic

def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- uhhh, what?? it's showing underlying impl, not 2

example : M.det = -2 := by norm_num [matrix.det_fin_two, M]

David Chanin (Jul 18 2022 at 18:32):

I can't get these examples to run, it doesn't seem to like the !![1, 2; 3, 4] syntax in Lean for me

Yakov Pechersky (Jul 18 2022 at 18:32):

Do you have data.matrix.notation imported? What mathlib commit are you on?

Yakov Pechersky (Jul 18 2022 at 18:32):

I am on current master, or maybe 13 commits behind

David Chanin (Jul 18 2022 at 18:35):

Mine is 16 days old, let me try updating...

David Chanin (Jul 18 2022 at 18:38):

oh yeah it works now! strange

Yakov Pechersky (Jul 18 2022 at 18:39):

It was a recent PR

David Chanin (Jul 18 2022 at 18:46):

Yakov Pechersky said:

And yet another change:

import data.matrix.notation
import linear_algebra.matrix.determinant
import data.real.basic

def M : matrix (fin 2) (fin 2)  := !![1, 2; 3, 4]

#eval M.det -- uhhh, what?? it's showing underlying impl, not 2

example : M.det = -2 := by norm_num [matrix.det_fin_two, M]

whoa why does the #eval break so hard with real numbers? Stuff only works correctly on integers?

Ruben Van de Velde (Jul 18 2022 at 18:58):

Reals are equivalence classes of Cauchy sequences of rational numbers - it's not so obvious to compute with those

Yaël Dillies (Jul 18 2022 at 19:00):

It's not obvious what it even means to print out a real!

David Chanin (Jul 18 2022 at 19:04):

🤯 math is hard

Eric Wieser (Jul 18 2022 at 19:25):

@Yakov Pechersky, docs#linear_map.to_matrix uses choice for totally silly reasons (in order to state that a basis is a linear map to coordinates), the actual data is computed without it.

Eric Wieser (Jul 18 2022 at 19:26):

But you have to choose _some_ order for the matrix to actually exist.

Not true, docs#matrix and even docs#matrix.det requires no order

Eric Wieser (Jul 18 2022 at 19:28):

@Yaël Dillies, I was planning a PR such that reals that are coerced naturals have a reasonable repr

Yakov Pechersky (Jul 18 2022 at 19:30):

Sorry for the FUD. What's the silly reason for that basis issue?

Eric Wieser (Jul 18 2022 at 19:49):

docs#finsupp.has_add

Eric Wieser (Jul 18 2022 at 19:50):

To avoid earning a :butterfly:; by "totally silly" I mean "not mathematical" as opposed to "this was a bad design decision".

Eric Wieser (Jul 18 2022 at 19:52):

As a tip: you can hover over the word "noncomputable" in the docs to see why something is noncomputable

Yaël Dillies (Jul 18 2022 at 20:42):

Eric Wieser said:

Yaël Dillies, I was planning a PR such that reals that are coerced naturals have a reasonable repr

And fractions too?

Anne Baanen (Jul 19 2022 at 09:57):

Yaël Dillies said:

Eric Wieser said:

Yaël Dillies, I was planning a PR such that reals that are coerced naturals have a reasonable repr

And fractions too?

Currently not possible, but should be doable after #14894.


Last updated: Dec 20 2023 at 11:08 UTC