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 }