├── .formatter.exs
├── LICENSE
├── README.md
├── config
└── config.exs
├── docs
├── gaussian_plane.jpg
├── latex_out
│ └── numerical_formula.pdf
├── matrix_operation_logo.png
└── numerical_formula.tex
├── lib
└── matrix_operation.ex
├── mix.exs
├── mix.lock
└── test
├── matrix_operation_test.exs
└── test_helper.exs
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
3 | ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 kenken-neko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MatrixOperation
2 |
3 | *MatrixOperation* is a linear algebra library in Elixir language. For example, this library can be used to solve eigenvalue equations and singular value decompositions. There are several other functions that can be used to solve some of these problems. You can refer to the online documentation at https://hexdocs.pm/matrix_operation/MatrixOperation.html#content and mathematical description at 'docs/latex_out/numerical_formula.pdf' in this package.
4 | Moreover, several patterns of functions are implemented as algorithms for solving each problem. The functions are methods that QR decomposition techniques to solve eigenvalue equations of arbitrary dimensions, or algebraic techniques that are limited in the number of dimensions but provide exact solutions. There is also function of the Jacobi method, which is a method for solving eigenvalue equations of real symmetric matrices.
5 |
6 | ## Notice
7 | A matrix of any dimension can be created.
8 | ```
9 | iex> MatrixOperation.even_matrix(1, {3, 2})
10 | [[1, 1], [1, 1], [1, 1]]
11 | ```
12 | The first argument is the number of row number, the second argument is the number of column number and the third argument is value of the elements.
13 | Matrix indices of a row and column is an integer starting from 1 (not from 0).
14 |
15 | For example,
16 | ```
17 | iex> rand_matrix = MatrixOperation.random_matrix(0, 5, {2, 3}, "real")
18 | [[1.1578724, 2.23742, 1.4504169], [1.2531625, 4.4657427, 1.4510925]]
19 |
20 | iex> MatrixOperation.get_one_element(rand_matrix, {1, 2})
21 | 2.23742
22 | ```
23 |
24 | ## Installation
25 | You can install this package by adding this code to dependencies in your mix.exs file:
26 | ```elixir
27 | def deps do
28 | [
29 | {:matrix_operation, "~> 0.5.0"}
30 | ]
31 | end
32 | ```
33 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # 3rd-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :matrix_operation, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:matrix_operation, :key)
18 | #
19 | # You can also configure a 3rd-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env()}.exs"
31 |
--------------------------------------------------------------------------------
/docs/gaussian_plane.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenken-neko/elixir-matrix-operation/49da0fa6e0f1f8cac0db7c276bbb3b25a3cf8392/docs/gaussian_plane.jpg
--------------------------------------------------------------------------------
/docs/latex_out/numerical_formula.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenken-neko/elixir-matrix-operation/49da0fa6e0f1f8cac0db7c276bbb3b25a3cf8392/docs/latex_out/numerical_formula.pdf
--------------------------------------------------------------------------------
/docs/matrix_operation_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenken-neko/elixir-matrix-operation/49da0fa6e0f1f8cac0db7c276bbb3b25a3cf8392/docs/matrix_operation_logo.png
--------------------------------------------------------------------------------
/docs/numerical_formula.tex:
--------------------------------------------------------------------------------
1 | \documentclass[dvipdfmx]{article}
2 | \usepackage[dvipdfmx]{graphicx}
3 | \usepackage[dvipdfmx]{hyperref}
4 | \begin{document}
5 |
6 | \section*{Transpose}
7 | The arbitrary matrix $A$ is transposed to matrix $A^T$.\\
8 | The example is shown as
9 | \[
10 | A = \left(
11 | \begin{array}{ccc}
12 | 1 & 2 & 3 \\
13 | 4 & 5 & 6 \\
14 | 7 & 8 & 9
15 | \end{array}
16 | \right)
17 | , \ \ \
18 | A^T = \left(
19 | \begin{array}{ccc}
20 | 1 & 4 & 7 \\
21 | 2 & 5 & 8 \\
22 | 3 & 6 & 9
23 | \end{array}
24 | \right) .
25 | \]
26 |
27 |
28 | \section*{Trace}
29 | The trace of a square matrix A (${\rm{tr}}A$) is defined to be the sum of elements on the main diagonal of A.
30 | The example is shown as
31 | \[
32 | A =
33 | \left(
34 | \begin{array}{ccc}
35 | a_{11} & a_{12} & a_{13} \\
36 | a_{21} & a_{22} & a_{23} \\
37 | a_{31} & a_{32} & a_{33}
38 | \end{array}
39 | \right) =
40 | \left(
41 | \begin{array}{ccc}
42 | 1 & 2 & 3 \\
43 | 4 & 5 & 6 \\
44 | 7 & 8 & 9
45 | \end{array}
46 | \right)
47 | \]
48 | then \
49 | ${\rm{tr}}A = {\sum_i}a_{ii} = 15$.
50 |
51 |
52 | \section*{Determinant}
53 | For a 3 × 3 matrix A, its determinant is \\
54 | \[
55 | |A| =
56 | \left|
57 | \begin{array}{ccc}
58 | a_{11} & a_{12} & a_{13} \\
59 | a_{21} & a_{22} & a_{23} \\
60 | a_{31} & a_{32} & a_{33}
61 | \end{array}
62 | \right| =
63 | a_{11}
64 | \left|
65 | \begin{array}{ccc}
66 | \times & \times & \times \\
67 | \times & a_{22} & a_{23} \\
68 | \times & a_{32} & a_{33}
69 | \end{array}
70 | \right|
71 | - a_{12}
72 | \left|
73 | \begin{array}{ccc}
74 | \times & \times & \times \\
75 | a_{21} & \times & a_{23} \\
76 | a_{31} & \times & a_{33}
77 | \end{array}
78 | \right|
79 | + a_{13}
80 | \left|
81 | \begin{array}{ccc}
82 | \times & \times & \times \\
83 | a_{21} & a_{22} & \times \\
84 | a_{31} & a_{32} & \times
85 | \end{array}
86 | \right|
87 | \]
88 | \[
89 | = a_{11}
90 | \left|
91 | \begin{array}{ccc}
92 | a_{22} & a_{23} \\
93 | a_{32} & a_{33}
94 | \end{array}
95 | \right|
96 | - a_{12}
97 | \left|
98 | \begin{array}{ccc}
99 | a_{21} & a_{23} \\
100 | a_{31} & a_{33}
101 | \end{array}
102 | \right|
103 | + a_{13}
104 | \left|
105 | \begin{array}{ccc}
106 | a_{21} & a_{22} \\
107 | a_{31} & a_{32}
108 | \end{array}
109 | \right|
110 | \]
111 | \[
112 | \ \ \ \ \ \ = a_{11} a_{22}
113 | \left|
114 | \begin{array}{ccc}
115 | \times & \times \\
116 | \times & a_{33}
117 | \end{array}
118 | \right|
119 | - a_{11} a_{23}
120 | \left|
121 | \begin{array}{ccc}
122 | \times & \times \\
123 | a_{32} & \times
124 | \end{array}
125 | \right|
126 | - a_{12} a_{21}
127 | \left|
128 | \begin{array}{ccc}
129 | \times & \times \\
130 | \times & a_{33}
131 | \end{array}
132 | \right|
133 | \]
134 | \[
135 | \ \ \ \ \ \ + a_{12} a_{23}
136 | \left|
137 | \begin{array}{ccc}
138 | \times & \times \\
139 | a_{31} & \times
140 | \end{array}
141 | \right|
142 | + a_{13} a_{21}
143 | \left|
144 | \begin{array}{ccc}
145 | \times & \times \\
146 | \times & a_{32}
147 | \end{array}
148 | \right|
149 | - a_{13} a_{22}
150 | \left|
151 | \begin{array}{ccc}
152 | \times & \times \\
153 | a_{31} & \times
154 | \end{array}
155 | \right|
156 | \]
157 | \ \ \ \ \ = $a_{11}a_{22}a_{33} - a_{11}a_{23}a_{32} - a_{12}a_{21}a_{33} + a_{12}a_{23}a_{31} + a_{13}a_{22}a_{31}$
158 |
159 |
160 | \section*{Cramer's rule}
161 | In a system of $n$ linear equations, represented in matrix multiplication form
162 | $A{\bf{x}} = {\bf{b}}$ \\
163 | where $A$ is the $n {\times} n$ matrix and {\bf{x}} and {\bf{b}} are the $n$-th column vectors.
164 | ${\bf{x}} = (x_1, {\cdots}, x_n)^T$ , ${\bf{b}} = (b_1, {\cdots}, b_n)^T$. \\
165 | Then, if $|A| {\neq} 0$, \\
166 | \[
167 | x_i = |A_i| / |A|, \ \ \
168 | A_i =
169 | \left(
170 | \begin{array}{ccccc}
171 | a_{11} & \cdots & b_{1i} & \cdots & a_{1n} \\
172 | \vdots & \ddots & \vdots & & \vdots \\
173 | a_{k1} & & b_{ki} & & a_{kn} \\
174 | \vdots & & \vdots & \ddots & \vdots \\
175 | a_{n1} & \cdots & b_{ni} & \cdots & a_{nn}
176 | \end{array}
177 | \right)
178 | \]
179 | This is Cramer's rule.
180 |
181 |
182 | \section*{LU decomposition}
183 | $A = LU$ where
184 | \[
185 | A =
186 | \left(
187 | \begin{array}{ccc}
188 | a_{11} & a_{12} & a_{13} \\
189 | a_{21} & a_{22} & a_{23} \\
190 | a_{31} & a_{32} & a_{33}
191 | \end{array}
192 | \right) ,
193 | L =
194 | \left(
195 | \begin{array}{ccc}
196 | 1 & 0 & 0 \\
197 | l_{21} & 1 & 0 \\
198 | l_{31} & l_{32} & 1
199 | \end{array}
200 | \right) ,
201 | U =
202 | \left(
203 | \begin{array}{ccc}
204 | u_{11} & u_{12} & u_{13} \\
205 | 0 & u_{22} & u_{23} \\
206 | 0 & 0 & u_{33}
207 | \end{array}
208 | \right)
209 | \]
210 |
211 |
212 | \section*{Direct method by LU decomposition}
213 | In linear equation $A{\bf{x}} = {\bf{b}}$,
214 | $LU{\bf{x}} = {\bf{b}}$
215 | by using LU decomposition $A = LU$.
216 |
217 | Here, we consider $L{\bf{y}} = {\bf{b}}$ and $U{\bf{x}} = {\bf{y}}$.
218 |
219 | In forward substitution,
220 | \begin{eqnarray}
221 | y_1 &&= b_1 \nonumber \\
222 | y_2 &&= b_2 - l_{21}y_1 \nonumber \\
223 | {\vdots} \nonumber \\
224 | y_n &&= b_n - {\sum_{j=1}^{n-1}} l_{nj}y_j \nonumber
225 | \end{eqnarray}
226 |
227 | In backforward substitution,
228 | \begin{eqnarray}
229 | x_n &&= y_n / u_{nn} \nonumber \\
230 | x_{n-1} &&= (y_{n-1} - u_{n-1, n}x_n) / u_{n-1, n-1} \nonumber \\
231 | {\vdots} \nonumber \\
232 | x_1 &&= (y_1 - {\sum_{j=2}^{n}} u_{1, j}x_j) / u_{11} \nonumber
233 | \end{eqnarray} .
234 |
235 |
236 | \section*{Constant multiple}
237 | \[
238 | c
239 | \left(
240 | \begin{array}{ccc}
241 | a_{11} & \cdots & a_{1n} \\
242 | \vdots & \ddots & \vdots \\
243 | a_{n1} & \cdots & a_{nn}
244 | \end{array}
245 | \right)
246 | =
247 | \left(
248 | \begin{array}{ccc}
249 | ca_{11} & \cdots & ca_{1n} \\
250 | \vdots & \ddots & \vdots \\
251 | ca_{n1} & \cdots & ca_{nn}
252 | \end{array}
253 | \right)
254 | \]
255 | where $c$ is the scalar constant.
256 |
257 |
258 | \section*{Inverse matrix}
259 | $AB = BA = I$ \\
260 | where $A$ and $B$ is the $n$ × $n$ matrices and $I$ is the $n$ × $n$ unit matrix.
261 | In the case, the matrix $B$ is uniquely determined by $A$ and is called the inverse matrix of $A$.
262 | The inverse matrix of $A$ is denoted by $A^{-1}$.
263 |
264 |
265 | \section*{Product}
266 | The elements of the matrix product $C = AB$ is that
267 | $c_{ij} = [AB]_{ij} = {\sum_k}a_{ik}b_{kj}$
268 | where $A$ is an $n \times m$ matrix and $B$ is an $m \times l$ matrix.
269 |
270 |
271 | \section*{Addition and Subtraction}
272 | \[
273 | A=
274 | \left(
275 | \begin{array}{ccc}
276 | a_{11} & a_{12} & a_{13} \\
277 | a_{21} & a_{22} & a_{23} \\
278 | a_{31} & a_{32} & a_{33}
279 | \end{array}
280 | \right) ,
281 | B =
282 | \left(
283 | \begin{array}{ccc}
284 | b_{11} & b_{12} & b_{13} \\
285 | b_{21} & b_{22} & b_{23} \\
286 | b_{31} & b_{32} & b_{33}
287 | \end{array}
288 | \right) ,
289 | \]
290 | then the addition/subtraction is that
291 | \[
292 | A {\pm} B=
293 | \left(
294 | \begin{array}{ccc}
295 | a_{11} {\pm} b_{11} & a_{12} {\pm} b_{12} & a_{13} {\pm} b_{13} \\
296 | a_{21} {\pm} b_{21} & a_{22} {\pm} b_{22} & a_{23} {\pm} b_{23} \\
297 | a_{31} {\pm} b_{31} & a_{32} {\pm} b_{32} & a_{33} {\pm} b_{33}
298 | \end{array}
299 | \right)
300 | \]
301 |
302 |
303 | \section*{Hadamard product}
304 | \[
305 | A=
306 | \left(
307 | \begin{array}{ccc}
308 | a_{11} & a_{12} & a_{13} \\
309 | a_{21} & a_{22} & a_{23} \\
310 | a_{31} & a_{32} & a_{33}
311 | \end{array}
312 | \right) ,
313 | B =
314 | \left(
315 | \begin{array}{ccc}
316 | b_{11} & b_{12} & b_{13} \\
317 | b_{21} & b_{22} & b_{23} \\
318 | b_{31} & b_{32} & b_{33}
319 | \end{array}
320 | \right) ,
321 | \]
322 | then the Hadamard product is that
323 | \[
324 | A {\circ} B=
325 | \left(
326 | \begin{array}{ccc}
327 | a_{11} b_{11} & a_{12} b_{12} & a_{13} b_{13} \\
328 | a_{21} b_{21} & a_{22} b_{22} & a_{23} b_{23} \\
329 | a_{31} b_{31} & a_{32} b_{32} & a_{33} b_{33}
330 | \end{array}
331 | \right)
332 | \]
333 |
334 |
335 | \section*{Hadamard division}
336 | \[
337 | A=
338 | \left(
339 | \begin{array}{ccc}
340 | a_{11} & a_{12} & a_{13} \\
341 | a_{21} & a_{22} & a_{23} \\
342 | a_{31} & a_{32} & a_{33}
343 | \end{array}
344 | \right) ,
345 | B =
346 | \left(
347 | \begin{array}{ccc}
348 | b_{11} & b_{12} & b_{13} \\
349 | b_{21} & b_{22} & b_{23} \\
350 | b_{31} & b_{32} & b_{33}
351 | \end{array}
352 | \right) ,
353 | \]
354 | then the Hadamard division is that
355 | \[
356 | A / B=
357 | \left(
358 | \begin{array}{ccc}
359 | a_{11} / b_{11} & a_{12} / b_{12} & a_{13} / b_{13} \\
360 | a_{21} / b_{21} & a_{22} / b_{22} & a_{23} / b_{23} \\
361 | a_{31} / b_{31} & a_{32} / b_{32} & a_{33} / b_{33}
362 | \end{array}
363 | \right)
364 | \]
365 |
366 |
367 | \section*{Hadamard power}
368 | \[
369 | A^{(n)}=
370 | \left(
371 | \begin{array}{ccc}
372 | a_{11}^n & a_{12}^n & a_{13}^n \\
373 | a_{21}^n & a_{22}^n & a_{23}^n \\
374 | a_{31}^n & a_{32}^n & a_{33}^n
375 | \end{array}
376 | \right)
377 | \]
378 | where $n$ is scalar.
379 |
380 |
381 | \section*{Tensor product}
382 | \[
383 | A=
384 | \left(
385 | \begin{array}{ccc}
386 | a_{11} & a_{12} & a_{13} \\
387 | a_{21} & a_{22} & a_{23} \\
388 | a_{31} & a_{32} & a_{33}
389 | \end{array}
390 | \right) ,
391 | B =
392 | \left(
393 | \begin{array}{ccc}
394 | b_{11} & b_{12} & b_{13} \\
395 | b_{21} & b_{22} & b_{23} \\
396 | b_{31} & b_{32} & b_{33}
397 | \end{array}
398 | \right) ,
399 | \]
400 | then the tensor product is that
401 | \[
402 | A {\otimes} B =
403 | \left(
404 | \begin{array}{ccc}
405 | a_{11} B & a_{12} B & a_{13} B \\
406 | a_{21} B & a_{22} B & a_{23} B \\
407 | a_{31} B & a_{32} B & a_{33} B
408 | \end{array}
409 | \right)
410 | \]
411 | \[
412 | =
413 | \left(
414 | \begin{array}{ccccccccc}
415 | a_{11} b_{11} & a_{11} b_{12} & a_{11} b_{13} & a_{12} b_{11} & a_{12} b_{12} & a_{12} b_{13} & a_{13} b_{11} & a_{13} b_{12} & a_{13} b_{13} \\
416 | a_{11} b_{21} & a_{11} b_{22} & a_{11} b_{23} & a_{12} b_{21} & a_{12} b_{22} & a_{12} b_{23} & a_{13} b_{21} & a_{13} b_{22} & a_{13} b_{23} \\
417 | a_{11} b_{31} & a_{11} b_{32} & a_{11} b_{33} & a_{12} b_{31} & a_{12} b_{32} & a_{12} b_{33} & a_{13} b_{31} & a_{13} b_{32} & a_{13} b_{33} \\
418 | a_{21} b_{11} & a_{21} b_{12} & a_{21} b_{13} & a_{22} b_{11} & a_{22} b_{12} & a_{22} b_{13} & a_{23} b_{11} & a_{23} b_{12} & a_{23} b_{13} \\
419 | a_{21} b_{21} & a_{21} b_{22} & a_{21} b_{23} & a_{22} b_{21} & a_{22} b_{22} & a_{22} b_{23} & a_{23} b_{21} & a_{23} b_{22} & a_{23} b_{23} \\
420 | a_{21} b_{31} & a_{21} b_{32} & a_{21} b_{33} & a_{22} b_{31} & a_{22} b_{32} & a_{22} b_{33} & a_{23} b_{31} & a_{23} b_{32} & a_{23} b_{33} \\
421 | a_{31} b_{11} & a_{31} b_{12} & a_{31} b_{13} & a_{32} b_{11} & a_{32} b_{12} & a_{32} b_{13} & a_{33} b_{11} & a_{33} b_{12} & a_{33} b_{13} \\
422 | a_{31} b_{21} & a_{31} b_{22} & a_{31} b_{23} & a_{32} b_{21} & a_{32} b_{22} & a_{32} b_{23} & a_{33} b_{21} & a_{33} b_{22} & a_{33} b_{23} \\
423 | a_{31} b_{31} & a_{31} b_{32} & a_{31} b_{33} & a_{32} b_{31} & a_{32} b_{32} & a_{32} b_{33} & a_{33} b_{31} & a_{33} b_{32} & a_{33} b_{33}
424 | \end{array}
425 | \right)
426 | \]
427 |
428 |
429 | \section*{Eigenvalue (Algebraic method)}
430 | An eigen equation is written as
431 | $A{\bf{u}} = {\lambda}{\bf{u}}$
432 | where ${\lambda}$ is scalar and ${\bf{u}}$ is vector, known as the eigenvalue and eigenvector.
433 |
434 | By rearranging above equation, we obtain: $A{\bf{u}} = {\lambda}{\bf{u}}$, $(A - {\lambda}I){\bf{u}} = {\bf{0}}$.
435 | If this equation has a nontrivial solution (${\bf{u}} {\neq} 0$),
436 | the determinant $|A - {\lambda}I| = 0$.
437 |
438 | \begin{flushleft}
439 | [$2 {\times} 2$ matrix case]
440 | \end{flushleft}
441 | When the matrix $A$ is written as
442 | \[
443 | A=
444 | \left(
445 | \begin{array}{cc}
446 | a_{11} & a_{12} \\
447 | a_{21} & a_{22} \\
448 | \end{array}
449 | \right) ,
450 | \]
451 | the quadratic equation ${\lambda}^2 -(a_{11} - a_{22}){\lambda} + a_{11}a_{22} - a_{12}a_{21}$ is obtained.
452 | By using quadratic formula,
453 | \begin{eqnarray}
454 | {\lambda} = \frac{ a_{11} -a_{22} {\pm} \sqrt{(a_{11}-a_{22})^2 - 4(a_{11}a_{22} - a_{12}a_{21})} }{2}. \nonumber
455 | \end{eqnarray} \\
456 |
457 | \begin{flushleft}
458 | [$3 {\times} 3$ matrix case]
459 | \end{flushleft}
460 | When the matrix $A$ is written as
461 | \[
462 | A=
463 | \left(
464 | \begin{array}{ccc}
465 | a_{11} & a_{12} & a_{13} \\
466 | a_{21} & a_{22} & a_{23} \\
467 | a_{31} & a_{32} & a_{33} \\
468 | \end{array}
469 | \right) ,
470 | \]
471 | we obtain the cubic equation $a{\lambda}^3 + b{\lambda}^2 + c{\lambda} + d = 0$ where \\
472 | $a = -1$, \\
473 | $b = a_{11} + a_{22} + a_{33}$, \\
474 | $c = a_{21}a_{12} + a_{13}a_{31} + a_{32}a_{23} - a_{11}a_{22} - a_{11}a_{33} - a_{22}a_{33}$, \\
475 | $d = a_{11}a_{22}a_{33} + a_{12}a_{23}a_{31} + a_{13}a_{32}a_{21} - a_{11}a_{32}a_{23} - a_{22}a_{31}a_{13} - a_{33}a_{21}a_{12}$ . \\
476 |
477 | Therefore,
478 | we can solve the eigen equation in the case of the $3{\times}3$ matrix $A$ by substituting above $a$, $b$, $c$ and $d$ for the cubic formula.
479 | The cubic formula is that
480 | \begin{eqnarray}
481 | {\lambda}_1 &&= -\frac{b}{3a} \nonumber \\
482 | &&- \frac{1}{3a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d + \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \nonumber \\
483 | &&- \frac{1}{3a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d - \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \ \ \ , \nonumber \\
484 | \nonumber \\
485 | {\lambda}_2 &&= -\frac{b}{3a} \nonumber \\
486 | &&- \frac{1 + i \sqrt{3}}{6a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d + \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \nonumber \\
487 | &&- \frac{1 - i \sqrt{3}}{6a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d - \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \ \ \ , \nonumber \\
488 | \nonumber \\
489 | {\lambda}_3 &&= -\frac{b}{3a} \nonumber \\
490 | &&- \frac{1 - i \sqrt{3}}{6a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d + \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \nonumber \\
491 | &&- \frac{1 + i \sqrt{3}}{6a} \sqrt[3]{ \frac{1}{2} (2b^3 -9abc + 27a^2d - \sqrt{(ab^3) - 9abc + 27a^2d)^2 - 4(b^2 - 3ac)^3} ) } \ \ \ . \nonumber
492 | \end{eqnarray}
493 |
494 | In this Elixir library, the complex numbers in the above equations are calculated as Gaussian plane.
495 | \begin{center}
496 | \includegraphics[width=10cm]{gaussian_plane.jpg}
497 | \end{center}
498 |
499 | The real part and imaginary part are calculated by using arctangent's integral formula, written as
500 | \begin{eqnarray}
501 | \arctan{x} = {\int_0^x} \frac{1}{z^2 + 1} dz. \nonumber
502 | \end{eqnarray}
503 | This formula is treated as the numerical integration.
504 |
505 |
506 | \section*{Eigenvalue and eigenvector (Power iteration method to solve maximum eigenvalue and eigenvector of $n$-th eigen equation)}
507 | An arbitrary (initial) vector ${\bf{b}}^0$ is written by the linear combination of eigenvectors ${\sum_i} c_i{\bf{u}}_i$
508 | because eigenvectors are linearly independent.
509 | \begin{eqnarray}
510 | {\bf{b}}^k {\equiv} A^k {\bf{b}}^0 = A^k {\sum_i} c_i{\bf{u}}_i = {\sum_{i=1}} c_i {\lambda}^k_i {\bf{u}}_i \nonumber \\
511 | = {\lambda}_1^k (c_1{\bf{u}}_1 + {\sum_{i=2}} c_i \frac{{\lambda}^k_i}{{\lambda}^k_1} {\bf{u}}_i) \nonumber
512 | \end{eqnarray}
513 | where ${\lambda}_1$ is maximum value of the eigenvalue so that $|\frac{{\lambda}^k_i}{{\lambda}^k_1}| < 1$.
514 |
515 | If $k$ is a large enough number,
516 | we can write the eigenvector of the maximum eigenvalue, shown as
517 | \begin{eqnarray}
518 | {\bf{b}}^k {\simeq} {\lambda}^k_1c_1{\bf{u}}_1. \nonumber
519 | \end{eqnarray}
520 | Moreover, we can write the maximum eigenvalue
521 | \begin{eqnarray}
522 | {\lambda}_1 = \frac{({\bf{b}}^k)^TA{\bf{b}}^k}{({\bf{b}}^k)^T{\bf{b}}^k}. \nonumber
523 | \end{eqnarray}
524 |
525 |
526 | \section*{Eigenvalue and eigenvector (Jacobi method)}
527 | The Jacobi method is an iterative method for the numerical calculation of the eigenvalues and eigenvectors of a real $n$-th symmetric matrix.
528 | (cf. \url{https://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm} )
529 |
530 |
531 | \section*{Eigenvalue (iterative method using QR decomposition)}
532 | The iterative method using QR decomposition is used to calculate eigenvalues of a $n$-th real square matrix.
533 | The QR decomposition is the decomposition of matrix $A$ into orthogonal matrix $Q$ and upper-triangular matrix $R$ as shown below.
534 | $$
535 | A = QR
536 | $$
537 | In this section, QR decomposition is performed by using Householder transformation.
538 | Householder transformation corresponds to matrix $H$ below.
539 | \begin{eqnarray}
540 | {\bf{x}} = H {\bf{y}} \nonumber \\
541 | {\bf{y}} = H {\bf{x}} \nonumber
542 | \end{eqnarray}
543 | \begin{eqnarray}
544 | H = I - \frac{2({\bf{x}} - {\bf{y}})({\bf{x}} - {\bf{y}})^T}{|{\bf{x}} - {\bf{y}}|^2} \nonumber
545 | \end{eqnarray}
546 | $H$ is orthogonal matrix where $HH^T=I$.
547 | Here,
548 | \[
549 | A = A^{(1)} =
550 | \left(
551 | \begin{array}{ccc}
552 | a_{11} & \cdots & a_{1n} \\
553 | \vdots & \ddots & \vdots \\
554 | a_{n1} & \cdots & a_{nn}
555 | \end{array}
556 | \right),
557 | \]
558 | $$
559 | A^{(2)}=H^{(1)}A^{(1)}, \\
560 | A^{(2)} =
561 | \left(
562 | \begin{array}{cccc}
563 | a_{11} & a_{12} & \cdots & a_{1n} \\
564 | 0 & \vdots & \vdots & \vdots \\
565 | \vdots & \vdots & \vdots & \vdots \\
566 | 0 & a_{n2} & \cdots & a_{nn}
567 | \end{array}
568 | \right).
569 | $$
570 | $H^{(1)}$ to Householder transform the first column into a finite vector with only the top element.
571 |
572 | $$
573 | A^{(3)}=H^{(2)}A^{(2)}, \\
574 | A^{(3)} =
575 | \left(
576 | \begin{array}{ccccc}
577 | a_{11} & a_{12} & a_{12} & \cdots & a_{1n} \\
578 | 0 & a_{22} & \vdots & \vdots & \vdots \\
579 | 0 & 0 & \vdots & \vdots & \vdots \\
580 | \vdots & \vdots & \vdots & \vdots & \vdots \\
581 | 0 & 0 & a_{n2} & \cdots & a_{nn}
582 | \end{array}
583 | \right).
584 | $$
585 | $H^{(2)}$ to Householder transform the second column into a finite vector with the top 2 elements.
586 |
587 | $$
588 | H^{(n-1)} {\cdots} H^{(1)} A^{(1)} = R,
589 | $$
590 | $$
591 | A = A^{(1)} = H^{(n-1)T} {\cdots} H^{(1)T} R = QR.
592 | $$
593 | The product of orthogonal matricies $Q_1$ and $Q_2$ is also orthogonal matrix $Q_3(=Q_1Q_2)$.
594 |
595 | Thus,
596 | $$
597 | A = A_1 = Q_1R_1, A_2 = R_1Q_1,
598 | $$
599 | $$
600 | A_{k+1} = (Q_k^{-1}{\cdots}Q_1^{-1}) A_1 (Q_1{\cdots}Q_k) = {\tilde{Q}}_k^{-1}A_1{\tilde{Q}}_k.
601 | $$
602 |
603 | On the other hand,
604 | $$
605 | A_1^k = Q_1R_1 {\cdots} Q_1R_1 = Q_1(R_1Q_1){\cdots}(R_1Q_1)R_1 = Q_1{\cdots}Q_k R_k{\cdots}R_1 = {\tilde{Q}}_k {\tilde{R}}_k.
606 | $$
607 |
608 | The eigen equations are written as follows.
609 | $$
610 | AX=X{\Lambda}.
611 | $$
612 | Then,
613 | $$
614 | A^k = A_1^k = X{\Lambda}X^{-1} {\cdots} X{\Lambda}X^{-1} = X{\Lambda}^kX^{-1}.
615 | $$
616 |
617 | $QR$ decomposition of $X$ and $LU$ decomposition of $X^{-1}$.
618 | $$
619 | X = Q_XR_X, X^{^-1} = LU.
620 | $$
621 | $$
622 | A_1^k = X{\Lambda}X^{-1} = Q_XR_X {\Lambda}^k LU = Q_XR_X ({\Lambda}^k L {\Lambda}^{-k})({\Lambda}^kU).
623 | $$
624 | Since it is
625 | $$
626 | {\Lambda}^k L {\Lambda}^{-k} =
627 | \left(
628 | \begin{array}{cccc}
629 | 1 & 0 & 0 & 0 \\
630 | l_{21}({\lambda}_2/{\lambda}_1)^k & 1 & 0 & 0 \\
631 | l_{31}({\lambda}_3/{\lambda}_1)^k & l_{32}({\lambda}_3/{\lambda}_2)^k & 1 & 0 \\
632 | \vdots & \vdots & \ddots & \vdots \\
633 | l_{n1}({\lambda}_3/{\lambda}_1)^k & l_{n2}({\lambda}_3/{\lambda}_1)^k & \cdots & 1
634 | \end{array}
635 | \right),
636 | $$
637 | $$
638 | {\lim_{k{\rightarrow}{\infty}}} A_1^k
639 | = {\lim_{k{\rightarrow}{\infty}}} Q_XR_X ({\Lambda}^k L {\Lambda}^{-k})({\Lambda}^kU)
640 | = Q_X(R_X {\lim_{k{\rightarrow}{\infty}}} {\Lambda}^k U)
641 | $$
642 | On the other hand,
643 | $$
644 | {\lim_{k{\rightarrow}{\infty}}} A_1^k = {\lim_{k{\rightarrow}{\infty}}} {\tilde{Q}}_k {\tilde{R}}_k.
645 | $$
646 | $$
647 | Q_X = {\lim_{k{\rightarrow}{\infty}}} {\tilde{Q}}_k
648 | $$
649 | $$
650 | {\lim_{k{\rightarrow}{\infty}}} Q_k
651 | = {\lim_{k{\rightarrow}{\infty}}} (Q_{k-1}^{-1}{\cdots}Q_{1}^{-1}) (Q_{1}{\cdots}Q_{k-1}) Q_k
652 | = {\lim_{k{\rightarrow}{\infty}}} {\tilde{Q}}_{k-1}^{-1} {\lim_{k{\rightarrow}{\infty}}} {\tilde{Q}}_{k}
653 | = Q_X^{-1}Q_X
654 | = I,
655 | $$
656 | $$
657 | {\lim_{k{\rightarrow}{\infty}}} R_k
658 | = {\lim_{k{\rightarrow}{\infty}}} A_{k+1}Q_k^{-1}
659 | = {\lim_{k{\rightarrow}{\infty}}} ({\tilde{Q}}_k^{-1}A_1{\tilde{Q}}_k)Q_k^{-1}
660 | = {\lim_{k{\rightarrow}{\infty}}} {\tilde{Q}}_k^{-1}A_1(Q_1{\cdots}Q_k)Q_k^{-1}
661 | = Q_X^{-1}A_1Q_X.
662 | $$
663 | Since $X=Q_XR_X$,
664 | $$
665 | {\lim_{k{\rightarrow}{\infty}}} R_k = Q_X^{-1}A_1Q_X = (R_XX^{-1})A_1(XR_X^{-1}) = R_X{\Lambda}R_X^{-1},
666 | $$
667 | Therefore,
668 | $$
669 | {\lim_{k{\rightarrow}{\infty}}} A_k =
670 | {\lim_{k{\rightarrow}{\infty}}} Q_kR_k =
671 | R_X{\Lambda}R_X^{-1} =
672 | \left(
673 | \begin{array}{cccc}
674 | {\lambda_1} & * & * & * \\
675 | 0 & {\lambda_2} & * & * \\
676 | \vdots & {\ddots} & {\ddots} & * \\
677 | 0 & {\cdots} & 0 & {\lambda_n}
678 | \end{array}
679 | \right).
680 | $$
681 | Also, by finding the eigenvalues above, we can use inverse power iteration and eigenvalue shift to find the eigenvectors.
682 |
683 |
684 | \section*{Rank}
685 | Since the rank of matrix is equal to the number of nontrivial nonzero eigenvalues,
686 | it is calculated from the eigenvalues obtained by the Jacobi method or iterative method using QR decomposition.
687 | In this library, we use iterative method using QR decomposition, which has a faster processing speed.
688 |
689 |
690 | \section*{Singular Value Decomposition}
691 | Singular value decomposition (SVD) states:
692 | $$
693 | A = U {\Sigma} V^T
694 | $$
695 | where $A$ and ${\Sigma}$ is ${n{\times}m}$ matrix, $U$ is ${n{\times}n}$ orthogonal matrix, $U$ is ${m{\times}m}$ orthogonal matrix.
696 | In the case $m > n$,
697 | \[
698 | {\Sigma} =
699 | \left(
700 | \begin{array}{ccc|c}
701 | {\sigma}_{11} & \ & O & \ \\
702 | \ & {\ddots} & \ & O \\
703 | O & \ & {\sigma}_{nn} & \
704 | \end{array}
705 | \right)
706 | \]
707 | where ${\sigma}$ is singular value.
708 |
709 | It can be replaced by an eigenvalue problem from the following relation.
710 | $$
711 | AA^T = U {\Sigma} V^T (U {\Sigma} V^T)^T = U{\Sigma}^2U^T ,
712 | $$
713 | \[
714 | {\Sigma}^2 =
715 | \left(
716 | \begin{array}{ccc}
717 | {\sigma}_{11}^2 & \ & O \\
718 | \ & {\ddots} & \ \\
719 | O & \ & {\sigma}_{nn}^2
720 | \end{array}
721 | \right) =
722 | \left(
723 | \begin{array}{ccc}
724 | {\lambda}_{11} & \ & O \\
725 | \ & {\ddots} & \ \\
726 | O & \ & {\lambda}_{nn}
727 | \end{array}
728 | \right)
729 | \]
730 | where $\lambda$ is eigenvalue of $AA^T$.
731 |
732 |
733 | \section*{Diagonalization}
734 | An $n{\times}n$ matrix $A$ is diagonalizable when $A$ has $n$ eigenvectors that are linear independent of each other.
735 | We consider the matrix $P$ that is written as $P=[{\bf{x}}_1, {\bf{x}}_2, {\cdots}, {\bf{x}}_n]$ where ${\bf{x}}_i, i=1,{\cdots},n$ linear independent eigenvector of $A$.
736 |
737 | \begin{eqnarray}
738 | AP = A[{\bf{x}}_1, {\bf{x}}_2, {\cdots}, {\bf{x}}_n]
739 | = [{\lambda}_1{\bf{x}}_1, {\lambda}_2{\bf{x}}_2, {\cdots}, {\lambda}_n{\bf{x}}_n] \nonumber
740 | \end{eqnarray}
741 | where ${\lambda}_i, i=1,{\cdots},n$ eigenvalue of $A$.
742 | Since ${\bf{x}}_1, {\bf{x}}_2, {\cdots}, {\bf{x}}_n$ are linear independent,
743 | \[
744 | P^{-1}AP =
745 | \left(
746 | \begin{array}{ccc}
747 | {\lambda}_{1} & \ & O \\
748 | \ & \ddots & \ \\
749 | O & \ & {\lambda}_{n}
750 | \end{array}
751 | \right)
752 | \] .
753 | This matrix is the diagonal matrix of $A$.
754 |
755 |
756 | \section*{Jordan normal form}
757 | Since $(A - {\lambda}E){\bf{u}} = {\bf{x}}$ and $(A - {\lambda}E){\bf{x}} = {\bf{0}}$,
758 | \begin{eqnarray}
759 | \left\{ \begin{array}{ll}
760 | A{\bf{u}}= {\bf{x}} + {\lambda}{\bf{u}} \\
761 | A{\bf{x}}={\lambda}{\bf{x}} \\
762 | \end{array} \right.
763 | \end{eqnarray} .
764 |
765 | Therefore,
766 | \[
767 | A
768 | \left(
769 | \begin{array}{cc}
770 | {\bf{x}} & {\bf{u}}
771 | \end{array}
772 | \right)
773 | =
774 | \left(
775 | \begin{array}{cc}
776 | {\bf{x}} & {\bf{u}}
777 | \end{array}
778 | \right)
779 | \left(
780 | \begin{array}{cc}
781 | {\lambda} & 1 \\
782 | 0 & {\lambda}
783 | \end{array}
784 | \right)
785 | \] .
786 |
787 | $$P^{-1}AP = J$$ where
788 | \[
789 | P =
790 | \left(
791 | \begin{array}{cc}
792 | {\bf{x}} & {\bf{u}}
793 | \end{array}
794 | \right) ,
795 | J =
796 | \left(
797 | \begin{array}{cc}
798 | {\lambda} & 1 \\
799 | 0 & {\lambda}
800 | \end{array}
801 | \right).
802 | \] .
803 |
804 |
805 | \section*{Matrix norms}
806 | $A$ is $n{\times}m$ matrix.
807 |
808 | Frobenius norm:
809 | $$
810 | ||A||_F = \sqrt{{\sum_i^n}{\sum_j^m} |a_{ij}|^2} \ \ .
811 | $$
812 |
813 | $L_1$ norm:
814 | $$
815 | ||A||_1 = \max_{j} {\sum_i^n} |a_{ij}| \ \ .
816 | $$
817 |
818 | Max norm:
819 | $$
820 | ||A||_{\infty} = \max_{i} {\sum_j^n} |a_{ij}| \ \ .
821 | $$
822 |
823 | $L_2$ norm:
824 | $$
825 | ||A||_2 = \max_{ij} {\sigma}_{ij}
826 | $$
827 | where $\sigma$ is singular value of $A$.
828 |
829 |
830 | \section*{Variance covariance matrix}
831 | A variance covariance matrix can be defined as
832 | \[
833 | S =
834 | \left(
835 | \begin{array}{cc}
836 | s_{xx} & s_{xy} \\
837 | s_{yx} & s_{yy}
838 | \end{array}
839 | \right)
840 | \]
841 | where $s_{xx}$ is variance value and $s_{xy}$ is covariance value.
842 | $s_{xy} = \frac{1}{n}({\bf{x}} - \bar{\bf{x}})({\bf{y}} - \bar{\bf{y}})$, $\bar{\bf{x}} = {\sum_{i=1}^n} x_i /n$.
843 |
844 | By the way, we can consider the Principal Component Analysis (PCA) by this variance covariance matrix with above power Iteration library.
845 |
846 | \end{document}
847 |
--------------------------------------------------------------------------------
/lib/matrix_operation.ex:
--------------------------------------------------------------------------------
1 | defmodule MatrixOperation do
2 | @moduledoc """
3 | *MatrixOperation* is a linear algebra library in Elixir language.
4 | Matrix indices of a row and column is an integer starting from 1 (not from 0).
5 | """
6 |
7 | @doc """
8 | Numbers of rows and columns of a matrix are got.
9 | #### Argument
10 | - matrix: Target matrix for finding the numbers of rows and columns.
11 | #### Output
12 | {num_rows, num_cols}: Numbers of rows and columns of a matrix
13 | #### Example
14 | iex> MatrixOperation.size([[3, 2, 3], [2, 1, 2]])
15 | {2, 3}
16 | """
17 | def size(matrix) when is_list(hd(matrix)) do
18 | col_num = Enum.map(matrix, &size_sub(&1, 0))
19 | max_num = Enum.max(col_num)
20 | if(max_num == Enum.min(col_num), do: {length(matrix), max_num}, else: nil)
21 | end
22 |
23 | def size(_matrix) do
24 | nil
25 | end
26 |
27 | defp size_sub(row, i) when i != length(row) do
28 | if(is_number(Enum.at(row, i)), do: size_sub(row, i + 1), else: nil)
29 | end
30 |
31 | defp size_sub(row, i) when i == length(row) do
32 | i
33 | end
34 |
35 | @doc """
36 | A n-th unit matrix is got.
37 | #### Argument
38 | - n: Number of rows / columns in the unit matrix to output.
39 | #### Output
40 | A n-th unit matrix
41 | #### Example
42 | iex> MatrixOperation.unit_matrix(3)
43 | [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
44 | """
45 | def unit_matrix(n) when n > 0 and is_integer(n) do
46 | idx_list = Enum.to_list(1..n)
47 | Enum.map(idx_list, fn x -> Enum.map(idx_list, &unit_matrix_sub(x, &1)) end)
48 | end
49 |
50 | defp unit_matrix_sub(i, j) when i == j do
51 | 1
52 | end
53 |
54 | defp unit_matrix_sub(_i, _j) do
55 | 0
56 | end
57 |
58 | @doc """
59 | A m×n matrix having even-elements is got.
60 | #### Argument
61 | - elem: Value of the common element of the matrix to output.
62 | - {row_num, col_num}: Size of the matrix to output.
63 | #### Output
64 | A row_num×col_num matrix having even elements
65 | #### Example
66 | iex> MatrixOperation.even_matrix(0, {2, 3})
67 | [[0, 0, 0], [0, 0, 0]]
68 | iex> MatrixOperation.even_matrix(1, {3, 2})
69 | [[1, 1], [1, 1], [1, 1]]
70 | """
71 | def even_matrix(elem, {row_num, col_num})
72 | when row_num > 0 and col_num > 0 and is_number(elem) do
73 | List.duplicate(elem, col_num)
74 | |> List.duplicate(row_num)
75 | end
76 |
77 | def even_matrix(_elem, _size) do
78 | nil
79 | end
80 |
81 | @doc """
82 | A m×n matrix having random elements is got.
83 | #### Argument
84 | - min_val: Minimum value of random number.
85 | - max_val: Maximum value of random number.
86 | - {row_num, col_num}: Size of the matrix to output.
87 | - type: Data type of elements. "int" or "real".
88 | #### Output
89 | A row_num×col_num matrix having random elements
90 | """
91 | def random_matrix(min_val, max_val, {row_num, col_num}, type \\ "int")
92 | when row_num > 0 and col_num > 0 and max_val > min_val do
93 | Enum.to_list(1..row_num)
94 | |> Enum.map(
95 | fn _ ->
96 | Enum.map(
97 | Enum.to_list(1..col_num), & &1 * 0 + random_element(min_val, max_val, type)
98 | )
99 | end
100 | )
101 | end
102 |
103 | def random_matrix(_min_val, _max_val, _size, _type) do
104 | nil
105 | end
106 |
107 | defp random_element(min_val, max_val, "int") do
108 | Enum.random(min_val..max_val)
109 | end
110 |
111 | defp random_element(min_val, max_val, "real") do
112 | const = 10000000
113 | min_val_real = min_val * const
114 | max_val_real = max_val * const
115 | Enum.random(min_val_real..max_val_real) / const
116 | end
117 |
118 | @doc """
119 | An element of a matrix is got.
120 | #### Argument
121 | - matrix: Target matrix from which to extract the element.
122 | - {row_idx, col_idx}: Index of row and column of the element to be extracted.
123 | #### Output
124 | An element of a matrix
125 | #### Example
126 | iex> MatrixOperation.get_one_element([[1, 2, 3], [4, 5, 6], [7, 8, 9] ], {1, 1})
127 | 1
128 | """
129 | def get_one_element(matrix, {row_idx, col_idx}) do
130 | matrix
131 | |> Enum.at(row_idx - 1)
132 | |> Enum.at(col_idx - 1)
133 | end
134 |
135 | @doc """
136 | A row of a matrix is got.
137 | #### Argument
138 | - matrix: Target matrix from which to extract the row.
139 | - row_idx: Index of the row to be extracted.
140 | #### Output
141 | A row of a matrix
142 | #### Example
143 | iex> MatrixOperation.get_one_row([[1, 2, 3], [4, 5, 6], [7, 8, 9] ], 1)
144 | [1, 2, 3]
145 | """
146 | def get_one_row(matrix, row_idx) do
147 | matrix
148 | |> Enum.at(row_idx - 1)
149 | end
150 |
151 | @doc """
152 | A column of a matrix is got.
153 | #### Argument
154 | - matrix: Target matrix from which to extract the column.
155 | - col_idx: Index of the column to be extracted.
156 | #### Output
157 | A column of a matrix
158 | #### Example
159 | iex> MatrixOperation.get_one_column([[1, 2, 3], [4, 5, 6], [7, 8, 9] ], 1)
160 | [1, 4, 7]
161 | """
162 | def get_one_column(matrix, col_idx) do
163 | matrix
164 | |> transpose()
165 | |> Enum.at(col_idx - 1)
166 | end
167 |
168 | @doc """
169 | A row of a matrix is deleted.
170 | #### Argument
171 | - matrix: Target matrix from which to delete the row.
172 | - del_idx: Index of the row to be deleted.
173 | #### Output
174 | The matrix from which the specified row was deleted.
175 | #### Example
176 | iex> MatrixOperation.delete_one_row([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 3)
177 | [[1, 2, 3], [4, 5, 6]]
178 | """
179 | def delete_one_row(matrix, del_idx) do
180 | matrix
181 | |> Enum.with_index()
182 | |> Enum.reject(fn {_x, idx} -> idx == del_idx - 1 end)
183 | |> Enum.map(fn {x, _idx} -> x end)
184 | end
185 |
186 | @doc """
187 | A column of a matrix is deleted.
188 | #### Argument
189 | - matrix: Target matrix from which to delete the column.
190 | - del_idx: Index of the column to be deleted.
191 | #### Output
192 | The matrix from which the specified column was deleted.
193 | #### Example
194 | iex> MatrixOperation.delete_one_column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 2)
195 | [[1, 3], [4, 6], [7, 9]]
196 | """
197 | def delete_one_column(matrix, del_idx) do
198 | matrix
199 | |> transpose()
200 | |> Enum.with_index()
201 | |> Enum.reject(fn {_x, idx} -> idx == del_idx - 1 end)
202 | |> Enum.map(fn {x, _idx} -> x end)
203 | |> transpose()
204 | end
205 |
206 | @doc """
207 | A row of a matrix is exchanged.
208 | #### Argument
209 | - matrix: Target matrix from which to exchange the row.
210 | - exchange_idx: Index of the row to be exchanged.
211 | - exchange_list: List of the row to be exchanged.
212 | #### Output
213 | The matrix from which the specified row was exchanged.
214 | #### Example
215 | iex> MatrixOperation.exchange_one_row([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 3, [1, 1, 1])
216 | [[1, 2, 3], [4, 5, 6], [1, 1, 1]]
217 | """
218 | def exchange_one_row(matrix, exchange_idx, exchange_list) do
219 | matrix
220 | |> Enum.with_index()
221 | |> Enum.map(fn {x, idx} -> if(idx == exchange_idx - 1, do: exchange_list, else: x) end)
222 | end
223 |
224 | @doc """
225 | A column of a matrix is exchanged.
226 | #### Argument
227 | - matrix: Target matrix from which to exchange the column.
228 | - exchange_idx: Index of the column to be exchanged.
229 | - exchange_list: List of the column to be exchanged.
230 | #### Output
231 | The matrix from which the specified column was exchanged.
232 | #### Example
233 | iex> MatrixOperation.exchange_one_column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 2, [1, 1, 1])
234 | [[1, 1, 3], [4, 1, 6], [7, 1, 9]]
235 | """
236 | def exchange_one_column(matrix, exchange_idx, exchange_list) do
237 | matrix
238 | |> transpose
239 | |> Enum.with_index()
240 | |> Enum.map(fn {x, idx} -> if(idx == exchange_idx - 1, do: exchange_list, else: x) end)
241 | |> transpose()
242 | end
243 |
244 | @doc """
245 | Transpose of a matrix
246 | #### Argument
247 | - matrix: Target matrix to transpose.
248 | #### Output
249 | Transposed matrix
250 | #### Example
251 | iex> MatrixOperation.transpose([[1.0, 2.0], [3.0, 4.0]])
252 | [[1.0, 3.0], [2.0, 4.0]]
253 | """
254 | def transpose(matrix) do
255 | Enum.zip(matrix)
256 | |> Enum.map(&Tuple.to_list(&1))
257 | end
258 |
259 | @doc """
260 | Trace of a matrix
261 | #### Argument
262 | - matrix: Target matrix to output trace.
263 | #### Output
264 | Trance of the matrix
265 | #### Example
266 | iex> MatrixOperation.trace([[1.0, 2.0], [3.0, 4.0]])
267 | 5.0
268 | """
269 | def trace(matrix) do
270 | {row_num, col_num} = size(matrix)
271 | matrix_with_idx = add_index(matrix)
272 |
273 | Enum.map(matrix_with_idx, &trace_sub(&1, row_num, col_num))
274 | |> Enum.sum()
275 | end
276 |
277 | defp trace_sub(_, row_num, col_num) when row_num != col_num do
278 | nil
279 | end
280 |
281 | defp trace_sub([idx, row], _row_num, _col_num) do
282 | Enum.at(row, idx - 1)
283 | end
284 |
285 | @doc """
286 | A determinant of a n×n square matrix is got.
287 | #### Argument
288 | - matrix: Target matrix to output determinant.
289 | #### Output
290 | Determinant of the matrix
291 | #### Example
292 | iex> MatrixOperation.determinant([[1, 2, 1], [2, 1, 0], [1, 1, 2]])
293 | -5
294 | iex> MatrixOperation.determinant([[1, 2, 1, 1], [2, 1, 0, 1], [1, 1, 2, 1], [1, 2, 3, 4]])
295 | -13
296 | iex> MatrixOperation.determinant([ [3,1,1,2,1], [5,1,3,4,1], [2,0,1,0,1], [1,3,2,1,1], [1,1,1,1,1] ])
297 | -14
298 | """
299 | def determinant(matrix) do
300 | determinant_sub(1, matrix)
301 | end
302 |
303 | # 1×1 matrix
304 | defp determinant_sub(_, matrix) when length(matrix) == 1 do
305 | Enum.at(matrix, 0)
306 | |> Enum.at(0)
307 | end
308 |
309 | # 2×2 matrix
310 | defp determinant_sub(co, [[a11, a12], [a21, a22]]) do
311 | co * (a11 * a22 - a12 * a21)
312 | end
313 |
314 | # 3×3 or over matrix
315 | defp determinant_sub(co, matrix) do
316 | matrix_with_idx = add_index(matrix)
317 |
318 | Enum.map(
319 | matrix_with_idx,
320 | &determinant_sub(
321 | (-1 + 2 * rem(hd(&1), 2)) * co * hd(Enum.at(&1, 1)),
322 | minor_matrix(matrix_with_idx, &1)
323 | )
324 | )
325 | |> Enum.sum()
326 | end
327 |
328 | defp minor_matrix(matrix_with_idx, row) do
329 | (matrix_with_idx -- [row])
330 | |> Enum.map(&Enum.at(&1, 1))
331 | |> Enum.map(&Enum.drop(&1, 1))
332 | end
333 |
334 | # add index
335 | defp add_index(matrix) do
336 | Stream.iterate(1, &(&1 + 1))
337 | |> Enum.zip(matrix)
338 | |> Enum.map(&(&1 |> Tuple.to_list()))
339 | end
340 |
341 | @doc """
342 | Cramer's rule
343 | #### Argument
344 | - matrix: Target matrix to perform Cramer's rule.
345 | - vertical_vec: Vertical vector to perform Cramer's rule.
346 | - select_idx: Index of the target to perform Cramer's rule.
347 | #### Output
348 | Solution to the linear equation when Cramer's rule is applied.
349 | #### Example
350 | iex> MatrixOperation.cramer([[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[1], [0], [0]], 1)
351 | 1.0
352 | iex> MatrixOperation.cramer([[0, -2, 1], [-1, 1, -4], [3, 3, 1]], [[3], [-7], [4]], 1)
353 | 2.0
354 | """
355 | def cramer(matrix, vertical_vec, select_idx) do
356 | [t] = transpose(vertical_vec)
357 | det = determinant(matrix)
358 | cramer_sub(matrix, t, select_idx - 1, det)
359 | end
360 |
361 | defp cramer_sub(_, _, _, nil), do: nil
362 | defp cramer_sub(_, _, _, 0), do: nil
363 |
364 | defp cramer_sub(a, t, select_idx, det) do
365 | rep_det = transpose(a) |> replace_element_in_list(select_idx, t, 0, []) |> determinant
366 | rep_det / det
367 | end
368 |
369 | defp replace_element_in_list(list, i, replace_element, i, output) when i < length(list) do
370 | replace_element_in_list(list, i, replace_element, i + 1, output ++ [replace_element])
371 | end
372 |
373 | defp replace_element_in_list(list, select_idx, replace_element, i, output)
374 | when i < length(list) do
375 | replace_element_in_list(
376 | list,
377 | select_idx,
378 | replace_element,
379 | i + 1,
380 | output ++ [Enum.at(list, i)]
381 | )
382 | end
383 |
384 | defp replace_element_in_list(list, _select_idx, _replace_element, i, output)
385 | when i == length(list),
386 | do: output
387 |
388 | @doc """
389 | Leading principal minor is generetaed.
390 | #### Argument
391 | - matrix: Target matrix to find leading principal minor.
392 | - idx: Index of a row and column to find leading principal minor.
393 | #### Output
394 | Leading principal minor
395 | #### Example
396 | iex> MatrixOperation.leading_principal_minor([[1, 3, 2], [2, 5, 1], [3, 4, 5]], 2)
397 | [[1, 3], [2, 5]]
398 | """
399 | def leading_principal_minor(matrix, idx) do
400 | matrix
401 | |> Enum.slice(0, idx)
402 | |> Enum.map(& Enum.slice(&1, 0, idx))
403 | end
404 |
405 | @doc """
406 | LU decomposition
407 | #### Argument
408 | - matrix: Target matrix to solve LU decomposition.
409 | #### Output
410 | {L, U}. L(U) is L(U)-matrix of LU decomposition.
411 | #### Example
412 | iex> MatrixOperation.lu_decomposition([[1, 1, 0, 3], [2, 1, -1, 1], [3, -1, -1, 2], [-1, 2, 3, -1]])
413 | {
414 | [[1, 0, 0, 0], [2.0, 1, 0, 0], [3.0, 4.0, 1, 0], [-1.0, -3.0, 0.0, 1]],
415 | [[1, 1, 0, 3], [0, -1.0, -1.0, -5.0], [0, 0, 3.0, 13.0], [0, 0, 0, -13.0]]
416 | }
417 | """
418 | def lu_decomposition(matrix) do
419 | {row_num, col_num} = size(matrix)
420 | # check the setupufficient condition
421 | check_num = lu_decomposition_check(matrix, row_num, col_num)
422 | if(check_num == 0, do: nil, else: lu_decomposition_sub(matrix, 0, length(matrix), [], []))
423 | end
424 |
425 | defp lu_decomposition_check(_matrix, row_num, col_num) when row_num != col_num do
426 | nil
427 | end
428 |
429 | defp lu_decomposition_check(matrix, row_num, _col_num) do
430 | Enum.to_list(1..row_num)
431 | |> Enum.map(& leading_principal_minor(matrix, &1) |> determinant)
432 | |> Enum.reduce(fn x, acc -> x * acc end)
433 | end
434 |
435 | defp lu_decomposition_sub(matrix, k, matrix_len, _l_matrix, _u_matrix) when k == 0 do
436 | u_matrix = even_matrix(0, {matrix_len, matrix_len})
437 | |> exchange_one_row(1, hd(matrix))
438 | inverce_u11 = 1.0 / hd(hd(u_matrix))
439 | factor = matrix
440 | |> transpose()
441 | |> get_one_row(1)
442 | |> Enum.slice(1, matrix_len)
443 | l_row = [1] ++ hd(const_multiple(inverce_u11, [factor]))
444 | l_matrix = even_matrix(0, {matrix_len, matrix_len})
445 | |> exchange_one_row(1, l_row)
446 | lu_decomposition_sub(matrix, k + 1, matrix_len, l_matrix, u_matrix)
447 | end
448 |
449 | defp lu_decomposition_sub(matrix, k, matrix_len, l_matrix, u_matrix) when k != matrix_len do
450 | t_matrix = transpose(matrix)
451 | u_solve = u_cal(matrix, k, matrix_len, l_matrix, u_matrix)
452 | u_matrix_2 = exchange_one_row(u_matrix, k + 1, u_solve)
453 | l_solve = l_cal(t_matrix, k, matrix_len, l_matrix, u_matrix_2)
454 | l_matrix_2 = exchange_one_row(l_matrix, k + 1, l_solve)
455 | lu_decomposition_sub(matrix, k + 1, matrix_len, l_matrix_2, u_matrix_2)
456 | end
457 |
458 | defp lu_decomposition_sub(_matrix, _k, _matrix_len, l_matrix, u_matrix) do
459 | {transpose(l_matrix), u_matrix}
460 | end
461 |
462 | defp l_cal(t_matrix, k, matrix_len, l_matrix, u_matrix) do
463 | factor = Enum.at(t_matrix, k) |> Enum.slice(k + 1, matrix_len)
464 | u_extract = transpose(u_matrix) |> Enum.at(k)
465 | l_row = transpose(l_matrix)
466 | |> Enum.slice(k + 1, matrix_len)
467 | |> Enum.map(& inner_product(&1, u_extract))
468 | |> Enum.zip(factor)
469 | |> Enum.map(fn {x, y} -> y - x end)
470 |
471 | inverce_uii = 1.0 / Enum.at(Enum.at(u_matrix, k), k)
472 | [l_row_2] = const_multiple(inverce_uii, [l_row])
473 | [1] ++ l_row_2
474 | |> add_zero_element(0, k)
475 | end
476 |
477 | defp u_cal(matrix, k, matrix_len, l_matrix, u_matrix) do
478 | factor = Enum.at(matrix, k) |> Enum.slice(k, matrix_len)
479 | l_extract = transpose(l_matrix) |> Enum.at(k)
480 | transpose(u_matrix)
481 | |> Enum.slice(k, matrix_len)
482 | |> Enum.map(& inner_product(&1, l_extract))
483 | |> Enum.zip(factor)
484 | |> Enum.map(fn {x, y} -> y - x end)
485 | |> add_zero_element(0, k)
486 | end
487 |
488 | defp add_zero_element(list, init, fin) when init != fin do
489 | add_zero_element([0] ++ list, init + 1, fin)
490 | end
491 |
492 | defp add_zero_element(list, _init, _fin) do
493 | list
494 | end
495 |
496 | @doc """
497 | Linear equations are solved by LU decomposition.
498 | #### Argument
499 | - matrix: Target matrix to solve simultaneous linear equations.
500 | - vertical_vec: Vertical vector to solve linear equations.
501 | #### Output
502 | Solutions of the linear equations
503 | #### Example
504 | iex> MatrixOperation.solve_sle([[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[1], [0], [0]])
505 | [1.0, 0.0, 0.0]
506 | iex> MatrixOperation.solve_sle([[4, 1, 1], [1, 3, 1], [2, 1, 5]], [[9], [10], [19]])
507 | [1.0, 2.0, 3.0]
508 | """
509 | def solve_sle(matrix, vertical_vec) do
510 | # check the setupufficient condition
511 | if determinant(matrix) == 0 do
512 | nil
513 | else
514 | [t] = transpose(vertical_vec)
515 | solve_sle_sub(matrix, t)
516 | end
517 | end
518 |
519 | defp solve_sle_sub(matrix, t) do
520 | {l_matrix, u_matrix} = lu_decomposition(matrix)
521 | dim = length(l_matrix)
522 | y = forward_substitution(l_matrix, t, [], 0, dim)
523 | backward_substitution(u_matrix, y, [], dim, dim)
524 | end
525 |
526 | defp forward_substitution(l_matrix, t, _y, k, dim) when k == 0 do
527 | forward_substitution(l_matrix, t, [hd(t)], k + 1, dim)
528 | end
529 |
530 | defp forward_substitution(l_matrix, t, y, k, dim) when k != dim do
531 | l_extract = Enum.at(l_matrix, k) |> Enum.slice(0, k)
532 | y_extract = y |> Enum.slice(0, k)
533 | ly = inner_product(l_extract, y_extract)
534 | t_ly = Enum.at(t, k) - ly
535 | forward_substitution(l_matrix, t, y ++ [t_ly], k + 1, dim)
536 | end
537 |
538 | defp forward_substitution(_l_matrix, _t, y, k, dim) when k == dim do
539 | y
540 | end
541 |
542 | defp backward_substitution(u_matrix, y, _b, k, dim) when k == dim do
543 | dim_1 = dim - 1
544 | y_n = Enum.at(y, dim_1)
545 | u_nn = Enum.at(Enum.at(u_matrix, dim_1), dim_1)
546 | backward_substitution(u_matrix, y, [y_n / u_nn], k - 1, dim)
547 | end
548 |
549 | defp backward_substitution(_, _, b, k, _) when k == 0 do
550 | b
551 | end
552 |
553 | defp backward_substitution(u_matrix, y, b, k, dim) when k != dim do
554 | k_1 = k - 1
555 | u_extract = Enum.at(u_matrix, k_1) |> Enum.slice(k, dim)
556 | lb = inner_product(u_extract, b)
557 | inverce_uii = Enum.at(Enum.at(u_matrix, k_1), k_1)
558 | t_lb = (Enum.at(y, k_1) - lb) / inverce_uii
559 | backward_substitution(u_matrix, y, [t_lb] ++ b, k_1, dim)
560 | end
561 |
562 | @doc """
563 | A matrix is multiplied by a constant.
564 | #### Argument
565 | - const: Constant to multiply the matrix.
566 | - matrix: Target vector/matrix to be multiplied by a constant.
567 | #### Output
568 | Vector/Matrix multiplied by the constant.
569 | #### Example
570 | iex> MatrixOperation.const_multiple(-1, [1.0, 2.0, 3.0])
571 | [-1.0, -2.0, -3.0]
572 | iex> MatrixOperation.const_multiple(2, [[1, 2, 3], [2, 2, 2], [3, 8, 9]])
573 | [[2, 4, 6], [4, 4, 4], [6, 16, 18]]
574 | """
575 | def const_multiple(const, x) when is_number(x) do
576 | const * x
577 | end
578 |
579 | def const_multiple(const, x) when is_list(x) do
580 | Enum.map(x, &const_multiple(const, &1))
581 | end
582 |
583 | @doc """
584 | A matrix is added by a constant.
585 | #### Argument
586 | - const: Constant to add the matrix.
587 | - matrix: Target vector/matrix to be added by a constant.
588 | #### Output
589 | Vector/Matrix multiplied by the constant.
590 | #### Example
591 | iex> MatrixOperation.const_addition(1, [1.0, 2.0, 3.0])
592 | [2.0, 3.0, 4.0]
593 | iex> MatrixOperation.const_addition(1, [[1, 2, 3], [2, 2, 2], [3, 8, 9]])
594 | [[2, 3, 4], [3, 3, 3], [4, 9, 10]]
595 | """
596 | def const_addition(const, x) when is_number(x) do
597 | const + x
598 | end
599 |
600 | def const_addition(const, x) when is_list(x) do
601 | Enum.map(x, &const_addition(const, &1))
602 | end
603 |
604 | @doc """
605 | Inverse Matrix
606 | #### Argument
607 | - matrix: Matrix to be inverse Matrix.
608 | #### Output
609 | Inverse Matrix
610 | #### Example
611 | iex> MatrixOperation.inverse_matrix([[1, 1, -1], [-2, -1, 1], [-1, -2, 1]])
612 | [[-1.0, -1.0, 0.0], [-1.0, 0.0, -1.0], [-3.0, -1.0, -1.0]]
613 | """
614 | def inverse_matrix(matrix) when is_list(hd(matrix)) do
615 | det = determinant(matrix)
616 |
617 | create_index_matrix(matrix)
618 | |> Enum.map(&map_index_row(matrix, det, &1))
619 | |> transpose()
620 | end
621 |
622 | def inverse_matrix(_) do
623 | nil
624 | end
625 |
626 | defp create_index_matrix(matrix) do
627 | idx_list = Enum.to_list(1..length(matrix))
628 | Enum.map(idx_list, fn x -> Enum.map(idx_list, &[x, &1]) end)
629 | end
630 |
631 | defp map_index_row(_matrix, det, _row) when det == 0 do
632 | nil
633 | end
634 |
635 | defp map_index_row(matrix, det, row) do
636 | Enum.map(row, &minor_matrix(matrix, det, &1))
637 | end
638 |
639 | defp minor_matrix(matrix, det, [row_num, col_num]) do
640 | det_temp_matrix =
641 | delete_one_row(matrix, row_num)
642 | |> transpose
643 | |> delete_one_row(col_num)
644 | |> determinant
645 |
646 | if(rem(row_num + col_num, 2) == 0,
647 | do: det_temp_matrix / det,
648 | else: -1 * det_temp_matrix / det
649 | )
650 | end
651 |
652 | @doc """
653 | Matrix product
654 | #### Argument
655 | - a: Left side of the product of matrices.
656 | - b: Right side of the product of matrices.
657 | #### Output
658 | Product of two matrices
659 | #### Example
660 | iex> MatrixOperation.product([[3, 2, 3], [2, 1, 2]], [[2, 3], [2, 1], [3, 5]])
661 | [[19, 26], [12, 17]]
662 | """
663 | def product(a, b) do
664 | check_product(a, b)
665 | end
666 |
667 | defp check_product(a, b) do
668 | {_, col_num_a} = size(a)
669 | {row_num_b, _} = size(b)
670 | if(col_num_a == row_num_b, do: product_sub(a, b), else: nil)
671 | end
672 |
673 | defp product_sub(a, b) do
674 | Enum.map(a, fn row_a ->
675 | transpose(b)
676 | |> Enum.map(&inner_product(row_a, &1))
677 | end)
678 | end
679 |
680 | defp inner_product(row_a, col_b) do
681 | Enum.zip(row_a, col_b)
682 | |> Enum.map(&Tuple.to_list(&1))
683 | |> Enum.map(&Enum.reduce(&1, fn x, acc -> x * acc end))
684 | |> Enum.sum()
685 | end
686 |
687 | @doc """
688 | Matrix addition
689 | #### Argument
690 | - a: Left side of the addition of matrices.
691 | - b: Right side of the addition of matrices.
692 | #### Output
693 | Addition of two matrices
694 | #### Example
695 | iex> MatrixOperation.add([[3, 2, 3], [2, 1, 2]], [[2, 3, 1], [3, 2, 2]])
696 | [[5, 5, 4], [5, 3, 4]]
697 | """
698 | def add(a, b) do
699 | check_add(a, b)
700 | end
701 |
702 | defp check_add(a, b) do
703 | size_a = size(a)
704 | size_b = size(b)
705 | if(size_a == size_b, do: add_sub(a, b), else: nil)
706 | end
707 |
708 | defp add_sub(a, b) do
709 | Enum.zip(a, b)
710 | |> Enum.map(fn {x, y} ->
711 | Enum.zip(x, y)
712 | |> Enum.map(&Tuple.to_list(&1))
713 | |> Enum.map(&Enum.reduce(&1, fn x, acc -> x + acc end))
714 | end)
715 | end
716 |
717 | @doc """
718 | Matrix subtraction
719 | #### Argument
720 | - a: Left side of the subtraction of matrices.
721 | - b: Right side of the subtraction of matrices.
722 | #### Output
723 | Subtraction of two matrices
724 | #### Example
725 | iex> MatrixOperation.subtract([[3, 2, 3], [2, 1, 2]], [[2, 3, 1], [3, 2, 2]])
726 | [[1, -1, 2], [-1, -1, 0]]
727 | """
728 | def subtract(a, b) do
729 | check_subtract(a, b)
730 | end
731 |
732 | defp check_subtract(a, b) do
733 | size_a = size(a)
734 | size_b = size(b)
735 | if(size_a == size_b, do: subtract_sub(a, b), else: nil)
736 | end
737 |
738 | defp subtract_sub(a, b) do
739 | Enum.zip(a, b)
740 | |> Enum.map(fn {x, y} ->
741 | Enum.zip(x, y)
742 | |> Enum.map(&Tuple.to_list(&1))
743 | |> Enum.map(&Enum.reduce(&1, fn x, acc -> acc - x end))
744 | end)
745 | end
746 |
747 | @doc """
748 | Hadamard product
749 | #### Argument
750 | - a: Left side of the Hadamard production of matrices.
751 | - b: Right side of the Hadamard production of matrices.
752 | #### Output
753 | Hadamard production of two matrices
754 | #### Example
755 | iex> MatrixOperation.hadamard_product([[3, 2, 3], [2, 1, 2]], [[2, 3, 1], [3, 2, 2]])
756 | [[6, 6, 3], [6, 2, 4]]
757 | """
758 | def hadamard_product(a, b) do
759 | Enum.zip(a, b)
760 | |> Enum.map(fn {x, y} -> hadamard_product_sub(x, y) end)
761 | end
762 |
763 | defp hadamard_product_sub(row_a, row_b) do
764 | Enum.zip(row_a, row_b)
765 | |> Enum.map(&Tuple.to_list(&1))
766 | |> Enum.map(&Enum.reduce(&1, fn x, acc -> x * acc end))
767 | end
768 |
769 | @doc """
770 | Hadamard division
771 | #### Argument
772 | - a: Left side of the Hadamard division of matrices.
773 | - b: Right side of the Hadamard division of matrices.
774 | #### Output
775 | Hadamard division of two matrices
776 | #### Example
777 | iex> MatrixOperation.hadamard_division([[3, 2, 3], [2, 1, 2]], [[2, 3, 1], [3, 2, 2]])
778 | [[1.5, 0.6666666666666666, 3.0], [0.6666666666666666, 0.5, 1.0]]
779 | """
780 | def hadamard_division(a, b) do
781 | Enum.zip(a, b)
782 | |> Enum.map(fn {x, y} -> hadamard_division_sub(x, y) end)
783 | end
784 |
785 | defp hadamard_division_sub(row_a, row_b) do
786 | Enum.zip(row_a, row_b)
787 | |> Enum.map(&Tuple.to_list(&1))
788 | |> Enum.map(&Enum.reduce(&1, fn x, acc -> acc / x end))
789 | end
790 |
791 | @doc """
792 | Hadamard power
793 | #### Argument
794 | - matrix: Target matrix that elements are to be n-th powered.
795 | - n: Exponent of a power.
796 | #### Output
797 | Matrix that elements are to be n-th powered
798 | #### Example
799 | iex> MatrixOperation.hadamard_power([[3, 2, 3], [2, 1, 2]], 2)
800 | [[9.0, 4.0, 9.0], [4.0, 1.0, 4.0]]
801 | """
802 | def hadamard_power(matrix, n) do
803 | Enum.map(matrix, &Enum.map(&1, fn x -> :math.pow(x, n) end))
804 | end
805 |
806 | @doc """
807 | Tensor product
808 | #### Argument
809 | - a: Left side of the tensor production of matrices.
810 | - b: Right side of the tensor production of matrices.
811 | #### Output
812 | Tensor production of two matrices
813 | #### Example
814 | iex> MatrixOperation.tensor_product([[3, 2, 3], [2, 1, 2]], [[2, 3, 1], [2, 1, 2], [3, 5, 3]])
815 | [
816 | [
817 | [[6, 9, 3], [6, 3, 6], [9, 15, 9]],
818 | [[4, 6, 2], [4, 2, 4], [6, 10, 6]],
819 | [[6, 9, 3], [6, 3, 6], [9, 15, 9]]
820 | ],
821 | [
822 | [[4, 6, 2], [4, 2, 4], [6, 10, 6]],
823 | [[2, 3, 1], [2, 1, 2], [3, 5, 3]],
824 | [[4, 6, 2], [4, 2, 4], [6, 10, 6]]
825 | ]
826 | ]
827 | """
828 | def tensor_product(a, b) when is_list(a) do
829 | Enum.map(a, &tensor_product(&1, b))
830 | end
831 |
832 | def tensor_product(a, b) when is_number(a) do
833 | const_multiple(a, b)
834 | end
835 |
836 | @doc """
837 | Calculate eigenvalue using algebra method [R^2×R^2/R^3×R^3 matrix]
838 | #### Argument
839 | - [[a11, a12], [a21, a22]] or [[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]]:
840 | R^2×R^2/R^3×R^3 matrix
841 | #### Output
842 | Eigenvalues which is a non-trivial value other than zero.
843 | #### Example
844 | iex> MatrixOperation.eigenvalue_algebra([[3, 1], [2, 2]])
845 | {4.0, 1.0}
846 | iex> MatrixOperation.eigenvalue_algebra([[6, -3], [4, -1]])
847 | {3.0, 2.0}
848 | iex> MatrixOperation.eigenvalue_algebra([[1, 1, 1], [1, 2, 1], [1, 2, 3]])
849 | {4.561552806429505, 0.43844714673139706, 1.0000000468390973}
850 | iex> MatrixOperation.eigenvalue_algebra([[2, 1, -1], [1, 1, 0], [-1, 0, 1]])
851 | {3.0000000027003626, 0.9999999918989121}
852 | """
853 | # 2×2 algebra method
854 | def eigenvalue_algebra([[a11, a12], [a21, a22]]) do
855 | quadratic_formula(1, -a11 - a22, a11 * a22 - a12 * a21)
856 | |> exclude_zero_eigenvalue()
857 | |> List.to_tuple()
858 | end
859 |
860 | # 3×3 algebratic method
861 | def eigenvalue_algebra([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]]) do
862 | a = -1
863 | b = a11 + a22 + a33
864 | c = a21 * a12 + a13 * a31 + a32 * a23 - a11 * a22 - a11 * a33 - a22 * a33
865 | d =
866 | a11 * a22 * a33 + a12 * a23 * a31 + a13 * a32 * a21 - a11 * a32 * a23 - a22 * a31 * a13 -
867 | a33 * a21 * a12
868 |
869 | dis = -4 * a * c * c * c - 27 * a * a * d * d + b * b * c * c + 18 * a * b * c * d - 4 * b * b * b * d
870 | if(dis > 0, do: cubic_formula(a, b, c, d), else: nil)
871 | |> exclude_zero_eigenvalue()
872 | |> List.to_tuple()
873 | end
874 |
875 | def eigenvalue_algebra(_a) do
876 | "2×2 or 3×3 matrix only"
877 | end
878 |
879 | defp quadratic_formula(a, b, c) do
880 | quadratic_formula_sub(a, b, c)
881 | end
882 |
883 | defp quadratic_formula_sub(a, b, c) when b * b < 4 * a * c do
884 | nil
885 | end
886 |
887 | defp quadratic_formula_sub(a, b, c) do
888 | d = :math.sqrt(b * b - 4 * a * c)
889 | [0.5 * (-b + d) / a, 0.5 * (-b - d) / a]
890 | end
891 |
892 | defp cubic_formula(a, b, c, d)
893 | when -4 * a * c * c * c - 27 * a * a * d * d + b * b * c * c + 18 * a * b * c * d -
894 | 4 * b * b * b * d < 0 do
895 | nil
896 | end
897 |
898 | defp cubic_formula(a, b, c, d) do
899 | ba = b / a
900 | ca = c / a
901 | da = d / a
902 |
903 | const1 = (27 * da + 2 * ba * ba * ba - 9 * ba * ca) / 54
904 | const2 = cubic_formula_sub(const1 * const1 + :math.pow((3 * ca - ba * ba) / 9, 3))
905 | const_plus = csqrt([-const1 + Enum.at(const2, 0), Enum.at(const2, 1)], 3)
906 | const_minus = csqrt([-const1 - Enum.at(const2, 0), -Enum.at(const2, 1)], 3)
907 | root3 = :math.sqrt(3)
908 |
909 | x1 = Enum.at(const_plus, 0) + Enum.at(const_minus, 0) - ba / 3
910 |
911 | x2 =
912 | -0.5 * Enum.at(const_plus, 0) - 0.5 * root3 * Enum.at(const_plus, 1) -
913 | 0.5 * Enum.at(const_minus, 0) + 0.5 * root3 * Enum.at(const_minus, 1) - ba / 3
914 |
915 | x3 =
916 | -0.5 * Enum.at(const_plus, 0) + 0.5 * root3 * Enum.at(const_plus, 1) -
917 | 0.5 * Enum.at(const_minus, 0) - 0.5 * root3 * Enum.at(const_minus, 1) - ba / 3
918 |
919 | [x1, x2, x3]
920 | |> Enum.map(& zero_approximation(&1))
921 | end
922 |
923 | defp cubic_formula_sub(x) when x < 0 do
924 | [0, :math.sqrt(-x)]
925 | end
926 |
927 | defp cubic_formula_sub(x) do
928 | [:math.sqrt(x), 0]
929 | end
930 |
931 | defp atan(x) when x < 0 do
932 | y = atan(-x)
933 | -1 * y
934 | end
935 |
936 | defp atan(x) do
937 | atan_sub(x, 0, 0)
938 | end
939 |
940 | defp atan_sub(x, z, s) when z < x do
941 | del = 0.0000001
942 | z = z + del
943 | s = s + del / (z * z + 1)
944 | atan_sub(x, z, s)
945 | end
946 |
947 | defp atan_sub(_, _, s) do
948 | s
949 | end
950 |
951 | defp csqrt([re, im], _n) when re == 0 and im == 0 do
952 | [0, 0]
953 | end
954 |
955 | defp csqrt([re, im], n) when re == 0 and im > 0 do
956 | r = :math.pow(im * im, 0.5 / n)
957 | re2 = r * :math.pow(3, 0.5) * 0.5
958 | im2 = r * 0.5
959 | [re2, im2]
960 | end
961 |
962 | defp csqrt([re, im], n) when re == 0 and im < 0 do
963 | r = :math.pow(im * im, 0.5 / n)
964 | re2 = r * :math.pow(3, 0.5) * 0.5
965 | im2 = -r * 0.5
966 | [re2, im2]
967 | end
968 |
969 | defp csqrt([re, im], n) when re < 0 do
970 | r = :math.pow(re * re + im * im, 0.5 / n)
971 | re2 = -r * :math.cos(atan(im / re) / n)
972 | im2 = r * :math.sin(atan(im / re) / n)
973 | [re2, im2]
974 | end
975 |
976 | defp csqrt([re, im], n) do
977 | r = :math.pow(re * re + im * im, 0.5 / n)
978 | re2 = r * :math.cos(atan(im / re) / n)
979 | im2 = r * :math.sin(atan(im / re) / n)
980 | [re2, im2]
981 | end
982 |
983 | # Due to a numerical calculation error
984 | defp zero_approximation(delta) when abs(delta) < 0.000001 do
985 | 0
986 | end
987 |
988 | defp zero_approximation(delta) do
989 | delta
990 | end
991 |
992 | defp exclude_zero_eigenvalue(eigenvalues) do
993 | eigenvalues2 = Enum.map(eigenvalues, & zero_approximation(&1))
994 | len = length(eigenvalues)
995 | zero_list = Enum.to_list(1..len)
996 | |> Enum.map(& &1 * 0)
997 | eigenvalues2 -- zero_list
998 | end
999 |
1000 | defp exclude_zero_eigenvalue(eigenvalues, eigenvectors) do
1001 | Enum.map(eigenvalues, & zero_approximation(&1))
1002 | |> Enum.zip(eigenvectors)
1003 | |> Enum.map(fn {val, vec} -> if(val==0, do: nil, else: {val, vec}) end)
1004 | |> Enum.filter(& !is_nil(&1))
1005 | |> Enum.unzip()
1006 | end
1007 |
1008 | """
1009 | Matrix diagonalization using algebra method [R^2×R^2/R^3×R^3 matrix]
1010 | #### Argument
1011 | - matrix: R^2×R^2/R^3×R^3 matrix. Target matrix to be diagonalized.
1012 | #### Output
1013 | Diagonalized matrix
1014 | #### Example
1015 | iex> MatrixOperation.diagonalization_algebra([[1, 3], [4, 2]])
1016 | [[5.0, 0], [0, -2.0]]
1017 | iex> MatrixOperation.diagonalization_algebra([[2, 1, -1], [1, 1, 5], [-1, 2, 1]])
1018 | [[-2.6170355131217935, 0, 0], [0, 4.1017849347870765, 0], [0, 0, 2.515250578334717]]
1019 | iex> MatrixOperation.diagonalization_algebra([[2, 1, -1], [1, 1, 0], [-1, 0, 1]])
1020 | nil
1021 | """
1022 | defp diagonalization_algebra(matrix) do
1023 | ev = matrix
1024 | |> eigenvalue_algebra()
1025 | |> Tuple.to_list()
1026 | if(length(ev)==length(matrix), do: ev, else: nil)
1027 | |> diagonalization_algebra_condition()
1028 | end
1029 |
1030 | defp diagonalization_algebra_condition(matrix) when matrix == nil do
1031 | nil
1032 | end
1033 |
1034 | defp diagonalization_algebra_condition(matrix) do
1035 | matrix
1036 | |> Enum.with_index()
1037 | |> Enum.map(& diagonalization_algebra_sub(&1, length(matrix), 0, []))
1038 | end
1039 |
1040 | defp diagonalization_algebra_sub(_, dim, i, row) when i + 1 > dim do
1041 | row
1042 | end
1043 |
1044 | defp diagonalization_algebra_sub({ev, index}, dim, i, row) when i != index do
1045 | diagonalization_algebra_sub({ev, index}, dim, i + 1, row ++ [0])
1046 | end
1047 |
1048 | defp diagonalization_algebra_sub({ev, index}, dim, i, row) when i == index do
1049 | diagonalization_algebra_sub({ev, index}, dim, i + 1, row ++ [ev])
1050 | end
1051 |
1052 | @doc """
1053 | Jordan_normal_form [R^2×R^2/R^3×R^3 matrix]
1054 | #### Argument
1055 | - matrix: R^2×R^2/R^3×R^3 matrix. Target matrix to be Jordan normal form.
1056 | #### Output
1057 | Jordan normal form matrix
1058 | #### Example
1059 | iex> MatrixOperation.jordan_normal_form([[1, 3], [4, 2]])
1060 | [[5.0, 0], [0, -2.0]]
1061 | iex> MatrixOperation.jordan_normal_form([[7, 2], [-2, 3]])
1062 | [[5.0, 1], [0, 5.0]]
1063 | iex> MatrixOperation.jordan_normal_form([[2, 1, -1], [1, 1, 0], [-1, 0, 1]])
1064 | nil
1065 | iex> MatrixOperation.jordan_normal_form([[1, -1, 1], [0, 2, -2], [1, 1, 3]])
1066 | [[2.0, 1, 0], [0, 2.0, 1], [0, 0, 2.0]]
1067 | iex> MatrixOperation.jordan_normal_form([[3, 0, 1], [-1, 2, -1], [-1, 0, 1]])
1068 | [[2.0, 1, 0], [0, 2.0, 0], [0, 0, 2.0]]
1069 | iex> MatrixOperation.jordan_normal_form([[1, 0, -1], [0, 2, 0], [0, 1, 1]])
1070 | [[2.0, 0, 0], [0, 0.9999999999999999, 1], [0, 0, 0.9999999999999999]]
1071 | iex> MatrixOperation.jordan_normal_form([[6, 2, 3], [-3, 0, -2], [-4, -2, -1]])
1072 | [[1.0, 0, 0], [0, 2.0, 1], [0, 0, 2.0]]
1073 | """
1074 | # R^2×R^2 matrix
1075 | def jordan_normal_form([[m11, m12], [m21, m22]]) do
1076 | b = -m11 - m22
1077 | c = m11 * m22 - m12 * m21
1078 | jordan_R2R2(b, c, [[m11, m12], [m21, m22]])
1079 | end
1080 | # R^3×R^3 matrix
1081 | def jordan_normal_form([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]]) do
1082 | b = m11 + m22 + m33
1083 | c = m21 * m12 + m13 * m31 + m32 * m23 - m11 * m22 - m11 * m33 - m22 * m33
1084 | d =
1085 | m11 * m22 * m33 + m12 * m23 * m31 + m13 * m32 * m21 - m11 * m32 * m23 - m22 * m31 * m13 -
1086 | m33 * m21 * m12
1087 | jordan_R3R3(b, c, d, [[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])
1088 | end
1089 |
1090 | def jordan_normal_form(_) do
1091 | nil
1092 | end
1093 |
1094 | defp jordan_R2R2(b, c, m) when (b * b > 4 * c) do
1095 | diagonalization_algebra(m)
1096 | end
1097 |
1098 | defp jordan_R2R2(b, c, m) when b * b == 4 * c do
1099 | m_lambda = subtract(m, [[-b * 0.5, 0], [0, -b * 0.5]])
1100 | max_jordan_dim = jordan_R2R2_sub(m_lambda, 1)
1101 | jordan_R2R2_sub2(b, max_jordan_dim)
1102 | end
1103 |
1104 | defp jordan_R2R2(_, _, _) do
1105 | nil
1106 | end
1107 |
1108 | defp jordan_R2R2_sub(ml, n) when ml != [[0, 0], [0, 0]] and n <= 2 do
1109 | product(ml, ml)
1110 | |> jordan_R2R2_sub(n + 1)
1111 | end
1112 |
1113 | defp jordan_R2R2_sub(_, n) when n > 2 do
1114 | nil
1115 | end
1116 |
1117 | defp jordan_R2R2_sub(_, n) do
1118 | n
1119 | end
1120 |
1121 | defp jordan_R2R2_sub2(b, mjd) when mjd == 2 do
1122 | [[-b * 0.5, 1], [0, -b * 0.5]]
1123 | end
1124 |
1125 | defp jordan_R2R2_sub2(b, mjd) when mjd == 1 do
1126 | [[-b * 0.5, 0], [0, -b * 0.5]]
1127 | end
1128 |
1129 | defp jordan_R2R2_sub2(_, _) do
1130 | nil
1131 | end
1132 |
1133 | defp jordan_R3R3(b, c, d, m)
1134 | when 4 * c * c * c - 27 * d * d + b * b * c * c - 18 * b * c * d -
1135 | 4 * b * b * b * d > 0 do
1136 | diagonalization_algebra(m)
1137 | end
1138 | # Triple root
1139 | defp jordan_R3R3(b, c, d, m)
1140 | when (4 * c * c * c - 27 * d * d + b * b * c * c - 18 * b * c * d -
1141 | 4 * b * b * b * d == 0) and (b * b == -3 * c and b * b * b == 27 * d) do
1142 | m_lambda = subtract(m, [[b/3, 0, 0], [0, b/3, 0], [0, 0, b/3]])
1143 | max_jordan_dim = jordan_R3R3_sub(m_lambda, 1)
1144 | jordan_R3R3_sub2(b, max_jordan_dim)
1145 | end
1146 | # Double root
1147 | defp jordan_R3R3(b, c, d, _)
1148 | when (4 * c * c * c - 27 * d * d + b * b * c * c - 18 * b * c * d -
1149 | 4 * b * b * b * d == 0) do
1150 | lambda = cubic_formula(-1, b, c, d)
1151 | jordan_R3R3_sub3(lambda)
1152 | end
1153 |
1154 | defp jordan_R3R3(_, _, _, _) do
1155 | nil
1156 | end
1157 |
1158 | defp jordan_R3R3_sub(ml, n) when ml != [[0, 0, 0], [0, 0, 0], [0, 0, 0]] and n < 3 do
1159 | product(ml, ml)
1160 | |> Enum.map(& Enum.map(&1, fn x -> zero_approximation(x) end))
1161 | |> jordan_R3R3_sub(n + 1)
1162 | end
1163 |
1164 | defp jordan_R3R3_sub(_, n) when n > 3 do
1165 | nil
1166 | end
1167 |
1168 | defp jordan_R3R3_sub(_, n) do
1169 | n
1170 | end
1171 |
1172 | defp jordan_R3R3_sub2(b, mjd) when mjd == 3 do
1173 | [[b/3, 1, 0], [0, b/3, 1], [0, 0, b/3]]
1174 | end
1175 |
1176 | defp jordan_R3R3_sub2(b, mjd) when mjd == 2 do
1177 | [[b/3, 1, 0], [0, b/3, 0], [0, 0, b/3]]
1178 | end
1179 |
1180 | defp jordan_R3R3_sub2(b, mjd) when mjd == 1 do
1181 | [[b/3, 0, 0], [0, b/3, 0], [0, 0, b/3]]
1182 | end
1183 |
1184 | defp jordan_R3R3_sub2(_, _) do
1185 | nil
1186 | end
1187 |
1188 | defp jordan_R3R3_sub3([l1, l2, l3]) when l1 == l2 do
1189 | [[l1, 1, 0], [0, l2, 0], [0, 0, l3]]
1190 | end
1191 |
1192 | defp jordan_R3R3_sub3([l1, l2, l3]) when l2 == l3 do
1193 | [[l1, 0, 0], [0, l2, 1], [0, 0, l3]]
1194 | end
1195 |
1196 | defp jordan_R3R3_sub3([l1, l2, l3]) when l1 == l3 do
1197 | [[l1, 1, 0], [0, l3, 0], [0, 0, l2]]
1198 | end
1199 |
1200 | defp jordan_R3R3_sub3(_) do
1201 | nil
1202 | end
1203 |
1204 | @doc """
1205 | Power iteration method (maximum eigen value and eigen vector)
1206 | #### Argument
1207 | - matrix: Matrix to adapt the power iteration method.
1208 | - iter_max: iteration number of the power iteration method. The default value is 1000.
1209 | #### Output
1210 | Maximum eigenvalue and normalized eigenvector corresponding to the maximum eigenvalue
1211 | #### Example
1212 | iex> MatrixOperation.power_iteration([[3, 1], [2, 2]])
1213 | {
1214 | 4.0,
1215 | [0.7071067811865476, 0.7071067811865476]
1216 | }
1217 | iex> MatrixOperation.power_iteration([[1, 1, 2], [0, 2, -1], [0, 0, 3]])
1218 | {
1219 | 3.0,
1220 | [0.3333333333333333, -0.6666666666666666, 0.6666666666666666]
1221 | }
1222 | """
1223 | def power_iteration(matrix, iter_max \\ 1000) do
1224 | init_vec = random_column(length(matrix))
1225 | xk_pre = power_iteration_sub(matrix, init_vec, iter_max)
1226 | # eigen vector
1227 | [xk_vec] = product(matrix, xk_pre) |> transpose
1228 | [xk_pre_vec] = transpose(xk_pre)
1229 | # eigen value
1230 | eigen_value = inner_product(xk_vec, xk_vec) / inner_product(xk_vec, xk_pre_vec)
1231 | norm_xk_vec = :math.sqrt(inner_product(xk_vec, xk_vec))
1232 | normalized_eigen_vec = Enum.map(xk_vec, & &1/norm_xk_vec)
1233 | {eigen_value, normalized_eigen_vec}
1234 | end
1235 |
1236 | defp random_column(num) when num > 1 do
1237 | tmp = Enum.reduce(1..num, [], fn _, acc -> [Enum.random(0..50000) / 10000 | acc] end)
1238 | transpose([tmp])
1239 | end
1240 |
1241 | defp random_column(_num) do
1242 | nil
1243 | end
1244 |
1245 | defp power_iteration_sub(matrix, v, iter_max) do
1246 | # Normarization is for overflow suppression
1247 | Enum.reduce(1..iter_max, v, fn _, acc ->
1248 | vp = product(matrix, acc)
1249 | [vpt] = transpose(vp)
1250 | const_multiple(1 / :math.sqrt(inner_product(vpt, vpt)), vp)
1251 | end)
1252 | end
1253 |
1254 | @doc """
1255 | Calculate eigenvalues and eigenvectors by using Jacobi method
1256 | #### Argument
1257 | - matrix: Matrix to adapt the power iteration method.
1258 | - iter_max: iteration number of the power iteration method. The default value is 1000.
1259 | #### Output
1260 | [Eigenvalues list, Eigenvectors list]: Eigenvalues and eigenvectors
1261 | #### Example
1262 | iex> MatrixOperation.jacobi([[10, 3, 2], [3, 5, 1], [2, 1, 0]])
1263 | {
1264 | [11.827601656660915, 3.5956497715829547, -0.42325142824210527],
1265 | [
1266 | [0.8892872578006493, -0.42761854121985043, -0.16220529066103917],
1267 | [0.4179466723082575, 0.9038581385546461, -0.09143874712126684],
1268 | [0.1857114757355714, 0.013522151221627882, 0.982511271796136]
1269 | ]
1270 | }
1271 | """
1272 | def jacobi(matrix, iter_max \\ 1000) do
1273 | [pap, p] = jacobi_iteration(matrix, iter_max, 0, unit_matrix(length(matrix)))
1274 | p_rnd = Enum.map(p, & Enum.map(&1, fn x -> zero_approximation(x) end))
1275 |
1276 | pap
1277 | |> Enum.with_index()
1278 | |> Enum.map(& jacobi_sub4(&1))
1279 | |> Enum.map(& zero_approximation(&1))
1280 | |> exclude_zero_eigenvalue(p_rnd)
1281 | end
1282 |
1283 | defp jacobi_iteration(matrix, iter_max, l, p_pre) when l != iter_max do
1284 | {row_num, col_num} = size(matrix)
1285 | odts = off_diagonal_terms(matrix, row_num, col_num, 0, 0, [])
1286 | |> Enum.map(& abs(&1))
1287 |
1288 | max_odt = Enum.max(odts)
1289 | [max_i, max_j] = Enum.with_index(odts)
1290 | |> jocobi_sub(max_odt, 0)
1291 | |> jocobi_sub2(col_num, 0)
1292 |
1293 | a_ij = get_one_element(matrix, {max_i + 1, max_j + 1})
1294 | a_ii = get_one_element(matrix, {max_i + 1, max_i + 1})
1295 | a_jj = get_one_element(matrix, {max_j + 1, max_j + 1})
1296 | phi = phi_if(a_ii - a_jj, a_ij)
1297 |
1298 | p = jacobi_sub3(phi, col_num, max_i, max_j, 0, 0, [], [])
1299 | p_pi = product(p_pre, p)
1300 | p
1301 | |> transpose()
1302 | |> product(matrix)
1303 | |> product(p)
1304 | |> jacobi_iteration(iter_max, l + 1, p_pi)
1305 | end
1306 |
1307 | defp jacobi_iteration(matrix, _, _, p) do
1308 | [matrix, p]
1309 | end
1310 |
1311 | defp phi_if(denominator, a_ij) when denominator < 0.0000001 and a_ij > 0 do
1312 | -0.78539816339 # -pi/2
1313 | end
1314 |
1315 | defp phi_if(denominator, a_ij) when denominator < 0.0000001 and a_ij < 0 do
1316 | 0.78539816339 # -pi/2
1317 | end
1318 |
1319 | defp phi_if(denominator, a_ij) do
1320 | atan(-2 * a_ij / denominator) * 0.5
1321 | end
1322 |
1323 | defp off_diagonal_terms(m, row_num, col_num, i, j, output) when i < j and row_num >= i and col_num > j do
1324 | off_diagonal_terms(m, row_num, col_num, i, j + 1, output ++ [get_one_element(m, {i + 1, j + 1})])
1325 | end
1326 |
1327 | defp off_diagonal_terms(m, row_num, col_num, i, j, output) when i < j and row_num > i and col_num == j do
1328 | off_diagonal_terms(m, row_num, col_num, i + 1, 0, output)
1329 | end
1330 |
1331 | defp off_diagonal_terms(_, row_num, col_num, i, j, output) when row_num == i and col_num == j do
1332 | output
1333 | end
1334 |
1335 | defp off_diagonal_terms(m, row_num, col_num, i, j, output) do
1336 | off_diagonal_terms(m, row_num, col_num, i, j + 1, output)
1337 | end
1338 |
1339 | defp jocobi_sub(element_idx_list, target_element, i) when hd(element_idx_list) == {target_element, i} do
1340 | i
1341 | end
1342 |
1343 | defp jocobi_sub(element_idx_list, target_element, i) do
1344 | [_|tail] = element_idx_list
1345 | jocobi_sub(tail, target_element, i + 1)
1346 | end
1347 |
1348 | defp jocobi_sub2(idx, col_num, i) when idx < (i + 1) * col_num - ((i + 1) * (2 + i) * 0.5) do
1349 | [max_i, max_j] = [i, idx - i * (2 * col_num - i - 1) * 0.5 + i + 1]
1350 | [max_i, round(max_j)]
1351 | end
1352 |
1353 | defp jocobi_sub2(idx, col_num, i) do
1354 | jocobi_sub2(idx, col_num, i + 1)
1355 | end
1356 |
1357 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when i == j and ( i == target_i or j == target_j) do
1358 | jacobi_sub3(phi, col_num, target_i, target_j, i, j + 1, o_row ++ [:math.cos(phi)], output)
1359 | end
1360 |
1361 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when i == target_i and j == target_j and j != col_num do
1362 | jacobi_sub3(phi, col_num, target_i, target_j, i, j + 1, o_row ++ [:math.sin(phi)], output)
1363 | end
1364 |
1365 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when i == target_i and j == target_j and j == col_num do
1366 | jacobi_sub3(phi, col_num, target_i, target_j, i + 1, 0, [] , output ++ [o_row ++ [:math.sin(phi)]])
1367 | end
1368 |
1369 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when i == target_j and j == target_i do
1370 | jacobi_sub3(phi, col_num, target_i, target_j, i, j + 1, o_row ++ [:math.sin(-phi)], output)
1371 | end
1372 |
1373 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when (i != target_i or j != target_j) and i == j and j != col_num do
1374 | jacobi_sub3(phi, col_num, target_i, target_j, i, j + 1, o_row ++ [1], output)
1375 | end
1376 |
1377 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) when (i != target_i or j != target_j) and i != j and j == col_num do
1378 | jacobi_sub3(phi, col_num, target_i, target_j, i + 1, 0, [], output ++ [o_row])
1379 | end
1380 |
1381 | defp jacobi_sub3(_, col_num, _, _, i, j, _, output) when i == j and j == col_num do
1382 | output
1383 | end
1384 |
1385 | defp jacobi_sub3(phi, col_num, target_i, target_j, i, j, o_row, output) do
1386 | jacobi_sub3(phi, col_num, target_i, target_j, i, j + 1, o_row ++ [0], output)
1387 | end
1388 |
1389 | defp jacobi_sub4({list, index}) do
1390 | Enum.at(list, index)
1391 | end
1392 |
1393 | @doc """
1394 | Singular Value Decomposition (SVD) using Jacobi method.
1395 | #### Argument
1396 | - matrix: Matrix to adapt the SVD by using the QR decomposition method.
1397 | #### Output
1398 | [Singular values, U-matrix, V-matrix]:
1399 | Singular values, U-matrix and V-matrix.
1400 | Singular value is a non-trivial value other than zero.
1401 | #### Example
1402 | iex> MatrixOperation.svd([[1, 0, 0], [0, 1, 1]])
1403 | {
1404 | [1.0, 1.4142135623730951],
1405 | [
1406 | [1.0, 0.0],
1407 | [0.0, 1.0]
1408 | ],
1409 | [
1410 | [1.0, 0.0, 0.0],
1411 | [0.0, 0.7071067811865475, 0.7071067811865475]
1412 | ]
1413 | }
1414 | iex> MatrixOperation.svd([[1, 1], [1, -1], [1, 0]])
1415 | {
1416 | [1.7320508075688772, 1.4142135623730951],
1417 | [
1418 | [0.5773502691896258, 0.5773502691896258, 0.5773502691896258],
1419 | [0.7071067811865476, -0.7071067811865476, 0.0]
1420 | ],
1421 | [
1422 | [1.0, 0.0],
1423 | [0.0, 1.0]
1424 | ]
1425 | }
1426 | iex> MatrixOperation.svd([[1, 1], [1, 1]])
1427 | {
1428 | [1.9999999999999998],
1429 | [[0.7071067811865476, 0.7071067811865476]],
1430 | [[0.7071067811865476, 0.7071067811865476]]
1431 | }
1432 | """
1433 | def svd(a) do
1434 | a_t = transpose(a)
1435 | svd_sub(a, a_t)
1436 | end
1437 |
1438 | def svd_sub(a, a_t) when length(a) <= length(a_t) do
1439 | # U matrix
1440 | aat = product(a, a_t)
1441 | {sv_sq, u} = eigen(aat)
1442 | # V matirx
1443 | ata = product(a_t, a)
1444 | {_, v} = eigen(ata)
1445 | # Singular value
1446 | s = Enum.map(sv_sq, & :math.sqrt(&1))
1447 | # A = USV^t
1448 | {s, u, v}
1449 | end
1450 |
1451 | def svd_sub(a, a_t) do
1452 | # U matrix
1453 | aat = product(a, a_t)
1454 | {_, u} = eigen(aat)
1455 | # V matirx
1456 | ata = product(a_t, a)
1457 | {sv_sq, v} = eigen(ata)
1458 | # Singular value
1459 | s = Enum.map(sv_sq, & :math.sqrt(&1))
1460 | # A = USV^t
1461 | {s, u, v}
1462 | end
1463 |
1464 | @doc """
1465 | Moore-Penrose general inverse matrix
1466 | #### Argument
1467 | - matrix: Matrix to be Moore-Penrose general inverse matrix.
1468 | #### Output
1469 | Moore-Penrose general inverse matrix
1470 | #### Example
1471 | iex> MatrixOperation.mp_inverse_matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
1472 | [
1473 | [-0.6388888888888877, -0.16666666666666777, 0.30555555555555647],
1474 | [-0.0555555555555557, -1.8041124150158794e-16, 0.05555555555555575],
1475 | [0.5277777777777768, 0.16666666666666755, -0.19444444444444522]
1476 | ]
1477 | """
1478 | def mp_inverse_matrix(matrix) do
1479 | svd(matrix)
1480 | |> sv_matrix_inv()
1481 | end
1482 |
1483 | defp sv_matrix_inv({sv, u, v}) do
1484 | # Zero matrix with index
1485 | sv_len = length(sv)
1486 | zm_idx =
1487 | even_matrix(0, {sv_len, sv_len})
1488 | |> Enum.with_index()
1489 | # Inverse singular value matrix
1490 | svm_inv =
1491 | Enum.map(
1492 | zm_idx,
1493 | fn {zm_row, idx} ->
1494 | List.replace_at(
1495 | zm_row,
1496 | idx,
1497 | 1/Enum.at(sv, idx)
1498 | )
1499 | end
1500 | )
1501 | # VΣ^-U^T
1502 | vt = transpose(v)
1503 | vt
1504 | |> product(svm_inv)
1505 | |> product(u)
1506 | end
1507 |
1508 | @doc """
1509 | Calculate eigenvalues and eigenvectors by using QR decomposition for symmetric matrices.
1510 | #### Argument
1511 | - a: Symmetric matrix to calculate eigenvalues and eigenvectors by using the QR decomposition.
1512 | #### Output
1513 | [Eigenvalues list, Eigenvectors list]: Eigenvalues and eigenvectors.
1514 | Eigenvalue is a non-trivial value other than zero, and complex numbers are not supported.
1515 | #### Example
1516 | iex> MatrixOperation.eigh([[3, 0], [0, 2]])
1517 | {[3.0, 2.0], [[1.0, 0.0], [0.0, 1.0]]}
1518 | iex> MatrixOperation.eigh([[1, 4, 5], [4, 2, 6], [5, 6, 3]])
1519 | {
1520 | [12.175971065046884, -2.50728796709364, -3.6686830979532647],
1521 | [
1522 | [0.496599784546191, 0.8095854617397509, -0.3129856771935597],
1523 | [0.577350269189626, -0.577350269189626, -0.5773502691896257],
1524 | [0.6481167492476515, -0.10600965430705458, 0.7541264035547063]
1525 | ]
1526 | }
1527 | iex> row1 = [ 5, -1, 0, 1, 2]
1528 | iex> row2 = [-1, 5, 0, 5, 3]
1529 | iex> row3 = [ 0, 0, 4, 7, 2]
1530 | iex> row4 = [ 1, 5, 7, 0, 9]
1531 | iex> row5 = [ 2, 3, 2, 9, 2]
1532 | iex> MatrixOperation.eigh([row1, row2, row3, row4, row5])
1533 | {
1534 | [16.394097630317376, 5.901499037899706, 4.334013998770404, -0.891690865956603, -9.737919801031268],
1535 | [
1536 | [0.11199211262602528, -0.8283773397697639, -0.4403916223463706, 0.3275456024443265, -0.00422456530824197],
1537 | [0.39542664705563546, 0.5332887206459925, -0.5342108202525103, 0.4973517482650887, 0.16279110925630544],
1538 | [0.4267472595014673, -0.13695943658576812, 0.6991586689712901, 0.4519460705200494, 0.3256544091239611],
1539 | [0.6029452475982553, -0.007822597120772413, 0.07907415791820135, -0.1297224632045824, -0.7831444282267664],
1540 | [0.5342652322719152, -0.10283502852688214, -0.15999516131462643, -0.651361611317911, 0.5040984210950804]
1541 | ]
1542 | }
1543 | """
1544 | def eigh(a) do
1545 | # Set the number of iterations according to the number of dimensions.
1546 | # Refer to the LAPACK (ex. dlahqr).
1547 | iter_max = 30 * Enum.max([10, length(a)])
1548 | matrix_len = length(a)
1549 | u = unit_matrix(matrix_len)
1550 | # Hessenberg transform
1551 | {hess, q_h} = hessenberg(a, matrix_len, u, u, 1)
1552 | # Compute eigenvalues and eigenvectors
1553 | {eigenvals, eigenvecs} = hess
1554 | |> qr_iter(matrix_len, q_h, u, 0, iter_max)
1555 | {eigenvals, eigenvecs} = exclude_zero_eigenvalue(eigenvals, eigenvecs)
1556 | # Normalize
1557 | eigenvecs_norm_t = eigenvecs
1558 | |> Enum.map(& normalize_vec(&1))
1559 | |> transpose()
1560 | {eigenvals, eigenvecs_norm_t}
1561 | end
1562 |
1563 | defp qr_iter(a, matrix_len, q, u, count, iter_max)
1564 | when count != iter_max and matrix_len <= 2 do
1565 | q_n = a
1566 | |> qr_for_ev(u, matrix_len, u, 1)
1567 | # Compute matrix a_k
1568 | a_k = q_n
1569 | |> transpose()
1570 | |> product(a)
1571 | |> product(q_n)
1572 | # Compute matrix q_k
1573 | q_k = product(q, q_n)
1574 | qr_iter(a_k, matrix_len, q_k, u, count+1, iter_max)
1575 | end
1576 |
1577 | defp qr_iter(a, matrix_len, q, u, count, iter_max) when count != iter_max do
1578 | shift = wilkinson_shift_value(a)
1579 | a_s = eigen_shift(a, -shift)
1580 | q_n = qr_for_ev(a_s, u, matrix_len, u, 1)
1581 | # Compute matrix a_k
1582 | a_k = q_n
1583 | |> transpose()
1584 | |> product(a_s)
1585 | |> product(q_n)
1586 | |> eigen_shift(shift)
1587 |
1588 | # Compute matrix q_k
1589 | q_k = product(q, q_n)
1590 | qr_iter(a_k, matrix_len, q_k, u, count+1, iter_max)
1591 | end
1592 |
1593 | defp qr_iter(a_k, _, q_k, _, _, _) do
1594 | # Compute eigenvalues
1595 | eigenvals = a_k
1596 | |> Enum.with_index()
1597 | |> Enum.map(fn {x, i} -> Enum.at(x, i) end)
1598 | # Compute eigenvectors
1599 | eigenvecs = transpose(q_k)
1600 | {eigenvals, eigenvecs}
1601 | end
1602 |
1603 | defp wilkinson_shift_value(a) do
1604 | # The bottom right elements of the matrix
1605 | matrix_len = length(a)
1606 | w11 = get_one_element(a, {matrix_len-1, matrix_len-1})
1607 | w12 = get_one_element(a, {matrix_len-1, matrix_len})
1608 | w21 = w12
1609 | w22 = get_one_element(a, {matrix_len, matrix_len})
1610 | # Wilkinson shift value
1611 | e = w11 + w22
1612 | f = :math.sqrt(e * e - 4 * (w11 * w22 - w12 * w21))
1613 | k1 = 0.5 * (e + f)
1614 | k2 = 0.5 * (e - f)
1615 | if(abs(w22 - k1) < abs(w22 - k2), do: k1, else: k2)
1616 | end
1617 |
1618 | defp eigen_shift(a, shift) do
1619 | um = a
1620 | |> length()
1621 | |> unit_matrix()
1622 | shift_matrix = const_multiple(shift, um)
1623 | add(a, shift_matrix)
1624 | end
1625 |
1626 | defp hessenberg(a, matrix_len, q, u, num) when matrix_len != num + 1 do
1627 | q_n = a
1628 | |> get_one_column(num)
1629 | |> replace_zero(num)
1630 | |> householder(num, u)
1631 |
1632 | q_nt = transpose(q_n)
1633 | hess = q_n
1634 | |> product(a)
1635 | |> product(q_nt)
1636 | # Compute matrix q_k
1637 | q_k = product(q, q_n)
1638 | hessenberg(hess, matrix_len, q_k, u, num+1)
1639 | end
1640 |
1641 | defp hessenberg(hess, _, q_k, _, _) do
1642 | {hess, q_k}
1643 | end
1644 |
1645 | defp normalize_vec(vec) do
1646 | norm = vec
1647 | |> Enum.map(& &1*&1)
1648 | |> Enum.sum()
1649 | |> :math.sqrt()
1650 | Enum.map(vec, & &1/norm)
1651 | end
1652 |
1653 | @doc """
1654 | Calculate eigenvalues and eigenvectors by using QR decomposition.
1655 | #### Argument
1656 | - a: Matrix to calculate eigenvalues and eigenvectors by using the QR decomposition.
1657 | #### Output
1658 | [Eigenvalues list, Eigenvectors list]: Eigenvalues and eigenvectors.
1659 | Eigenvalue is a non-trivial value other than zero, and complex numbers are not supported.
1660 | #### Example
1661 | iex> MatrixOperation.eigen([[1, 4, 5], [4, 2, 6], [5, 6, 3]])
1662 | {
1663 | [12.175971065046914, -3.6686830979532736, -2.507287967093643],
1664 | [
1665 | [0.4965997845461912, 0.5773502691896258, 0.6481167492476514],
1666 | [-0.3129856771935595, -0.5773502691896258, 0.7541264035547063],
1667 | [-0.8095854617397507, 0.577350269189626, 0.10600965430705471]
1668 | ]
1669 | }
1670 | """
1671 | def eigen(a) do
1672 | delta = 0.0001 # avoid division by zero
1673 | evals = eigenvalue(a)
1674 | evecs = evals
1675 | |> Enum.map(
1676 | & eigenvalue_shift(a, -&1+delta)
1677 | |> inverse_matrix()
1678 | |> power_iteration()
1679 | |> extract_second()
1680 | )
1681 | {evals, evecs}
1682 | end
1683 |
1684 | defp eigenvalue(a) do
1685 | # Set the number of iterations according to the number of dimensions.
1686 | # Refer to the LAPACK (ex. dlahqr).
1687 | iter_max = 30 * Enum.max([10, length(a)])
1688 | matrix_len = length(a)
1689 | u = unit_matrix(matrix_len)
1690 | # Hessenberg transform
1691 | {hess, _q} = hessenberg(a, matrix_len, u, u, 1)
1692 | # Compute eigenvalues and eigenvectors
1693 | hess
1694 | |> eigenvalue_sub(matrix_len, u, 0, iter_max)
1695 | |> exclude_zero_eigenvalue()
1696 | end
1697 |
1698 | defp eigenvalue_sub(a, matrix_len, u, count, iter_max) when count != iter_max do
1699 | q_n = qr_for_ev(a, u, matrix_len, u, 1)
1700 | a_k = q_n
1701 | |> transpose()
1702 | |> product(a)
1703 | |> product(q_n)
1704 | eigenvalue_sub(a_k, matrix_len, u, count+1, iter_max)
1705 | end
1706 |
1707 | defp eigenvalue_sub(a_k, _, _, _, _) do
1708 | a_k
1709 | |> Enum.with_index()
1710 | |> Enum.map(fn {x, i} -> Enum.at(x, i) end)
1711 | end
1712 |
1713 | defp qr_for_ev(a, q, matrix_len, u, num) when matrix_len != num do
1714 | h = a
1715 | |> get_one_column(num)
1716 | |> replace_zero(num-1)
1717 | |> householder(num-1, u)
1718 |
1719 | a_n = product(h, a)
1720 | q_n = product(q, h)
1721 |
1722 | qr_for_ev(a_n, q_n, matrix_len, u, num+1)
1723 | end
1724 |
1725 | defp qr_for_ev(_, q_n, _, _, _) do
1726 | q_n
1727 | end
1728 |
1729 | defp replace_zero(list, thresh_num) do
1730 | list
1731 | |> Enum.with_index()
1732 | |> Enum.map(fn {x, i} -> if(i < thresh_num, do: 0, else: x) end)
1733 | end
1734 |
1735 | defp householder(col, index, u) do
1736 | col_norm = col
1737 | |> Enum.map(& &1*&1)
1738 | |> Enum.sum()
1739 | |> :math.sqrt()
1740 |
1741 | top = Enum.at(col, index)
1742 | top_cn = if(top >= 0, do: top + col_norm, else: top - col_norm)
1743 | v = List.replace_at(col, index, top_cn)
1744 |
1745 | cn_top = if(top >= 0, do: col_norm + top, else: col_norm - top)
1746 | vtv = [v]
1747 | |> transpose
1748 | |> product([v])
1749 |
1750 | # avoid division by zero
1751 | norm = if(
1752 | col_norm * cn_top == 0,
1753 | do: 0.0001,
1754 | else: col_norm * cn_top
1755 | )
1756 | m = const_multiple(1/norm, vtv)
1757 | subtract(u, m)
1758 | end
1759 |
1760 | defp eigenvalue_shift(a, ev) do
1761 | unit = a
1762 | |> length
1763 | |> unit_matrix()
1764 | b = const_multiple(ev, unit)
1765 | add(a, b)
1766 | end
1767 |
1768 | defp extract_second({_first, second}) do
1769 | second
1770 | end
1771 |
1772 | @doc """
1773 | Matrix diagonalization using the QR decomposition.
1774 | #### Argument
1775 | - a: Matrix to be diagonalized by using the QR decomposition.
1776 | #### Output
1777 | Diagonalized matrix
1778 | #### Example
1779 | iex> MatrixOperation.diagonalization([[1, 3], [4, 2]])
1780 | [[5.000000000000018, 0], [0, -1.999999999999997]]
1781 | iex> MatrixOperation.diagonalization([[2, 1, -1], [1, 1, 5], [-1, 2, 1]])
1782 | [[4.101784906061095, 0, 0], [0, -2.6170329440542233, 0], [0, 0, 2.515248037993127]]
1783 | iex> MatrixOperation.diagonalization([[2, 1, -1], [1, 1, 0], [-1, 0, 1]])
1784 | nil
1785 | iex> MatrixOperation.diagonalization([[2, 1, -1], [1, 1, 0], [-1, 0, 1]])
1786 | nil
1787 | iex> MatrixOperation.diagonalization([[16, -1, 1, 2, 3], [2, 12, 1, 5, 6], [1, 3, -24, 8, 9], [3, 4, 9, 1, 23], [5, 3, 1, 2, 1]])
1788 | [
1789 | [-26.608939298557207, 0, 0, 0, 0],
1790 | [0, 20.42436493500135, 0, 0, 0],
1791 | [0, 0, 14.665793374162678, 0, 0],
1792 | [0, 0, 0, -3.5477665464080044, 0],
1793 | [0, 0, 0, 0, 1.0665475358009446]
1794 | ]
1795 | """
1796 | def diagonalization(a) do
1797 | ev = eigenvalue(a)
1798 | if(length(ev)==length(a), do: ev, else: nil)
1799 | |> diagonalization_condition()
1800 | end
1801 |
1802 | defp diagonalization_condition(a) when a == nil do
1803 | nil
1804 | end
1805 |
1806 | defp diagonalization_condition(a) do
1807 | a
1808 | |> Enum.with_index()
1809 | |> Enum.map(& diagonalization_sub(&1, length(a), 0, []))
1810 | |> Enum.map(& Enum.map(&1, fn x -> zero_approximation(x) end))
1811 | end
1812 |
1813 | defp diagonalization_sub(_, dim, i, row) when i + 1 > dim do
1814 | row
1815 | end
1816 |
1817 | defp diagonalization_sub({ev, index}, dim, i, row) when i != index do
1818 | diagonalization_sub({ev, index}, dim, i + 1, row ++ [0])
1819 | end
1820 |
1821 | defp diagonalization_sub({ev, index}, dim, i, row) when i == index do
1822 | diagonalization_sub({ev, index}, dim, i + 1, row ++ [ev])
1823 | end
1824 |
1825 | @doc """
1826 | Calculate singular Value by using QR decomposition.
1827 | #### Argument
1828 | - a: Matrix to calculate singular values.
1829 | #### Output
1830 | Singular values list. Singular value is a non-trivial value other than zero.
1831 | #### Example
1832 | iex> MatrixOperation.singular_value([[1, 2, 3, 1], [2, 4, 1, 5], [3, 3, 10, 8]])
1833 | {14.9121726205599, 4.23646340778201, 1.6369134152873912}
1834 | """
1835 | def singular_value(a) do
1836 | a
1837 | |> transpose()
1838 | |> product(a)
1839 | |> eigenvalue()
1840 | |> Enum.map(& :math.sqrt(&1))
1841 | |> List.to_tuple()
1842 | end
1843 |
1844 | @doc """
1845 | Calculate the rank of a matrix by using QR decomposition
1846 | #### Example
1847 | iex> MatrixOperation.rank([[2, 3, 4], [1, 4, 2], [2, 1, 4]])
1848 | 2
1849 | iex> MatrixOperation.rank([[2, 3, 4, 2], [1, 4, 2, 3], [2, 1, 4, 4]])
1850 | 3
1851 | iex> input = [[2, 3, 4, 3], [1, 42, 2, 11], [2, 1, 4, 4], [3, 7, 2, 2], [35, 6, 4, 6], [7, 23, 5, 2]]
1852 | iex> MatrixOperation.rank(input)
1853 | 4
1854 | """
1855 | def rank(matrix) do
1856 | matrix
1857 | |> singular_value()
1858 | |> Tuple.to_list()
1859 | |> length()
1860 | end
1861 |
1862 | @doc """
1863 | Frobenius norm
1864 | #### Argument
1865 | - a: Matrix to calculate Frobenius norm.
1866 | #### Output
1867 | Frobenius norm
1868 | #### Example
1869 | iex> MatrixOperation.frobenius_norm([[2, 3], [1, 4], [2, 1]])
1870 | 5.916079783099616
1871 | iex> MatrixOperation.frobenius_norm([[1, 3, 3], [2, 4, 1], [2, 3, 2]])
1872 | 7.54983443527075
1873 | """
1874 | def frobenius_norm(a) do
1875 | a
1876 | |> Enum.map(& Enum.map(&1, fn x -> x * x end))
1877 | |> Enum.map(& Enum.sum(&1))
1878 | |> Enum.sum()
1879 | |> :math.sqrt()
1880 | end
1881 |
1882 | @doc """
1883 | The one norm
1884 | #### Argument
1885 | - a: Matrix to calculate the one norm.
1886 | #### Output
1887 | one norm
1888 | #### Example
1889 | iex> MatrixOperation.one_norm([[2, 3], [1, 4], [2, 1]])
1890 | 5
1891 | iex> MatrixOperation.one_norm([[1, 3, 3], [2, 4, 1], [2, 3, 2]])
1892 | 7
1893 | """
1894 | def one_norm(a) do
1895 | a
1896 | |> Enum.map(& Enum.map(&1, fn x -> if(x > 0, do: x, else: -x) end))
1897 | |> Enum.map(& Enum.sum(&1))
1898 | |> Enum.max()
1899 | end
1900 |
1901 | @doc """
1902 | The two norm
1903 | #### Argument
1904 | - a: Matrix to calculate the two norm.
1905 | #### Output
1906 | The two norm
1907 | #### Example
1908 | iex> MatrixOperation.two_norm([[2, 3], [1, 4], [2, 1]])
1909 | 5.674983803488139
1910 | iex> MatrixOperation.two_norm([[1, 3, 3], [2, 4, 1], [2, 3, 2]])
1911 | 7.329546646114923
1912 | """
1913 | def two_norm(a) do
1914 | a
1915 | |> singular_value()
1916 | |> Tuple.to_list()
1917 | |> Enum.max()
1918 | end
1919 |
1920 | @doc """
1921 | The max norm
1922 | #### Argument
1923 | - a: Matrix to calculate the max norm.
1924 | #### Output
1925 | The max norm
1926 | #### Example
1927 | iex> MatrixOperation.max_norm([[2, 3], [1, 4], [2, 1]])
1928 | 8
1929 | iex> MatrixOperation.max_norm([[1, 3, 3], [2, 4, 1], [2, 3, 2]])
1930 | 10
1931 | """
1932 | def max_norm(a) do
1933 | a
1934 | |> transpose()
1935 | |> Enum.map(& Enum.map(&1, fn x -> if(x > 0, do: x, else: -x) end))
1936 | |> Enum.map(& Enum.sum(&1))
1937 | |> Enum.max()
1938 | end
1939 |
1940 | @doc """
1941 | A variance-covariance matrix is generated.
1942 | #### Argument
1943 | - data: x and y coordinate lists ([[x_1, y_1], [x_2, y_2], ...]) to calculate variance-covariance matrix.
1944 | #### Output
1945 | Variance-covariance matrix
1946 | #### Example
1947 | iex> MatrixOperation.variance_covariance_matrix([[40, 80], [80, 90], [90, 100]])
1948 | [
1949 | [466.66666666666663, 166.66666666666666],
1950 | [166.66666666666666, 66.66666666666666]
1951 | ]
1952 | """
1953 | def variance_covariance_matrix(data) do
1954 | x = data
1955 | |> transpose()
1956 | |> Enum.map(& Enum.map(&1, fn x -> x - Enum.sum(&1)/length(&1) end))
1957 | xt = transpose(x)
1958 | xtx = product(x, xt)
1959 | const_multiple(1/length(xt), xtx)
1960 | end
1961 |
1962 | end
1963 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule MatrixOperation.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :matrix_operation,
7 | version: "0.5.0",
8 | elixir: "~> 1.12.3",
9 | description: "Matrix operation library",
10 | start_permanent: Mix.env() == :prod,
11 | package: [
12 | maintainers: ["tanaka kenta"],
13 | licenses: ["MIT"],
14 | links: %{"GitHub" => "https://github.com/kenken-neko/elixir-matrix-operation"}
15 | ],
16 | deps: deps()
17 | ]
18 | end
19 |
20 | # Run "mix help compile.app" to learn about applications.
21 | def application do
22 | [
23 | extra_applications: [:logger]
24 | ]
25 | end
26 |
27 | # Run "mix help deps" to learn about dependencies.
28 | defp deps do
29 | [{:ex_doc, "~> 0.10", only: :dev}]
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
3 | "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
4 | "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
5 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
6 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
7 | }
8 |
--------------------------------------------------------------------------------
/test/matrix_operation_test.exs:
--------------------------------------------------------------------------------
1 | defmodule MatrixOperationTest do
2 | use ExUnit.Case
3 |
4 | doctest MatrixOperation
5 | import MatrixOperation
6 |
7 | describe "inverse_matrix" do
8 | test "property" do
9 | a = [[1, 1, -1], [-2, -1, 1], [-1, -2, 1]]
10 | result = a
11 | |> inverse_matrix()
12 | |> product(a)
13 |
14 | expect = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
15 | assert result == expect
16 | end
17 | end
18 |
19 | @tag :skip
20 | describe "mp_inverse_matrix" do
21 | test "property" do
22 | a = [[1, 1, -1], [-2, -1, 1], [-1, -2, 1]]
23 | result = a
24 | |> mp_inverse_matrix()
25 | |> product(a)
26 |
27 | expect = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
28 | # TODO: The Moore-Penrose general inverse matrix does not match the inverse matrix
29 | assert result == expect
30 | end
31 | end
32 |
33 | describe "eigh" do
34 | test "property" do
35 | a = [[1, 4, 5], [4, 2, 6], [5, 6, 3]]
36 | {evals, evecs} = eigh(a)
37 | evals_matrix = [
38 | [Enum.at(evals, 0), 0, 0],
39 | [0, Enum.at(evals, 1), 0],
40 | [0, 0, Enum.at(evals, 2)]
41 | ]
42 | evecs_t = transpose(evecs)
43 | left = product(evals_matrix, evecs_t) |> Enum.map(& Enum.map(&1, fn x -> Float.round(x, 7) end))
44 | right = product(evecs_t, a) |> Enum.map(& Enum.map(&1, fn x -> Float.round(x, 7) end))
45 | assert left == right
46 | end
47 | end
48 |
49 | describe "eigen" do
50 | test "property" do
51 | a = [[1, 4, 5], [4, 2, 6], [5, 6, 3]]
52 | {evals, evecs} = eigen(a)
53 | evals_matrix = [
54 | [Enum.at(evals, 0), 0, 0],
55 | [0, Enum.at(evals, 1), 0],
56 | [0, 0, Enum.at(evals, 2)]
57 | ]
58 | left = product(evals_matrix, evecs) |> Enum.map(& Enum.map(&1, fn x -> Float.round(x, 7) end))
59 | right = product(evecs, a) |> Enum.map(& Enum.map(&1, fn x -> Float.round(x, 7) end))
60 | assert left == right
61 | end
62 | end
63 |
64 | describe "solve_sle" do
65 | test "property" do
66 | # Solve simultaneous linear equations: a.x = y
67 | a = [[4, 1, 1], [1, 3, 1], [2, 1, 5]]
68 | y = [[9], [10], [19]]
69 | insert_in_vec = fn x -> [x] end
70 | x = a
71 | |> solve_sle(y)
72 | |> insert_in_vec.()
73 | |> transpose()
74 | assert product(a, x) == y
75 | end
76 | end
77 |
78 | @tag :skip
79 | describe "svd" do
80 | test "property" do
81 | # Singular Value Decomposition: a = u.s.v_t
82 | a = [[4, 1, 2], [1, 3, 1], [2, 1, 5]]
83 | {sv, u, v} = svd(a)
84 | v_t = transpose(v)
85 | s = [
86 | [Enum.at(sv, 0), 0, 0],
87 | [0, Enum.at(sv, 1), 0],
88 | [0, 0, Enum.at(sv, 2)]
89 | ]
90 | result = u
91 | |> product(s)
92 | |> product(v_t)
93 | |> Enum.map(& Enum.map(&1, fn x -> Float.round(x, 7) end))
94 | # TODO: The signs don't match
95 | assert a == result
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------