Класс Matrix
В Eigen, все матрицы и векторы являются объектами шаблона класса Matrix. Векторы являются всего лишь специальным случаем матриц, имеющих либо 1 строку, либо 1 столбец.
Первые три параметра шаблона Matrix
Класс Matrix принимает шесть параметров шаблона, но пока достаточно узнать о первых трёх. Остальные три параметра имеют значения по умолчанию, которые мы пока оставим без изменений, и о которых мы будем говорить ниже.
Три обязательных параметра шаблона Matrix:
Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
-
Scalar— тип скаляра, то есть тип коэффициентов. То есть, если вам нужна матрица с плавающей точкой, выберитеfloatздесь. См. Типы скаляров для списка всех поддерживаемых типов скаляров и того, как расширить поддержку для новых типов. -
RowsAtCompileTimeиColsAtCompileTime— количество строк и столбцов матрицы, известное во время компиляции (см. ниже, что делать, если количество неизвестно во время компиляции).
Мы предлагаем множество удобных псевдонимов типов для обычных случаев. Например, Matrix4f — это матрица 4x4 с плавающей точкой. Вот как она определена в Eigen:
typedef Matrix<float, 4, 4> Matrix4f;
Мы обсуждаем ниже эти удобные псевдонимы типов.
Векторы
Как упоминалось выше, в Eigen, векторы — это всего лишь особый случай матриц с одной строкой или одним столбцом. Наиболее распространённым случаем является вектор с одним столбцом; такие векторы называются столбцовыми векторами, часто сокращённо — векторы. В другом случае, когда у них одна строка, они называются строчными векторами.
Например, удобный псевдоним типа Vector3f — это (столбцовый) вектор из 3 чисел с плавающей точкой. Он определяется следующим образом в Eigen:
typedef Matrix<float, 3, 1> Vector3f;
Мы также предлагаем удобные псевдонимы типов для строчных векторов, например:
typedef Matrix<int, 1, 2> RowVector2i;
Специальное значение Dynamic
Конечно, Eigen не ограничивается матрицами, размер которых известен на этапе компиляции. Параметры шаблона RowsAtCompileTime и ColsAtCompileTime могут принимать специальное значение Dynamic, которое указывает, что размер неизвестен на этапе компиляции, поэтому должен обрабатываться как переменная времени выполнения. В терминологии Eigen такой размер называется динамическим размером; размер, известный на этапе компиляции, называется фиксированным размером. Например, удобный псевдоним типа MatrixXd, обозначающий матрицу с типом double и динамическим размером, определяется следующим образом:
typedef Matrix<double, Dynamic, Dynamic> MatrixXd;
И аналогично, мы определяем понятный псевдоним типа VectorXi следующим образом:
typedef Matrix<int, Dynamic, 1> VectorXi;
Вы можете иметь, например, фиксированное количество строк с динамическим количеством столбцов, как в:
Matrix<float, 3, Dynamic>
Конструкторы
Конструктор по умолчанию всегда доступен, никогда не выполняет динамическое выделение памяти и никогда не инициализирует коэффициенты матрицы. Вы можете сделать так:
Matrix3f a; MatrixXf b;
Здесь,
-
a— это матрица 3x3 с обычным массивом float[9] неинициализированных коэффициентов, -
b— это матрица с динамическим размером, размер которой в данный момент 0x0, и массив коэффициентов ещё не выделен.
Также доступны конструкторы, принимающие размеры. Для матриц количество строк всегда передаётся первым. Для векторов передаётся только размер вектора. Они выделяют массив коэффициентов заданного размера, но сами коэффициенты не инициализируют:
MatrixXf a(10,15); VectorXf b(30);
Здесь,
-
a— это динамическая матрица размером 10x15 с выделенными, но пока неинициализированными коэффициентами. -
b— это динамический вектор размером 30 с выделенными, но пока неинициализированными коэффициентами.
Для обеспечения единообразного API для матриц с фиксированным и динамическим размером, эти конструкторы можно использовать для матриц с фиксированным размером, даже если передача размеров в этом случае бесполезна. Так что это допустимо:
Matrix3f a(3,3);
и это не выполняет никаких операций.
Матрицы и векторы также могут быть инициализированы списками коэффициентов. До C++11 эта функция ограничена небольшими фиксированными столбцовыми или строчными векторами до размера 4:
Vector2d a(5.0, 6.0); Vector3d b(5.0, 6.0, 7.0); Vector4d c(5.0, 6.0, 7.0, 8.0);
Если включён C++11, фиксированные столбцовые или строчные векторы произвольного размера могут быть инициализированы путём передачи произвольного количества коэффициентов:
Vector2i a(1, 2); // A column vector containing the elements {1, 2} Matrix<int, 5, 1> b {1, 2, 3, 4, 5}; // A row-vector containing the elements {1, 2, 3, 4, 5} Matrix<int, 1, 5> c = {1, 2, 3, 4, 5}; // A column vector containing the elements {1, 2, 3, 4, 5}
В общем случае для матриц и векторов с фиксированным или динамическим размером коэффициенты должны быть сгруппированы по строкам и переданы как список инициализаторов списка инициализаторов (подробности):
MatrixXi a { // construct a 2x2 matrix
{1, 2}, // first row
{3, 4} // second row
};
Matrix<double, 2, 3> b {
{2, 3, 4},
{5, 6, 7},
};
Для столбцовых или строчных векторов разрешена неявная транспозиция. Это означает, что столбцовый вектор может быть инициализирован по одной строке:
VectorXd a {{1.5, 2.5, 3.5}}; // A column-vector with 3 coefficients
RowVectorXd b {{1.0, 2.0, 3.0, 4.0}}; // A row-vector with 4 coefficients
Доступ к коэффициентам
Основными средствами доступа и изменения коэффициентов в Eigen являются перегруженные операторы круглых скобок. Для матриц индекс строки всегда передаётся первым. Для векторов передаётся только один индекс. Нумерация начинается с 0. Этот пример самодокументируемый:
| Пример: | Вывод: |
|---|---|
#include <iostream> #include <Eigen/Dense> using namespace Eigen; int main() { MatrixXd m(2,2); m(0,0) = 3; m(1,0) = 2.5; m(0,1) = -1; m(1,1) = m(1,0) + m(0,1); std::cout << "Here is the matrix m:\n" << m << std::endl; VectorXd v(2); v(0) = 4; v(1) = v(0) - 1; std::cout << "Here is the vector v:\n" << v << std::endl; } |
Here is the matrix m: 3 -1 2.5 1.5 Here is the vector v: 4 3 |
Обратите внимание, что синтаксис m(index) не ограничен векторами, он также доступен для общих матриц, что означает доступ на основе индекса в массиве коэффициентов. Однако это зависит от порядка хранения матрицы. Все матрицы Eigen по умолчанию используют порядок хранения по столбцам, но это можно изменить на порядок хранения по строкам, см. Порядки хранения.
Оператор[] также перегружен для доступа на основе индексов в векторах, но имейте в виду, что C++ не позволяет оператору[] принимать более одного аргумента. Мы ограничиваем оператор[] векторами, потому что нелепость языка C++ заставит matrix[i,j] скомпилироваться так же, как matrix[j]!
Инициализация запятой
Коэффициенты матриц и векторов удобно устанавливать с помощью так называемого синтаксиса инициализации запятой. Пока достаточно знать этот пример:
| Пример: | Вывод: |
|---|---|
Matrix3f m;
m << 1, 2, 3,
4, 5, 6,
7, 8, 9;
std::cout << m;
|
1 2 3 4 5 6 7 8 9 |
Правая часть также может содержать матричные выражения, как обсуждается в этой странице.
Изменение размера
Текущий размер матрицы можно получить с помощью rows(), cols() и size(). Эти методы возвращают количество строк, количество столбцов и количество коэффициентов соответственно. Изменение размера матрицы с динамическим размером выполняется методом resize().
| Пример: | Вывод: |
|---|---|
#include <iostream> #include <Eigen/Dense> using namespace Eigen; int main() { MatrixXd m(2,5); m.resize(4,3); std::cout << "The matrix m is of size " << m.rows() << "x" << m.cols() << std::endl; std::cout << "It has " << m.size() << " coefficients" << std::endl; VectorXd v(2); v.resize(5); std::cout << "The vector v is of size " << v.size() << std::endl; std::cout << "As a matrix, v is of size " << v.rows() << "x" << v.cols() << std::endl; } |
The matrix m is of size 4x3 It has 12 coefficients The vector v is of size 5 As a matrix, v is of size 5x1 |
Метод resize() является пустой операцией, если фактический размер матрицы не изменяется; в противном случае он деструктивный: значения коэффициентов могут измениться. Если вам нужна консервативная версия resize(), которая не изменяет коэффициенты, используйте conservativeResize(), см. эту страницу для получения дополнительной информации.
Все эти методы по-прежнему доступны для матриц с фиксированным размером, ради единообразия API. Конечно, вы не можете изменить размер матрицы с фиксированным размером. Попытка изменить фиксированный размер на фактически другое значение вызовет ошибку проверки, но следующий код допустим:
| Пример: | Вывод: |
|---|---|
#include <iostream> #include <Eigen/Dense> using namespace Eigen; int main() { Matrix4d m; m.resize(4,4); // no operation std::cout << "The matrix m is of size " << m.rows() << "x" << m.cols() << std::endl; } |
The matrix m is of size 4x4 |
Присваивание и изменение размера
Присваивание — это действие копирования матрицы в другую, используя operator=. Eigen автоматически изменяет размер матрицы слева так, чтобы он соответствовал размеру матрицы справа. Например:
| Пример: | Вывод: |
|---|---|
MatrixXf a(2,2); std::cout << "a is of size " << a.rows() << "x" << a.cols() << std::endl; MatrixXf b(3,3); a = b; std::cout << "a is now of size " << a.rows() << "x" << a.cols() << std::endl; |
a is of size 2x2 a is now of size 3x3 |
Конечно, если размер левой стороны фиксирован, её изменение размера не разрешено.
Если вам не нужно, чтобы происходило это автоматическое изменение размера (например, для отладки), вы можете отключить его, см. эту страницу.
Фиксированный против динамического размера
Когда следует использовать фиксированный размер (например, Matrix4f), а когда предпочтительнее динамический размер (например, MatrixXf)? Простой ответ: используйте фиксированный размер для очень маленьких размеров, где это возможно, и используйте динамический размер для больших размеров или когда это необходимо. Для небольших размеров, особенно для размеров меньше (примерно) 16, использование фиксированных размеров имеет огромные преимущества для производительности, поскольку оно позволяет Eigen избежать динамического выделения памяти и разворачивать циклы. Внутренне, фиксированная Eigen матрица — это просто обычный массив, то есть выполнение
Matrix4f mymatrix;
действительно сводится к выполнению
float mymatrix[16];
так что это действительно не несёт никаких затрат во время выполнения. В отличие от этого, массив матрицы с динамическим размером всегда выделяется в куче, поэтому выполнение
MatrixXf mymatrix(rows,columns);
сводится к выполнению
float *mymatrix = new float[rows*columns];
и кроме того, объект MatrixXf хранит своё количество строк и столбцов как переменные-члены.
Ограничение использования фиксированных размеров, конечно же, заключается в том, что это возможно только при известном размере во время компиляции. Кроме того, для достаточно больших размеров, скажем, для размеров больше (приблизительно) 32, выгода от использования фиксированных размеров становится незначительной. Хуже того, попытка создания очень большой матрицы с помощью фиксированных размеров внутри функции может привести к переполнению стека, так как Eigen попытается автоматически выделить массив как локальную переменную, и это обычно происходит в стеке. Наконец, в зависимости от обстоятельств, Eigen может также более активно пытаться векторизовать (использовать инструкции SIMD) при использовании динамических размеров, см. Векторизацию.
Дополнительные параметры шаблона
В начале этой страницы мы упомянули, что класс Matrix принимает шесть параметров шаблона, но до сих пор мы обсуждали только первые три. Остальные три параметра являются необязательными. Вот полный список параметров шаблона:
Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime, int Options = 0, int MaxRowsAtCompileTime = RowsAtCompileTime, int MaxColsAtCompileTime = ColsAtCompileTime>
-
Options— это битовое поле. Здесь мы обсудим только один бит:RowMajor. Он указывает, что матрицы этого типа используют порядок хранения строк; по умолчанию порядок хранения — столбцов. См. страницу о порядках хранения. Например, этот тип означает матрицы 3x3 с порядком хранения строк:Matrix<float, 3, 3, RowMajor>
-
MaxRowsAtCompileTimeиMaxColsAtCompileTimeполезны, когда вы хотите указать, что, хотя точные размеры ваших матриц неизвестны во время компиляции, известен фиксированный верхний предел во время компиляции. Основная причина, по которой вы можете это сделать, — избежать динамического выделения памяти. Например, следующий тип матрицы использует обычный массив из 12 чисел с плавающей точкой без динамического выделения памяти:Matrix<float, Dynamic, Dynamic, 0, 3, 4>
Удобные псевдонимы типов
Eigen определяет следующие псевдонимы типов Matrix:
- MatrixNt для Matrix<type, N, N>. Например, MatrixXi для Matrix<int, Dynamic, Dynamic>.
- VectorNt для Matrix<type, N, 1>. Например, Vector2f для Matrix<float, 2, 1>.
- RowVectorNt для Matrix<type, 1, N>. Например, RowVector3d для Matrix<double, 1, 3>.
Где:
- N может быть любым из
2,3,4, илиX(означаяDynamic). - t может быть любым из
i(означая int),f(означая float),d(означая double),cf(означая complex<float>) илиcd(означая complex<double>). Тот факт, что псевдонимы типов определены только для этих пяти типов, не означает, что они единственные поддерживаемые скалярные типы. Например, поддерживаются все стандартные целочисленные типы, см. Скалярные типы.
© Eigen.
Licensed under the MPL2 License.
https://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html