class Vector:
"""This will be a simple 2-dimensional vector.
In case you never encountered Python before, this string is a
comment I can put on the definition of the class or any function.
It's just one of many cool features of Python, so learn it here!
"""
def __init__(self, x, y):
self.x = x
self.y = y
现在您可以编写
v = Vector(5, 3)
w = Vector(7, -1)
,但它本身并没有多大乐趣。 让我们添加更多有用的方法:
def __str__(self: 'vector') -> 'readable form of vector':
return '({0}, {1})'.format(self.x, self.y)
def __add__(self:'vector', v: 'another vector') -> 'their sum':
return Vector(self.x + v.x, self.y + v.y)
def __mul__(self:'vector', number: 'a real number') -> 'vector':
'''Multiplies the vector by a number'''
return Vector(self.x * number, self.y * number)
import math # we'll need math from now on
...
class Vector:
...
def rotate(self:'vector', angle:'rotation angle') -> 'rotated vector':
cos = math.cos(angle)
sin = math.sin(angle)
new_x = cos * self.x - sin * self.y
new_y = sin * self.x + cos * self.y
return Vector(new_x, new_y)
def dilate(self:'vector', axe_x:'x dilation', axe_y:'y dilation'):
'''Dilates a vector along the x and y axes'''
new_x = axe_x * self.x
new_y = axe_y * self.y
return Vector(new_x, new_y)
class Matrix:
def __init__(self:'new matrix', m:'matrix data'):
'''Create a new matrix.
So far a matrix for us is just a 4-tuple, but the action
will get hotter once The (R)evolution happens!
'''
self.m = m
def __call__(self:'matrix', v:'vector'):
new_x = self.m[0] * v.x + self.m[1] * v.y
new_y = self.m[2] * v.x + self.m[3] * v.y
return Vector(new_x, new_y)
def __add__(self:'matrix', snd:'another matrix'):
"""This will add two matrix arguments.
snd is a standard notation for the second argument.
(i for i in array) is Python's powerful list comprehension.
zip(a, b) is used to iterate over two sequences together
"""
new_m = tuple(i + j for i, j in zip(self.m, snd.m))
return Matrix(new_m)
def as_block(self:'matrix') -> '2-line string':
"""Prints the matrix as a 2x2 block.
This function is a simple one without any advanced formatting.
Writing a better one is an exercise.
"""
return ('| {0} {1} |\n' .format(self.m[0], self.m[1]) +
'| {0} {1} |\n' .format(self.m[2], self.m[3]) )
如果你真的尝试过,你应该得到 A.det() * vx = (Am[3]) * bx + (-Am[1]) * by,这表明你总是可以将 b 乘以其他矩阵即可得到 v。 该矩阵称为 A 的逆:
def inv(self: 'matrix') -> 'inverse matrix':
'''This function returns an inverse matrix when it exists,
or raises ZeroDivisionError when it doesn't.
'''
new_m = ( self.m[3] / self.det(), -self.m[1] / self.det(),
????? )
return Matrix(new_m)
如您所见,当矩阵的行列式为零时,此方法会严重失败。 如果您确实想要,可以通过以下方式捕获此异常:
try:
print(zero.inv())
except ZeroDivisionError as e: ...
这就是数学家用 A-1 表示 A.inv() 的原因。 我们写一个怎么样
使用 A ** n 表示法表示 An 是一个不错的函数吗? 请注意,天真的 for i in range(n): answer *= self 循环是 O(|n|) 这肯定太慢了,因为
这可以通过 log |n| 的复杂度来完成:
def __pow__(self: 'matrix', n:'integer') -> 'n-th power':
'''This function returns n-th power of the matrix.
It does it more efficiently than a simple cycle. A
while loop goes over all bits of n, multiplying answer
by self ** (2 ** k) whenever it encounters a set bit.
...
练习: 填写此函数中的详细信息。 测试一下
X,Y = A ** 5,A ** -5 打印(X, Y, X * Y, sep = '\n')
此函数仅适用于 n 的整数值,即使对于某些矩阵我们也可以定义分数幂,例如平方根(换句话说,矩阵 B 使得 B * B = A)。
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).
Matrices: Overloaded[Matrix, __add__, __str__, __mul__, zero, det, inv, __pow__]
Bonus: Complex numbers
Matrices: The (R)evolution. It's already in the making (there's a summary at the end)
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 then 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:
This post is long. You might consider printing this and going slow, like one part a day.
Code is essential. This is a course for programmers. Exercises are essential too.
It's "2 for the price of 1" special: you can also learn Python 3 here. And complex numbers.
I'll highly value any attempt to read this (do I officially qualify for the longest post ever?), so feel free to comment if you don't understand something (and also if you do).
Matrices
=
Vectors
Before matrices come vectors. You sure know how to handle the 2- and 3-dimensional vectors:
class Vector:
"""This will be a simple 2-dimensional vector.
In case you never encountered Python before, this string is a
comment I can put on the definition of the class or any function.
It's just one of many cool features of Python, so learn it here!
"""
def __init__(self, x, y):
self.x = x
self.y = y
now you can write
v = Vector(5, 3)
w = Vector(7, -1)
but it's not much fun by itself. Let's add more useful methods:
def __str__(self: 'vector') -> 'readable form of vector':
return '({0}, {1})'.format(self.x, self.y)
def __add__(self:'vector', v: 'another vector') -> 'their sum':
return Vector(self.x + v.x, self.y + v.y)
def __mul__(self:'vector', number: 'a real number') -> 'vector':
'''Multiplies the vector by a number'''
return Vector(self.x * number, self.y * number)
That makes things more interesting as we can now write:
print(v + w * 2)
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 the (0,0) point, you can reflect it around x or y axis, you can rotate it clockwise or counterclockwise (it's a good idea to draw a picture here).
Let's do some simple operations:
...
def flip(self:'vector') -> 'vector flipped around 0':
return Vector(-self.x, -self.y)
def reflect_x(self:'vector') -> 'vector reflected around x axis':
return Vector(self.x, -self.y)
print(v.flip(), v.reflect_x())
Question: is it possible to express flip(...) using the operations I had below? What about reflect_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:
def reflect_y(self:'vector') -> 'vector reflected around y axis':
return self.flip().reflect_x()
See, if you look at 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 and reflect_x. For all, I care, reflect_y could be defined in a derived class without access to x and y and it would still work!
Mathematicians would call these functions operators. They would say that reflect_y is an operator obtained by composition of operators flip and reflect_x which is
denoted by reflect_y = flip ○ reflect_x (you should see the small circle, a Unicode symbol 25CB).
Note: I will quite freely use the = 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
print(v.reflect_y())
I get the result (-5, 3). Go and picture it!
Question: Consider a composition 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:
Question: Prove that flip = rotate_90 ◦ rotate_90.
Anyway, I won't hide the secret ingredient for longer:
import math # we'll need math from now on
...
class Vector:
...
def rotate(self:'vector', angle:'rotation angle') -> 'rotated vector':
cos = math.cos(angle)
sin = math.sin(angle)
new_x = cos * self.x - sin * self.y
new_y = sin * self.x + cos * self.y
return Vector(new_x, new_y)
Now let's try something along the lines:
print(x_axis.rotate(90), y_axis.rotate(90))
If you expect the same result as before, (0, 1) (-1, 0), you're bound to be disappointed. That code prints:
Notation: I will say that we applied operation rotate(90) to x in the example above. The knowledge we gained is that rotate(90) != rotate_90.
Question: What happened here? How to express rotate_90 in terms of rotate? How to express flip in terms of rotate?
Dilations
Those rotations are certainly useful, but they are not everything you need to do even the 2D graphics. Consider the following transformations:
def dilate(self:'vector', axe_x:'x dilation', axe_y:'y dilation'):
'''Dilates a vector along the x and y axes'''
new_x = axe_x * self.x
new_y = axe_y * self.y
return Vector(new_x, new_y)
This dilate thing dilates the x and y axes in a possibly different way.
Exercise: Fill in the question marks in dilate(?, ?) = flip, dilate(?, ?) = reflect_x.
I will use this dilate function to demonstrate a thing mathematicians call commutativity: that is, for every value of parameters a, b, c, d you can be sure that
dilate(a, b) ◦ dilate(c, d) = dilate(c, d) ◦ dilate(a, b)
Exercise: 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
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
Exercise: what are the tuples m that describe flip, 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:
The final battle: Add to the Vector class all vector operations that you can come up with (how many standard operators can you overload for vectors? Check out my answer).
Matrices: Overloaded
=
As we found out in the previous section, a matrix can be thought of as 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 for m to be fixed while vector changes, so from now on our transformations will be methods of matrix class:
class Matrix:
def __init__(self:'new matrix', m:'matrix data'):
'''Create a new matrix.
So far a matrix for us is just a 4-tuple, but the action
will get hotter once The (R)evolution happens!
'''
self.m = m
def __call__(self:'matrix', v:'vector'):
new_x = self.m[0] * v.x + self.m[1] * v.y
new_y = self.m[2] * v.x + self.m[3] * v.y
return Vector(new_x, new_y)
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:
Exercise: repeat this example with matrices from the previous exercise.
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 operation on vectors. Note that for two functions m1(x) and m2(x) I can create a new function (using lambda notation) m = lambda x: m1(x) + m2(x). It turns out if m1 and m2 were encoded by matrices, you can also encode this m using matrices!
Exercise: Think through any difficulties you might have with this statement.
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:
def __add__(self:'matrix', snd:'another matrix'):
"""This will add two matrix arguments.
snd is a standard notation for the second argument.
(i for i in array) is Python's powerful list comprehension.
zip(a, b) is used to iterate over two sequences together
"""
new_m = tuple(i + j for i, j in zip(self.m, snd.m))
return Matrix(new_m)
Now we can write expressions like J + J or even J + 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 the Matrix.__call__ function that the numbers should be organized into a 2x2 block:
def as_block(self:'matrix') -> '2-line string':
"""Prints the matrix as a 2x2 block.
This function is a simple one without any advanced formatting.
Writing a better one is an exercise.
"""
return ('| {0} {1} |\n' .format(self.m[0], self.m[1]) +
'| {0} {1} |\n' .format(self.m[2], self.m[3]) )
If you look at this function in action you'll notice there is some room for improvement:
print((J + J + J).as_block())
Exercise: write a nicer function 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:
def R(a: 'angle') -> 'matrix of rotation by a':
cos = math.cos(a)
sin = math.sin(a)
m = ( ????? )
return Matrix(m)
Exercise: Examine the code for Vector.rotate(self, angle) and fill in the question marks. Test with
from math import pi
print(R(pi/4) + R(-pi/4))
Multiplication
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 how Matrix(m1) ( Matrix(m2) (v)) works. If you expand it, you'll notice that
and similarly for m(v).y, which, if you open the parentheses, looks suspiciously similar
to Matrix.__call__ using a new tuple m, such that m[0] = m1[0] * m2[0] + m1[2] * m2[2]. So let's take this as a hint for a new definiton:
def compose(self:'matrix', snd:'another matrix'):
"""Returns a matrix that corresponds to composition of operators"""
new_m = (self.m[0] * snd.m[0] + self.m[1] * snd.m[2],
self.m[0] * snd.m[1] + self.m[1] * snd.m[3],
???,
???)
return Matrix(new_m)
Exercise: Fill in the question marks here. Test it with
print(R(1).compose(R(2)))
print(R(3))
Math exercise: Prove that R(a).compose(R(b)) is always the same as R(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 describes operator A ○ 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:
class Matrix:
...
__mul__ = compose
Exercise: Compute (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 with D(a, b), but I'll
use a chance to introduce a standard notation:
def diag(a: 'number', b: 'number') -> 'diagonal 2x2 matrix':
m = (a, 0, 0, b)
return Matrix(m)
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.
Exercise: go back and refresh the commutativity thing if necessary. Then give examples of matrices A, B, made from R and diag,
such that A * B is not equal to B * 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
Exercise: Prove these statements. How to define A - B in terms of +, *, and diag? What does A - A equal to? Add the method __sub__ to the class Matrix. What happens if you compute R(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 form A * B * C:
Let's find analogues to regular numbers 0 and 1 and subtraction:
zero = diag(0, 0)
one = diag(1, 1)
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:
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 as lambda v: A(v), but it can be written in the form lambda v: A(v) + b for some matrix A and vector b.
Exercise: find the A and b such that a rotation by pi/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 coefficient is called the determinant of A and can be computed applying the formula for an area to two vectors A(x_axis) and A(y_axis):
def det(self: 'matrix') -> 'determinant of a matrix':
return self.m[0]*self.m[3] - self.m[1] * self.m[2]
As a sanity check, diag(a, b).det() is equal to a * b.
Exercise: Check this. What happens when one of the arguments is 0? When it's negative?
As you can see, the determinant of rotation matrix is always the same:
from random import random
r = R(random())
print (r, 'det =', r.det())
One interesting thing about det is that it is multiplicative (it kind of follows from the definition if you meditate long enough):
A = Matrix((1, 2, -3, 0))
B = Matrix((4, 1, 1, 2))
print(A.det(), '*', B.det(), 'should be', (A * B).det())
Inverse
A useful thing you can do with matrices is writing 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 the first equation by A.m[3], second by -A.m1, and add (if in doubt, do this on a piece of paper) to solve for v.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 get v by multiplying b by some other matrix. This matrix is called inverse of A:
def inv(self: 'matrix') -> 'inverse matrix':
'''This function returns an inverse matrix when it exists,
or raises ZeroDivisionError when it doesn't.
'''
new_m = ( self.m[3] / self.det(), -self.m[1] / self.det(),
????? )
return Matrix(new_m)
As you see, this method fails loudly when the determinant of a matrix is zero. If you really want you can catch this exception with:
try:
print(zero.inv())
except ZeroDivisionError as e: ...
Exercise: Finish the method. Prove that inverse matrix doesn't exist when self.det() == 0. Write the method to divide matrices and test it. Use the inverse matrix to solve an equation A(v) = x_axis (A was defined above).
Powers
The main property of inverse matrix is that A * A.inv() always equals to one
Exercise: check that yourself. Explain why that should be so from the definition of the inverse matrix.
That's why mathematicians denote A.inv() by A-1. How about we write a
nice function to use A ** n notation for An? Note that the naive for i in range(n): answer *= self cycle is O(|n|) which is certainly too slow, because
this can be done with a complexity of log |n|:
def __pow__(self: 'matrix', n:'integer') -> 'n-th power':
'''This function returns n-th power of the matrix.
It does it more efficiently than a simple cycle. A
while loop goes over all bits of n, multiplying answer
by self ** (2 ** k) whenever it encounters a set bit.
...
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 matrix B such that B * B = A).
Exercise: Find a square root of diag(-1, -1). Is this the only possible answer?
Find an example of a 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 and one, we can make a matrix out of any real number by doing diag(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 or 5 * B where A and B 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.
Exercise for gurus: Change the operators in a Matrix class so that in all standard operations where one of the operands is a matrix and another a number, the number is automatically converted to the diag matrix. Also, add a comparison for equality.
Here's an example test:
print( 3 * A - B / 2 + 5 )
Now here's the first interesting complex number: the matrix J, introduced in the beginning and equal to Matrix((0, 1, -1, 0)), has a funny property that J * J == -1 (try it!). That means J is certainly not a normal number, but, as I just said, matrices and numbers easily mix together. For example,
using the rules listed sometime before. What happens if we test this in Python?
(1 + J) * (2 + J) == 1 + 3*J
That should happily say True. Another example:
(3 + 4*J) / (1 - 2*J) == -1 + 2*J
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*Jcomplex numbers.
Because those are still instances of our Matrix 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 an operation like E = (1 + 2*J) * (1 + 3*J) so that it looks like an expression with J rather than a 2x2 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 exactly E(x_axis)!) Those who know the difference between str() and repr() should see it's natural to name a function that would produce expression of such form as repr().
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 a complex number is: how they are connected? What is the absolute value of a? of a*J?
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 as 1 x N matrices and why numbers are the same as diagonal matrices. As a side note, we'll explore the complex numbers as 2 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 were 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 a reasonable length of a single document).
MIT has alot of their math courses' materials online at http://ocw.mit.edu/OcwWeb/Mathematics/. Once you have the basics down, they have the physics notes online too.
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.
Jim Hefferon 的免费《线性代数》教科书非常好。 与太多免费电子书不同,吉姆显然花了时间制作一本优秀的阅读器和线性代数入门书。 它并没有过多地负担正式的数学写作,这些数学写作往往过于密集,难以理解。 它还包含许多线性代数在现实世界中应用的非常优秀的例子——坐标变换只是一个例子。 价格无可比拟,而且它还附带可选的练习解决方案。
PS:如果您喜欢坐标变换,那么在学完线性代数后,您可能会对微分几何感兴趣。
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.
(Searching for "Matrices" at Google books gives You lots of lecutures, some of which are directly connected with transformations - this is one of the first results, but I cheer You to check more.)
Articles like this one, or maybe this or this are easily found with help of Google, so I will not post more.
I also encourage (I don't know if this is the right word, I am just learning English) You, to look for this kind of information in one of those books (though they are not free, but You can find large parts of older ones on Google Books):
If I will find more, I will edit and add links here, but to be honest - I found those links in about 10 minutes of using google. World's most popular browser stores data about everything - and yes, "everything" means matrices too.
我认为你应该花几天时间用 3D 向量做点积和叉积。 然后学习三角函数和向量之间的关系。 在那之后,矩阵对你来说会更有意义。
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.
MIT-OCW's course on Linear Algebra by Gilbert Strang. Incredible lectures by an incredible man; if your understanding of matrices is solely derived from programming sources (like MATLAB) then a course on Linear Algebra will definitely give you the fundamentals to do crazy things with matrices.
发布评论
评论(10)
原始答案:我不确定您是否会喜欢数学课程通常介绍矩阵的方式。 作为一名程序员,您可能会更乐意阅读一本像样的 3D 图形书。 它当然应该有非常具体的 3x3 矩阵。 另外,找出那些可以教您射影变换的内容(射影几何是一个非常美丽的领域低维几何且易于编程)。
使用 Python 3 进行矩阵数学迷你课程
内容:
[Vector、__add__、reflect_y、rotate、dilate、transform]
[矩阵,__add__,__str__,__mul__,零,det,inv,__pow__]
前言:根据我的教学经验,我认为别人参考的课程都是很好的课程。 这意味着,如果您的目标是像数学家一样理解矩阵,那么您无论如何都应该学习整个课程。 但是,如果您的目标比较温和,那么我会尝试一些更适合您需求的内容(但仍然是为了传达许多理论概念而编写的,这有点与我最初的建议相矛盾。)
如何使用:
=
向量
在矩阵之前是向量。 您肯定知道如何处理 2 维和 3 维向量:
现在您可以编写
,但它本身并没有多大乐趣。 让我们添加更多有用的方法:
这使得事情变得更有趣,因为我们现在可以编写:
并得到答案
(19, 1)
很好地打印为向量(如果示例看起来不熟悉,请想想这段代码将如何看看C++)。转换
现在能够编写
1274 * w
已经很酷了,但是您需要更多的图形矢量操作。 以下是其中一些:您可以围绕(0,0)
点翻转矢量,可以围绕x
或y
轴反射它,你可以顺时针或逆时针旋转它(这里画图是个好主意)。让我们做一些简单的操作:
flip(...)
?reflect_x
怎么样?现在您可能想知道为什么我省略了
reflect_y
。 嗯,这是因为我想让你停下来写一下你自己的版本。 好吧,这是我的:看,如果你看看这个函数是如何计算的,它实际上非常简单。 但突然发生了一件令人惊奇的事情:我能够仅使用现有的转换
flip
和reflect_x
编写一个转换。 无论如何,我关心的是,reflect_y
可以在派生类中定义,而无需访问x
和y
并且它仍然可以工作!数学家将这些函数称为“运算符”。 他们会说
reflect_y
是由运算符flip
和reflect_x
组合而成的运算符,即由
reflect_y = Flip ○ Reflect_x
表示(您应该看到小圆圈,一个 Unicode 符号25CB
)。=
符号来表示两个操作产生相同的结果,如上面的段落所示。 这是一个“数学=
”,它不能用程序来表达。因此,如果我这样做,
我会得到结果
(-5, 3)
。 去想象一下吧!reflect_y ◦reflect_y
。 你会怎么命名它?旋转
这些操作很好而且很有用,但您可能想知道为什么引入旋转这么慢。 好吧,我开始了:
此时,如果你知道如何旋转向量,你应该继续填写问号。 否则请耐心听我讲一个更简单的情况:逆时针旋转
90
度。 在一张纸上画这个并不难:现在尝试
给出
(0, 1) (-1, 0)
。 自己运行吧!flip =rotate_90°rotate_90
。无论如何,我不会再隐藏秘密成分了:
现在让我们尝试一下:
如果您期望与之前相同的结果,
(0, 1) (-1, 0)
,您一定会失望的。 该代码打印:天哪,它丑吗?
符号:我会说我们在上面的示例中将操作
rotate(90)
应用于x
。 我们获得的知识是rotate(90) !=rotate_90
。问题:这里发生了什么? 如何用
rotate
来表达rotate_90
? 如何用rotate
来表达flip
?膨胀
这些旋转当然很有用,但它们并不是您完成 2D 图形所需的一切。 考虑以下转换:
此
dilate
事物以可能不同的方式扩大x
和y
轴。dilate(?, ?) = Flip
、dilate(?, ?) =reflect_x
中的问号。我将使用这个
dilate
函数来演示数学家所说的交换性:也就是说,对于参数a
、b<的每个值/code>,
c
,d
你可以确定练习:证明它。 另外,对于所有可能的参数值,下面的值是否都成立?
矩阵
让我们总结一下这里的所有内容,向量
x
上的运算符flip
、reflect_x
、*
,rotate(angle)
,dilate(x, y)
从中我们可以做出一些非常疯狂的东西,比如
flip ◦rotate(angle ) ◦ dilate(x, y) ◦rotate(angle_2) ◦reflect_y + Reflect_x = ???
当您创建越来越复杂的表达式时,人们会希望某种顺序能够突然将所有可能的表达式减少到一种有用的形式。 不要害怕! 神奇的是,上述形式的每个表达式都可以简化为
使用一些数字和/或参数而不是
?
。__mul__(2) ◦rotate(pi/4)
dilate(x, y) 一样的问题 ◦rotate(pi/4)< /code>
这使我们能够编写一个通用函数
,它可以接受任何 4 元组的数字,称为矩阵,并将其应用于向量
x
。 这是一个示例:打印
(5, 3) (-3, 5) (-3, 5)
。 请注意,如果您应用transform
任何矩阵到原点,你仍然得到原点:
flip
、dilate(x, y)的元组
m
是什么),旋转(角度)
?在我们讨论
Vector
类时,这里为那些想要测试向量数学知识和 Python 技能的人提供了一个练习:Vector
对您可以想到的所有向量操作进行分类(您可以为向量重载多少个标准运算符?查看我的答案)。=
正如我们在上一节中发现的,矩阵可以被认为是一种简写,它允许我们以简单的方式对向量运算进行编码。 例如,
rotation_90_matrix
编码旋转 90 度。矩阵对象
现在,当我们将注意力从向量转移到矩阵时,我们无论如何应该有一个类
对于矩阵也是如此。 此外,在上面的函数
Vector.transform(...)
中,矩阵的作用在某种程度上被歪曲了。 当向量发生变化时,m
更常见的是固定的,因此从现在开始我们的转换将是矩阵类的方法:如果您不了解 Python,
__call__
会重载(...)
对于矩阵的含义,因此我可以使用作用于向量的矩阵的标准表示法。 此外,矩阵通常使用单个大写字母编写:加法
现在,让我们看看我们还能用矩阵做什么。 请记住,矩阵 m 实际上只是对向量操作进行编码的一种方法。 请注意,对于两个函数
m1(x)
和m2(x)
我可以创建一个新函数(使用 lambda 表示法)m = lambda x: m1(x) + m2(x)
。 事实证明,如果m1
和m2
是由矩阵编码的,您也可以使用矩阵对这个m
进行编码!您只需添加其数据,例如
(0, 1, -1, 0) + (0, 1, -1, 0) = (0, 2, -2, 0)
。 以下是如何在 Python 中添加两个元组,其中使用了一些非常有用且高度 Pythonic 的技术:现在我们可以编写像
J + J
甚至J + J + J
这样的表达式,但是要查看结果,我们必须弄清楚如何打印矩阵。 一种可能的方法是打印 4 元组的数字,但让我们从 Matrix.__call__ 函数中得到提示,数字应该组织成一个2x2
块:如果您实际查看此函数,您会发现还有一些改进的空间:
Matrix.__str__
,该函数将四舍五入数字并将其打印在固定长度的字段中。
现在您应该能够编写旋转矩阵:
练习: 检查
Vector.rotate(self, angle)
的代码并填写问号。 测试乘法
进行测试对于单参数函数,我们可以做的最重要的事情就是组合它们:
f = lambda v: f1(f2(v))
。 如何用矩阵来反映它? 这需要我们检查Matrix(m1) ( Matrix(m2) (v))
是如何工作的。 如果展开它,您会注意到,对于
m(v).y
也是如此,如果打开括号,它看起来非常相似使用新元组
m
转换为Matrix.__call__
,使得m[0] = m1[0] * m2[0] + m1[2] * m2 [2]
。 因此,让我们将此作为新定义的提示:练习:填写此处的问号。 测试一下
<前><代码> print(R(1).compose(R(2)))
打印(R(3))
数学练习进行测试:证明
R(a).compose(R(b))
始终与R(a + b )
。现在让我说实话:这个
compose
函数实际上是数学家决定乘矩阵的方法。作为一种表示法,这是有意义的:
A * B
是一个描述运算符A ○ B
的矩阵,正如我们接下来将看到的,将其称为“乘法”有更深层次的原因’也是如此。要开始在 Python 中使用乘法,我们所要做的就是在矩阵中对其进行排序
类:
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi))
。 首先尝试自己在一张纸上找到答案。+
和*
的规则让我们为与
dilate(a, b)
运算符对应的矩阵起一个好名字。 现在D(a, b)
没有任何问题,但我会借此机会介绍一个标准符号:
尝试
print(diag(2, 12345))
看看为什么它被称为对角矩阵。由于之前发现运算的组合并不总是可交换的,因此
*
运算符对于矩阵也并不总是可交换的。R
和diag
组成的矩阵A
、B
的示例,这样
A * B
不等于B * A
。这有点奇怪,因为数字的乘法总是可交换的,并提出了
compose
是否真的值得被称为__mul__
的问题。 这里有很多+
和*
do 满足的规则:A + B = B + A
A * (B + C) = A * B + A * C
(A + B) * C = A * C + B * C
(A * B) * C = A * (B * C)
A - B
和(A - B) + B = A
+
、*
、diag
来定义A - B
?A - A
等于什么? 将方法__sub__
添加到类Matrix
中。 如果计算R(2) - R(1)*R(1)
会发生什么? 它应该等于什么?(A * B) * C = A * (B * C)
等式称为关联性,它特别好,因为它意味着我们不必担心在表达式中放入括号形式为
A * B * C
:让我们找到常规数字
0
和1
的类似物以及减法:通过以下易于验证的加法:
A * Zero = Zero
A * one = one * A = A
规则变得完整,即存在它们的简称:环公理。
因此,数学家会说矩阵形成一个环,并且在谈论环时他们确实总是使用符号
+
和*
,我们也应该如此。使用这些规则,可以轻松计算上一节中的表达式:
(R(a) + R(b)) * (R(a) - R(b)) = R(2a) - R(2b)
。仿射变换 是
时候回到我们如何定义矩阵了:它们是您可以使用向量执行的某些操作的快捷方式,因此您可以实际绘制它。 您可能需要拿笔或查看其他人建议的材料来查看不同平面变换的示例。
在这些变换中,我们将寻找仿射变换,即那些看起来到处“相同”的变换(没有弯曲)。 例如,围绕某个点
(x, y)
的旋转就符合条件。 现在这个不能表示为 lambda v: A(v) ,但对于某个矩阵 <,它可以写成 lambda v: A(v) + b 的形式代码>A 和向量b
。A
和b
,使得围绕点旋转
具有上述形式。 它们是独一无二的吗?pi/2
( 1, 0)请注意,对于每个向量都有一个仿射变换,它是向量的平移。
仿射变换可以拉伸或膨胀形状,但它在任何地方都应该以相同的方式进行。 现在我希望你相信任何图形的面积在变换下都会改变一个常数。 对于矩阵
A
给出的变换,该系数称为A
的行列式,可以通过将面积公式应用到两个向量 < code>A(x_axis) 和A(y_axis)
:作为健全性检查,
diag(a, b).det()
等于a * b
。正如您所看到的,旋转矩阵的行列式始终相同:
关于
det
的一个有趣的事情是它是乘法的(如果您冥想足够长的时间,它可以从定义中得出):逆
A 有用 编写两个线性方程组
您可以使用矩阵做的事情是以更简单的方式 :
A(v) = b
。 让我们按照(某些)高中教学的方式求解该系统:将第一个方程乘以Am[3]
,第二个方程乘以 -Am1,并添加(如果有疑问,请在一张纸上执行此操作)以求解vx
。如果你真的尝试过,你应该得到
A.det() * vx = (Am[3]) * bx + (-Am[1]) * by
,这表明你总是可以将b
乘以其他矩阵即可得到v
。 该矩阵称为A
的逆:如您所见,当矩阵的行列式为零时,此方法会严重失败。 如果您确实想要,可以通过以下方式捕获此异常:
A(v) = x_axis
(A
已在上面定义)。幂
逆矩阵的主要属性是
A * A.inv()
始终等于one
这就是数学家用
A
-1 表示A.inv()
的原因。 我们写一个怎么样使用
A ** n
表示法表示A
n 是一个不错的函数吗? 请注意,天真的for i in range(n): answer *= self
循环是 O(|n|) 这肯定太慢了,因为这可以通过
log |n|
的复杂度来完成:练习: 填写此函数中的详细信息。 测试一下
X,Y = A ** 5,A ** -5
打印(X, Y, X * Y, sep = '\n')
此函数仅适用于
n
的整数值,即使对于某些矩阵我们也可以定义分数幂,例如平方根(换句话说,矩阵B
使得B * B = A
)。diag(-1, -1)
的平方根。 这是唯一可能的答案吗?找出一个没有有平方根的矩阵的例子。
奖励:复数
这里我将用一节的时间向您介绍这个主题!
由于这是一个复杂的主题,我很可能会失败,所以请提前原谅我。
首先,与矩阵
zero
和one
类似,我们可以通过执行diag(number, number)
从任何实数创建矩阵>。 这种形式的矩阵可以相加、相减、相乘、求逆,其结果将模拟数字本身所发生的情况。 因此,出于所有实际目的,我们可以说,例如,diag(5, 5)
是 5。但是,Python 还不知道如何处理形式为
A + 1
或5 * B
,其中A
和B
是矩阵。 如果您有兴趣,您一定应该去做以下练习或查看我的实现(它使用了一个很酷的 Python 功能,称为装饰器); 否则,只需知道它已实施即可。Matrix
类中的运算符,以便在所有标准运算中,其中一个操作数是矩阵,另一个是数字,该数字会自动转换为diag 矩阵。 另外,添加相等比较。这是一个示例测试:
现在这是第一个有趣的复数:矩阵
J
,在开头引入,等于Matrix((0, 1, -1 , 0))
,有一个有趣的属性:J * J == -1
(试试吧!)。 这意味着J
肯定不是一个普通的数字,但是,正如我刚才所说,矩阵和数字很容易混合在一起。 例如,使用之前列出的规则。 如果我们用 Python 测试这个会发生什么?
这应该很高兴地说
True
。 另一个例子:正如您可能已经猜到的那样,数学家不会将这些称为“疯狂的数字”,但他们会做类似的事情 - 他们将表达式称为
a + b*J
复数。因为它们仍然是我们的 Matrix 类的实例,所以我们可以用它们做很多运算:加法、减法、乘法、除法、幂 - 这些都已经实现了! 矩阵是不是很神奇?
我忽略了如何打印
E = (1 + 2*J) * (1 + 3*J)
等操作的结果的问题,使其看起来像带有的表达式J
而不是2x2
矩阵。 如果你仔细观察的话,您会发现需要以
... + ...J
格式打印该矩阵的左列(还有一件好事:它正是E(x_axis)< /code>!) 那些知道
。str()
和repr()
之间差异的人应该会很自然地看到,命名一个会生成如下形式的表达式的函数是很自然的>repr()练习:编写函数
Matrix.__repr__
来实现这一点,并尝试用它进行一些测试,例如(1 + J) ** 3< /code>,首先在纸上计算结果,然后用 Python 尝试。
数学问题:
a + b*J
的行列式是什么? 如果您知道复数的绝对值是什么:它们是如何连接的?a
的绝对值是多少?a*J
?=
在这个三部曲的最后一部分,我们将看到一切都是一个矩阵。 我们将从一般的
M x N
矩阵开始,了解如何将向量视为1 x N
矩阵以及为什么数字与对角矩阵相同。 作为旁注,我们将探索作为2 x 2
矩阵的复数。最后,我们将学习使用矩阵编写仿射和射影变换。
因此计划的类是
[MNMatrix、NVector、Affine、Projective]
。我想如果你能耐心听我讲到这里,你可能会对这部续集感兴趣,所以我想听听我是否应该继续这个(以及在哪里,因为我非常确定我已经超越了被认为是单个文档的合理长度)。
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 then 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:
=
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 the(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 at 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 isdenoted by
reflect_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?
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
withany 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 standard operators can you overload for vectors? Check out my answer).=
As we found out in the previous section, a matrix can be thought of as 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 operation 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 encoded 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 thenumbers 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 similarto
Matrix.__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 describes 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'lluse 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 that
A * 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)
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 expressionof the form
A * 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 it 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 coefficient 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 writing 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 the 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 the determinant of a matrix is zero. If you really want you can catch this exception 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 anice function to use
A ** n
notation forA
n? Note that the naivefor i in range(n): answer *= self
cycle is O(|n|) which is certainly too slow, becausethis can be done with a complexity of
log |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 a 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 the operands is a matrix and another a number, the number is automatically converted to thediag
matrix. Also, add a 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 sometime 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 our
Matrix
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 an 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 a complex number is: how they are connected? What is the absolute value ofa
? ofa*J
?=
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 were 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 a reasonable length of a single document).
麻省理工学院有很多在线数学课程材料,网址为 http://ocw.mit.edu/OcwWeb/Mathematics /。 一旦你掌握了基础知识,他们也会在网上提供物理笔记。
MIT has alot of their math courses' materials online at http://ocw.mit.edu/OcwWeb/Mathematics/. Once you have the basics down, they have the physics notes online too.
这是http://en.wikipedia.org/wiki/Computer_graphics。 其中两个关键概念是 http://mathworld.wolfram.com/LinearTransformation.html 和http://mathworld.wolfram.com/AffineTransformation.html。
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.
这份 MIT 文档是获得有关转型基础知识的必备知识。
http:// stellar.mit.edu/S/course/6/fa08/6.837/courseMaterial/topics/topic2/lectureNotes/03_transform/03_transform.pdf
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
对于初学者来说最好的书籍之一是 Carl Meyer 的《矩阵分析和应用线性代数》。
您可以在此处在线查看整本书(尽管它有版权水印):
http://www.matrixanalysis.com/DownloadChapters.html
您可能想看一下在第 5 章。 326 - 332 涵盖了 3 维计算机图形中的旋转
One of the best books for beginners is Carl Meyer's "Matrix Analysis and Applied Linear Algebra".
You can view the entire book online here (although it has a copyright watermark):
http://www.matrixanalysis.com/DownloadChapters.html
You might want to take a look at Chapter 5 pg. 326 - 332 which covers the rotations in 3 dimensional computer graphics
您可能想看看 I-Hsiung Lin、Yixiong Lin 的《几何线性代数》(ISBN:9812560874)。 这本书专门针对您想要的内容(2 维和 3 维向量空间的线性变换),并使用几何方法以完整、渐进的细节对其进行处理(每个维度 300 页)。 恐怕它不是免费购买的,但你应该可以在任何好的大学图书馆找到它。 否则,Bookfinder 应该可以帮助您以相对适中的价格获得它。
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.
Jim Hefferon 的免费《线性代数》教科书非常好。 与太多免费电子书不同,吉姆显然花了时间制作一本优秀的阅读器和线性代数入门书。 它并没有过多地负担正式的数学写作,这些数学写作往往过于密集,难以理解。 它还包含许多线性代数在现实世界中应用的非常优秀的例子——坐标变换只是一个例子。 价格无可比拟,而且它还附带可选的练习解决方案。
PS:如果您喜欢坐标变换,那么在学完线性代数后,您可能会对微分几何感兴趣。
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.
这些是我找到的信息。 其中一些可能对您有价值:
理论:
(搜索 "矩阵" Google 图书为您提供了大量讲座,其中一些与转换直接相关 - 这是第一个结果,但我鼓励您检查更多
我也鼓励(我不知道这个词是否正确,我正在学习英语)你,在其中一本书中寻找此类信息(虽然它们不是免费的,但你可以找到大部分内容) Google 图书上的较早版本):
其中每一个都有关于数学宝石的部分 - 并且那里有很多巧妙的技巧。 这些书每一分钱都值钱。
还有 GPU 编程精华,所以您也可以尝试一下。
实践:
(此外,此处和此处)
如果我能找到更多,我会在这里编辑和添加链接,但说实话 - 我在使用谷歌大约 10 分钟内找到了这些链接。 世界上最受欢迎的浏览器存储有关所有内容的数据 - 是的,“所有内容”也意味着矩阵。
队友的欢呼声。
Those are the information that I found. Some of them might be valuable to You:
Theory:
(Searching for "Matrices" at Google books gives You lots of lecutures, some of which are directly connected with transformations - this is one of the first results, but I cheer You to check more.)
I also encourage (I don't know if this is the right word, I am just learning English) You, to look for this kind of information in one of those books (though they are not free, but You can find large parts of older ones on Google Books):
Each of those has section about math gems - and there are lots of neat tricks there. Those books are worth every cent.
There are also GPU Programming gems, so You might try them too.
Practice:
(also, using matrices in OpenGl is described here and here)
If I will find more, I will edit and add links here, but to be honest - I found those links in about 10 minutes of using google. World's most popular browser stores data about everything - and yes, "everything" means matrices too.
Cheers, Mate.
我认为你应该花几天时间用 3D 向量做点积和叉积。 然后学习三角函数和向量之间的关系。 在那之后,矩阵对你来说会更有意义。
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.
MIT-OCW 的线性代数课程,作者:Gilbert Strang。 一位令人难以置信的人的令人难以置信的讲座; 如果您对矩阵的理解完全来自编程资源(例如 MATLAB),那么线性代数课程肯定会为您提供使用矩阵做疯狂事情的基础知识。
http://www.ocw.cn/OcwWeb/数学/18-06Spring-2005/VideoLectures/index.htm
MIT-OCW's course on Linear Algebra by Gilbert Strang. Incredible lectures by an incredible man; if your understanding of matrices is solely derived from programming sources (like MATLAB) then a course on Linear Algebra will definitely give you the fundamentals to do crazy things with matrices.
http://www.ocw.cn/OcwWeb/Mathematics/18-06Spring-2005/VideoLectures/index.htm