Исходный ответ: Я не уверен, понравится ли вам, как в математических курсах обычно вводятся матрицы. Как программист, вы могли бы быть более счастливы, схватив любую приличную книгу по 3D-графике. У него обязательно должны быть очень конкретные матрицы 3x3. Также найдите те, которые научат вас проективным преобразованиям (проективная геометрия - очень красивая область низкоразмерной геометрии и простой в программировании).
Мини-курс матричной математики на Python 3
Содержание:
- Матрицы
[Vector, __add__, reflect_y, rotate, dilate, transform]
- Матрицы: перегружены
[Matrix, __add__, __str__, __mul__, zero, det, inv, __pow__]
- Бонус: комплексные числа
- Матрицы: (R) эволюция. Он уже в разработке (в конце есть резюме)
Предисловие: Исходя из моего опыта преподавания, я считаю, что курсы, на которые ссылаются другие, являются очень хорошими курсами. Это означает, что если ваша цель - понять матрицы как математики, вам непременно следует пройти весь курс. Но если ваши цели более скромные, вот моя попытка сделать что-то более адаптированное к вашим потребностям (но все же написанное с целью передать многие теоретические концепции, что как бы противоречит моему первоначальному совету).
Как использовать:
- Этот пост длинный. Вы можете распечатать это и делать это медленно, например, одну часть в день.
- Код необходим. Это курс для программистов. Упражнения тоже необходимы.
- Вам следует взглянуть на сопутствующий код, который содержит весь этот код и многое другое
- Это 2 по цене 1 специального: вы также можете изучить Python 3 здесь. И комплексные числа.
- Я высоко ценю любую попытку прочитать это (могу ли я официально претендовать на самый длинный пост в истории?), Поэтому не стесняйтесь комментировать, если вы чего-то не понимаете (а также, если понимаете).
- Матрицы =
Векторы
Перед матрицами идут векторы. Вы точно знаете, как работать с двумерными и трехмерными векторами:
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)
Это делает вещи более интересными, поскольку теперь мы можем написать:
print(v + w * 2)
и получите ответ (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())
- Вопрос: можно ли выразить
flip(...)
, используя описанные ниже операции? А что насчет reflect_x
?
Теперь вы можете задаться вопросом, почему я пропустил reflect_y
. Ну, это потому, что я хочу, чтобы вы на мгновение остановились и написали свою собственную версию. Хорошо, вот мой:
def reflect_y(self:'vector') -> 'vector reflected around y axis':
return self.flip().reflect_x()
Видите ли, если вы посмотрите, как эта функция вычисляет, на самом деле это довольно тривиально. Но внезапно случилось удивительное: я смог написать преобразование, используя только существующие преобразования flip
и reflect_x
. Мне все равно, reflect_y
можно определить в производном классе без доступа к x
и y
, и он все равно будет работать!
Математики назвали бы эти функции операторами. Они сказали бы, что reflect_y
- это оператор, полученный композицией операторов flip
и reflect_x
, который обозначается reflect_y = flip ○ reflect_x
(вы должны увидеть маленький кружок, символ Юникода 25CB
).
- Примечание. Я буду свободно использовать символ
=
, чтобы обозначить, что две операции дают одинаковый результат, как в предыдущем абзаце. Это математическое =
, которое нельзя выразить в виде программы.
So if I do
print(v.reflect_y())
Получаю результат (-5, 3)
. Иди и представь это!
- Вопрос: Рассмотрим композицию
reflect_y ◦ reflect_y
. Как бы вы это назвали?
Вращения
Эти операции были хорошими и полезными, но вы, вероятно, задаетесь вопросом, почему я так медленно ввожу ротации. Хорошо, я иду:
def rotate(self:'vector', angle:'rotation angle') -> 'vector':
??????
На этом этапе, если вы знаете, как вращать векторы, вам следует продолжить и заполнить вопросительные знаки. В противном случае, пожалуйста, проявите ко мне еще один простой случай: вращение против часовой стрелки на 90
градуса. Это несложно нарисовать на листе бумаги:
def rotate_90(self:'vector') -> 'rotated vector':
new_x = - self.y
new_y = self.x
return Vector(new_x, new_y)
Пытающийся
x_axis = Vector(1, 0)
y_axis = Vector(0, 1)
print(x_axis.rotate_90(), y_axis.rotate_90())
теперь дает (0, 1) (-1, 0)
. Запускай сам!
- Вопрос: Докажите, что
flip = rotate_90 ◦ rotate_90
.
В любом случае, я не буду скрывать секретный ингредиент дольше:
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)
Теперь давайте попробуем что-нибудь в этом роде:
print(x_axis.rotate(90), y_axis.rotate(90))
Если вы ожидаете того же результата, что и раньше, (0, 1) (-1, 0)
, вы обязательно будете разочарованы. Этот код печатает:
(-0.448073616129, 0.893996663601) (-0.893996663601, -0.448073616129)
и мальчик, это некрасиво!
Обозначение: я скажу, что мы применили операцию rotate(90)
к x
в приведенном выше примере. Знания, которые мы получили, таковы, что rotate(90) != rotate_90
.
Вопрос: Что здесь произошло? Как выразить rotate_90
через rotate
? Как выразить flip
через rotate
?
Расширения
Эти вращения, безусловно, полезны, но это не все, что вам нужно, даже для 2D-графики. Рассмотрим следующие преобразования:
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)
Эта штука dilate
расширяет оси x
и y
, возможно, по-другому.
- Упражнение. Заполните вопросительные знаки в
dilate(?, ?) = flip
, dilate(?, ?) = reflect_x
.
Я буду использовать эту dilate
функцию, чтобы продемонстрировать то, что математики называют коммутативностью: то есть для каждого значения параметров a
, b
, c
, d
вы можете быть уверены, что
dilate(a, b) ◦ dilate(c, d) = dilate(c, d) ◦ dilate(a, b)
Матрицы
Давайте подытожим все, что у нас было здесь, наши операторы в векторе x
flip
, reflect_x
, *
, rotate(angle)
, dilate(x, y)
из которого можно было бы сделать действительно сумасшедшие вещи вроде
flip ◦ rotate(angle) ◦ dilate(x, y) ◦ rotate(angle_2) ◦ reflect_y + reflect_x = ???
По мере того, как вы создаете все более и более сложные выражения, можно было бы надеяться на какой-то порядок, который внезапно сведет все возможные выражения к полезной форме. Не бойся! Волшебным образом каждое выражение приведенной выше формы можно упростить до
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)
с некоторыми числами и / или параметрами вместо ?
s.
- Пример. Выясните, какие значения у "?" предназначены для
__mul__(2) ◦ rotate(pi/4)
- Другой пример: тот же вопрос для
dilate(x, y) ◦ rotate(pi/4)
Это позволяет нам написать универсальную функцию
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)
который принимает любой кортеж из 4 чисел, называемый матрицей, и применяет его к вектору x
. Вот пример:
rotationclass 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)
matrix = (0, -1, 1, 0)
print(v, v.rotate_90(), v.transform(rotationclass 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)
matrix))
который печатает (5, 3) (-3, 5) (-3, 5)
. Обратите внимание, что если вы примените transform
с любой матрицей к origin, вы все равно получите origin:
origin = Vector(0, 0)
print(origin.transform(rotationclass 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)
matrix))
- Упражнение: какие кортежи
m
описывают flip
, dilate(x, y)
, rotate(angle)
?
Поскольку мы расстаемся с классом Vector
, вот упражнение для тех, кто хочет проверить как свои знания векторной математики, так и навыки Python:
- Последняя битва: добавьте в класс
Vector
все векторные операции, которые вы можете придумать (сколько стандартных операторов вы можете перегрузить для векторов? Посмотрите мой ответ).
- Матрицы: перегружены =
Как мы узнали в предыдущем разделе, матрицу можно рассматривать как сокращение, которое позволяет нам кодировать векторную операцию простым способом. Например, rotationclass 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)
matrix
кодирует поворот на 90 градусов.
Матричные объекты
Теперь, когда мы переключаем наше внимание с векторов на матрицы, мы непременно должны иметь класс для матрицы. Более того, в этой функции Vector.transform(...)
роль матрицы была несколько искажена. Обычно m
фиксируется при изменении вектора, поэтому с этого момента наши преобразования будут методами матричного класса:
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)
Если вы не знаете Python, __call__
перегружает значение (...)
для матриц, поэтому я могу использовать стандартные обозначения для матрицы, действующей на вектор. Кроме того, матрицы обычно пишутся одной заглавной буквой:
J = Matrix(rotation_90_matrix)
print(w, 'rotated is', J(w))
- Упражнение: повторите этот пример с матрицами из предыдущего упражнения.
Добавление
Теперь давайте выясним, что еще мы можем делать с матрицами. Помните, что матрица m
на самом деле всего лишь способ кодирования операции над векторами. Обратите внимание, что для двух функций m1(x)
и m2(x)
я могу создать новую функцию (используя лямбда-нотацию) m = lambda x: m1(x) + m2(x)
. Оказывается, если m1
и m2
были закодированы с помощью матриц, вы также можете кодировать это m
с помощью матриц!
- Упражнение. Обдумайте любые трудности, которые могут возникнуть у вас с этим утверждением.
Вам просто нужно добавить его данные, например (0, 1, -1, 0) + (0, 1, -1, 0) = (0, 2, -2, 0)
. Вот как добавить два кортежа в Python с помощью некоторых очень полезных и высоко питонических приемов:
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)
Теперь мы можем писать выражения типа J + J
или даже J + J + J
, но чтобы увидеть результаты, нам нужно выяснить, как напечатать матрицу. Возможный способ - напечатать 4-кортеж чисел, но давайте воспользуемся подсказкой от функции Matrix.__call__
, что числа должны быть организованы в блок 2x2
:
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]) )
Если вы посмотрите на эту функцию в действии, то заметите, что есть возможности для улучшения:
print((J + J + J).as_block())
- Упражнение: напишите более удобную функцию
Matrix.__str__
, которая округляет числа и печатает их в полях фиксированной длины.
Теперь у вас должна быть возможность написать матрицу для вращения:
def R(a: 'angle') -> 'matrix of rotation by a':
cos = math.cos(a)
sin = math.sin(a)
m = ( ????? )
return Matrix(m)
Упражнение. Изучите код для Vector.rotate(self, angle)
и заполните вопросительные знаки. Тест с
from math import pi
print(R(pi/4) + R(-pi/4))
Умножение
Самое важное, что мы можем сделать с однопараметрическими функциями, - это составить их: f = lambda v: f1(f2(v))
. Как это отразить с помощью матриц? Это требует от нас изучения того, как Matrix(m1) ( Matrix(m2) (v))
работает. Если вы расширите его, вы заметите, что
m(v).x = m1[0] * (m2[0]*v.x + m2[1]*v.y) + m1[1] * (m2[2]*v.x + m2[3]*v.y)
и то же самое для m(v).y
, который, если открыть круглые скобки, выглядит подозрительно похожим на Matrix.__call__
, использующий новый кортеж m
, такой, что m[0] = m1[0] * m2[0] + m1[2] * m2[2]
. Итак, давайте воспримем это как подсказку для нового определения:
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)
Упражнение: поставьте здесь вопросительные знаки. Проверить это с помощью
print(R(1).compose(R(2)))
print(R(3))
Упражнение по математике. Докажите, что R(a).compose(R(b))
всегда то же самое, что R(a + b)
.
Теперь позвольте мне сказать правду: эта compose
функция на самом деле является тем, как математики решили умножить матрицы. В качестве обозначения это имеет смысл: A * B
- это матрица, описывающая оператор A ○ B
, и, как мы увидим далее, есть более глубокие причины для того, чтобы называть это «умножением».
Чтобы начать использовать умножение в Python, все, что нам нужно сделать, - это упорядочить его так в классе Matrix
:
class Matrix:
...
__mul__ = compose
- Упражнение: вычислите
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi))
. Попробуйте сначала найти ответ на листке бумаги.
Правила для +
и *
Давайте придумаем хорошее название для матрицы, соответствующей оператору dilate(a, b)
. Теперь в D(a, b)
нет ничего плохого, но я воспользуюсь возможностью ввести стандартную нотацию:
def diag(a: 'number', b: 'number') -> 'diagonal 2x2 matrix':
m = (a, 0, 0, b)
return Matrix(m)
Попробуйте print(diag(2, 12345))
понять, почему это называется диагональной матрицей.
Поскольку ранее было установлено, что композиция операций не всегда коммутативна, оператор *
также не всегда будет коммутативным для матриц.
- Упражнение: вернитесь и обновите элемент коммутативности, если необходимо. Затем приведите примеры матриц
A
, B
, сделанных из R
и diag
, таких, что A * B
не равно B * A
.
Это несколько странно, поскольку умножение чисел всегда коммутативно, и возникает вопрос, действительно ли compose
заслуживает того, чтобы называться __mul__
. Вот довольно много правил, которым +
и *
действительно удовлетворяют:
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
- Упражнение. Докажите эти утверждения. Как определить
A - B
в терминах +
, *
и diag
? Чему равно A - A
? Добавьте метод __sub__
в класс Matrix
. Что произойдет, если вы вычислите R(2) - R(1)*R(1)
? На что оно должно равняться?
(A * B) * C = A * (B * C)
равенство называется ассоциативностью и особенно удобно, поскольку означает, что нам не нужно беспокоиться о том, чтобы заключить круглые скобки в выражение вида A * B * C
:
print(R(1) * (diag(2,3) * R(2)))
print((R(1) * diag(2,3)) * R(2))
Найдем аналоги регулярным числам 0
и 1
и произведем вычитание:
zero = diag(0, 0)
one = diag(1, 1)
Со следующими легко проверяемыми дополнениями:
A + zero = A
A * zero = zero
A * one = one * A = A
правила становятся полными в том смысле, что у них есть короткое название: аксиомы кольца. Таким образом, математики сказали бы, что матрицы образуют кольцо, и они действительно всегда используют символы +
и *
, когда говорят о кольцах, и мы тоже.
Используя правила, можно легко вычислить выражение из предыдущего раздела:
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi)) = R(pi/2) * R(-pi/2) + ... = one + ...
- Упражнение. Закончите. Докажите, что
(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
и может быть вычислен, применяя формулу для площади к двум векторам A(x_axis)
и A(y_axis)
:
def det(self: 'matrix') -> 'determinant of a matrix':
return self.m[0]*self.m[3] - self.m[1] * self.m[2]
Для проверки работоспособности diag(a, b).det()
равно a * b
.
- Упражнение: проверьте это. Что происходит, когда один из аргументов равен 0? Когда отрицательно?
Как видите, определитель матрицы вращения всегда один и тот же:
from random import random
r = R(random())
print (r, 'det =', r.det())
Одна интересная особенность det
заключается в том, что она мультипликативна (это как бы следует из определения, если вы медитируете достаточно долго):
A = Matrix((1, 2, -3, 0))
B = Matrix((4, 1, 1, 2))
print(A.det(), '*', B.det(), 'should be', (A * B).det())
Обратный
Полезная вещь, которую вы можете сделать с матрицами, - это написать систему двух линейных уравнений
A.m[0]*v.x + A.m[1]*v.y = b.x
A.m[2]*v.x + A.m[3]*v.y = b.y
проще: A(v) = b
. Давайте решим систему, как преподают в (некоторых) средних школах: умножьте первое уравнение на A.m[3]
, второе на -Am 1 и добавьте (если сомневаетесь, сделайте это на листе бумаги), чтобы найти v.x
.
Если вы действительно пробовали это сделать, у вас должно было получиться A.det() * v.x = (A.m[3]) * b.x + (-A.m[1]) * b.y
, что предполагает, что вы всегда можете получить v
, умножив b
на другую матрицу. Эта матрица называется обратной для 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: ...
- Упражнение: завершите метод. Докажите, что обратной матрицы не существует, когда
self.det() == 0
. Напишите метод разделения матриц и протестируйте его. Используйте обратную матрицу, чтобы решить уравнение A(v) = x_axis
(A
было определено выше).
Полномочия
Основным свойством обратной матрицы является то, что A * A.inv()
всегда равно one
- Упражнение: убедитесь в этом сами. Объясните, почему это должно быть так, исходя из определения обратной матрицы.
Вот почему математики обозначают A.inv()
как A
-1. Как насчет того, чтобы написать красивую функцию, использующую нотацию A ** n
для A
n? Обратите внимание, что наивный цикл 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
print (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 под названием decorator); в противном случае просто знайте, что это реализовано.
- Упражнение для гуру: измените операторы в классе
Matrix
так, чтобы во всех стандартных операциях, где один из операндов является матрицей, а другой - числом, число автоматически преобразуется в матрицу diag
. Также добавьте сравнение на равенство.
Вот пример теста:
print( 3 * A - B / 2 + 5 )
А теперь первое интересное комплексное число: матрица J
, представленная в начале и равная Matrix((0, 1, -1, 0))
, имеет забавное свойство J * J == -1
(попробуйте!). Это означает, что J
определенно не является нормальным числом, но, как я только что сказал, матрицы и числа легко смешиваются друг с другом. Например,
(1 + J) * (2 + J) == 2 + 2 * J + 1 * J + J * J = 1 + 3 * J
используя правила, перечисленные ранее. Что произойдет, если мы протестируем это на Python?
(1 + J) * (2 + J) == 1 + 3*J
Это должно с радостью сказать True
. Другой пример:
(3 + 4*J) / (1 - 2*J) == -1 + 2*J
Как вы, возможно, догадались, математики не называют эти «сумасшедшие числа», но они делают нечто подобное - они называют выражения вида a + b*J
комплексные числа. Поскольку это все еще экземпляры нашего класса Matrix
, мы можем выполнять с ними довольно много операций: сложение, вычитание, умножение, деление, степень - все это уже реализовано! Разве матрицы не удивительны?
Я упустил из виду вопрос о том, как распечатать результат такой операции, как E = (1 + 2*J) * (1 + 3*J)
, чтобы он выглядел как выражение с J
, а не с матрицей 2x2
. Если вы внимательно изучите его, вы увидите, что вам нужно вывести левый столбец этой матрицы в формате ... + ...J
(еще одна приятная вещь: это точно E(x_axis)
!) Те, кто знает разницу между str()
и repr()
, должны видеть, что это естественно. чтобы назвать функцию, которая будет производить выражение такой формы, как repr()
.
Упражнение: напишите функцию Matrix.__repr__
, которая будет делать именно это, и попробуйте с ней несколько тестов, например (1 + J) ** 3
, сначала вычислив результат на бумаге, а затем попробовав его с помощью Python.
Математический вопрос: каков определитель a + b*J
? Если вы знаете, что такое абсолютное значение комплексного числа: как они связаны? Какое абсолютное значение a
? из a*J
?
- Матрицы: (R) эволюция =
В заключительной части этой трилогии мы увидим, что все представляет собой матрицу. Мы начнем с общих M x N
матриц и выясним, как векторы можно рассматривать как 1 x N
матрицы и почему числа совпадают с диагональными матрицами. В качестве примечания, мы исследуем комплексные числа как 2 x 2
матрицы.
Наконец, мы научимся писать аффинные и проективные преобразования с помощью матриц.
Итак, запланированных занятий [MNMatrix, NVector, Affine, Projective]
.
Я полагаю, если бы вы были в состоянии терпеть меня до этого момента, вы могли бы заинтересоваться этим продолжением, поэтому я хотел бы услышать, следует ли мне продолжать это (и где, поскольку я почти уверен, что я за пределами того, что считается разумной длиной одного документа).
person
Community
schedule
20.06.2009