Eigen 라이브러리에서 가장 중요한 클래스 중 하나는 벡터와 행렬을 다루는 클래스이다. 이들은 다양한 수학적 연산을 효율적으로 처리할 수 있도록 설계되어 있으며, 특히 선형대수학적 문제를 해결하는 데 매우 유용하다. 본 장에서는 Vector 클래스와 Matrix 클래스의 구조, 기능, 사용 방법을 다룬다.

Vector 클래스

Eigen에서 벡터는 1차원 배열로 표현되며, 선형대수에서 다루는 벡터와 동일한 의미를 갖는다. 벡터는 방향과 크기를 가지며, 2차원, 3차원, 혹은 그 이상의 차원에서도 정의될 수 있다. Eigen의 벡터 클래스는 Eigen::VectorXd와 같은 형식으로 사용되며, 이는 차원이 동적으로 할당된 벡터를 의미한다. 차원이 정해진 벡터는 Eigen::Vector3d와 같은 형식으로 정의된다.

예를 들어, 3차원 벡터는 다음과 같이 정의할 수 있다.

Eigen::Vector3d v;
v << 1, 2, 3;

이러한 벡터는 수학적으로 다음과 같이 표현된다.

\mathbf{v} = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}

이러한 벡터의 기본 연산에는 덧셈, 뺄셈, 스칼라 곱, 내적 등이 포함된다. 벡터 간의 덧셈은 다음과 같이 정의된다.

\mathbf{v_1} + \mathbf{v_2} = \begin{bmatrix} v_{1x} \\ v_{1y} \\ v_{1z} \end{bmatrix} + \begin{bmatrix} v_{2x} \\ v_{2y} \\ v_{2z} \end{bmatrix} = \begin{bmatrix} v_{1x} + v_{2x} \\ v_{1y} + v_{2y} \\ v_{1z} + v_{2z} \end{bmatrix}

행렬 클래스

Eigen의 행렬 클래스는 2차원 배열로, 행과 열로 구성되어 있다. 행렬은 다양한 형태로 정의될 수 있으며, 차원이 동적으로 할당되거나, 고정된 형태로 정의될 수 있다. 기본적인 행렬 클래스는 Eigen::MatrixXd로, 이는 동적으로 할당된 크기의 행렬을 의미한다. 정해진 크기의 행렬은 Eigen::Matrix3d와 같은 형식으로 정의된다.

예를 들어, 3x3 행렬은 다음과 같이 정의된다.

Eigen::Matrix3d m;
m << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;

수학적으로, 이는 다음과 같은 행렬로 표현된다.

\mathbf{M} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix}

행렬의 기본 연산으로는 덧셈, 뺄셈, 스칼라 곱, 행렬 곱셈 등이 있다. 두 행렬의 곱셈은 다음과 같이 정의된다.

\mathbf{A} \cdot \mathbf{B} = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} \cdot \begin{bmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{bmatrix} = \begin{bmatrix} a_{11}b_{11} + a_{12}b_{21} & a_{11}b_{12} + a_{12}b_{22} \\ a_{21}b_{11} + a_{22}b_{21} & a_{21}b_{12} + a_{22}b_{22} \end{bmatrix}

Eigen의 행렬 클래스는 다양한 크기의 행렬을 매우 효율적으로 처리하며, 이를 통해 대규모 수학적 계산을 빠르게 수행할 수 있다.

Vector 클래스와 행렬 클래스의 크기 동적 할당

Eigen 라이브러리에서 Vector와 Matrix 클래스는 동적 크기 할당이 가능한다. 즉, 프로그램 실행 중에 벡터나 행렬의 크기를 설정할 수 있으며, 이를 통해 더욱 유연한 구조로 사용할 수 있다.

동적 크기의 벡터

동적 크기의 벡터는 Eigen::VectorXd로 정의되며, 크기를 실행 중에 할당할 수 있다. 예를 들어, 크기가 5인 벡터를 다음과 같이 정의할 수 있다.

Eigen::VectorXd v(5);
v << 1, 2, 3, 4, 5;

이 경우, 벡터의 수학적 표현은 다음과 같다.

\mathbf{v} = \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \\ 5 \end{bmatrix}

또한, 벡터의 크기는 실행 중에 다시 변경할 수 있다. 예를 들어, 벡터의 크기를 10으로 늘리고 값을 초기화하는 코드는 다음과 같다.

v.resize(10);
v.setZero();  // 모든 원소를 0으로 초기화

이처럼 resize() 함수와 초기화 함수인 setZero(), setOnes() 등을 통해 크기와 내용을 동적으로 조정할 수 있다.

동적 크기의 행렬

동적 크기의 행렬은 Eigen::MatrixXd로 정의되며, 마찬가지로 실행 중에 크기를 설정할 수 있다. 예를 들어, 3x4 행렬을 정의하는 코드는 다음과 같다.

Eigen::MatrixXd m(3, 4);
m << 1, 2, 3, 4,
     5, 6, 7, 8,
     9, 10, 11, 12;

이 행렬은 다음과 같이 표현된다.

\mathbf{M} = \begin{bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \end{bmatrix}

동적 크기의 행렬 역시 실행 중에 크기를 변경할 수 있다. 예를 들어, 이 행렬의 크기를 5x5로 조정하려면 다음과 같이 코딩할 수 있다.

m.resize(5, 5);
m.setIdentity();  // 단위 행렬로 설정

위 코드에서는 resize() 함수를 사용하여 행렬의 크기를 조정하고, setIdentity() 함수로 단위 행렬을 생성하였다. 이렇게 Eigen의 행렬 클래스는 크기를 동적으로 조정하면서 다양한 수학적 연산을 효율적으로 수행할 수 있다.

벡터와 행렬의 변환

벡터와 행렬 사이에는 여러 가지 변환이 가능한다. 예를 들어, Eigen에서는 벡터를 행렬로 변환하거나 그 반대의 작업을 쉽게 수행할 수 있다.

벡터를 행렬로 변환

Eigen의 벡터는 단순히 열 벡터로 간주될 수 있으며, 이를 행렬로 변환하는 것은 자연스럽습니다. 예를 들어, Eigen::Vector3d 타입의 벡터를 3x1 행렬로 변환하려면 별도의 변환 작업이 필요 없다. 벡터는 기본적으로 행렬의 한 형태로 취급되기 때문이다.

Eigen::Vector3d v(1, 2, 3);
Eigen::MatrixXd m = v;

이 경우, 행렬 m은 다음과 같이 생성된다.

\mathbf{M} = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}

행렬에서 벡터 추출

행렬에서 특정 열 또는 행을 추출하여 벡터로 변환할 수도 있다. 예를 들어, 3x3 행렬의 첫 번째 열을 벡터로 추출하는 코드는 다음과 같다.

Eigen::Matrix3d m;
m << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;

Eigen::Vector3d v = m.col(0);

위 코드에서는 행렬 m의 첫 번째 열을 v에 할당했으며, v는 다음과 같이 나타난다.

\mathbf{v} = \begin{bmatrix} 1 \\ 4 \\ 7 \end{bmatrix}

이와 같이 벡터와 행렬의 변환은 매우 직관적이며, 이를 통해 다양한 형태의 수학적 계산을 유연하게 처리할 수 있다.

벡터와 행렬의 기본 연산

Eigen 라이브러리에서 벡터와 행렬의 기본 연산은 수학적 정의와 일치하며, 매우 직관적으로 사용할 수 있다. 이러한 연산에는 벡터 간 또는 행렬 간의 덧셈, 뺄셈, 스칼라 곱셈, 내적, 외적 등이 포함된다.

벡터 덧셈과 뺄셈

벡터의 덧셈과 뺄셈은 동일한 크기의 벡터끼리만 수행할 수 있다. 각 원소는 해당하는 위치의 원소끼리 더하거나 빼는 방식으로 연산된다. 예를 들어, 두 3차원 벡터 \mathbf{v_1}\mathbf{v_2}의 덧셈은 다음과 같다.

\mathbf{v_1} = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}, \quad \mathbf{v_2} = \begin{bmatrix} 4 \\ 5 \\ 6 \end{bmatrix}
\mathbf{v_1} + \mathbf{v_2} = \begin{bmatrix} 1 + 4 \\ 2 + 5 \\ 3 + 6 \end{bmatrix} = \begin{bmatrix} 5 \\ 7 \\ 9 \end{bmatrix}

이 연산은 코드로는 다음과 같이 표현된다.

Eigen::Vector3d v1(1, 2, 3);
Eigen::Vector3d v2(4, 5, 6);
Eigen::Vector3d v_sum = v1 + v2;

벡터 뺄셈의 경우도 유사하게 다음과 같이 정의된다.

\mathbf{v_1} - \mathbf{v_2} = \begin{bmatrix} 1 - 4 \\ 2 - 5 \\ 3 - 6 \end{bmatrix} = \begin{bmatrix} -3 \\ -3 \\ -3 \end{bmatrix}

벡터의 스칼라 곱

벡터와 스칼라의 곱은 벡터의 각 원소에 스칼라 값을 곱하는 방식으로 이루어진다. 예를 들어, 벡터 \mathbf{v} = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}에 스칼라 2를 곱하면 다음과 같은 벡터가 된다.

2 \cdot \mathbf{v} = \begin{bmatrix} 2 \cdot 1 \\ 2 \cdot 2 \\ 2 \cdot 3 \end{bmatrix} = \begin{bmatrix} 2 \\ 4 \\ 6 \end{bmatrix}

코드로는 다음과 같이 작성된다.

Eigen::Vector3d v(1, 2, 3);
Eigen::Vector3d v_scaled = 2 * v;

벡터 내적 (Dot Product)

내적은 두 벡터의 대응하는 원소를 곱한 뒤 합산하는 연산이다. 예를 들어, 두 3차원 벡터 \mathbf{v_1}\mathbf{v_2}의 내적은 다음과 같이 계산된다.

\mathbf{v_1} \cdot \mathbf{v_2} = 1 \cdot 4 + 2 \cdot 5 + 3 \cdot 6 = 4 + 10 + 18 = 32

이는 코드로 다음과 같이 표현된다.

Eigen::Vector3d v1(1, 2, 3);
Eigen::Vector3d v2(4, 5, 6);
double dot_product = v1.dot(v2);

내적의 결과는 스칼라 값이 되며, 이는 두 벡터 사이의 방향성을 측정하는 데 사용될 수 있다.

벡터 외적 (Cross Product)

외적은 3차원 벡터에서만 정의되며, 두 벡터의 외적은 새로운 3차원 벡터를 생성한다. 외적은 두 벡터가 이루는 평면에 수직인 벡터를 구하는 연산으로, 다음과 같이 정의된다.

\mathbf{v_1} \times \mathbf{v_2} = \begin{bmatrix} v_{1y}v_{2z} - v_{1z}v_{2y} \\ v_{1z}v_{2x} - v_{1x}v_{2z} \\ v_{1x}v_{2y} - v_{1y}v_{2x} \end{bmatrix}

예를 들어, 두 벡터 \mathbf{v_1} = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}\mathbf{v_2} = \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}의 외적은 다음과 같다.

\mathbf{v_1} \times \mathbf{v_2} = \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix}

코드로는 다음과 같이 구현된다.

Eigen::Vector3d v1(1, 0, 0);
Eigen::Vector3d v2(0, 1, 0);
Eigen::Vector3d cross_product = v1.cross(v2);

행렬 덧셈과 뺄셈

행렬의 덧셈과 뺄셈은 벡터와 유사하게 같은 차원의 행렬끼리만 가능한다. 두 행렬 \mathbf{A}\mathbf{B}가 있을 때, 이들의 덧셈은 다음과 같이 정의된다.

\mathbf{A} + \mathbf{B} = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} + \begin{bmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{bmatrix} = \begin{bmatrix} a_{11} + b_{11} & a_{12} + b_{12} \\ a_{21} + b_{21} & a_{22} + b_{22} \end{bmatrix}

예를 들어, 두 행렬을 더하는 코드는 다음과 같다.

Eigen::Matrix2d A, B;
A << 1, 2, 3, 4;
B << 5, 6, 7, 8;
Eigen::Matrix2d C = A + B;

행렬의 스칼라 곱

행렬의 스칼라 곱은 행렬의 각 원소에 스칼라 값을 곱하는 연산이다. 예를 들어, 2x2 행렬 \mathbf{A}와 스칼라 값 k에 대한 곱셈은 다음과 같이 정의된다.

k \cdot \mathbf{A} = k \cdot \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} = \begin{bmatrix} k \cdot a_{11} & k \cdot a_{12} \\ k \cdot a_{21} & k \cdot a_{22} \end{bmatrix}

예를 들어, 스칼라 3과 행렬 \mathbf{A} = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}의 곱은 다음과 같다.

3 \cdot \mathbf{A} = \begin{bmatrix} 3 \cdot 1 & 3 \cdot 2 \\ 3 \cdot 3 & 3 \cdot 4 \end{bmatrix} = \begin{bmatrix} 3 & 6 & \\ 9 & 12 \end{bmatrix}

이를 코드로 구현하면 다음과 같다.

Eigen::Matrix2d A;
A << 1, 2, 3, 4;
Eigen::Matrix2d B = 3 * A;

행렬 곱셈

행렬 곱셈은 두 행렬의 곱을 계산하는 매우 중요한 연산이다. 두 행렬 \mathbf{A}\mathbf{B}가 곱해질 수 있으려면, \mathbf{A}의 열 수와 \mathbf{B}의 행 수가 동일해야 한다. 두 행렬의 곱은 다음과 같이 계산된다.

\mathbf{C} = \mathbf{A} \cdot \mathbf{B}

예를 들어, \mathbf{A}가 2x3 행렬이고 \mathbf{B}가 3x2 행렬일 때, 이들의 곱은 다음과 같다.

\mathbf{A} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}, \quad \mathbf{B} = \begin{bmatrix} 7 & 8 \\ 9 & 10 \\ 11 & 12 \end{bmatrix}
\mathbf{C} = \mathbf{A} \cdot \mathbf{B} = \begin{bmatrix} 1 \cdot 7 + 2 \cdot 9 + 3 \cdot 11 & 1 \cdot 8 + 2 \cdot 10 + 3 \cdot 12 \\ 4 \cdot 7 + 5 \cdot 9 + 6 \cdot 11 & 4 \cdot 8 + 5 \cdot 10 + 6 \cdot 12 \end{bmatrix} = \begin{bmatrix} 58 & 64 \\ 139 & 154 \end{bmatrix}

이를 코드로 구현하면 다음과 같다.

Eigen::MatrixXd A(2, 3);
Eigen::MatrixXd B(3, 2);
A << 1, 2, 3, 4, 5, 6;
B << 7, 8, 9, 10, 11, 12;
Eigen::MatrixXd C = A * B;

전치 행렬 (Transpose)

전치 행렬은 주어진 행렬의 행과 열을 뒤바꾸는 연산이다. 예를 들어, 2 \times 3 행렬 \mathbf{A}의 전치 행렬 \mathbf{A}^T는 다음과 같이 정의된다.

\mathbf{A} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}, \quad \mathbf{A}^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix}

코드로는 transpose() 함수를 사용하여 전치 행렬을 구할 수 있다.

Eigen::MatrixXd A(2, 3);
A << 1, 2, 3, 4, 5, 6;
Eigen::MatrixXd A_T = A.transpose();

역행렬 (Inverse)

정방 행렬 (행과 열의 수가 같은 행렬) \mathbf{A}의 역행렬 \mathbf{A}^{-1}\mathbf{A} \cdot \mathbf{A}^{-1} = \mathbf{I}가 성립할 때, \mathbf{A}에 대해 정의된다. 여기서 \mathbf{I}는 단위 행렬이다.

예를 들어, 2 \times 2 행렬 \mathbf{A}가 있을 때, 그 역행렬은 다음과 같이 계산된다.

\mathbf{A} = \begin{bmatrix} a & b \\ c & d \end{bmatrix}, \quad \mathbf{A}^{-1} = \frac{1}{ad - bc} \begin{bmatrix} d & -b \\ -c & a \end{bmatrix}

코드로는 inverse() 함수를 사용하여 역행렬을 구할 수 있다.

Eigen::Matrix2d A;
A << 1, 2, 3, 4;
Eigen::Matrix2d A_inv = A.inverse();

행렬식 (Determinant)

행렬식은 정방 행렬에 대해 정의되며, 주어진 행렬이 역행렬을 가질 수 있는지 여부를 결정하는 데 사용된다. 예를 들어, 2 \times 2 행렬 \mathbf{A}의 행렬식은 다음과 같이 계산된다.

\text{det}(\mathbf{A}) = ad - bc

코드로는 determinant() 함수를 사용하여 행렬식을 구할 수 있다.

Eigen::Matrix2d A;
A << 1, 2, 3, 4;
double det = A.determinant();

단위 행렬과 영 행렬

단위 행렬은 주대각선에 1이 있고, 나머지 원소가 모두 0인 정방 행렬이다. 3 \times 3 단위 행렬 \mathbf{I}는 다음과 같다.

\mathbf{I} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

Eigen에서는 setIdentity() 함수를 사용하여 단위 행렬을 만들 수 있다.

Eigen::Matrix3d I;
I.setIdentity();

영 행렬은 모든 원소가 0인 행렬이다. 2 \times 2 영 행렬 \mathbf{O}는 다음과 같이 나타낸다.

\mathbf{O} = \begin{bmatrix} 0 & 0 \\ 0 & 0 \end{bmatrix}

코드로는 setZero() 함수를 사용하여 영 행렬을 만들 수 있다.

Eigen::Matrix2d O;
O.setZero();

블록 연산 (Block Operations)

Eigen 라이브러리에서는 행렬의 일부를 추출하거나 특정 영역에 대해 연산을 수행할 수 있는 블록 연산을 제공한다. 이를 통해 행렬의 부분적인 조작이 가능한다. 블록 연산은 행렬의 부분 집합을 참조하거나 변경할 때 유용하게 사용된다.

블록 추출

블록 연산을 사용하여 행렬의 일부를 추출할 수 있다. 블록은 block(i, j, p, q) 함수를 사용하여 추출할 수 있으며, 이 함수는 행렬의 i번째 행, j번째 열부터 시작하여 p \times q 크기의 블록을 반환한다.

예를 들어, 4x4 행렬 \mathbf{A}에서 2x2 블록을 추출하는 코드는 다음과 같다.

Eigen::Matrix4d A;
A << 1, 2, 3, 4,
     5, 6, 7, 8,
     9, 10, 11, 12,
     13, 14, 15, 16;

Eigen::Matrix2d block = A.block(1, 1, 2, 2);

위 코드에서 A.block(1, 1, 2, 2)는 행렬 \mathbf{A}의 두 번째 행, 두 번째 열에서 시작하여 2x2 크기의 블록을 추출한다. 추출된 블록은 다음과 같다.

\text{block} = \begin{bmatrix} 6 & 7 \\ 10 & 11 \end{bmatrix}

블록 삽입

블록 연산은 행렬의 일부분을 변경하는 데도 사용될 수 있다. 특정 위치에 새로운 값을 삽입하는 방식으로 행렬의 일부를 변경할 수 있다. 예를 들어, 2x2 행렬 \mathbf{B}를 4x4 행렬 \mathbf{A}의 한 부분에 삽입하는 코드는 다음과 같다.

Eigen::Matrix4d A;
A.setZero();  // 4x4 영 행렬로 초기화

Eigen::Matrix2d B;
B << 1, 2, 3, 4;

A.block(1, 1, 2, 2) = B;

이 코드는 \mathbf{A}의 두 번째 행, 두 번째 열에서 시작하는 위치에 \mathbf{B}를 삽입한다. 삽입 후의 \mathbf{A}는 다음과 같다.

\mathbf{A} = \begin{bmatrix} 0 & 0 & 0 & 0 \\ 0 & 1 & 2 & 0 \\ 0 & 3 & 4 & 0 \\ 0 & 0 & 0 & 0 \end{bmatrix}

행과 열 추출

Eigen 라이브러리에서는 행렬의 특정 행이나 열을 추출할 수 있다. 예를 들어, 3x3 행렬 \mathbf{A}의 첫 번째 열을 추출하는 코드는 다음과 같다.

Eigen::Matrix3d A;
A << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;

Eigen::Vector3d col = A.col(0);

추출된 첫 번째 열은 다음과 같이 나타난다.

\mathbf{col} = \begin{bmatrix} 1 \\ 4 \\ 7 \end{bmatrix}

마찬가지로, 특정 행을 추출하려면 row() 함수를 사용할 수 있다. 예를 들어, 두 번째 행을 추출하는 코드는 다음과 같다.

Eigen::Vector3d row = A.row(1);

추출된 두 번째 행은 다음과 같다.

\mathbf{row} = \begin{bmatrix} 4 & 5 & 6 \end{bmatrix}

배열과 행렬의 연산 차이

Eigen 라이브러리에서 행렬 클래스는 수학적 행렬 연산을 수행할 때 사용된다. 그러나 때로는 원소별 연산이 필요한 경우가 있다. 이때 Array 클래스를 사용하면 원소별 연산을 쉽게 수행할 수 있다.

원소별 연산

행렬 연산과 배열 연산의 주요 차이점은 배열 연산에서는 원소별로 연산이 수행된다는 점이다. 예를 들어, 두 2x2 행렬 \mathbf{A}\mathbf{B}의 원소별 곱셈은 다음과 같이 수행된다.

\mathbf{A} = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}, \quad \mathbf{B} = \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix}

행렬 곱셈이 아닌 원소별 곱셈은 다음과 같이 계산된다.

\mathbf{A} \circ \mathbf{B} = \begin{bmatrix} 1 \cdot 5 & 2 \cdot 6 \\ 3 \cdot 7 & 4 \cdot 8 \end{bmatrix} = \begin{bmatrix} 5 & 12 \\ 21 & 32 \end{bmatrix}

이를 코드로 표현하면 다음과 같다.

Eigen::Matrix2d A, B;
A << 1, 2, 3, 4;
B << 5, 6, 7, 8;
Eigen::Matrix2d result = A.array() * B.array();

여기서 .array()를 사용하여 행렬을 배열로 변환하고, 원소별 연산을 수행한다. 이 방식은 덧셈, 뺄셈, 곱셈, 나눗셈 등 다양한 원소별 연산에 적용할 수 있다.

효율적인 메모리 관리

Eigen 라이브러리는 메모리 관리 측면에서도 매우 효율적으로 설계되었다. 특히, 행렬 크기가 커질수록 메모리 할당과 관련된 최적화가 중요한데, Eigen은 이를 잘 지원한다. Eigen의 다양한 메모리 관리 기법을 통해 고성능 계산을 지원한다.

참조에 의한 전달 (Pass by Reference)

행렬을 함수에 전달할 때 복사가 발생하면 성능에 큰 영향을 미칠 수 있다. 따라서 큰 행렬을 함수에 전달할 때는 참조로 전달하는 것이 좋다. 예를 들어, 다음과 같이 참조를 통해 행렬을 전달할 수 있다.

void processMatrix(const Eigen::MatrixXd& mat) {
    // 행렬을 처리하는 코드
}

여기서 const Eigen::MatrixXd&는 복사가 아닌 참조를 사용하여 성능을 최적화하는 방식이다.

메모리 보존 (Lazy Evaluation)

Eigen은 지연 평가(lazy evaluation)를 통해 불필요한 계산을 줄이는 메커니즘을 가지고 있다. 이로 인해 다중 연산을 수행할 때 중간 결과를 저장하지 않고, 최종 결과만 계산하는 방식으로 메모리 사용을 줄일 수 있다.

예를 들어, 다음과 같은 코드에서:

Eigen::Vector3d v1, v2, v3;
Eigen::Vector3d result = v1 + v2 + v3;

Eigen은 내부적으로 v1 + v2의 중간 결과를 저장하지 않고, 한 번의 계산으로 최종 결과를 구하도록 최적화한다.

메모리 매핑 (Memory Mapping)

Eigen에서는 외부 데이터 배열을 행렬이나 벡터로 직접 매핑하여 사용하는 기능을 제공한다. 이는 메모리를 복사하지 않고 외부 데이터에 직접 접근할 수 있어 메모리 사용량과 계산 성능을 최적화하는 데 매우 유용하다.

외부 데이터 매핑

Eigen에서 제공하는 Map 클래스를 사용하면 외부에 정의된 배열을 직접 Eigen 객체로 매핑할 수 있다. 이를 통해 복사 없이 원본 데이터에 접근하여 연산을 수행할 수 있다.

예를 들어, C++에서 이미 존재하는 배열을 Eigen의 행렬로 매핑하는 방법은 다음과 같다.

double data[6] = {1, 2, 3, 4, 5, 6};
Eigen::Map<Eigen::Matrix<double, 2, 3>> mat(data);

이 코드는 배열 data2x3 행렬로 매핑한다. 여기서 중요한 점은, mat은 배열 data를 복사하지 않고 그 데이터를 참조한다는 것이다. 수학적으로, 이 행렬은 다음과 같이 나타난다.

\mathbf{M} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}

이처럼 Map을 통해 배열을 직접 참조하면 불필요한 메모리 할당을 피할 수 있으며, 이는 대규모 데이터를 처리할 때 매우 유용하다.

행렬을 부분적으로 매핑

또한 Map 클래스를 사용하면 행렬의 일부분만을 참조할 수도 있다. 예를 들어, 4x4 행렬에서 특정 2x2 블록만을 매핑하는 것은 다음과 같다.

Eigen::Matrix4d A;
A << 1, 2, 3, 4,
     5, 6, 7, 8,
     9, 10, 11, 12,
     13, 14, 15, 16;

Eigen::Map<Eigen::Matrix2d> block(A.data() + 5);

여기서 A.data() + 5는 4x4 행렬 \mathbf{A}의 특정 위치를 참조한다. 매핑된 2x2 블록은 다음과 같다.

\text{block} = \begin{bmatrix} 6 & 7 \\ 10 & 11 \end{bmatrix}

메모리 정렬과 벡터화

Eigen은 성능 최적화를 위해 메모리 정렬(memory alignment)과 벡터화(vectorization)를 적극적으로 활용한다. 이를 통해 현대 CPU의 SIMD(single instruction, multiple data) 명령어 세트를 사용하여 병렬 연산을 수행할 수 있다.

Eigen은 기본적으로 메모리를 16바이트 단위로 정렬하여, 벡터 연산을 최적화한다. 이는 대규모 연산을 수행할 때 CPU 캐시와 메모리 접근 속도를 최적화하는 데 기여한다.

메모리 정렬을 수동으로 조정하려면 Eigen의 aligned_allocator를 사용할 수 있다. 예를 들어, 다음과 같이 정렬된 벡터를 정의할 수 있다.

std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d>> vecs;

이 방식은 대규모 벡터나 행렬을 사용할 때 특히 성능 향상을 제공한다.

Eigen의 고급 기능

Eigen은 기본적인 선형대수 연산 외에도 다양한 고급 기능을 제공한다. 이러한 기능에는 행렬 분해, 특이값 분해(SVD), 고유값 분해(Eigenvalue Decomposition) 등이 포함된다. 이 섹션에서는 이러한 고급 기능에 대해 간략히 살펴본다.

LU 분해

LU 분해는 정방 행렬을 하삼각 행렬(Lower triangular matrix)과 상삼각 행렬(Upper triangular matrix)의 곱으로 분해하는 방법이다. LU 분해는 선형 방정식 시스템을 푸는 데 사용된다.

행렬 \mathbf{A}에 대한 LU 분해는 다음과 같이 나타낼 수 있다.

\mathbf{A} = \mathbf{L} \cdot \mathbf{U}

여기서 \mathbf{L}은 하삼각 행렬, \mathbf{U}는 상삼각 행렬이다.

Eigen에서 LU 분해는 FullPivLU 클래스를 사용하여 수행할 수 있다.

Eigen::Matrix3d A;
A << 1, 2, 3, 4, 5, 6, 7, 8, 9;
Eigen::FullPivLU<Eigen::Matrix3d> lu(A);

Eigen::Matrix3d L = lu.matrixL();
Eigen::Matrix3d U = lu.matrixU();

QR 분해

QR 분해는 행렬을 직교 행렬 \mathbf{Q}와 상삼각 행렬 \mathbf{R}의 곱으로 분해하는 방법이다. QR 분해는 행렬의 고유값을 구하거나 최소제곱 문제를 해결하는 데 유용하게 사용된다.

\mathbf{A} = \mathbf{Q} \cdot \mathbf{R}

Eigen에서 QR 분해는 HouseholderQR 클래스를 사용하여 수행할 수 있다.

Eigen::MatrixXd A(3, 3);
A << 12, -51, 4,
     6, 167, -68,
     -4, 24, -41;
Eigen::HouseholderQR<Eigen::MatrixXd> qr(A);

Eigen::MatrixXd Q = qr.householderQ();
Eigen::MatrixXd R = qr.matrixQR().triangularView<Eigen::Upper>();

특이값 분해 (SVD)

특이값 분해(SVD, Singular Value Decomposition)는 임의의 행렬을 세 개의 행렬로 분해하는 강력한 기법이다. 행렬 \mathbf{A}는 다음과 같이 분해된다.

\mathbf{A} = \mathbf{U} \cdot \mathbf{\Sigma} \cdot \mathbf{V}^T

여기서 \mathbf{U}\mathbf{V}는 직교 행렬, \mathbf{\Sigma}는 대각 행렬이다. SVD는 행렬의 순위(rank)를 구하거나 차원 축소(dimensionality reduction) 문제에 매우 유용하게 사용된다.

Eigen에서 SVD는 JacobiSVD 클래스를 사용하여 수행할 수 있다.

Eigen::MatrixXd A(3, 2);
A << 1, 0, 0, 1, 0, 0;
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);

Eigen::MatrixXd U = svd.matrixU();
Eigen::MatrixXd V = svd.matrixV();
Eigen::VectorXd S = svd.singularValues();

고유값 분해 (Eigenvalue Decomposition)

고유값 분해는 행렬을 고유값(eigenvalue)과 고유벡터(eigenvector)로 분해하는 방법이다. 정방 행렬 \mathbf{A}에 대해, 고유값 \lambda와 고유벡터 \mathbf{v}는 다음 관계를 만족한다.

\mathbf{A} \mathbf{v} = \lambda \mathbf{v}

Eigen에서 고유값 분해는 EigenSolver 클래스를 사용하여 수행할 수 있다.

Eigen::Matrix3d A;
A << 1, 2, 3, 4, 5, 6, 7, 8, 9;
Eigen::EigenSolver<Eigen::Matrix3d> es(A);

Eigen::Vector3d eigenvalues = es.eigenvalues().real();
Eigen::Matrix3d eigenvectors = es.eigenvectors().real();

고유값 분해는 행렬의 성질을 이해하거나 시스템의 안정성을 분석하는 데 유용하다.