Все курсы > Линейная алгебра > Занятие 3
На прошлых занятиях мы поговорили про векторы и векторные пространства. Сегодня рассмотрим матрицы и линейные преобразования.
Ноутбук к сегодняшнему занятию⧉
Как матрицы преобразовывают пространство
Посмотрим, как матрица может изменить положение вектора.
Преобразование базисных векторов
Начнем с базисных векторов. Возьмем матрицу $A$ и два базисных вектора $\mathbf i$ и $\mathbf j$.
1 2 3 4 5 |
A = np.array([[2, 3], [5, 1]]) i = np.array([1, 0]) j = np.array([0, 1]) |
Если поочередно умножить матрицу на каждый из векторов, то первый столбец матрицы $A$ определит новые координаты вектора $\mathbf i$, второй столбец — вектора $\mathbf j$.
$$ \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 2 \\ 5 \end{bmatrix} $$
$$ \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \cdot \begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 3 \\ 1 \end{bmatrix} $$
Так трансформационная матрица (transformation matrix, левый множитель) оказывает влияние на базисные (и все остальные) векторы и, таким образом, меняет пространство.
1 |
np.dot(A, i) |
1 |
array([2, 5]) |
1 |
np.dot(A, j) |
1 |
array([3, 1]) |
Посмотрим на результат на графике.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ax = plt.axes() plt.xlim([-0.5, 5]) plt.ylim([-0.5, 6]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # найдем координаты с помощью произведения arrow_a = ax.arrow(0, 0, np.dot(A, i)[0], np.dot(A, i)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') arrow_b = ax.arrow(0, 0, np.dot(A, j)[0], np.dot(A, j)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') plt.show() |

Продемонстрируем, что при преобразовании любого вектора, мы по сути меняем базисные векторы, умноженные на скаляр. Рассмотрим, как в результате умножения матрицы A на вектор $\mathbf r$, мы получим новый вектор $\mathbf r’$.
$$A \cdot \mathbf r = \mathbf r’ $$
Очевидно, мы можем умножать векторы $\mathbf r$ и $\mathbf r’$ на скаляр $n$ или представить их в виде суммы двух (базисных) векторов.
$$A \cdot n \mathbf r = n \mathbf r’ $$
$$A \cdot (\mathbf i + \mathbf j) = (\mathbf i + \mathbf j)’ $$
Тогда справедливо, что
$$A \cdot (n \mathbf i+m \mathbf j) = (n \mathbf i+m \mathbf j)’ $$
Выполним умножение.
$$ A \cdot (n \mathbf i + m \mathbf j) = (nA \mathbf i+mA \mathbf j) = n \mathbf i’ + m \mathbf j’ $$
Обратите внимание, что умножение матрицы на скаляр коммутативно, то есть $A n \mathbf i = n A \mathbf i $.
Таким образом, можно сказать, что при преобразовании пространства матрица преобразует масштабированные (scaled) базисные векторы. Приведем пример.
$$ \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \begin{bmatrix} 2 \\ 4 \end{bmatrix} = \begin{bmatrix} 16 \\ 14 \end{bmatrix} $$
$$ \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \left( 2 \begin{bmatrix} 1 \\ 0 \end{bmatrix} + 4 \begin{bmatrix} 0 \\ 1 \end{bmatrix} \right) = \begin{bmatrix} 16 \\ 14 \end{bmatrix} $$
$$ 2 \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \begin{bmatrix} 1 \\ 0 \end{bmatrix} + 4 \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 16 \\ 14 \end{bmatrix} $$
$$ 2 \begin{bmatrix} 2 \\ 5 \end{bmatrix} + 4 \begin{bmatrix} 3 \\ 1 \end{bmatrix} = \begin{bmatrix} 16 \\ 14 \end{bmatrix} $$
Иначе говоря, «новые», преобразованные векторы будут иметь те же координаты относительно нового базиса, что и исходный вектор, относительно исходного базиса (потому что $n$ и $m$ или 2 и 4 в примере выше не изменяются при преобразовании).
Свойства преобразований
Вначале дадим описательное определение линейной трансформации пространства.
При линейном преобразовании начало координат не смещается, а линии координатной сетки остаются параллельными и сохраняют исходное расстояние друг от друга (origin remains fixed, grid lines remain parallel and evenly spaced).
Более формально свойства трансформации ($T$), которую также можно назвать функцией (function) или отображением (mapping) относительно векторов $\mathbf v$ и $\mathbf w$ можно выразить через две заданные в линейном пространстве операции сложения и умножения на скаляр.
- $ T(\mathbf v + \mathbf w) = T(\mathbf v) + T(\mathbf w) $
- $ T(c \mathbf v) = c T(\mathbf v) $
Примечение. Несмещение начала координат можно рассматривать как частный случай свойства 2, так как преобразование нулевого вектора должно дать нулевой вектор $T(\mathbf 0) = \mathbf 0 $. Приведем пример линейного и нелинейного преобразований.
Пример 1. Проекция
Рассмотрим проекцию $T: \mathbb R^2 \rightarrow \mathbb R^2 $.

Проверим приведенные выше свойства:
- $ T(\mathbf v + \mathbf w) = T(\mathbf v) + T(\mathbf w) $
- если, например, вектор $\mathbf v$ увеличить в два раза, то и проекция увеличится в два раза
- начало координат при проекции не смещается, то есть $T(\mathbf 0) = \mathbf 0 $
Пример 2. Нелинейное преобразование
Предположим, что мы хотим сместить каждый вектор проскости (в частности, вектор $ \mathbf v $) на некоторый вектор $ \mathbf d $.

Очевидно нарушается второе свойство, например, $ T(2 \mathbf v) \not= 2T(\mathbf v) $. Более того, смещается начало координат, $T(\mathbf 0) = \mathbf d $.
Умножение матрицы на вектор
Важно, что умножение матрицы $A$ на векторы $\mathbf v$ и $\mathbf w$, т.е. $T(\mathbf v) = A \mathbf v$ и $T(\mathbf w) = A \mathbf w$ всегда линейно, так как
- $ A(\mathbf v + \mathbf w) = A(\mathbf v) + A(\mathbf w) $
- $ A(c \mathbf v) = c A(\mathbf v) $
Соответственно, задача линейного преобразования сводится к нахождению правильной трансформационной матрицы (причем в известной, заданной системе координат).
Например, если мы хотим перейти от трех измерений к двум, $T: \mathbb R^3 \rightarrow \mathbb R^2$, то нам потребуется матрица $2 \times 3$:

В общем случае матрица $A$ размерностью $m \times n$ соответствует $ T: R^n \rightarrow R^m $.
Смена базиса
Как уже было сказано, если линейное преобразование задано матрицей, то введена система координат (базис).
- На вход матрица получает базис $ \mathbf v_1,…, \mathbf v_n \in R^n $
- На выходе выдает $ \mathbf w_1,…, \mathbf w_m \in R^m $
Другими словами, при преобразовании линейной комбинации $\mathbf v_1,…, \mathbf v_n$ в линейную комбинацию $\mathbf w_1,…, \mathbf w_m$ происходит смена базиса. Например,
$$ \mathbf v = c_1 \mathbf v_1 + c_2 \mathbf v_2 \rightarrow T(\mathbf v) = c_1 \mathbf w_1 + c_2 \mathbf w_2 $$
Пример 3. Производная как линейное преобразование
Интересно, что взятие производной линейно. Предположим, что у нас есть некоторая функция $ f(x) = c_1 + c_2 x + c_3 x^2 $. Ее базис: $\{ 1, x, x^2 \}$. Тогда производной будет $f'(x) = c_2 + 2 c_3 x $ с базисом $\{ 1, x \}$. Найти производную можно с помощью матрицы
$$ \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 2 \end{bmatrix} \begin{bmatrix} c_1 \\ c_2 \\ c_3 \end{bmatrix} = \begin{bmatrix} c_2 \\ 2c_3 \end{bmatrix} $$
Решение системы уравнений как преобразование
Теперь рассмотрим решение системы линейных уравнений (simultaneous equations) с точки зрения трансформации пространства.
$$ \begin{bmatrix} 2 & 3 \\ 5 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \end{bmatrix} = \begin{bmatrix} 8 \\ 13 \end{bmatrix}$$
По сути, нам нужно найти такой вектор $\begin{bmatrix} a \\ b \end{bmatrix}$, при умножении матрицы на который мы окажемся в точке $ \begin{bmatrix} 8 \\ 13 \end{bmatrix} $.
Виды преобразований
Можно выделить некоторые часто встречающиеся виды преобразований. Создадим вектор $\mathbf x$.
1 |
x = np.array([3, 4]) |
Единичная матрица
Если умножить единичную матрицу (identity matrix) на вектор $\mathbf x$, ничего не произойдет.
1 2 3 4 |
I = np.array([[1, 0], [0, 1]]) np.dot(I, x) |
1 |
array([3, 4]) |
Обратите внимание, мы легко можем догадаться, что с вектором после преобразования ничего не произойдет, потому что столбцы трансформационной матрицы в точности повторяют векторы базиса $\mathbf i$ и $\mathbf j$.
Сжатие и растяжение
При сжатии или растяжении (scaling) координаты вектора соответственно уменьшаются или увеличиваются в размере. В случае базисных векторов, они не меняют направления. Приведем пример растяжения.
1 2 3 4 5 6 |
# столбцы - это тот же базис, но # единицы увеличены в три и два раза соответственно Scale = np.array([[3, 0], [0, 2]]) np.dot(Scale, x) |
1 |
array([9, 8]) |
Посмотрим, как это выглядит на графике.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
ax = plt.axes() plt.xlim([-0.5, 9.5]) plt.ylim([-0.5, 9.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, np.dot(Scale, i)[0], np.dot(Scale, i)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Scale, j)[0], np.dot(Scale, j)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), 3, 2, fill = False, edgecolor = 'b', lw = 2)) ax.arrow(0, 0, np.dot(Scale, x)[0], np.dot(Scale, x)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') plt.show() |

Дадим некоторые пояснения. Преобразования пространства удобно описывать с помощью площади, образованной двумя (в пространстве $R^2$) векторами.
- Зеленый квадрат: площадь базисных векторов
- Синий квадрат: площадь масштабированных базисных векторов
- Красный вектор: масштабированный вектор $\mathbf x$
Обратите внимание, векторы базиса $\mathbf i$ и $\mathbf j$ после трансформации сохранили направление, вектор $ \mathbf r $ сместился выше. Понимание того, что некоторые векторы сохраняют направление, а некоторые — нет, очень пригодится позднее.
Приведем пример сжатия. Для этого нужно, чтобы ненулевые координаты матрицы были меньше единицы.
1 2 3 4 |
Squish = np.array([[.5, 0], [0, .5]]) np.dot(Squish, x) |
1 |
array([1.5, 2. ]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
ax = plt.axes() plt.xlim([-0.5, 2.5]) plt.ylim([-0.5, 2.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, np.dot(Squish, i)[0], np.dot(Squish, i)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Squish, j)[0], np.dot(Squish, j)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), .5, .5, fill = False, edgecolor = 'b', lw = 2)) ax.arrow(0, 0, np.dot(Squish, x)[0], np.dot(Squish, x)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') plt.show() |

Отражение
Отражение (reflection) как бы «перекидывает» вектор на другую сторону от осей координат. Посмотрим на отражение относительно оси y (то есть «перекидывать» мы будем вектор $\mathbf i$). Для этого в первом столбце трансформационной матрицы 1 меняется на $-1$.
1 2 3 4 5 |
Reflect_y = np.array([[-1, 0], [0, 2]]) # посмотрим, где окажется вектор x np.dot(Reflect_y, x) |
1 |
array([-3, 8]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
ax = plt.axes() plt.xlim([-1.5, 1.5]) plt.ylim([-0.2, 2.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # отражается относительно оси y ax.arrow(0, 0, np.dot(Reflect_y, i)[0], np.dot(Reflect_y, i)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') # вектор j мы растягиваем ax.arrow(0, 0, np.dot(Reflect_y, j)[0], np.dot(Reflect_y, j)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), -1, 2, fill = False, edgecolor = 'b', lw = 2)) plt.show() |

Выполним отражение относительно обеих осей.
1 2 3 4 |
Invert = np.array([[-1, 0], [0, -1]]) np.dot(Invert, x) |
1 |
array([-3, -4]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
ax = plt.axes() plt.xlim([-1.5, 1.5]) plt.ylim([-1.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # отражается относительно оси y ax.arrow(0, 0, np.dot(Invert, i)[0], np.dot(Invert, i)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') # отражается относительно оси x ax.arrow(0, 0, np.dot(Invert, j)[0], np.dot(Invert, j)[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), -1, -1, fill = False, edgecolor = 'b', lw = 2)) plt.show() |

Матрица перестановки
Матрица перестановки (permutation matrix) позволяет поменять векторы $\mathbf i$ и $\mathbf j$ местами.
1 2 3 4 5 |
P = np.array([[0, 1], [1, 0]]) # посмотрим, где окажется вектор x np.dot(P, x) |
1 |
array([4, 3]) |
1 2 |
# а также базисные векторы np.dot(P, i), np.dot(P, j) |
1 |
(array([0, 1]), array([1, 0])) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ax = plt.axes() plt.xlim([-0.5, 1.5]) plt.ylim([-0.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # i и j меняются местами ax.arrow(0, 0, np.dot(P, i)[0], np.dot(P, i)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(P, j)[0], np.dot(P, j)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'b', lw = 1)) plt.show() |

Сдвиг (трансвекция)
Сдвиг или трансвекция (shear) предполагает, что один из базисных векторов остается на месте, второй сдвигается.
1 2 3 4 5 |
# вектор i остается на месте, j сдвигается Shear = np.array([[1, 1], [0, 1]]) np.dot(Shear, x) |
1 |
array([7, 4]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ax = plt.axes() plt.xlim([-0.5, 2.5]) plt.ylim([-0.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, np.dot(Shear, i)[0], np.dot(Shear, i)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Shear, j)[0], np.dot(Shear, j)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Polygon([[0, 0], [1, 0], [2, 1], [1, 1]], fill = False, edgecolor = 'b', lw = 2)) plt.show() |

Вращение
Посмотрим, как можно повернуть (rotate) базисные векторы на 90 градусов против часовой стрелки.
1 2 3 4 5 |
# поворот на 90 градусов против часовой стрелки Rotate = np.array([[0, -1], [1, 0]]) np.dot(Rotate, x) |
1 |
array([-4, 3]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ax = plt.axes() plt.xlim([-1.5, 1.5]) plt.ylim([-0.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, np.dot(Rotate, i)[0], np.dot(Rotate, i)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Rotate, j)[0], np.dot(Rotate, j)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.add_patch(Rectangle((0, 0), 1, 1, fill = False, edgecolor = 'g', lw = 2)) ax.add_patch(Rectangle((0, 0), -1, 1, fill = False, edgecolor = 'b', lw = 2)) plt.show() |

Поворот на определенный угол против часовой стрелки.
1 2 3 4 5 6 |
theta = np.radians(90) Rotate = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) np.dot(Rotate, x) |
1 |
array([-4., 3.]) |
Смысл такой трансформации представлен на схеме ниже. Для поворота на 90 градусов против часовой стрелки вспомним, что
- для вектора $\mathbf i \quad cos(90^{\circ}) = 0, \quad -sin(90^{\circ}) = -1$
- для вектора $\mathbf j \quad sin(90^{\circ}) = 1, \quad cos(90^{\circ}) = 0$

Видео про линейные преобразования⧉.
Композиция преобразований
Посмотрим, что произойдет, если применить сначала поворот на 90 градусов по часовой стрелке, затем отражение относительно оси y.
Так как матрица преобразования стоит слева от преобразуемого вектора, то для применения двух преобразований, матрица второго преобразования будет стоять слева от матрицы первого.
$$ Reflect_y \cdot (Rotate \cdot \mathbf x ) $$
Подготовим матрицы.
1 2 3 4 5 6 7 |
# поворот по часовой стрелки theta = np.radians(90) Rotate = np.array([[np.cos(theta), np.sin(theta)], [-np.sin(theta), np.cos(theta)]]).round() Rotate |
1 2 |
array([[ 0., 1.], [-1., 0.]]) |
1 2 3 4 5 |
# отражение относительно оси y Reflect_y = np.array([[-1, 0], [0, 1]]) Reflect_y |
1 2 |
array([[-1, 0], [ 0, 1]]) |
Выполним преобразование.
1 |
np.dot(Reflect_y, np.dot(Rotate, i)), np.dot(Reflect_y, np.dot(Rotate, j)) |
1 |
(array([ 0., -1.]), array([-1., 0.])) |
Посмотрим на этот процесс графически. Вначале первое преобразование (вращение).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ax = plt.axes() plt.xlim([-1.5, 1.5]) plt.ylim([-1.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # поворот по часовой стрелке ax.arrow(0, 0, np.dot(Rotate, i)[0], np.dot(Rotate, i)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Rotate, j)[0], np.dot(Rotate, j)[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') plt.show() |

Теперь применим второе преобразование (отражение) к результату первого.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ax = plt.axes() plt.xlim([-1.5, 1.5]) plt.ylim([-1.5, 1.5]) plt.grid() ax.arrow(0, 0, i[0], i[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.03, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') # отображение относительно оси y ax.arrow(0, 0, np.dot(Reflect_y, np.dot(Rotate, i))[0], np.dot(Reflect_y, np.dot(Rotate, i))[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, np.dot(Reflect_y, np.dot(Rotate, j))[0], np.dot(Reflect_y, np.dot(Rotate, j))[1], width = 0.01, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') plt.show() |

Запишем, где оказались векторы $\mathbf i$ и $\mathbf j$ после второго преобразования и соединим эти координаты в матрицу. Затем умножим на исходные векторы $\mathbf i$ и $\mathbf j$.
1 2 3 4 |
Composition = np.array([[0, -1], [-1, 0]]) np.dot(Composition, i), np.dot(Composition, j) |
1 |
(array([ 0, -1]), array([-1, 0])) |
Результат аналогичен последовательному применению предыдущих преобразований.
1 2 |
# то же самое мы получим, перемножив матрицы преобразований np.dot(Reflect_y, Rotate) |
1 2 |
array([[ 0., -1.], [-1., 0.]]) |
Обратите внимание, что порядок операций важен. Поменяв матрицы преобразований местами, мы получим другой результат.
1 2 3 |
# выполнив отражение, а затем поворот, # мы вернем векторы в исходное положение np.dot(Rotate, np.dot(Reflect_y, i)), np.dot(Rotate, np.dot(Reflect_y, j)) |
1 |
(array([0., 1.]), array([1., 0.])) |
Это еще раз демонстрирует некоммутативность, но ассоциативность умножения матриц.
Приведем ссылки на видео:
- Умножение матриц как последовательность преобразований⧉
- Линейные преобразования в трехмерном пространстве⧉
Подведем итог
Мы посмотрели, как матрицы преобразовывают пространство и на примерах изучили, чем линейные преобразования отличаются от нелинейных. Кроме того, мы познакомились с основными видами линейных преобразований.
Рассмотрим системы линейных уравнений.