├── .gitignore ├── LICENSE ├── README.md ├── moon.mod.json └── src ├── lib └── matrix │ ├── cholesky.mbt │ ├── det.mbt │ ├── det_Gauss.mbt │ ├── diagonal.mbt │ ├── eigen.mbt │ ├── inv.mbt │ ├── lstsq.mbt │ ├── matrix.mbt │ ├── matrix.mbti │ ├── moon.pkg.json │ ├── norm.mbt │ ├── qr.mbt │ ├── rref.mbt │ └── svd.mbt └── main ├── main.mbt ├── main.mbti └── moon.pkg.json /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .mooncakes/ 3 | src/lib/.DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linear Algebra Library 2 | 3 | This repository contains a linear algebra library implemented in Moonbit. The library provides various matrix operations such as addition, subtraction, multiplication, determinant calculation, and matrix inversion. 4 | 5 | ## Features 6 | - **Matrix Addition**: Add two matrices of the same dimensions. 7 | - **Matrix Subtraction**: Subtract one matrix from another of the same dimensions. 8 | - **Matrix Multiplication**: Multiply two matrices with compatible dimensions. 9 | - **Determinant Calculation**: Compute the determinant of a square matrix with two methods. 10 | - **Matrix Inversion**: Compute the inverse of a non-singular square matrix. 11 | - **Matrix Transposition**: Transpose a given matrix. 12 | - **Scalar Multiplication**: Multiply each element of a matrix by a scalar. 13 | - **Trace Calculation**: Compute the trace of a square matrix. 14 | - **Dot Product**: Calculate the dot product of two matrices of the same shape. 15 | - **Identity Matrix**: Create an identity matrix of a given size. 16 | - **Diagonal Matrix**: Create a diagonal matrix from a given array. 17 | - **Zero Matrix**: Create a matrix filled with zeros. 18 | - **Matrix Norms**: Calculate various norms for matrices and vectors. 19 | - **Matrix Slicing**: Extract submatrices from a given matrix. 20 | - **Matrix Reshaping**: Reshape a matrix to new dimensions. 21 | - **System of Linear Equations**: Solve systems of linear equations. 22 | ... And So On 23 | See More in [Mooncakes](https://mooncakes.io/docs/#/xunyoyo/linalg/) 24 | 25 | ## Usage 26 | 27 | ### Matrix Addition 28 | 29 | ```moonbit 30 | let a = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 31 | let b = new_matrix([[5.0, 6.0], [7.0, 8.0]]) 32 | let c = a + b 33 | ``` 34 | 35 | ### Matrix Inversion 36 | 37 | ```moonbit 38 | let mat = new_matrix([[4.0, 7.0], [2.0, 6.0]]) 39 | let inverse = mat.inv() 40 | ``` 41 | 42 | ### Determinant Calculation 43 | 44 | ```moonbit 45 | let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 46 | let determinant = m.det() 47 | ``` 48 | 49 | ## Examples 50 | 51 | ### Matrix Multiplication 52 | 53 | ```moonbit 54 | let a = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 55 | let b = new_matrix([[2.0, 0.0], [1.0, 2.0]]) 56 | let c = a * b 57 | ``` 58 | 59 | ### Transpose 60 | 61 | ```moonbit 62 | let m = new_matrix([[1, 2], [3, 4]]) 63 | let mt = m.transpose() 64 | ``` 65 | 66 | ### Scalar Multiplication 67 | 68 | ```moonbit 69 | let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 70 | let scaled = m.k(2.0) 71 | ``` 72 | See More Details in [Mooncakes](https://mooncakes.io/docs/#/xunyoyo/linalg/) 73 | 74 | ## License 75 | 76 | This project is licensed under the Apache-2.0 License. 77 | 78 | ## Contributing 79 | 80 | Contributions are welcome! Please open an issue or submit a pull request. 81 | 82 | ## Contact 83 | 84 | For any questions or suggestions, please contact [1279416582@qq.com]. 85 | -------------------------------------------------------------------------------- /moon.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xunyoyo/linalg", 3 | "version": "0.2.7", 4 | "readme": "README.md", 5 | "repository": "https://github.com/xunyoyo/linalg", 6 | "license": "Apache-2.0", 7 | "keywords": ["algebra", "math"], 8 | "description": "Linear Algebra Library", 9 | "source": "src" 10 | } -------------------------------------------------------------------------------- /src/lib/matrix/cholesky.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// Performs Cholesky decomposition of a positive-definite matrix. 3 | /// Decomposes a matrix A into the product L·L^T where L is a lower triangular matrix. 4 | /// 5 | /// Parameters: 6 | /// * `matrix`: Input matrix to decompose. Must be positive-definite. 7 | /// 8 | /// Returns a lower triangular matrix L such that A = L·L^T. 9 | /// 10 | /// Throws: 11 | /// * `MatrixShapeError` if the input matrix is not square. 12 | /// * `MatrixError` if the input matrix is not positive-definite. 13 | /// 14 | /// Example: 15 | /// ```moonbit 16 | /// test "cholesky/basic" { 17 | /// let a = new_matrix([[4.0, 12.0, -16.0], [12.0, 37.0, -43.0], [-16.0, -43.0, 98.0]]) 18 | /// let l = a.cholesky() 19 | /// 20 | /// // Verify L is lower triangular 21 | /// for i = 0; i < l.rows; i = i + 1 { 22 | /// for j = i + 1; j < l.cols; j = j + 1 { 23 | /// inspect!(l.data[i][j], content="0.0") 24 | /// } 25 | /// } 26 | /// 27 | /// // Verify A = L·L^T 28 | /// let lt = l.transpose() 29 | /// let a2 = l * lt 30 | /// inspect!(a2.approx_eq(a), content="true") 31 | /// } 32 | /// ``` 33 | pub fn cholesky(self : Matrix) -> Matrix { 34 | if self.rows != self.cols { 35 | abort("MatrixShapeError: Matrix must be square for Cholesky decomposition") 36 | } 37 | let n = self.rows 38 | let l = zero(n, n) 39 | for i = 0; i < n; i = i + 1 { 40 | for j = 0; j <= i; j = j + 1 { 41 | let mut sum = 0.0 42 | for k = 0; k < j; k = k + 1 { 43 | sum += l.data[i][k] * l.data[j][k] 44 | } 45 | if i == j { 46 | l.data[i][j] = Double::sqrt(self.data[i][i] - sum) 47 | } else { 48 | l.data[i][j] = (self.data[i][j] - sum) / l.data[j][j] 49 | } 50 | } 51 | if l.data[i][i] <= 0.0 { 52 | abort("MatrixError: Matrix is not positive-definite") 53 | } 54 | } 55 | l 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/matrix/det.mbt: -------------------------------------------------------------------------------- 1 | ///| Calculates the determinant of a square matrix using the Laplace expansion 2 | /// along the first row. 3 | /// 4 | /// Parameters: 5 | /// 6 | /// * `matrix`: A square matrix whose determinant needs to be calculated. 7 | /// 8 | /// Returns a Double value representing the determinant of the matrix. 9 | /// 10 | /// Example: 11 | /// 12 | /// ```moonbit 13 | /// test "determinant" { 14 | /// let m1 = Matrix::new(1, 1) 15 | /// m1.data[0][0] = 5.0 16 | /// inspect!(m1.det(), content="5.0") 17 | /// 18 | /// let m2 = Matrix::new(2, 2) 19 | /// m2.data[0][0] = 1.0 20 | /// m2.data[0][1] = 2.0 21 | /// m2.data[1][0] = 3.0 22 | /// m2.data[1][1] = 4.0 23 | /// inspect!(m2.det(), content="-2.0") // 1*4 - 2*3 = -2 24 | /// } 25 | /// ``` 26 | pub fn det(self : Matrix) -> Double { 27 | if self.rows == 1 { 28 | return self.data[0][0] 29 | } 30 | if self.rows == 2 { 31 | return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0] 32 | } 33 | let mut det = 0.0 34 | for col = 0; col < self.cols; col = col + 1 { 35 | let sub_matrix = self.subMatrix(0, col) 36 | let sign = if col % 2 == 0 { 1.0 } else { -1.0 } 37 | det += sign * self.data[0][col] * sub_matrix.det() 38 | } 39 | det 40 | } 41 | 42 | ///| 43 | fn subMatrix(self : Matrix, exclude_row : Int, exclude_col : Int) -> Matrix { 44 | let sub_data = Array::makei(self.rows - 1, fn(row : Int) -> Array[Double] { 45 | let actual_row = if row < exclude_row { row } else { row + 1 } 46 | Array::makei(self.cols - 1, fn(col : Int) -> Double { 47 | let actual_col = if col < exclude_col { col } else { col + 1 } 48 | self.data[actual_row][actual_col] 49 | }) 50 | }) 51 | new_matrix(sub_data) 52 | } 53 | 54 | // fn main { 55 | // let m1 = Matrix::new(1, 1) 56 | // m1.data[0][0] = 5.0 57 | // println(m1.det()) 58 | // // inspect!(m1.det(), content="5.0") 59 | 60 | // let m2 = Matrix::new(2, 2) 61 | // m2.data[0][0] = 1.0 62 | // m2.data[0][1] = 2.0 63 | // m2.data[1][0] = 3.0 64 | // m2.data[1][1] = 4.0 65 | // // inspect!(m2.det(), content="-2.0") // 1*4 - 2*3 = -2 66 | // println(m2.det()) 67 | // } 68 | -------------------------------------------------------------------------------- /src/lib/matrix/det_Gauss.mbt: -------------------------------------------------------------------------------- 1 | ///| Calculates the determinant of a square matrix using the Laplace expansion 2 | /// along the first row. 3 | /// 4 | /// Parameters: 5 | /// 6 | /// * `matrix`: A square matrix whose determinant needs to be calculated. 7 | /// 8 | /// Returns a Double value representing the determinant of the matrix. 9 | /// 10 | /// Example: 11 | /// 12 | fn swap_array(arr1 : Array[Double], arr2 : Array[Double], lens : Int) -> Unit { 13 | for i = 0; i < lens; i = i + 1 { 14 | let temp = arr1[i] 15 | arr1[i] = arr2[i] 16 | arr2[i] = temp 17 | } 18 | } 19 | 20 | ///| 21 | pub fn det_Gauss(self : Matrix) -> Double { 22 | if self.cols == 1 { 23 | return self.data[0][0] 24 | } 25 | let now : Matrix = new_matrix(self.data) 26 | let mut res = 1.0 27 | for i = 0; i < self.cols; i = i + 1 { 28 | if now.data[i][i] == 0 { 29 | let mut pos = i 30 | for j = i + 1; j < self.cols; j = j + 1 { 31 | if now.data[j][i] > now.data[pos][i] { 32 | pos = j 33 | } 34 | } 35 | if res == 1.0 { 36 | res = -1.0 37 | } else { 38 | res = 1.0 39 | } 40 | swap_array(now.data[i], now.data[pos], self.cols) 41 | } 42 | if now.data[i][i] == 0 { 43 | return 0 44 | } 45 | for j = i + 1; j < self.cols; j = j + 1 { 46 | let div = now.data[j][i] / now.data[i][i] 47 | // let div = now.data[i][i] / now.data[j][i] 48 | for k = i; k < self.cols; k = k + 1 { 49 | now.data[j][k] -= now.data[i][k] * div 50 | } 51 | } 52 | } 53 | for i = 0; i < self.cols; i = i + 1 { 54 | res = res * now.data[i][i] 55 | } 56 | res 57 | } 58 | 59 | // test "Gauss" { 60 | // let m1 = new_matrix([[1, 0, 4], [0, 5, 7], [7, 5, 0.2]]) 61 | // // println(det(m1)) 62 | // // println(det_Gauss(m1)) 63 | // // println(det(m1)) 64 | // assert_eq!(det_Gauss(m1), det(m1)) 65 | // } 66 | ///| 67 | // fn main { 68 | // let m1 = Matrix::new(1, 1) 69 | // m1.data[0][0] = 5.0 70 | // println(m1.det_Gauss()) 71 | // // inspect!(m1.det(), content="5.0") 72 | 73 | // let m2 = Matrix::new(2, 2) 74 | // m2.data[0][0] = 1.0 75 | // m2.data[0][1] = 2.0 76 | // m2.data[1][0] = 3.0 77 | // m2.data[1][1] = 4.0 78 | // // inspect!(m2.det(), content="-2.0") // 1*4 - 2*3 = -2 79 | // println(m2.det_Gauss()) 80 | // } 81 | -------------------------------------------------------------------------------- /src/lib/matrix/diagonal.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | pub fn diagonal(matrix : Matrix, offset~ : Int = 0) -> Array[Double] { 3 | // 计算对角线长度 4 | let diag_length = if offset >= 0 { 5 | @math.minimum(matrix.rows, matrix.cols - offset) 6 | } else { 7 | @math.minimum(matrix.rows + offset, matrix.cols) 8 | } 9 | 10 | // 创建数组存储对角线元素 11 | let result = Array::new(capacity=diag_length) 12 | 13 | // 提取对角线元素 14 | for i = 0; i < diag_length; i = i + 1 { 15 | if offset >= 0 { 16 | result.push(matrix.data[i][i + offset]) 17 | } else { 18 | result.push(matrix.data[i - offset][i]) 19 | } 20 | } 21 | result 22 | } 23 | 24 | ///| 25 | test "diagonal extraction" { 26 | let m = new_matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) 27 | 28 | // 主对角线 29 | let d0 = diagonal(m) 30 | @json.inspect!(d0, content=[1.0, 5.0, 9.0]) 31 | 32 | // 上对角线 33 | let d1 = diagonal(m, offset=1) 34 | @json.inspect!(d1, content=[2.0, 6.0]) 35 | 36 | // 下对角线 37 | let d2 = diagonal(m, offset=-1) 38 | @json.inspect!(d2, content=[4.0, 8.0]) 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/matrix/eigen.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// Computes eigenvalues and eigenvectors of a square matrix using QR algorithm 3 | /// with implicit shifts. The QR algorithm iteratively decomposes the matrix into 4 | /// a product of an orthogonal matrix Q and an upper triangular matrix R, 5 | /// converging to a diagonal matrix containing eigenvalues. 6 | /// 7 | /// Parameters: 8 | /// 9 | /// * `matrix` : A square matrix whose eigenvalues and eigenvectors are to be 10 | /// computed. 11 | /// * `max_iterations` : Maximum number of iterations for the QR algorithm. 12 | /// Defaults to 1000. 13 | /// * `tolerance` : Convergence tolerance for off-diagonal elements. The 14 | /// algorithm stops when the Frobenius norm of off-diagonal elements is less than 15 | /// this value. Defaults to 1e-10. 16 | /// 17 | /// Returns a tuple `(eigenvalues, eigenvectors)` where: 18 | /// 19 | /// * `eigenvalues` : An array of Double values representing the eigenvalues of 20 | /// the matrix 21 | /// * `eigenvectors` : A matrix whose columns are the corresponding eigenvectors 22 | /// 23 | /// Throws "MatrixShapeError" if the input matrix is not square. 24 | /// 25 | /// Examples: 26 | /// 27 | /// ```moonbit 28 | /// test "eigen/symmetric" { 29 | /// // Create a 2x2 symmetric matrix 30 | /// let a = new_matrix([[3.0, 0.0], [0.0, 4.0]]) 31 | /// let (eigenvalues, eigenvectors) = a.eigen() 32 | /// // Eigenvalues should be 3.0 and 4.0 33 | /// inspect!(eigenvalues, content="[3.0, 4.0]") 34 | /// } 35 | /// 36 | /// test "panic eigen/non_square" { 37 | /// let a = new_matrix([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) 38 | /// panic!(a.eigen()) 39 | /// } 40 | /// ``` 41 | pub fn eigen( 42 | self : Matrix, 43 | max_iterations~ : Int = 1000, 44 | tol~ : Double = 0.0000000001 45 | ) -> (Array[Double], Matrix)!Error { 46 | if self.rows != self.cols { 47 | abort("MatrixShapeError: Matrix must be square to compute eigenvalues") 48 | } 49 | let n = self.rows 50 | let mut a = self.copy() 51 | let mut v = eye(n) 52 | for iter = 0; iter < max_iterations; iter = iter + 1 { 53 | let (q, r) = a.qr!() 54 | a = r * q 55 | v = v * q 56 | let mut off_diag_norm = 0.0 57 | for i = 0; i < n; i = i + 1 { 58 | for j = 0; j < n; j = j + 1 { 59 | if i != j { 60 | off_diag_norm += a.data[i][j] * a.data[i][j] 61 | } 62 | } 63 | } 64 | off_diag_norm = Double::sqrt(off_diag_norm) 65 | if off_diag_norm < tol { 66 | break 67 | } 68 | } 69 | let eigenvalues = Array::makei(n, fn(i : Int) -> Double { a.data[i][i] }) 70 | let eigenvectors = v 71 | (eigenvalues, eigenvectors) 72 | } 73 | -------------------------------------------------------------------------------- /src/lib/matrix/inv.mbt: -------------------------------------------------------------------------------- 1 | ///| Computes the inverse of a square matrix using Gauss-Jordan elimination 2 | /// method. 3 | /// 4 | /// Parameters: 5 | /// 6 | /// * `matrix` : A square matrix to be inverted. Must be a non-singular matrix 7 | /// with equal number of rows and columns. 8 | /// 9 | /// Returns a new matrix that is the inverse of the input matrix. 10 | /// 11 | /// Throws a runtime error if: 12 | /// 13 | /// * The input matrix is not square (rows ≠ columns) 14 | /// * The input matrix is singular (determinant = 0) 15 | /// 16 | /// Examples: 17 | /// 18 | /// ```moonbit 19 | /// test "matrix_inverse" { 20 | /// // Create a 2x2 matrix 21 | /// let mat = new_matrix([[4.0, 7.0], [2.0, 6.0]]) 22 | /// let inverse = mat.inv() 23 | /// 24 | /// // Expected inverse matrix 25 | /// let expected = new_matrix([[0.6, -0.7], [-0.2, 0.4]]) 26 | /// 27 | /// // Check if the inverse is correct by multiplying with original matrix 28 | /// let product = mat * inverse 29 | /// let identity = eye(2) 30 | /// 31 | /// // The product should be approximately the identity matrix 32 | /// inspect!(product.approx_eq(identity), content="true") 33 | /// } 34 | /// 35 | /// test "matrix_inverse_error" { 36 | /// // Create a non-square matrix 37 | /// let mat = new_matrix([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) 38 | /// 39 | /// // Attempting to invert a non-square matrix should panic 40 | /// panic!(mat.inv()) 41 | /// } 42 | /// 43 | /// test "matrix_inverse_singular" { 44 | /// // Create a singular matrix 45 | /// let mat = new_matrix([[1.0, 2.0], [2.0, 4.0]]) 46 | /// 47 | /// // Attempting to invert a singular matrix should panic 48 | /// panic!(mat.inv()) 49 | /// } 50 | /// ``` 51 | pub fn inv(self : Matrix) -> Matrix!Error { 52 | if self.rows != self.cols { 53 | raise MatrixShapeError("The matrix must be square to compute its inverse") 54 | } 55 | let dimension = self.rows 56 | let matrix_copy = self.data.copy() 57 | let inv = eye(dimension).data 58 | for i = 0; i < dimension; i = i + 1 { 59 | let mut pivot = matrix_copy[i][i] 60 | if pivot == 0.0 { 61 | for j = i + 1; j < dimension; j = j + 1 { 62 | if matrix_copy[j][i] != 0.0 { 63 | let temp = matrix_copy[i] 64 | matrix_copy[i] = matrix_copy[j] 65 | matrix_copy[j] = temp 66 | let temp_inv = inv[i] 67 | inv[i] = inv[j] 68 | inv[j] = temp_inv 69 | pivot = matrix_copy[i][i] 70 | break 71 | } 72 | } 73 | if pivot == 0.0 { 74 | raise MatrixShapeError("The matrix is singular and cannot be inverted") 75 | } 76 | } 77 | for j = 0; j < dimension; j = j + 1 { 78 | matrix_copy[i][j] /= pivot 79 | inv[i][j] /= pivot 80 | } 81 | for j = 0; j < dimension; j = j + 1 { 82 | if i != j { 83 | let factor = matrix_copy[j][i] 84 | for k = 0; k < dimension; k = k + 1 { 85 | matrix_copy[j][k] -= factor * matrix_copy[i][k] 86 | inv[j][k] -= factor * inv[i][k] 87 | } 88 | } 89 | } 90 | } 91 | new_matrix(inv) 92 | } 93 | 94 | // fn main { 95 | // let m = new_matrix([[0, 1],[2, 0]]) 96 | // let inv_m = m.inv() 97 | // println("Original Matrix: " + m.data.to_string()) 98 | // println("Inverse Matrix: " + inv_m.data.to_string()) 99 | // } 100 | -------------------------------------------------------------------------------- /src/lib/matrix/lstsq.mbt: -------------------------------------------------------------------------------- 1 | ///| Computes the least-squares solution to a linear matrix equation. 2 | /// 3 | /// Parameters: 4 | /// * `a`: (M, N) array - "Coefficient" matrix 5 | /// * `b`: (M,) array - Ordinate ("dependent variable") values 6 | /// 7 | /// Returns: 8 | /// * The least-squares solution to the equation ax = b 9 | /// 10 | /// Example: 11 | /// ```moonbit 12 | /// test "lstsq/basic" { 13 | /// // Solve the overdetermined system: 14 | /// // 1x + 1y = 2 15 | /// // 2x + 1y = 3 16 | /// // 1x + 2y = 4 17 | /// let a = new_matrix([ 18 | /// [1.0, 1.0], 19 | /// [2.0, 1.0], 20 | /// [1.0, 2.0] 21 | /// ]) 22 | /// let b = new_matrix([[2.0], [3.0], [4.0]]) 23 | /// 24 | /// let x = lstsq(a, b) 25 | /// // Expected solution: x ≈ 1, y ≈ 1.5 26 | /// inspect!(x.data[0][0], content="1.0000000000000002") 27 | /// inspect!(x.data[1][0], content="1.4999999999999998") 28 | /// } 29 | /// ``` 30 | pub fn lstsq(a : Matrix, b : Matrix) -> Matrix!Error { 31 | if a.rows < a.cols { 32 | abort("MatrixShapeError: System is underdetermined") 33 | } 34 | if a.rows != b.rows { 35 | abort("MatrixShapeError: Incompatible dimensions") 36 | } 37 | 38 | // 计算QR分解 39 | let (q, r) = a.qr!() 40 | 41 | // 计算 Q^T * b 42 | let qtb = q.transpose() * b 43 | 44 | // 解上三角系统 Rx = Q^T b 45 | // 只需要前n行,其中n是a的列数 46 | let n = a.cols 47 | let x = zero(n, 1) 48 | 49 | // Back substitution 50 | for i = n - 1; i >= 0; i = i - 1 { 51 | let mut sum = qtb.data[i][0] 52 | for j = i + 1; j < n; j = j + 1 { 53 | sum -= r.data[i][j] * x.data[j][0] 54 | } 55 | x.data[i][0] = sum / r.data[i][i] 56 | } 57 | x 58 | } 59 | 60 | ///| 61 | test { 62 | // 构造超定方程组: 63 | // 1x + 2y = 4 64 | // 2x + 1y = 5 65 | // 1x + 1y = 3 66 | let a = new_matrix([[1.0, 2.0], [2.0, 1.0], [1.0, 1.0]]) 67 | let b = new_matrix([[4.0], [5.0], [3.0]]) 68 | let x = lstsq!(a, b) 69 | @json.inspect!(x, content=[[2.0], [1.0]]) 70 | 71 | // 验证解的正确性: Ax ≈ b 72 | let ax = a * x 73 | @json.inspect!(ax, content=[[4.0], [5.0], [3.0]]) 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/matrix/matrix.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | struct Matrix { 3 | rows : Int 4 | cols : Int 5 | data : Array[Array[Double]] 6 | } 7 | 8 | ///| 9 | impl Show for Matrix with output(self : Matrix, logger : &Logger) -> Unit { 10 | Show::output(self.data, logger) 11 | } 12 | 13 | ///| 14 | impl ToJson for Matrix with to_json(self : Matrix) -> Json { 15 | self.data.to_json() 16 | } 17 | 18 | ///| 19 | type! MatrixShapeError String derive(Show) 20 | 21 | ///| 22 | type! SliceError String derive(Show) 23 | 24 | // 未来会实现的功能 25 | // struct MatrixView { 26 | // matrix : Matrix 27 | // start : Int 28 | // end : Int 29 | // rows : Int 30 | // cols : Int 31 | // } 32 | 33 | ///| 34 | fn op_add(self : Matrix, other : Matrix) -> Matrix { 35 | if self.rows != other.rows || self.cols != other.cols { 36 | abort( 37 | "MatrixShapeError: Matrices must have the same dimensions for addition", 38 | ) 39 | } 40 | for i = 0; i < self.rows; i = i + 1 { 41 | for j = 0; j < self.cols; j = j + 1 { 42 | self.data[i][j] += other.data[i][j] 43 | } 44 | } 45 | self 46 | } 47 | 48 | ///| 49 | fn op_eq(self : Matrix, other : Matrix) -> Bool { 50 | if self.rows != other.rows || self.cols != other.cols { 51 | false 52 | } else { 53 | for i = 0; i < self.rows; i = i + 1 { 54 | for j = 0; j < self.cols; j = j + 1 { 55 | if self.data[i][j] != other.data[i][j] { 56 | return false 57 | } 58 | } 59 | } 60 | true 61 | } 62 | } 63 | 64 | ///| 65 | fn op_sub(self : Matrix, other : Matrix) -> Matrix { 66 | if self.rows != other.rows || self.cols != other.cols { 67 | abort( 68 | "MatrixShapeError: Matrices must have the same dimensions for subtraction", 69 | ) 70 | } 71 | for i = 0; i < self.rows; i = i + 1 { 72 | for j = 0; j < self.cols; j = j + 1 { 73 | self.data[i][j] -= other.data[i][j] 74 | } 75 | } 76 | self 77 | } 78 | 79 | ///| 80 | impl Mul for Matrix with op_mul(self : Matrix, other : Matrix) -> Matrix { 81 | if self.cols != other.rows { 82 | abort("MatrixShapeError: Incompatible dimensions for matrix multiplication") 83 | } 84 | let result_data = make_matrix(self.rows, other.cols) 85 | for i = 0; i < self.rows; i = i + 1 { 86 | for j = 0; j < other.cols; j = j + 1 { 87 | let mut sum = 0.0 88 | for k = 0; k < self.cols; k = k + 1 { 89 | sum += self.data[i][k] * other.data[k][j] 90 | } 91 | result_data[i][j] = sum 92 | } 93 | } 94 | new_matrix(result_data) 95 | } 96 | 97 | ///| 98 | /// Computes the dot product of two matrices. If the matrices have different 99 | /// shapes, they will be flattened into 1D vectors before computation. The dot 100 | /// product is calculated as the sum of element-wise products of the flattened 101 | /// matrices. 102 | /// 103 | /// Parameters: 104 | /// 105 | /// * `self` : The first matrix operand. 106 | /// * `other` : The second matrix operand to compute the dot product with. 107 | /// 108 | /// Returns a scalar value representing the dot product of the two matrices. 109 | /// 110 | /// Throws "MatrixShapeError" if the total number of elements in the two matrices 111 | /// are different after flattening. 112 | /// 113 | /// Examples: 114 | /// 115 | /// ```moonbit 116 | /// // Same shape vectors 117 | /// let a = Matrix::new_matrix([[1.0, 2.0, 3.0]]) 118 | /// let b = Matrix::new_matrix([[2.0, 3.0, 4.0]]) 119 | /// a.dot(b) // 1*2 + 2*3 + 3*4 = 20 120 | /// 121 | /// // Different shapes but same total elements 122 | /// let a = Matrix::new_matrix([[1.0, 2.0], [3.0, 4.0]]) // 2x2 matrix 123 | /// let b = Matrix::new_matrix([[1.0, 2.0, 3.0, 4.0]]) // 1x4 vector 124 | /// a.dot(b) // 1*1 + 2*2 + 3*3 + 4*4 = 30 125 | /// 126 | /// let a = Matrix::new_matrix([[1.0, 2.0, 3.0]]) 127 | /// let b = Matrix::new_matrix([[1.0, 2.0]]) 128 | /// a.dot(b) // Different total elements 129 | /// ``` 130 | pub fn dot(self : Matrix, b : Matrix) -> Double!MatrixShapeError { 131 | let self = self.flat() 132 | let b = b.flat() 133 | if self.shape() != b.shape() { 134 | raise MatrixShapeError( 135 | "Matrices must have the same dimensions for dot product", 136 | ) 137 | } 138 | let mut ans = 0.0 139 | for i = 0; i < self.cols; i = i + 1 { 140 | ans += self.data[0][i] * b.data[0][i] 141 | } 142 | ans 143 | } 144 | 145 | ///| 146 | /// Computes the dot product of two matrices. If the matrices have different 147 | /// shapes, they will be flattened into 1D vectors before computation. The dot 148 | /// product is calculated as the sum of element-wise products of the flattened 149 | /// matrices. 150 | /// 151 | /// Parameters: 152 | /// 153 | /// * `self` : The first matrix operand. 154 | /// * `other` : The second matrix operand to compute the dot product with. 155 | /// 156 | /// Returns a scalar value representing the dot product of the two matrices. 157 | /// 158 | /// Throws "MatrixShapeError" if the total number of elements in the two matrices 159 | /// are different after flattening. 160 | /// 161 | /// Examples: 162 | /// 163 | /// ```moonbit 164 | /// let a = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 165 | /// let b = new_matrix([[2.0, 3.0], [4.0, 5.0]]) 166 | /// // 1*2 + 2*3 + 3*4 + 4*5 = 40 167 | /// inspect!(a.vdot!(b), content="40") 168 | /// ``` 169 | pub fn vdot(self : Matrix, b : Matrix) -> Double!MatrixShapeError { 170 | if self.shape() != b.shape() { 171 | raise MatrixShapeError( 172 | "Matrices must have the same dimensions for dot product", 173 | ) 174 | } 175 | let mut ans = 0.0 176 | for i = 0; i < self.rows; i = i + 1 { 177 | for j = 0; j < self.cols; j = j + 1 { 178 | ans += self.data[i][j] * b.data[i][j] 179 | } 180 | } 181 | ans 182 | } 183 | 184 | ///| 185 | test "vdot" { 186 | let a = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 187 | let b = new_matrix([[2.0, 3.0], [4.0, 5.0]]) 188 | // 1*2 + 2*3 + 3*4 + 4*5 = 40 189 | inspect!(a.vdot!(b), content="40") 190 | } 191 | 192 | ///| 193 | /// Computes the trace of the matrix, which is the sum of the elements on the main diagonal 194 | /// or a diagonal offset from the main diagonal. 195 | /// 196 | /// Parameters: 197 | /// 198 | /// * `self` : The matrix to compute the trace of. 199 | /// * `offset` : The offset from the main diagonal. A positive value refers to an upper diagonal, 200 | /// and a negative value refers to a lower diagonal. 201 | /// 202 | /// Returns a scalar value representing the trace of the matrix. 203 | /// 204 | /// Examples: 205 | /// 206 | /// ```moonbit 207 | /// let a = Matrix::new_matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) 208 | /// a.trace(0) // 1 + 5 + 9 = 15 209 | /// a.trace(1) // 2 + 6 = 8 210 | /// a.trace(-1) // 4 + 8 = 12 211 | /// ``` 212 | pub fn trace(self : Matrix, offset : Int) -> Double { 213 | let mut ans = 0.0 214 | for i = 0; i < self.rows; i = i + 1 { 215 | let j = i + offset 216 | if j >= 0 && j < self.cols { 217 | ans += self.data[i][j] 218 | } 219 | } 220 | ans 221 | } 222 | 223 | ///| 224 | test "trace" { 225 | let m = new_matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) 226 | // Trace of the main diagonal 227 | inspect!(m.trace(0), content="15") 228 | // Trace of the first superdiagonal 229 | inspect!(m.trace(1), content="8") 230 | // Trace of the first subdiagonal 231 | inspect!(m.trace(-1), content="12") 232 | // Trace of the second superdiagonal 233 | inspect!(m.trace(2), content="3") 234 | // Trace of the second subdiagonal 235 | inspect!(m.trace(-2), content="7") 236 | } 237 | 238 | ///| 239 | pub fn shape(self : Matrix) -> (Int, Int) { 240 | (self.rows, self.cols) 241 | } 242 | 243 | ///| Computes the trace of a square matrix, which is the sum of elements on the 244 | /// main diagonal (from top-left to bottom-right). 245 | /// 246 | /// Parameters: 247 | /// 248 | /// * `matrix` : A square matrix represented as a `Matrix` instance. The number 249 | /// of rows must equal the number of columns. 250 | /// 251 | /// Returns the trace of the matrix as a `Double`. 252 | /// 253 | /// Throws: 254 | /// The function aborts with a "MatrixShapeError" message if the input matrix is 255 | /// not square (i.e., number of rows != number of columns). 256 | /// 257 | /// Examples: 258 | /// 259 | /// ```moonbit 260 | /// test "matrix_trace" { 261 | /// let m = new_matrix(2, 2) 262 | /// m.data[0][0] = 1.0 263 | /// m.data[0][1] = 2.0 264 | /// m.data[1][0] = 3.0 265 | /// m.data[1][1] = 4.0 266 | /// 267 | /// // Trace should be 1.0 + 4.0 = 5.0 268 | /// inspect!(m.tr(), content="5.0") 269 | /// } 270 | /// 271 | /// test "panic_non_square_matrix" { 272 | /// let m = new_matrix(2, 3) 273 | /// // Should panic when matrix is not square 274 | /// inspect!(m.tr(), content="MatrixShapeError: Matrix must be square to compute trace") 275 | /// } 276 | /// ``` 277 | pub fn tr(self : Matrix) -> Double!MatrixShapeError { 278 | if self.cols != self.rows { 279 | raise MatrixShapeError("Matrix must be square to compute trace") 280 | } 281 | let mut ans = 0.0 282 | for i = 0; i < self.cols; i = i + 1 { 283 | ans += self.data[i][i] 284 | } 285 | ans 286 | } 287 | 288 | ///| Multiplies each element of a matrix by a scalar value, modifying the matrix 289 | /// in place. 290 | /// 291 | /// Parameters: 292 | /// 293 | /// * `matrix`: The matrix to be scaled. The operation will modify this matrix 294 | /// directly. 295 | /// * `scalar`: A floating-point number to multiply with each element of the 296 | /// matrix. 297 | /// 298 | /// Returns the modified matrix after scaling. 299 | /// 300 | /// Example: 301 | /// 302 | /// ```moonbit 303 | /// test "matrix_scalar_multiplication" { 304 | /// let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 305 | /// let result = m.k(2.0) 306 | /// inspect!(result.data, content="[[2.0, 4.0], [6.0, 8.0]]") 307 | /// // The original matrix is modified 308 | /// inspect!(m.data, content="[[2.0, 4.0], [6.0, 8.0]]") 309 | /// } 310 | /// ``` 311 | pub fn k(self : Matrix, k : Double) -> Matrix { 312 | self.apply(fn(x : Double) { x * k }) 313 | } 314 | 315 | ///| Transposes a matrix by swapping its rows and columns. 316 | /// 317 | /// Parameters: 318 | /// 319 | /// * `matrix`: A matrix to be transposed. The input matrix must be valid, i.e., 320 | /// all rows must have the same length. 321 | /// 322 | /// Returns a new matrix where rows and columns are swapped from the input 323 | /// matrix. 324 | /// 325 | /// Examples: 326 | /// 327 | /// ```moonbit 328 | /// test "transpose/square" { 329 | /// let m = new_matrix([[1, 2], [3, 4]]) 330 | /// let mt = transpose(m) 331 | /// inspect!(mt.data, content="[[1, 3], [2, 4]]") 332 | /// } 333 | /// 334 | /// test "transpose/rectangular" { 335 | /// let m = new_matrix([[1, 2, 3], [4, 5, 6]]) 336 | /// let mt = transpose(m) 337 | /// inspect!(mt.data, content="[[1, 4], [2, 5], [3, 6]]") 338 | /// } 339 | /// 340 | /// test "transpose/twice" { 341 | /// // Transposing a matrix twice returns the original matrix 342 | /// let m = new_matrix([[1, 2], [3, 4]]) 343 | /// let mtt = transpose(transpose(m)) 344 | /// inspect!(mtt.data, content="[[1, 2], [3, 4]]") 345 | /// } 346 | /// ``` 347 | pub fn transpose(self : Matrix) -> Matrix { 348 | let ans = make_matrix(self.cols, self.rows) 349 | for i = 0; i < self.rows; i = i + 1 { 350 | for j = 0; j < self.cols; j = j + 1 { 351 | ans[j][i] = self.data[i][j] 352 | } 353 | } 354 | new_matrix(ans) 355 | } 356 | 357 | ///| Creates a new matrix of the specified size with all elements initialized to 358 | /// zero. 359 | /// 360 | /// Parameters: 361 | /// 362 | /// * `rows`: The number of rows in the matrix. 363 | /// * `cols`: The number of columns in the matrix. 364 | /// 365 | /// Returns a new `Matrix` with all elements set to zero. 366 | /// 367 | /// Example: 368 | /// 369 | /// ```moonbit 370 | /// test "zero matrix" { 371 | /// let m = zero(2, 3) 372 | /// inspect!(m.rows(), content="2") 373 | /// inspect!(m.cols(), content="3") 374 | /// inspect!(m[0][0], content="0") 375 | /// inspect!(m[1][2], content="0") 376 | /// } 377 | /// ``` 378 | pub fn zero(rows : Int, cols : Int) -> Matrix { 379 | let matrix = make_matrix(rows, cols) 380 | for i = 0; i < rows; i = i + 1 { 381 | for j = 0; j < cols; j = j + 1 { 382 | matrix[i][j] = 0 383 | } 384 | } 385 | new_matrix(matrix) 386 | } 387 | 388 | ///| Creates a square identity matrix of the specified size. 389 | /// 390 | /// Parameters: 391 | /// 392 | /// * `size`: The dimension of the square matrix. Must be a positive integer. 393 | /// 394 | /// Returns a `Matrix` representing an identity matrix where all elements are 0.0 395 | /// except for the diagonal elements which are 1.0. 396 | /// 397 | /// Examples: 398 | /// 399 | /// ```moonbit 400 | /// test "eye/2x2" { 401 | /// let matrix = eye(2) 402 | /// inspect!(matrix[0][0], content="1.0") 403 | /// inspect!(matrix[0][1], content="0.0") 404 | /// inspect!(matrix[1][0], content="0.0") 405 | /// inspect!(matrix[1][1], content="1.0") 406 | /// } 407 | /// 408 | /// test "eye/3x3" { 409 | /// let matrix = eye(3) 410 | /// // Check diagonal elements 411 | /// inspect!(matrix[0][0], content="1.0") 412 | /// inspect!(matrix[1][1], content="1.0") 413 | /// inspect!(matrix[2][2], content="1.0") 414 | /// // Check non-diagonal elements 415 | /// inspect!(matrix[0][1], content="0.0") 416 | /// inspect!(matrix[1][2], content="0.0") 417 | /// inspect!(matrix[2][0], content="0.0") 418 | /// } 419 | /// ``` 420 | pub fn eye(size : Int) -> Matrix { 421 | let matrix = make_matrix(size, size) 422 | for i = 0; i < size; i = i + 1 { 423 | for j = 0; j < size; j = j + 1 { 424 | if i != j { 425 | matrix[i][j] = 0.0 426 | } else { 427 | matrix[i][j] = 1.0 428 | } 429 | } 430 | } 431 | new_matrix(matrix) 432 | } 433 | 434 | ///| Creates a square diagonal matrix from a given array, where the diagonal 435 | /// elements are taken from the array and all other elements are set to zero. 436 | /// 437 | /// Parameters: 438 | /// 439 | /// * `array` : An array of double-precision floating-point numbers that will 440 | /// form the diagonal elements of the matrix. 441 | /// 442 | /// Returns a `Matrix` structure where: 443 | /// 444 | /// * The diagonal elements are filled with values from the input array 445 | /// * All non-diagonal elements are set to 0.0 446 | /// * The dimensions (rows and columns) are equal to the length of the input 447 | /// array 448 | /// 449 | /// Example: 450 | /// 451 | /// ```moonbit 452 | /// test "diagonal_matrix" { 453 | /// let arr = [1.0, 2.0, 3.0] 454 | /// let matrix = diag(arr) 455 | /// 456 | /// // Check dimensions 457 | /// inspect!(matrix.rows, content="3") 458 | /// inspect!(matrix.cols, content="3") 459 | /// 460 | /// // Check diagonal elements 461 | /// inspect!(matrix.data[0][0], content="1.0") 462 | /// inspect!(matrix.data[1][1], content="2.0") 463 | /// inspect!(matrix.data[2][2], content="3.0") 464 | /// 465 | /// // Check non-diagonal elements 466 | /// inspect!(matrix.data[0][1], content="0.0") 467 | /// inspect!(matrix.data[1][0], content="0.0") 468 | /// inspect!(matrix.data[1][2], content="0.0") 469 | /// inspect!(matrix.data[2][1], content="0.0") 470 | /// } 471 | /// ``` 472 | pub fn diag(arr : Array[Double]) -> Matrix { 473 | let size = arr.length() 474 | let matrix = make_matrix(size, size) 475 | for i = 0; i < size; i = i + 1 { 476 | for j = 0; j < size; j = j + 1 { 477 | if i != j { 478 | matrix[i][j] = 0.0 479 | } else { 480 | matrix[i][j] = arr[i] 481 | } 482 | } 483 | } 484 | { rows: size, cols: size, data: matrix } 485 | } 486 | 487 | ///| Prints each element of the matrix to the standard output, with each element 488 | /// on a new line. 489 | /// 490 | /// Parameters: 491 | /// 492 | /// * `matrix`: A `Matrix` instance to be printed. 493 | /// 494 | /// Example: 495 | /// 496 | /// ```moonbit 497 | /// test "print_matrix" { 498 | /// let m = new_matrix(2, 2) 499 | /// print(m) 500 | /// } 501 | /// ``` 502 | pub fn print(self : Matrix) -> Unit { 503 | for item in self.data { 504 | println(item) 505 | } 506 | } 507 | 508 | ///| Solves a system of linear equations represented by matrices A and B in the 509 | /// form AX = B, where X is the solution matrix. 510 | /// Returns the solution matrix X by computing X = A^(-1)B. 511 | /// 512 | /// Parameters: 513 | /// 514 | /// * `coefficient_matrix`: A square matrix A containing the coefficients of the 515 | /// system of linear equations. 516 | /// * `constant_matrix`: A matrix B containing the constant terms of the system. 517 | /// 518 | /// Returns the solution matrix X that satisfies AX = B. 519 | /// 520 | /// Throws an error with message "MatrixShapeError: Incompatible dimensions for 521 | /// solving the system of equations" if: 522 | /// 523 | /// * The coefficient matrix is not square (rows ≠ columns) 524 | /// * The dimensions of the constant matrix are incompatible with the coefficient 525 | /// matrix 526 | /// 527 | /// Example: 528 | /// 529 | /// ```moonbit 530 | /// test "solve/simple_system" { 531 | /// // Solve the system: 532 | /// // 2x + y = 5 533 | /// // x + 3y = 10 534 | /// let a = new_matrix([ 535 | /// [2.0, 1.0], 536 | /// [1.0, 3.0] 537 | /// ]) 538 | /// let b = new_matrix([ 539 | /// [5.0], 540 | /// [10.0] 541 | /// ]) 542 | /// let x = solve(a, b) 543 | /// // Expected solution: x = 1, y = 3 544 | /// inspect!(x.rows, content="2") 545 | /// inspect!(x.cols, content="1") 546 | /// inspect!(x.data[0][0], content="1.0") 547 | /// inspect!(x.data[0][1], content="3.0") 548 | /// } 549 | /// ``` 550 | pub fn solve(a : Matrix, b : Matrix) -> Matrix!Error { 551 | if a.rows != a.cols && b.rows != a.cols { 552 | raise MatrixShapeError( 553 | "Coefficient matrix must be square and constant matrix must have the same number of rows as the coefficient matrix", 554 | ) 555 | } 556 | match a.inv?() { 557 | Ok(inv) => inv * b 558 | Err(e) => raise e 559 | } 560 | } 561 | 562 | ///| Retrieves the element at the specified row and column indices in a matrix. 563 | /// 564 | /// Parameters: 565 | /// 566 | /// * `matrix`: The matrix from which to retrieve the element. 567 | /// * `row`: The row index of the element (zero-based). 568 | /// * `column`: The column index of the element (zero-based). 569 | /// 570 | /// Returns a `Double` value representing the element at the specified position 571 | /// in the matrix. 572 | /// 573 | /// Example: 574 | /// 575 | /// ```moonbit 576 | /// test "matrix/get" { 577 | /// // Create a 2x2 matrix with elements [[1.0, 2.0], [3.0, 4.0]] 578 | /// let matrix = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 579 | /// 580 | /// // Get elements at different positions 581 | /// inspect!(matrix.get(0, 0), content="1.0") 582 | /// inspect!(matrix.get(0, 1), content="2.0") 583 | /// inspect!(matrix.get(1, 0), content="3.0") 584 | /// inspect!(matrix.get(1, 1), content="4.0") 585 | /// } 586 | /// ``` 587 | pub fn get(self : Matrix, i : Int, j : Int) -> Double { 588 | self.data[i][j] 589 | } 590 | 591 | ///| Applies a function to every element in the matrix, modifying the matrix 592 | /// in-place. 593 | /// 594 | /// Parameters: 595 | /// 596 | /// * `matrix`: The matrix to be modified. 597 | /// * `transform`: A function that takes a `Double` and returns a `Double`, which 598 | /// will be applied to each element in the matrix. 599 | /// 600 | /// Returns the modified matrix. 601 | /// 602 | /// Example: 603 | /// 604 | /// ```moonbit 605 | /// test "matrix/apply" { 606 | /// let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 607 | /// let square = fn(x: Double) -> Double { x * x } 608 | /// 609 | /// let result = m.apply(square) 610 | /// inspect!(result.data, content="[[1.0, 4.0], [9.0, 16.0]]") 611 | /// } 612 | /// 613 | /// test "matrix/apply_with_negative" { 614 | /// let m = ew_matrix([[1.0, -2.0], [-3.0, 4.0]]) 615 | /// let abs = fn(x: Double) -> Double { 616 | /// if x < 0.0 { -x } else { x } 617 | /// } 618 | /// 619 | /// let result = m.apply(abs) 620 | /// inspect!(result.data, content="[[1.0, 2.0], [3.0, 4.0]]") 621 | /// } 622 | /// ``` 623 | pub fn apply(self : Matrix, f : (Double) -> Double) -> Matrix { 624 | for i = 0; i < self.rows; i = i + 1 { 625 | for j = 0; j < self.cols; j = j + 1 { 626 | self.data[i][j] = f(self.data[i][j]) 627 | } 628 | } 629 | self 630 | } 631 | 632 | ///| 633 | /// Calculates the rank of a matrix using Gaussian elimination method. The rank 634 | /// is defined as the number of linearly independent rows or columns in the 635 | /// matrix. 636 | /// 637 | /// Parameters: 638 | /// 639 | /// * `matrix`: A matrix whose rank needs to be calculated. 640 | /// 641 | /// Returns an integer representing the rank of the matrix. 642 | /// 643 | /// Examples: 644 | /// 645 | /// ```moonbit 646 | /// test "rank/full_rank" { 647 | /// // Create a 2x2 matrix with full rank 648 | /// let m = new_matrix([[1.0, 0.0], [0.0, 1.0]]) 649 | /// inspect!(m.rank(), content="2") 650 | /// } 651 | /// 652 | /// test "rank/not_full_rank" { 653 | /// // Create a 3x3 matrix with rank 2 654 | /// let m = new_matrix( 655 | /// [ 656 | /// [1.0, 2.0, 3.0], 657 | /// [2.0, 4.0, 6.0], // This row is twice the first row 658 | /// [0.0, 1.0, 0.0], 659 | /// ], 660 | /// ) 661 | /// inspect!(m.rank(), content="2") 662 | /// } 663 | /// 664 | /// test "rank/zero_matrix" { 665 | /// // Create a zero matrix 666 | /// let m = zero(3, 3) 667 | /// inspect!(m.rank(), content="0") 668 | /// } 669 | /// 670 | /// test "rank/rectangular" { 671 | /// // Create a 2x3 matrix 672 | /// let m = new_matrix([[1.0, 0.0, 2.0], [0.0, 3.0, 4.0]]) 673 | /// inspect!(m.rank(), content="2") 674 | /// } 675 | /// ``` 676 | pub fn rank(self : Matrix) -> Int { 677 | let matrix_copy = self.data.copy() 678 | let mut rank = 0 679 | let rows = self.rows 680 | let cols = self.cols 681 | for col = 0; col < cols; col = col + 1 { 682 | let mut pivot_row = -1 683 | for row = rank; row < rows; row = row + 1 { 684 | if matrix_copy[row][col] != 0.0 { 685 | pivot_row = row 686 | break 687 | } 688 | } 689 | if pivot_row == -1 { 690 | continue 691 | } 692 | if pivot_row != rank { 693 | let temp = matrix_copy[rank] 694 | matrix_copy[rank] = matrix_copy[pivot_row] 695 | matrix_copy[pivot_row] = temp 696 | } 697 | let pivot_value = matrix_copy[rank][col] 698 | if pivot_value == 0.0 { 699 | continue 700 | } 701 | for row = rank + 1; row < rows; row = row + 1 { 702 | let factor = matrix_copy[row][col] / pivot_value 703 | for k = col; k < cols; k = k + 1 { 704 | matrix_copy[row][k] -= factor * matrix_copy[rank][k] 705 | } 706 | } 707 | rank = rank + 1 708 | } 709 | rank 710 | } 711 | 712 | ///| 713 | /// Creates a submatrix by extracting a rectangular region from an existing 714 | /// matrix using specified row and column ranges. 715 | /// 716 | /// Parameters: 717 | /// 718 | /// * `matrix`: The source matrix from which to extract the slice. 719 | /// * `row_start`: The starting row index (inclusive) of the slice. 720 | /// * `row_end`: The ending row index (exclusive) of the slice. 721 | /// * `col_start`: The starting column index (inclusive) of the slice. 722 | /// * `col_end`: The ending column index (exclusive) of the slice. 723 | /// 724 | /// Returns a new matrix containing the specified slice of the original matrix. 725 | /// 726 | /// Throws an error with message "SliceError: Invalid row indices" if: 727 | /// 728 | /// * `row_start` is negative 729 | /// * `row_end` exceeds the number of rows in the matrix 730 | /// * `row_start` is greater than `row_end` 731 | /// 732 | /// Throws an error with message "SliceError: Invalid column indices" if: 733 | /// 734 | /// * `col_start` is negative 735 | /// * `col_end` exceeds the number of columns in the matrix 736 | /// * `col_start` is greater than `col_end` 737 | /// 738 | /// Examples: 739 | /// 740 | /// ```moonbit 741 | /// test "slice/basic" { 742 | /// let m = new_matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) 743 | /// 744 | /// // Extract 2x2 submatrix from the middle 745 | /// let slice = m.slice(0, 2, 1, 3) 746 | /// inspect!(slice.rows, content="2") 747 | /// inspect!(slice.cols, content="2") 748 | /// inspect!(slice.data, content="[[2.0, 3.0], [5.0, 6.0]]") 749 | /// } 750 | /// 751 | /// test "panic slice/invalid_row_indices" { 752 | /// let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 753 | /// // Should panic with "SliceError: Invalid row indices" 754 | /// m.slice(-1, 2, 0, 2) 755 | /// } 756 | /// 757 | /// test "panic slice/invalid_column_indices" { 758 | /// let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 759 | /// // Should panic with "SliceError: Invalid column indices" 760 | /// m.slice(0, 2, 0, 3) 761 | /// } 762 | /// ``` 763 | pub fn slice( 764 | self : Matrix, 765 | row_start : Int, 766 | row_end : Int, 767 | col_start : Int, 768 | col_end : Int 769 | ) -> Matrix!SliceError { 770 | if row_start < 0 || row_end > self.rows || row_start > row_end { 771 | raise SliceError("Invalid row indices") 772 | } 773 | if col_start < 0 || col_end > self.cols || col_start > col_end { 774 | raise SliceError("Invalid column indices") 775 | } 776 | let new_rows = row_end - row_start 777 | let new_cols = col_end - col_start 778 | let new_data = Array::makei(new_rows, fn(i : Int) -> Array[Double] { 779 | Array::makei(new_cols, fn(j : Int) -> Double { 780 | self.data[row_start + i][col_start + j] 781 | }) 782 | }) 783 | new_matrix(new_data) 784 | } 785 | 786 | ///| Flattens a matrix into a 1 x (m\*n) matrix by concatenating all rows 787 | /// horizontally. 788 | /// 789 | /// Parameters: 790 | /// 791 | /// * `matrix` : A Matrix object to be flattened. 792 | /// 793 | /// Returns a new Matrix with dimensions 1 x (m\*n), where m and n are the number 794 | /// of rows and columns of the input matrix respectively. 795 | /// 796 | /// Example: 797 | /// 798 | /// ```moonbit 799 | /// test "matrix/flat" { 800 | /// let m = Matrix::new(2, 3) 801 | /// m.data[0][0] = 1 802 | /// m.data[0][1] = 2 803 | /// m.data[0][2] = 3 804 | /// m.data[1][0] = 4 805 | /// m.data[1][1] = 5 806 | /// m.data[1][2] = 6 807 | /// 808 | /// let flattened = m.flat() 809 | /// inspect!(flattened.rows, content="1") 810 | /// inspect!(flattened.cols, content="6") 811 | /// inspect!(flattened.data[0], content="[1, 2, 3, 4, 5, 6]") 812 | /// } 813 | /// ``` 814 | pub fn flat(self : Matrix) -> Matrix { 815 | self.reshape(1, self.cols * self.rows) 816 | } 817 | 818 | ///| Reshapes a matrix into a new matrix with specified dimensions, preserving the 819 | /// elements in row-major order. 820 | /// 821 | /// Parameters: 822 | /// 823 | /// * `matrix` : The original matrix to be reshaped. 824 | /// * `rows` : The number of rows in the new matrix. 825 | /// * `cols` : The number of columns in the new matrix. 826 | /// 827 | /// Returns a new matrix with the specified dimensions containing all elements 828 | /// from the input matrix. 829 | /// 830 | /// Example: 831 | /// 832 | /// ```moonbit 833 | /// test "reshape" { 834 | /// // Create a 2x3 matrix 835 | /// let m = new_matrix([[1, 2, 3], [4, 5, 6]]) 836 | /// 837 | /// // Reshape to 3x2 matrix 838 | /// let reshaped = m.reshape(3, 2) 839 | /// 840 | /// // The elements are preserved in row-major order 841 | /// inspect!(reshaped.data, content="[[1, 2], [3, 4], [5, 6]]") 842 | /// } 843 | /// 844 | /// test "reshape/identity" { 845 | /// // Reshaping to same dimensions should return equivalent matrix 846 | /// let m = new_matrix([[1, 2], [3, 4]]) 847 | /// let reshaped = m.reshape(2, 2) 848 | /// inspect!(reshaped.data, content="[[1, 2], [3, 4]]") 849 | /// } 850 | /// ``` 851 | pub fn reshape(self : Matrix, rows : Int, cols : Int) -> Matrix { 852 | let m = zero(rows, cols) 853 | for i = 0; i < self.rows * self.cols; i = i + 1 { 854 | let old_row = i / self.cols 855 | let old_col = i % self.cols 856 | let new_row = i / cols 857 | let new_col = i % cols 858 | m.data[new_row][new_col] = self.data[old_row][old_col] 859 | } 860 | m 861 | } 862 | 863 | ///| 864 | /// Creates a new matrix from a two-dimensional array of double-precision 865 | /// floating-point numbers. If the dimensions are not explicitly specified, they 866 | /// will be inferred from the input array. 867 | /// 868 | /// Parameters: 869 | /// 870 | /// * `data` : A two-dimensional array of type `Array[Array[Double]]` containing 871 | /// the matrix elements. 872 | /// * `rows` : Optional. The number of rows in the matrix. Defaults to the number 873 | /// of arrays in `data`. 874 | /// * `cols` : Optional. The number of columns in the matrix. Defaults to the 875 | /// length of the first array in `data`. 876 | /// 877 | /// Returns a new `Matrix` instance containing a deep copy of the input data. 878 | /// 879 | /// Examples: 880 | /// 881 | /// ```moonbit 882 | /// test "new_matrix/default_dimensions" { 883 | /// // Create a 2x2 matrix with default dimensions 884 | /// let data = [[1.0, 2.0], [3.0, 4.0]] 885 | /// let matrix = new_matrix(data) 886 | /// inspect!(matrix.shape(), content="(2, 2)") 887 | /// inspect!(matrix.data[0][0], content="1.0") 888 | /// } 889 | /// 890 | /// test "new_matrix/explicit_dimensions" { 891 | /// // Create a matrix with explicit dimensions 892 | /// let data = [[1.0, 2.0, 3.0]] 893 | /// let matrix = new_matrix(data, rows=1, cols=3) 894 | /// inspect!(matrix.shape(), content="(1, 3)") 895 | /// } 896 | /// ``` 897 | pub fn copy(self : Matrix) -> Matrix { 898 | let data = self.data.copy() 899 | new_matrix(data) 900 | } 901 | 902 | ///| Creates a new matrix from a 2D array of Double values. If rows and cols are 903 | /// not specified, they will be inferred from the input array dimensions. 904 | /// 905 | /// Parameters: 906 | /// 907 | /// * `data`: A 2D array of Double values representing the matrix elements. The 908 | /// array must be rectangular (all rows must have the same length). 909 | /// * `rows`: (Optional) The number of rows in the matrix. Defaults to the length 910 | /// of the input array. 911 | /// * `cols`: (Optional) The number of columns in the matrix. Defaults to the 912 | /// length of the first row in the input array. 913 | /// 914 | /// Returns a new Matrix instance containing a copy of the input data. 915 | /// 916 | /// Examples: 917 | /// 918 | /// ```moonbit 919 | /// test "new_matrix/default_dimensions" { 920 | /// let data = [[1.0, 2.0], [3.0, 4.0]] 921 | /// let matrix = new_matrix(data) 922 | /// inspect!(matrix.rows, content="2") 923 | /// inspect!(matrix.cols, content="2") 924 | /// inspect!(matrix.data[0][0], content="1") 925 | /// inspect!(matrix.data[1][1], content="4") 926 | /// } 927 | /// 928 | /// test "new_matrix/explicit_dimensions" { 929 | /// let data = [[1.0, 2.0, 3.0]] 930 | /// let matrix = new_matrix(data, rows=1, cols=3) 931 | /// inspect!(matrix.rows, content="1") 932 | /// inspect!(matrix.cols, content="3") 933 | /// inspect!(matrix.data[0][2], content="3") 934 | /// } 935 | /// ``` 936 | pub fn new_matrix( 937 | data_ : Array[Array[Double]], 938 | rows~ : Int = data_.length(), 939 | cols~ : Int = data_[0].length() 940 | ) -> Matrix { 941 | let ans = { rows, cols, data: data_.copy() } 942 | ans 943 | } 944 | 945 | ///| 946 | pub fn Matrix::new(rows : Int, cols : Int) -> Matrix { 947 | { rows, cols, data: make_matrix(rows, cols) } 948 | } 949 | 950 | ///| 951 | fn make_matrix(rows : Int, cols : Int) -> Array[Array[Double]] { 952 | if rows == 0 || cols == 0 { 953 | [[]] 954 | } else { 955 | Array::makei(rows, fn(row : Int) -> Array[Double] { 956 | Array::makei(cols, fn(col : Int) -> Double { 957 | (row * cols + col).to_double() 958 | }) 959 | }) 960 | } 961 | } 962 | 963 | ///| 964 | test { 965 | // let a = new_matrix([ 966 | // [3.0, 0.0], 967 | // [0.0, 4.0] 968 | // ]) 969 | // let (eigenvalues, eigenvectors) = a.eigen() 970 | // println(eigenvalues) 971 | // eigenvectors.print() 972 | let a = new_matrix([[3.0, 1.0], [1.0, 3.0]]) 973 | let (u, s, v) = a.svd!() 974 | @json.inspect!(u, content=[ 975 | [0.70710678118912, -0.707106781183975], 976 | [0.7071067811839754, 0.7071067811891202], 977 | ]) 978 | @json.inspect!(s, content=[[4, 0], [0, 2]]) 979 | @json.inspect!(v, content=[ 980 | [0.70710678118912, -0.707106781183975], 981 | [0.7071067811839754, 0.7071067811891202], 982 | ]) 983 | @json.inspect!(u * s * v.transpose(), content=[ 984 | [3.0000000000072755, 1.0000000000000007], 985 | [1.0000000000000007, 2.9999999999927267], 986 | ]) 987 | } 988 | -------------------------------------------------------------------------------- /src/lib/matrix/matrix.mbti: -------------------------------------------------------------------------------- 1 | package "xunyoyo/linalg/lib/matrix" 2 | 3 | // Values 4 | fn apply(Matrix, (Double) -> Double) -> Matrix 5 | 6 | fn cholesky(Matrix) -> Matrix 7 | 8 | fn copy(Matrix) -> Matrix 9 | 10 | fn det(Matrix) -> Double 11 | 12 | fn det_Gauss(Matrix) -> Double 13 | 14 | fn diag(Array[Double]) -> Matrix 15 | 16 | fn diagonal(Matrix, offset~ : Int = ..) -> Array[Double] 17 | 18 | fn dot(Matrix, Matrix) -> Double!MatrixShapeError 19 | 20 | fn eigen(Matrix, max_iterations~ : Int = .., tol~ : Double = ..) -> (Array[Double], Matrix)! 21 | 22 | fn exchange_column(Matrix, Int, Int) -> Matrix 23 | 24 | fn exchange_row(Matrix, Int, Int) -> Matrix 25 | 26 | fn eye(Int) -> Matrix 27 | 28 | fn flat(Matrix) -> Matrix 29 | 30 | fn get(Matrix, Int, Int) -> Double 31 | 32 | fn inv(Matrix) -> Matrix! 33 | 34 | fn k(Matrix, Double) -> Matrix 35 | 36 | fn lstsq(Matrix, Matrix) -> Matrix! 37 | 38 | fn new_matrix(Array[Array[Double]], rows~ : Int = .., cols~ : Int = ..) -> Matrix 39 | 40 | fn norm(Matrix, ord~ : NormType = .., axis~ : Type = ..) -> Double 41 | 42 | fn print(Matrix) -> Unit 43 | 44 | fn qr(Matrix) -> (Matrix, Matrix)! 45 | 46 | fn rank(Matrix) -> Int 47 | 48 | fn reduced_row_echelon_form(Matrix) -> Matrix 49 | 50 | fn reshape(Matrix, Int, Int) -> Matrix 51 | 52 | fn shape(Matrix) -> (Int, Int) 53 | 54 | fn slice(Matrix, Int, Int, Int, Int) -> Matrix!SliceError 55 | 56 | fn solve(Matrix, Matrix) -> Matrix! 57 | 58 | fn svd(Matrix) -> (Matrix, Matrix, Matrix)! 59 | 60 | fn tr(Matrix) -> Double!MatrixShapeError 61 | 62 | fn trace(Matrix, Int) -> Double 63 | 64 | fn transpose(Matrix) -> Matrix 65 | 66 | fn vdot(Matrix, Matrix) -> Double!MatrixShapeError 67 | 68 | fn zero(Int, Int) -> Matrix 69 | 70 | // Types and methods 71 | type Matrix 72 | impl Matrix { 73 | apply(Self, (Double) -> Double) -> Self 74 | cholesky(Self) -> Self 75 | copy(Self) -> Self 76 | det(Self) -> Double 77 | det_Gauss(Self) -> Double 78 | dot(Self, Self) -> Double!MatrixShapeError 79 | eigen(Self, max_iterations~ : Int = .., tol~ : Double = ..) -> (Array[Double], Self)! 80 | exchange_column(Self, Int, Int) -> Self 81 | exchange_row(Self, Int, Int) -> Self 82 | flat(Self) -> Self 83 | get(Self, Int, Int) -> Double 84 | inv(Self) -> Self! 85 | k(Self, Double) -> Self 86 | new(Int, Int) -> Self 87 | norm(Self, ord~ : NormType = .., axis~ : Type = ..) -> Double 88 | print(Self) -> Unit 89 | qr(Self) -> (Self, Self)! 90 | rank(Self) -> Int 91 | reduced_row_echelon_form(Self) -> Self 92 | reshape(Self, Int, Int) -> Self 93 | shape(Self) -> (Int, Int) 94 | slice(Self, Int, Int, Int, Int) -> Self!SliceError 95 | svd(Self) -> (Self, Self, Self)! 96 | tr(Self) -> Double!MatrixShapeError 97 | trace(Self, Int) -> Double 98 | transpose(Self) -> Self 99 | vdot(Self, Self) -> Double!MatrixShapeError 100 | } 101 | 102 | type MatrixShapeError 103 | impl Show for MatrixShapeError 104 | 105 | type NormType 106 | 107 | type SliceError 108 | impl Show for SliceError 109 | 110 | type Type 111 | 112 | // Type aliases 113 | 114 | // Traits 115 | 116 | -------------------------------------------------------------------------------- /src/lib/matrix/moon.pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "is-main": false, 3 | "import": [] 4 | } -------------------------------------------------------------------------------- /src/lib/matrix/norm.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | enum NormType { 3 | Frobenius 4 | Infinity 5 | Zero 6 | One 7 | Two 8 | P(Double) 9 | } 10 | 11 | ///| 12 | enum Type { 13 | Matrix 14 | Vector 15 | } 16 | 17 | ///| 18 | fn pow(a : Double, b : Double) -> Double { 19 | Double::exp(b * a.ln()) 20 | } 21 | 22 | ///| 23 | fn norm_vector_0(arr : Array[Double]) -> Double { 24 | let mut ans = 0.0 25 | for item in arr { 26 | ans += (item != 0).to_int().to_double() 27 | } 28 | ans 29 | } 30 | 31 | ///| 32 | fn norm_vector_1(arr : Array[Double]) -> Double { 33 | let mut ans = 0.0 34 | for item in arr { 35 | ans += item.abs() 36 | } 37 | ans 38 | } 39 | 40 | ///| 41 | fn norm_vector_2(arr : Array[Double]) -> Double { 42 | let mut ans = 0.0 43 | for item in arr { 44 | ans += item * item 45 | } 46 | ans.sqrt() 47 | } 48 | 49 | ///| 50 | fn norm_vector_p(arr : Array[Double], p : Double) -> Double { 51 | let mut ans = 0.0 52 | for item in arr { 53 | ans += pow(item.abs(), p) 54 | } 55 | pow(ans, 1.0 / p) 56 | } 57 | 58 | ///| 59 | fn norm_vector_inf(arr : Array[Double]) -> Double { 60 | let mut ans = 0.0 61 | for item in arr { 62 | ans = @math.maximum(ans, item.abs()) 63 | } 64 | ans 65 | } 66 | 67 | ///| 68 | fn norm_matrix_inf(matrix : Matrix) -> Double { 69 | let mut ans = 0.0 70 | for i = 0; i < matrix.rows; i = i + 1 { 71 | let mut sum = 0.0 72 | for j = 0; j < matrix.cols; j = j + 1 { 73 | sum += matrix.data[i][j].abs() 74 | } 75 | ans = @math.maximum(ans, sum) 76 | } 77 | ans 78 | } 79 | 80 | ///| 81 | fn norm_matrix_1(matrix : Matrix) -> Double { 82 | let mut ans = 0.0 83 | for j = 0; j < matrix.cols; j = j + 1 { 84 | let mut sum = 0.0 85 | for i = 0; i < matrix.rows; i = i + 1 { 86 | sum += matrix.data[i][j].abs() 87 | } 88 | ans = @math.maximum(ans, sum) 89 | } 90 | ans 91 | } 92 | 93 | ///| 94 | fn norm_matrix_frobenius(matrix : Matrix) -> Double { 95 | let mut ans = 0.0 96 | for i = 0; i < matrix.rows; i = i + 1 { 97 | for j = 0; j < matrix.cols; j = j + 1 { 98 | ans += matrix.data[i][j] * matrix.data[i][j] 99 | } 100 | } 101 | ans.sqrt() 102 | } 103 | 104 | ///| 105 | fn power_iteration(a : Matrix, iterations : Int) -> Double { 106 | let n = a.rows 107 | let b_k = zero(n, 1) 108 | b_k.data[0][0] = 1.0 // 初始向量 109 | for i = 0; i < iterations; i = i + 1 { 110 | // b_k1 = A * b_k 111 | let b_k1 = a * b_k 112 | 113 | // 计算 ||b_k1|| 114 | let norm = norm_vector_2(b_k1.data[0]) 115 | 116 | // b_k = b_k1 / ||b_k1|| 117 | for i = 0; i < n; i = i + 1 { 118 | b_k.data[i][0] = b_k1.data[i][0] / norm 119 | } 120 | } 121 | 122 | // Rayleigh商作为特征值的近似 123 | let ab = a * b_k 124 | let mut numerator = 0.0 125 | let mut denominator = 0.0 126 | for i = 0; i < a.rows; i = i + 1 { 127 | numerator += b_k.data[i][0] * ab.data[i][0] 128 | denominator += b_k.data[i][0] * b_k.data[i][0] 129 | } 130 | numerator / denominator 131 | } 132 | 133 | ///| 134 | fn norm_matrix_2(matrix : Matrix) -> Double { 135 | let ata = matrix.transpose() * matrix 136 | let max_eigen = power_iteration(ata, 100) 137 | max_eigen.sqrt() 138 | } 139 | 140 | ///| 141 | /// Calculates various types of norms for matrices and vectors. For vectors, the 142 | /// matrix is first flattened into a 1D array. For matrices, different matrix 143 | /// norms are computed based on the specified norm type. 144 | /// 145 | /// Parameters: 146 | /// 147 | /// * `matrix`: The input matrix for which to calculate the norm. 148 | /// * `norm_type`: The type of norm to calculate (default: `NormType::Two`). 149 | /// Available options: 150 | /// * For vectors (when `axis` is `Vector`): 151 | /// * `Zero`: Counts the number of non-zero elements 152 | /// * `One`: Manhattan/Taxicab norm (sum of absolute values) 153 | /// * `Two`: Euclidean norm (square root of sum of squares) 154 | /// * `P(p)`: p-norm where p is a positive real number 155 | /// * `Infinity`: Maximum absolute value 156 | /// * For matrices (when `axis` is `Matrix`): 157 | /// * `One`: Maximum absolute column sum 158 | /// * `Two`: Spectral norm (square root of largest eigenvalue of A^T A) 159 | /// * `Frobenius`: Square root of sum of squared elements 160 | /// * `Infinity`: Maximum absolute row sum 161 | /// * `axis`: Specifies whether to treat the input as a vector or matrix 162 | /// (default: `Vector`) 163 | /// 164 | /// Returns a `Double` value representing the calculated norm. 165 | /// 166 | /// Throws an error with message "NormType: Invalid norm type" if an unsupported 167 | /// norm type is specified for the chosen axis. 168 | /// 169 | /// Examples: 170 | /// 171 | /// ```moonbit 172 | /// test "norm/vector" { 173 | /// // Vector norms 174 | /// let v = new_matrix([[1.0, -2.0, 3.0]]) 175 | /// 176 | /// // L1 norm (Manhattan) 177 | /// inspect!(v.norm(ord=NormType::One), content="6.0") 178 | /// 179 | /// // L2 norm (Euclidean) 180 | /// inspect!(v.norm(ord=NormType::Two), content="3.7416573867739413") 181 | /// 182 | /// // L∞ norm (Maximum) 183 | /// inspect!(v.norm(ord=NormType::Infinity), content="3.0") 184 | /// } 185 | /// 186 | /// test "norm/matrix" { 187 | /// // Matrix norms 188 | /// let m = new_matrix([[1.0, 2.0], [-3.0, 4.0]]) 189 | /// 190 | /// // Frobenius norm 191 | /// inspect!( 192 | /// m.norm(ord=NormType::Frobenius, axis=Matrix), 193 | /// content="5.477225575051661", 194 | /// ) 195 | /// 196 | /// // Maximum absolute row sum (infinity norm) 197 | /// inspect!(m.norm(ord=NormType::Infinity, axis=Matrix), content="7.0") 198 | /// } 199 | /// 200 | /// test "panic norm/invalid" { 201 | /// let m = new_matrix([[1.0, 2.0], [3.0, 4.0]]) 202 | /// // Zero norm is not defined for matrices 203 | /// panic!(m.norm(ord=NormType::Zero, axis=Matrix)) 204 | /// } 205 | /// ``` 206 | pub fn norm( 207 | self : Matrix, 208 | ord~ : NormType = NormType::Two, 209 | axis~ : Type = Vector 210 | ) -> Double { 211 | let mut ans = 0.0 212 | match axis { 213 | Vector => { 214 | let tmp = self.flat() 215 | match ord { 216 | NormType::Infinity => ans = norm_vector_inf(tmp.data[0]) 217 | NormType::Zero => ans = norm_vector_0(tmp.data[0]) 218 | NormType::One => ans = norm_vector_1(tmp.data[0]) 219 | NormType::Two => ans = norm_vector_2(tmp.data[0]) 220 | NormType::P(p) => ans = norm_vector_p(tmp.data[0], p) 221 | _ => abort("NormType: Invalid norm type") 222 | } 223 | } 224 | Matrix => 225 | match ord { 226 | NormType::One => ans = norm_matrix_1(self) 227 | NormType::Two => ans = norm_matrix_2(self) 228 | NormType::Frobenius => ans = norm_matrix_frobenius(self) 229 | NormType::Infinity => ans = norm_matrix_inf(self) 230 | _ => abort("NormType: Invalid norm type") 231 | } 232 | } 233 | ans 234 | } 235 | -------------------------------------------------------------------------------- /src/lib/matrix/qr.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// Performs QR decomposition on a matrix using the Gram-Schmidt 3 | /// orthogonalization process. Decomposes a matrix A into a product of an 4 | /// orthogonal matrix Q and an upper triangular matrix R, such that A = QR. 5 | /// 6 | /// Parameters: 7 | /// 8 | /// * `matrix`: The input matrix to be decomposed. Must have at least as many 9 | /// rows as columns. 10 | /// 11 | /// Returns a tuple (Q, R) where: 12 | /// 13 | /// * Q is an m×n orthogonal matrix (Q^T Q = I) 14 | /// * R is an n×n upper triangular matrix 15 | /// 16 | /// Throws: 17 | /// 18 | /// * `MatrixShapeError` if the input matrix has fewer rows than columns 19 | /// * `MatrixError` if the input matrix is rank deficient (i.e., not full column 20 | /// rank) 21 | /// 22 | /// Examples: 23 | /// 24 | /// ```moonbit 25 | /// test "qr/basic" { 26 | /// let a = Matrix::new_matrix( 27 | /// [[12.0, -51.0, 4.0], [6.0, 167.0, -68.0], [-4.0, 24.0, -41.0]], 28 | /// ) 29 | /// let (q, r) = a.qr() 30 | /// 31 | /// // Verify Q is orthogonal (Q^T Q ≈ I) 32 | /// let qt = q.transpose() 33 | /// let qtq = qt * q 34 | /// let i = eye(3) 35 | /// inspect!(qtq.approx_eq(i), content="true") 36 | /// 37 | /// // Verify R is upper triangular 38 | /// for i = 1; i < 3; i = i + 1 { 39 | /// for j = 0; j < i; j = j + 1 { 40 | /// inspect!(r.data[i][j], content="0.0") 41 | /// } 42 | /// } 43 | /// 44 | /// // Verify A = QR 45 | /// let a2 = q * r 46 | /// inspect!(a2.approx_eq(a), content="true") 47 | /// } 48 | /// 49 | /// test "panic qr/invalid_shape" { 50 | /// // Matrix with more columns than rows 51 | /// let a = Matrix::new_matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) 52 | /// panic!(a.qr()) 53 | /// } 54 | /// 55 | /// test "panic qr/rank_deficient" { 56 | /// // Rank deficient matrix 57 | /// let a = Matrix::new_matrix([[1.0, 1.0], [2.0, 2.0]]) 58 | /// panic!(a.qr()) 59 | /// } 60 | /// ``` 61 | pub fn qr(self : Matrix) -> (Matrix, Matrix)!Error { 62 | if self.rows < self.cols { 63 | abort( 64 | "MatrixShapeError: Cannot perform QR decomposition, rows must be >= cols", 65 | ) 66 | } 67 | let m = self.rows 68 | let n = self.cols 69 | let q = zero(m, n) 70 | let r = zero(n, n) 71 | 72 | // Gram-Schmidt 73 | for j = 0; j < n; j = j + 1 { 74 | let v = zero(m, 1) 75 | for i = 0; i < m; i = i + 1 { 76 | v.data[i][0] = self.data[i][j] 77 | } 78 | for i = 0; i < j; i = i + 1 { 79 | let qi = zero(m, 1) 80 | for k = 0; k < m; k = k + 1 { 81 | qi.data[k][0] = q.data[k][i] 82 | } 83 | r.data[i][j] = qi.dot!(v) 84 | for k = 0; k < m; k = k + 1 { 85 | v.data[k][0] -= r.data[i][j] * qi.data[k][0] 86 | } 87 | } 88 | 89 | // 列向量的范数 90 | r.data[j][j] = v.norm(ord=NormType::Two, axis=Vector) 91 | 92 | // 检查奇异性 93 | if r.data[j][j] < 0.0000000001 { 94 | abort("MatrixError: Matrix is rank deficient") 95 | } 96 | for i = 0; i < m; i = i + 1 { 97 | q.data[i][j] = v.data[i][0] / r.data[j][j] 98 | } 99 | } 100 | (q, r) 101 | } 102 | -------------------------------------------------------------------------------- /src/lib/matrix/rref.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// In this fn, we swap two rows (row_1 and row_2) or swap two columns (col_1 and col_2) 3 | /// 4 | /// Parameters: 5 | /// 6 | /// * 'matrix': The matrix you want to swap two columns or two rows. 7 | /// * 'row_1' or 'col_1': The first row or column you want to swap. 8 | /// * 'row_2' or 'col_2': The second row or column you want to swap. 9 | /// 10 | /// Returns a new Matrix after swaping the two rows or columns. 11 | /// 12 | /// Examples: 13 | /// matrix = [ 14 | /// [1, 2, 3, 4] 15 | /// [2, 3, 4, 5] 16 | /// [3, 4, 5, 6] 17 | /// ] 18 | /// ```moonbit 19 | /// test "name" { 20 | /// let test_matrix = new_matrix([[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]) 21 | /// inspect!( 22 | /// exchange_column(test_matrix, 1, 2), 23 | /// content="[[1, 3, 2, 4], [2, 4, 3, 5], [3, 5, 4, 6]]", 24 | /// ) 25 | /// inspect!( 26 | /// exchange_row(test_matrix, 1, 2), 27 | /// content="[[1, 2, 3, 4], [3, 4, 5, 6], [2, 3, 4, 5]]", 28 | /// ) 29 | ///} 30 | /// ``` 31 | pub fn exchange_row(self : Matrix, row_1 : Int, row_2 : Int) -> Matrix { 32 | let rows = self.rows 33 | let final_matrix = new_matrix(self.data) 34 | if row_1 > rows || row_2 > rows { 35 | println("These two rows are not within the matrix range!") 36 | panic() 37 | } else if row_1 == row_2 { 38 | println("row_1 == row_2") 39 | panic() 40 | } 41 | let temp = final_matrix.data[row_1] 42 | final_matrix.data[row_1] = self.data[row_2] 43 | final_matrix.data[row_2] = temp 44 | final_matrix 45 | } 46 | 47 | ///| 48 | pub fn exchange_column(self : Matrix, col_1 : Int, col_2 : Int) -> Matrix { 49 | let cols = self.cols 50 | let final_matrix = new_matrix(self.data) 51 | let transpose_matrix = transpose(final_matrix) 52 | if col_1 > cols || col_2 > cols { 53 | println("These two columns are not within the matrix range!") 54 | panic() 55 | } else if col_1 == col_2 { 56 | println("col_1 == col_2") 57 | panic() 58 | } 59 | let change_matrix = exchange_row(transpose_matrix, col_1, col_2) 60 | let final_matrix = transpose(change_matrix) 61 | final_matrix 62 | } 63 | 64 | ///| 65 | pub fn reduced_row_echelon_form(self : Matrix) -> Matrix { 66 | let columns = self.cols 67 | let rows = self.rows 68 | let mut row = 0 69 | let final_matrix = new_matrix(self.data) 70 | for col = 0; col < columns; col = col + 1 { 71 | if row >= rows { 72 | break 73 | } 74 | let mut max_value : Int = row 75 | // get max rows 76 | for i = row; i < rows; i = i + 1 { 77 | if final_matrix.data[i][col].abs() >= 78 | final_matrix.data[max_value][col].abs() { 79 | max_value = i 80 | } 81 | } 82 | if final_matrix.data[max_value][col] == 0 { 83 | continue 84 | } 85 | // exchange max_rows with row 86 | if row != max_value { 87 | let temp = final_matrix.data[row] 88 | final_matrix.data[row] = final_matrix.data[max_value] 89 | final_matrix.data[max_value] = temp 90 | } 91 | final_matrix.data[row] = final_matrix.data[row].map(fn(x) { 92 | x / final_matrix.data[row][col] 93 | }) 94 | for r = 0; r < rows; r = r + 1 { 95 | if r != row { 96 | let factor = final_matrix.data[r][col] 97 | for column = 0; column < columns; column = column + 1 { 98 | final_matrix.data[r][column] = final_matrix.data[r][column] - 99 | factor * final_matrix.data[row][column] 100 | } 101 | } 102 | } 103 | row += 1 104 | } 105 | final_matrix 106 | } 107 | 108 | ///| 109 | test "rref" { 110 | let test_matrix = new_matrix([[1.0, 1.0, 1.0, 1.0], [2.0, 3.0, 3.0, 3.0]]) 111 | inspect!( 112 | reduced_row_echelon_form(test_matrix), 113 | content="[[1, 0, 0, 0], [0, 1, 1, 1]]", 114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /src/lib/matrix/svd.mbt: -------------------------------------------------------------------------------- 1 | ///| Performs Singular Value Decomposition (SVD) of a matrix. 2 | /// Decomposes a matrix A into the product U·Σ·V^T where: 3 | /// - U is an orthogonal matrix 4 | /// - Σ is a diagonal matrix with non-negative real numbers (singular values) 5 | /// - V^T is the transpose of an orthogonal matrix V 6 | /// 7 | /// Parameters: 8 | /// * `matrix`: Input matrix to decompose 9 | /// 10 | /// Returns a tuple (U, Σ, V) containing the three component matrices 11 | /// 12 | /// Example: 13 | /// ```moonbit 14 | /// test "svd/basic" { 15 | /// let a = Matrix::new_matrix([[3.0, 1.0], [1.0, 3.0]]) 16 | /// let (u, s, v) = a.svd() 17 | /// 18 | /// // Verify A = U·Σ·V^T 19 | /// let vt = v.transpose() 20 | /// let usv = u * (s * vt) 21 | /// inspect!(usv.approx_eq(a), content="true") 22 | /// 23 | /// // Verify U and V are orthogonal 24 | /// let ut = u.transpose() 25 | /// let utu = ut * u 26 | /// let i = eye(2) 27 | /// inspect!(utu.approx_eq(i), content="true") 28 | /// 29 | /// let vt = v.transpose() 30 | /// let vtv = vt * v 31 | /// inspect!(vtv.approx_eq(i), content="true") 32 | /// } 33 | /// ``` 34 | pub fn svd(self : Matrix) -> (Matrix, Matrix, Matrix)!Error { 35 | // 计算A^T·A和A·A^T 36 | let at = self.transpose() 37 | let ata = at * self // A^T·A 38 | let aat = self * at // A·A^T 39 | 40 | // 计算特征值和特征向量 41 | let (_, v) = ata.eigen!() // 右奇异向量 42 | let (_, u) = aat.eigen!() // 左奇异向量 43 | 44 | // 计算奇异值 45 | let s = zero(self.rows, self.cols) 46 | let (eigenvals, _) = ata.eigen!() 47 | for i = 0; i < eigenvals.length(); i = i + 1 { 48 | s.data[i][i] = Double::sqrt(eigenvals[i]) 49 | } 50 | 51 | // 校正U和V的符号,使它们满足A ≈ U·Σ·V^T 52 | let usv = u * (s * v.transpose()) 53 | for j = 0; j < self.cols; j = j + 1 { 54 | if (usv.data[0][j] - self.data[0][j]).abs() > 0.0001 { 55 | // 反转这一列的符号 56 | for i = 0; i < self.rows; i = i + 1 { 57 | v.data[i][j] = -v.data[i][j] 58 | } 59 | } 60 | } 61 | (u, s, v) 62 | } 63 | -------------------------------------------------------------------------------- /src/main/main.mbt: -------------------------------------------------------------------------------- 1 | ///| Calculates the determinant of a square matrix using the Laplace expansion 2 | /// along the first row. 3 | /// 4 | /// Parameters: 5 | /// 6 | /// * `matrix`: A square matrix whose determinant needs to be calculated. 7 | /// 8 | /// Returns a Double value representing the determinant of the matrix. 9 | /// 10 | /// Example: 11 | /// 12 | fn main { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/main.mbti: -------------------------------------------------------------------------------- 1 | package "xunyoyo/linalg/main" 2 | 3 | // Values 4 | 5 | // Types and methods 6 | 7 | // Type aliases 8 | 9 | // Traits 10 | 11 | -------------------------------------------------------------------------------- /src/main/moon.pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "is-main": true, 3 | "import": [] 4 | } --------------------------------------------------------------------------------