I've been having fun rendering charts and graphs from co-ordinates lately, and I'm fascinated by using matrices to transform co-ordinate spaces.
I've been able to successfully scale and invert 2 dimensional co-ordinate spaces, but now my appetite is whetted. :)
Where can I go for clear, informative, (free), educational material on matrices, matrix math, especially as applies to 2 and 3 dimensional space?
You may want to look at Geometric linear algebra by I-Hsiung Lin, Yixiong Lin (ISBN : 9812560874). The book is specifically geared towards what you want (linear transformations of 2 and 3-dimensional vector spaces) and treats it with a geometric approach in full, progressive detail (300 pages for each dimension). I'm afraid it's not free to buy, but you should be able to find it in any good university library. Otherwise Bookfinder should help you get it at a relatively modest price.
this is http://en.wikipedia.org/wiki/Computer_graphics. two of the key concepts are http://mathworld.wolfram.com/LinearTransformation.html, and http://mathworld.wolfram.com/AffineTransformation.html.
Original answer: I'm not sure if you will like how mathematical courses typically introduce matrices. As a programmer you might be happier with grabbing any decent 3D graphics book. It should certainly have very concrete 3x3 matrices. Also find out the ones that will teach you projective transformations (projective geometry is a very beautiful area of low-dimensional geometry and easy to program).
Mini-course in matrix math with Python 3
Contents:
[Vector, __add__, reflect_y, rotate, dilate, transform]
[Matrix, __add__, __str__, __mul__, zero, det, inv, __pow__]
Preface: Based on my teaching experience, I think that the courses referenced by others are very good courses. That means if your goal is understanding matrices as mathematicians do, than you should by all means get the whole course. But if your goals are more modest, here's my try at something more tailored to your needs (but still written with the goal to convey many theoretical concepts, kind of contradicting my original advice.)
How to use:
1. Matrices
Vectors
Before matrices come vectors. You sure know how to handle the 2- and 3- dimensional vectors:
now you can write
but it's not much fun by itself. Let's add more useful methods:
That makes things more interesting as we can now write:
and get the answer
(19, 1)
nicely printed as a vector (if the examples look unfamiliar, think how this code would look in C++).Transformations
Now it's all cool to be able to write
1274 * w
but you need more vector operations for the graphics. Here are some of them: you can flip the vector around(0,0)
point, you can reflect it aroundx
ory
axis, you can rotate it clockwise or counterclockwise (it's a good idea to draw a picture here).Let's do some simple operations:
flip(...)
using the operations I had below? What aboutreflect_x
?Now you may wonder why I omitted
reflect_y
. Well, it's because I want you to stop for a moment and write your own version of it. Ok, here's mine:See, if you look how this function computes, it's actually quite trivial. But suddenly an amazing thing happened: I was able to write a transformation using only the existing transformations
flip
andreflect_x
. For all I care,reflect_y
could be defined in a derived class without access tox
andy
and it would still work!Mathematicians would call these functions operators. They would say that
reflect_y
is an operator obtained by composition of operatorsflip
andreflect_x
which is denoted byreflect_y = flip ○ reflect_x
(you should see the small circle, a Unicode symbol25CB
).=
symbol from now to denote that two operations produce the same result, like in the paragraph above. This is a "mathematical=
", which cannot be expressed as a program.So if I do
I get the result
(-5, 3)
. Go and picture it!reflect_y ◦ reflect_y
. How would you name it?Rotations
Those operations were nice and useful, but you are probably wondering why am so slow to introduce rotations. Ok, here I go:
At this point, if you know how to rotate vectors, you should go on and fill in the question marks. Otherwise please bear with me for one more simple case: counterclockwise rotation by
90
degrees. This one is not hard to draw on a piece of paper:Trying
now gives
(0, 1) (-1, 0)
. Run it yourself!flip = rotate_90 ◦ rotate_90
.Anyway, I won't hide the secret ingredient for longer:
Now let's try something along the lines:
If you expect the same result as before,
(0, 1) (-1, 0)
, you're bound to be disappointed. That code prints:and boy, is it ugly!
Notation: I will say that we applied operation
rotate(90)
tox
in the example above. The knowledge we gained is thatrotate(90) != rotate_90
.Question: What happened here? How to express
rotate_90
in terms ofrotate
? How to expressflip
in terms ofrotate
?Dilations
Those rotations are certainly useful, but they are not everything you need to do even the 2D graphics. Consider the following transformations:
This
dilate
thing dilates thex
andy
axes in a possibly different way.dilate(?, ?) = flip
,dilate(?, ?) = reflect_x
.I will use this
dilate
function to demonstrate a thing mathematicians call commutativity: that is, for every value of parametersa
,b
,c
,d
you can be sure thatExercise: Prove it. Also, is it true that for all possible values of parameters those below would hold?
rotate(a) ◦ rotate(b) = rotate(b) ◦ rotate(a)
dilate(a, b) ◦ rotate(c) = rotate(c) ◦ dilate(a, b)
rotate(a) ◦ __mul__(b) = __mul__(b) ◦ rotate(a)
Matrices
Let's summarize all the stuff we had around here, our operators on vector
x
flip
,reflect_x
,*
,rotate(angle)
,dilate(x, y)
from which one could make some really crazy stuff like
flip ◦ rotate(angle) ◦ dilate(x, y) ◦ rotate(angle_2) ◦ reflect_y + reflect_x = ???
As you create more and more complicated expressions, one would hope for some kind of order that would suddenly reduce all possible expressions to a useful form. Fear not! Magically, every expression of the form above can be simplified to
with some numbers and/or parameters instead of
?
s.__mul__(2) ◦ rotate(pi/4)
dilate(x, y) ◦ rotate(pi/4)
This allows us to write a universal function
which would take any 4-tuple of numbers, called matrix, and apply it to vector
x
. Here's an example:which prints
(5, 3) (-3, 5) (-3, 5)
. Note that if you applytransform
with any matrix to origin, you still get origin:m
that describeflip
,dilate(x, y)
,rotate(angle)
?As we part with the
Vector
class, here's an exercise for those who want to test both their vector math knowledge and Pythonic skills:Vector
class all vector operations that you can come up with (how many of standard operators can you overload for vectors? Check out my answer).2. Matrices: Overloaded
As we found out in the previous section, a matrix can be thought of a shorthand that allows us to encode a vector operation in a simple way. For example,
rotation_90_matrix
encodes the rotation by 90 degrees.Matrix objects
Now as we shift our attention from vectors to matrices, we should by all means have a class for matrix as well. Moreover, in that function
Vector.transform(...)
above the role of the matrix was somewhat misrepresented. It's more usual form
to be fixed while vector changes, so from now on our transformations will be methods of matrix class:If you don't know Python,
__call__
overloads the meaning of(...)
for matrices so I can use the standard notation for a matrix acting on a vector. Also, the matrices are usually written using a single uppercase letter:Addition
Now, let's find out what else we can do with matrices. Remember that matrix
m
is really just a way to encode an operaton on vectors. Note that for two functionsm1(x)
andm2(x)
I can create a new function (using lambda notation)m = lambda x: m1(x) + m2(x)
. It turns out ifm1
andm2
were enconded by matrices, you can also encode thism
using matrices!You just have to add its data, like
(0, 1, -1, 0) + (0, 1, -1, 0) = (0, 2, -2, 0)
. Here's how to add two tuples in Python, with some very useful and highly Pythonic techniques:Now we can write expressions like
J + J
or evenJ + J + J
, but to see the results we have to figure out how to print a Matrix. A possible way would be to print a 4-tuple of numbers, but let's take a hint from theMatrix.__call__
function that the numbers should be organized into a2x2
block:If you look at this function in action you'll notice there is some room for improvement:
Matrix.__str__
that will round the numbers and print them in the fields of fixed length.Now you should be able to write the matrix for rotation:
Exercise: Examine the code for
Vector.rotate(self, angle)
and fill in the question marks. Test withMultiplication
The most important thing we can do with one-parameter functions is compose them:
f = lambda v: f1(f2(v))
. How to mirror that with matrices? This requires us to examine howMatrix(m1) ( Matrix(m2) (v))
works. If you expand it, you'll notice thatand similarly for
m(v).y
, which, if you open the parentheses, looks suspiciously similar toMatrix.__call__
using a new tuplem
, such thatm[0] = m1[0] * m2[0] + m1[2] * m2[2]
. So let's take this as a hint for a new definiton:Exercise: Fill in the question marks here. Test it with
Math exercise: Prove that
R(a).compose(R(b))
is always the same asR(a + b)
.Now let me tell the truth: this
compose
function is actually how mathematicians decided to multiply matrices. This makes sense as a notation:A * B
is a matrix that decribes operatorA ○ B
, and as we'll see next there are deeper reasons to call this 'multiplication' as well.To start using multiplication in Python all we have to do is to order it so in a
Matrix
class:(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi))
. Try to find the answer yourself first on a piece of paper.Rules for
+
and*
Let's make some good name for the matrix that corresponds to the
dilate(a, b)
operator. Now there's nothing wrong withD(a, b)
, but I'll use a chance to introduce a standard notation:Try
print(diag(2, 12345))
to see why it's called a diagonal matrix.As the composition of operations was found before to be not always commutative,
*
operator won't be always commutative for matrices either.A
,B
, made fromR
anddiag
, such thatA * B
is not equal toB * A
.This is somewhat strange, since multiplication for numbers is always commutative, and raises the question whether
compose
really deserves to be called__mul__
. Here's quite a lot of rules that+
and*
do satisfy:A + B = B + A
A * (B + C) = A * B + A * C
(A + B) * C = A * C + B * C
(A * B) * C = A * (B * C)
There is an operation called
A - B
and(A - B) + B = A
A - B
in terms of+
,*
anddiag
? What doesA - A
equal to? Add the method__sub__
to the classMatrix
. What happens if you computeR(2) - R(1)*R(1)
? What should it be equal to?The
(A * B) * C = A * (B * C)
equality is called associativity and is especially nice since it means that we don't have to worry about putting parentheses in an expression of the formA * B * C
:Let's find analogues to regular numbers
0
and1
and subtraction:With the following easily verifiable additions:
A + zero = A
A * zero = zero
A * one = one * A = A
the rules become complete, in the sense that there is a short name for them: ring axioms. Mathematicians thus would say that matrices form a ring, and they indeed always use symbols
+
and*
when talking about rings, and so shall we.Using the rules it's possible to easily compute the expression from the previous section:
(R(a) + R(b)) * (R(a) - R(b)) = R(2a) - R(2b)
.Affine Transformations
Time to return to how we defined matrices: they are a shortcut to some operations you can do with vectors, so it's something you can actually draw. You might want to take a pen or look at the materials that others suggested to see examples of different plane transformations.
Among the transformations we'll be looking for the affine ones, those who look 'the same' everywhere (no bending). For example, a rotation around some point
(x, y)
qualifies. Now this one cannot be expressed aslambda v: A(v)
, but in can be written in the formlambda v: A(v) + b
for some matrixA
and vectorb
.A
andb
such that a rotation bypi/2
around the point(1, 0)
has the form above. Are they unique?Note that for every vector there is an affine transformation which is a shift by the vector.
An affine transformation may stretch or dilate shapes, but it should do in the same way everywhere. Now I hope you believe that the area of any figure changes by a constant number under the transformation. For a transformation given by matrix
A
this coeffiecient is called the determinant ofA
and can be computed applying the formula for an area to two vectorsA(x_axis)
andA(y_axis)
:As a sanity check,
diag(a, b).det()
is equal toa * b
.As you can see, the determinant of rotation matrix is always the same:
One interesting thing about
det
is that it is multiplicative (it kind of follows from the definition if you meditate long enough):Inverse
A useful thing you can do with matrices is write a system of two linear equations
in a simpler way:
A(v) = b
. Let's solve the system as they teach in (some) high schools: multiply first equation byA.m[3]
, second by -A.m1 and add (if in doubt, do this on a piece of paper) to solve forv.x
.If you really tried it, you should have got
A.det() * v.x = (A.m[3]) * b.x + (-A.m[1]) * b.y
, which suggests that you can always getv
by multiplyingb
by some other matrix. This matrix is called inverse ofA
:As you see, this method fails loudly when determinant of matrix is zero. If you really want you can catch this expection with:
self.det() == 0
. Write the method to divide matrices and test it. Use the inverse matrix to solve an equationA(v) = x_axis
(A
was defined above).Powers
The main property of inverse matrix is that
A * A.inv()
always equals toone
That's why mathematicians denote
A.inv()
byA
-1. How about we write a nice function to useA ** n
notation forA
n? Note that the naivefor i in range(n): answer *= self
cycle is O(|n|) which is certainly too slow, because this can be done with a complexity oflog |n|
:Exercise: Fill in the details in this function. Test it with
X, Y = A ** 5, A ** -5
print (X, Y, X * Y, sep = '\n')
This function only works for integer values of
n
, even though for some matrices we can also define a fractional power, such as square root (in other words, a matrixB
such thatB * B = A
).diag(-1, -1)
. Is this the only possible answer? Find an example of matrix that doesn't have a square root.Bonus: Complex numbers
Here I'm going to introduce you to the subject in exactly one section! Since it's a complex subject, I'm likely to fail, so please forgive me in advance.
First, similarly to how we have matrices
zero
andone
, we can make a matrix out of any real number by doingdiag(number, number)
. Matrices of that form can be added, subtracted, multiplied, inverted and the results would mimic what happens with the numbers themselves. So for all practical purposes, one can say that, e.g.,diag(5, 5)
is 5.However, Python doesn't know yet how to handle expressions of the form
A + 1
or5 * B
whereA
andB
are matrices. If you're interested, you should by all means go and do the following exercise or look at my implementation (which uses a cool Python feature called decorator); otherwise, just know that it's been implemented.Matrix
class so that in all standard operations where one of operands is a matrix and another a number, the number is automatically converted to thediag
matrix. Also add comparison for equality.Here's an example test:
Now here's the first interesting complex number: the matrix
J
, introduced in the beginning and equal toMatrix((0, 1, -1, 0))
, has a funny property thatJ * J == -1
(try it!). That meansJ
is certainly not a normal number, but, as I just said, matrices and numbers easily mix together. For example,using the rules listed some time before. What happens if we test this in Python?
That should happily say
True
. Another example:As you might have guessed, the mathematicians don't call those 'crazy numbers', but they do something similar - they call expressions of the form
a + b*J
complex numbers. Because those are still instances of ourMatrix
class, we can do quite a lot of operations with those: addition, subtraction, multiplication, division, power - it's all already implemented! Aren't matrices amazing?I have overlooked the question of how to print the result of operation like
E = (1 + 2*J) * (1 + 3*J)
so that it looks like an expression withJ
rather than a2x2
matrix. If you examine it carefully, you'll see that you need to print the left column of that matrix in the format... + ...J
(just one more nice thing: it's exactlyE(x_axis)
!) Those who know the difference betweenstr()
andrepr()
should see it's natural to name a function that would produce expression of such form asrepr()
.Exercise: Write the function
Matrix.__repr__
that would do exactly that and try some tests with it, like(1 + J) ** 3
, first computing the result on paper and then trying it with Python.Math question: What is the determinant of
a + b*J
? If you know what the absolute value of complex number is: how they are connected? What is the absolute value ofa
? ofa*J
?3. Matrices: The (R)evolution
In the final part of this trilogy we will see that everything is a matrix. We'll start with general
M x N
matrices, and find out how vectors can be thought of as1 x N
matrices and why numbers are the same as diagonal matrices. As a side note we'll explore the complex numbers as2 x 2
matrices.Finally, we will learn to write affine and projective transformations using matrices.
So the classes planned are
[MNMatrix, NVector, Affine, Projective]
.I guess if you was able to bear with me until here, you could be interested in this sequel, so I'd like to hear if I should continue with this (and where, since I'm pretty much sure I'm beyond what considered reasonable length of a single document).
I think you should spend a few days doing dot products and cross products with vectors in 3D. Then learn the relation between trig and vectors. After that the matrices will make a lot more sense to you.
This MIT document is a must-have to get strong knowledge on the basics of Transformation.
http://stellar.mit.edu/S/course/6/fa08/6.837/courseMaterial/topics/topic2/lectureNotes/03_transform/03_transform.pdf
Jim Hefferon's free Linear Algebra textbook is really good. Unlike too many free ebooks, Jim has clearly taken the time to craft an excellent reader and introduction to linear algebra. It's not overly burdened with formal mathematical writing, which is often too dense with theorems and proofs to be easily comprehensible. It also contains lots of really excellent examples of real world applications of linear algebra - coordinate transformations being just one example. You can't beat the price, and it also comes with optional solutions to the exercises.
P.S. If coordinate transformations are your thing, you might be interested in differential geometry after you're done with linear algebra.