├── .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 | matrix_operation_logo 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 | --------------------------------------------------------------------------------