1 /* This file is part of the Amalthea library. 2 * 3 * Copyright (C) 2019, 2021 Eugene 'Vindex' Stulin 4 * 5 * Distributed under the Boost Software License 1.0 or (at your option) 6 * the GNU Lesser General Public License 3.0 or later. 7 */ 8 9 module amalthea.matrixmath; 10 11 import std.exception; 12 import std.conv; 13 14 15 /******************************************************************************* 16 * The function gets minor of matrix by i,j. 17 */ 18 auto calcMinor(T)(in T[][] mat, in size_t i, in size_t j) { 19 auto m = mat.length > i+1 ? (mat[0 .. i] ~ mat[i+1 .. $]).dup 20 : mat[0 .. i].dup; 21 if (m[0].length > j+1) { 22 foreach(rn; 0 .. m.length) { 23 m[rn] = m[rn][0 .. j] ~ m[rn][j+1 .. $]; 24 } 25 } else { 26 foreach(rn; 0 .. m.length) { 27 m[rn] = m[rn][0 .. j]; 28 } 29 } 30 return m.dup; 31 } 32 33 34 /******************************************************************************* 35 * The function gets determinant of matrix. 36 */ 37 T calcDeterminant(T)(in T[][] m) { 38 enforce(m.length == m[0].length, "Matrix is non-square."); 39 T d = 0; 40 size_t i = 0; 41 if (m.length == 1 && m[0].length == 1) { 42 return m[0][0]; 43 } 44 T minusOne = -1; 45 foreach(j; 0 .. m.length) { 46 d += m[i][j] * minusOne^^(i+j) * calcDeterminant(calcMinor(m, i, j)); 47 } 48 return d; 49 } 50 alias det = calcDeterminant; 51 unittest { 52 auto m = [[-8, 16], [4, 0]]; 53 assert(det(m) == -64); 54 55 m = [ 56 [-5, -3, -1], 57 [10, 11, -5], 58 [ 4, 8, 16] 59 ]; 60 assert(det(m) == -576); 61 62 m = [ 63 [-5, -3, -1, 1], 64 [ 4, 8, 16, 12], 65 [10, 11, -5, 10], 66 [ 9, 8, 7, 6] 67 ]; 68 assert(m.det == 1040); 69 } 70 71 /******************************************************************************* 72 * The function gets the sum of two matrices. 73 */ 74 auto summarize(T)(in T[][] m1, in T[][] m2) { 75 auto rowsNumber1 = m1.length; 76 auto columnsNumber1 = m1[0].length; 77 auto rowsNumber2 = m2.length; 78 auto columnsNumber2 = m2[0].length; 79 enforce( 80 columnsNumber1 == columnsNumber2 81 && rowsNumber1 == rowsNumber2, 82 "Wrong summation." 83 ); 84 auto m3 = new T[][](rowsNumber1, columnsNumber1); 85 foreach(i; 0 .. rowsNumber1) { 86 foreach(j; 0 .. columnsNumber1) { 87 m3[i][j] = m1[i][j] + m2[i][j]; 88 } 89 } 90 return m3; 91 } 92 unittest { 93 int[][] mat1 = [[-3, 9], [7, 16]]; 94 int[][] mat2 = [[7, -4], [1, -15]]; 95 assert(summarize(mat1, mat2) == [[4, 5], [8, 1]]); 96 } 97 98 99 /******************************************************************************* 100 * The function multiplies two matrices. 101 */ 102 auto multiply(T1, T2)(in T1[][] m1, in T2[][] m2) { 103 auto rowsNumber1 = m1.length; 104 auto columnsNumber1 = m1[0].length; 105 auto rowsNumber2 = m2.length; 106 auto columnsNumber2 = m2[0].length; 107 enforce(columnsNumber1 == rowsNumber2, "Wrong multiplication."); 108 auto m3 = new typeof(m1[0][0] + m2[0][0] + 0)[][] 109 (rowsNumber1, columnsNumber2); 110 foreach(i; 0 .. rowsNumber1) { 111 foreach(j; 0 .. columnsNumber2) { 112 m3[i][j] = 0; 113 foreach(r; 0 .. columnsNumber1) { 114 m3[i][j] += m1[i][r] * m2[r][j]; 115 } 116 } 117 } 118 return m3; 119 } 120 unittest { 121 int[][] mat1 = [[5, 1, 9]]; 122 int[][] mat2 = [[4], [7], [-1]]; 123 auto res = multiply(mat1, mat2); 124 assert(res == [[18]]); 125 int[][] arr1 = [[1, 2], [3, 5]]; 126 int[][] arr2 = [[4, 0], [8, 16]]; 127 res = multiply(arr1, arr2); 128 assert(res == [[20, 32], [52, 80]]); 129 } 130 131 132 /******************************************************************************* 133 * The function multiplies the matrix by a number. 134 */ 135 auto multiply(T, E)(in T[][] m, in E value) 136 if (is(typeof(m[0][0] != value) == bool)) { 137 auto res = to!(typeof(m[0][0] + value + 0)[][])(m.dup); 138 foreach(i; 0 .. m.length) { 139 foreach(j; 0 .. m[0].length) { 140 res[i][j] = m[i][j] * value; 141 } 142 } 143 return res; 144 } 145 auto multiply(T, E)(in E value, in T[][] m) 146 if (is(typeof(m[0][0] != value) == bool)) { 147 return multiply(m, value); 148 } 149 150 151 /******************************************************************************* 152 * Matrix transpose. 153 */ 154 T[][] transpose(T)(in T[][] m) { 155 auto res = to!(typeof(m[0][0] + 0)[][])(m); 156 foreach(i; 0 .. m.length) { 157 foreach(j; 0 .. m[0].length) { 158 res[i][j] = m[j][i]; 159 } 160 } 161 return res; 162 } 163 unittest { 164 auto m = [ 165 [1, 2, 3], 166 [4, 5, 6], 167 [7, 8, 9] 168 ]; 169 auto expected = [ 170 [1, 4, 7], 171 [2, 5, 8], 172 [3, 6, 9] 173 ]; 174 assert(transpose(m) == expected); 175 } 176 177 private { 178 enum DimCount(T: U[], U) = is(T: U[], U) ? 1 + DimCount!U : 0; 179 enum DimCount(T) = 0; 180 } 181 unittest { 182 assert(DimCount!(int[]) == 1); 183 assert(DimCount!(int[][]) == 2); 184 assert(DimCount!(int[4][3]) == 2); 185 assert(DimCount!(int[][2][]) == 3); 186 assert(DimCount!(string) == 1); 187 } 188 189 190 /******************************************************************************* 191 * Number of array dimensions. 192 */ 193 size_t ndim(T)(T matrix) { 194 return DimCount!T; 195 } 196 /// 197 unittest { 198 int[] arr1d; 199 int[][] arr2d; 200 int[3][][1] arr3d; 201 int[][][][][][][] arr7d; 202 assert(arr1d.ndim == 1); 203 assert(arr2d.ndim == 2); 204 assert(arr3d.ndim == 3); 205 assert(arr7d.ndim == 7); 206 } 207 208 209 /******************************************************************************* 210 * Lengths of the corresponding array dimensions. 211 */ 212 size_t[] shape(A)(A arr) { 213 import std.range : ElementType; 214 static if (DimCount!A == 1) { 215 return [arr.length]; 216 } else { 217 bool arrIsEmpty = arr.length == 0; 218 if (!arrIsEmpty) { 219 return shape!(ElementType!A)(arr[0]) ~ [arr.length]; 220 } else { 221 return shape!(ElementType!A)((ElementType!A).init) ~ [arr.length]; 222 } 223 } 224 } 225 /// 226 unittest { 227 int[4][5][6] arr456; 228 assert(arr456.shape == [4, 5, 6]); 229 int[][][] arr000; 230 assert(arr000.shape == [0, 0, 0]); 231 int[3][][7] arr307; 232 assert(arr307.shape == [3, 0, 7]); 233 int[][][7] arr007; 234 assert(arr007.shape == [0, 0, 7]); 235 int[5][][] arr500; 236 assert(arr500.shape == [5, 0, 0]); 237 int[] dynArr = new int[](8); 238 assert(dynArr.shape == [8]); 239 } 240 241 242 /******************************************************************************* 243 * The template function returns a two-dimensional array with ones 244 * on the main diagonal and zeros elsewhere. 245 */ 246 T[][] eye(T = double)(size_t matrixDimension) { 247 T[][] arr = new T[][](matrixDimension, matrixDimension); 248 foreach(i; 0 .. matrixDimension) { 249 arr[i][0 .. $] = 0; 250 arr[i][i] = 1; 251 } 252 return arr; 253 }