Joh*_*don 23 math matrix hyperlink coordinates
我最近很乐意从坐标渲染图表和图表,我很着迷使用矩阵来转换坐标空间.
我已经能够成功地缩放和反转二维坐标空间,但现在我的胃口被激发了.:)
我在哪里可以获得关于矩阵,矩阵数学的清晰,信息丰富,(免费)的教育材料,特别是适用于二维和三维空间?
ily*_* n. 44
原始答案:我不确定你是否会喜欢数学课程通常如何引入矩阵.作为一名程序员,你可能会更喜欢抓住任何体面的3D图形书.它当然应该具有非常具体的3x3矩阵.还要找出能教你投影变换的那些(投影几何是一个非常漂亮的低维几何区域,易于编程).
内容:
[Vector, __add__, reflect_y, rotate, dilate, transform][Matrix, __add__, __str__, __mul__, zero, det, inv, __pow__]前言:根据我的教学经验,我认为其他人引用的课程是非常好的课程.这意味着如果你的目标是像数学家一样理解矩阵,那么你应该通过各种手段来完成全部课程.但是,如果你的目标更加温和,那么我的尝试就是根据你的需求量身定做的东西(但仍然是为了传达许多理论概念而写的,与我原来的建议相矛盾.)
如何使用:
在矩阵到达向量之前.你确定知道如何处理二维和三维向量:
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
Run Code Online (Sandbox Code Playgroud)
现在你可以写
v = Vector(5, 3)
w = Vector(7, -1)
Run Code Online (Sandbox Code Playgroud)
但它本身并不是很有趣.让我们添加更多有用的方法:
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)
Run Code Online (Sandbox Code Playgroud)
这让事情变得更有趣,因为我们现在可以写:
print(v + w * 2)
Run Code Online (Sandbox Code Playgroud)
并将答案(19, 1)很好地打印为矢量(如果示例看起来不熟悉,请考虑这些代码在C++中的外观).
现在能够编写它们很酷1274 * w但是你需要更多的图形矢量操作.以下是其中一些:你可以在(0,0)点周围翻转矢量,你可以在周围x或y轴上反射它,你可以顺时针或逆时针旋转它(这里最好画一幅画).
我们来做一些简单的操作:
...
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())
Run Code Online (Sandbox Code Playgroud)
flip(...)使用我下面的操作来表达?怎么样reflect_x?现在你可能想知道为什么我省略了reflect_y.嗯,这是因为我希望你停下片刻并编写你自己的版本.好的,这是我的:
def reflect_y(self:'vector') -> 'vector reflected around y axis':
return self.flip().reflect_x()
Run Code Online (Sandbox Code Playgroud)
看,如果你看看这个函数如何计算,它实际上是非常微不足道的.但突然一个惊人的事情发生了:我能写仅使用现有变换的变换flip和reflect_x.对于我所关心的所有内容,reflect_y可以在派生类中定义而无需访问x,y并且它仍然可以工作!
数学家会称这些函数为运算符.他们会说这reflect_y是一个由运算符组合得到的运算符flip,reflect_x表示为reflect_y = flip ? reflect_x(你应该看到小圆圈,一个Unicode符号25CB).
= 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())
Run Code Online (Sandbox Code Playgroud)
I get the result (-5, 3). Go and picture it!
reflect_y ? reflect_y. How would you name it? Those operations were nice and useful, but you are probably wondering why am so slow to introduce rotations. Ok, here I go:
def rotate(self:'vector', angle:'rotation angle') -> 'vector':
??????
Run Code Online (Sandbox Code Playgroud)
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:
def rotate_90(self:'vector') -> 'rotated vector':
new_x = - self.y
new_y = self.x
return Vector(new_x, new_y)
Run Code Online (Sandbox Code Playgroud)
Trying
x_axis = Vector(1, 0)
y_axis = Vector(0, 1)
print(x_axis.rotate_90(), y_axis.rotate_90())
Run Code Online (Sandbox Code Playgroud)
now gives (0, 1) (-1, 0). Run it yourself!
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)
Run Code Online (Sandbox Code Playgroud)
Now let's try something along the lines:
print(x_axis.rotate(90), y_axis.rotate(90))
Run Code Online (Sandbox Code Playgroud)
If you expect the same result as before, (0, 1) (-1, 0), you're bound to be disappointed. That code prints:
(-0.448073616129, 0.893996663601) (-0.893996663601, -0.448073616129)
Run Code Online (Sandbox Code Playgroud)
and boy, is it ugly!
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?
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)
Run Code Online (Sandbox Code Playgroud)
This dilate thing dilates the x and y 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 parameters a, b, c, d you can be sure that
dilate(a, b) ? dilate(c, d) = dilate(c, d) ? dilate(a, b)
Run Code Online (Sandbox Code Playgroud)
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)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
def ???(self:'vector', parameters):
'''A magical representation of a crazy function'''
new_x = ? * self.x + ? * self.y
new_y = ? * self.x + ? * self.y
return Vector(new_x, new_y)
Run Code Online (Sandbox Code Playgroud)
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
def transform(self:'vector', m:'matrix') -> 'new vector':
new_x = m[0] * self.x + m[1] * self.y
new_y = m[2] * self.x + m[3] * self.y
return Vector(new_x, new_y)
Run Code Online (Sandbox Code Playgroud)
which would take any 4-tuple of numbers, called matrix, and apply it to vector x. Here's an example:
rotation_90_matrix = (0, -1, 1, 0)
print(v, v.rotate_90(), v.transform(rotation_90_matrix))
Run Code Online (Sandbox Code Playgroud)
which prints (5, 3) (-3, 5) (-3, 5). Note that if you apply transform with
any matrix to origin, you still get origin:
origin = Vector(0, 0)
print(origin.transform(rotation_90_matrix))
Run Code Online (Sandbox Code Playgroud)
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:
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).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.
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)
Run Code Online (Sandbox Code Playgroud)
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:
J = Matrix(rotation_90_matrix)
print(w, 'rotated is', J(w))
Run Code Online (Sandbox Code Playgroud)
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 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 enconded by matrices, you can also encode this m 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:
def __add__(self:'matrix', snd:'another matrix'):
"""This will add two matrix arguments.
snd is a standard notation for 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)
Run Code Online (Sandbox Code Playgroud)
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]) )
Run Code Online (Sandbox Code Playgroud)
If you look at this function in action you'll notice there is some room for improvement:
print((J + J + J).as_block())
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
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))
Run Code Online (Sandbox Code Playgroud)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
m(v).x = m1[0] * (m2[0]*v.x + m2[1]*v.y) + m1[1] * (m2[2]*v.x + m2[3]*v.y)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
Exercise: Fill in the question marks here. Test it with
print(R(1).compose(R(2)))
print(R(3))
Run Code Online (Sandbox Code Playgroud)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 decribes 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
Run Code Online (Sandbox Code Playgroud)
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi)). Try to find the answer yourself first on a piece of paper.+ 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)
Run Code Online (Sandbox Code Playgroud)
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 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 + AA * (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 +, * 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:
print(R(1) * (diag(2,3) * R(2)))
print((R(1) * diag(2,3)) * R(2))
Run Code Online (Sandbox Code Playgroud)
Let's find analogues to regular numbers 0 and 1 and subtraction:
zero = diag(0, 0)
one = diag(1, 1)
Run Code Online (Sandbox Code Playgroud)
With the following easily verifiable additions:
A + zero = AA * zero = zeroA * one = one * A = Athe 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(pi/2) + R(pi)) * (R(-pi/2) + R(pi)) = R(pi/2) * R(-pi/2) + ... = one + ...
Run Code Online (Sandbox Code Playgroud)
(R(a) + R(b)) * (R(a) - R(b)) = R(2a) - R(2b). 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 in can be written in the form lambda v: A(v) + b for some matrix A and vector b.
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 coeffiecient 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]
Run Code Online (Sandbox Code Playgroud)
As a sanity check, diag(a, b).det() is equal to a * b.
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())
Run Code Online (Sandbox Code Playgroud)
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())
Run Code Online (Sandbox Code Playgroud)
A useful thing you can do with matrices is write a system of two linear equations
A.m[0]*v.x + A.m[1]*v.y = b.x
A.m[2]*v.x + A.m[3]*v.y = b.y
Run Code Online (Sandbox Code Playgroud)
in a simpler way: A(v) = b. Let's solve the system as they teach in (some) high schools: multiply 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)
Run Code Online (Sandbox Code Playgroud)
As you see, this method fails loudly when determinant of matrix is zero. If you really want you can catch this expection with:
try:
print(zero.inv())
except ZeroDivisionError as e: ...
Run Code Online (Sandbox Code Playgroud)
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).The main property of inverse matrix is that A * A.inv() always equals to one
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 for cycle. A
while loop goes over all bits of n, multiplying answer
by self ** (2 ** k) whenever it encounters a set bit.
...
Run Code Online (Sandbox Code Playgroud)
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).
diag(-1, -1). Is this the only possible answer?
Find an example of matrix that doesn't have a square root.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.
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 the diag matrix. Also add comparison for equalit
麻省理工学院在http://ocw.mit.edu/OcwWeb/Mathematics/上有很多数学课程的资料.一旦掌握了基础知识,他们也会在线获得物理笔记.