1 /// Matrix linear algebra module
2 module gfx.math.mat;
3 
4 alias FMat(size_t R, size_t C) = Mat!(float, R, C);
5 alias DMat(size_t R, size_t C) = Mat!(double, R, C);
6 alias IMat(size_t R, size_t C) = Mat!(int, R, C);
7 
8 alias Mat2x2(T) = Mat!(T, 2, 2);
9 alias Mat3x3(T) = Mat!(T, 3, 3);
10 alias Mat4x4(T) = Mat!(T, 4, 4);
11 alias Mat2x3(T) = Mat!(T, 2, 3);
12 alias Mat2x4(T) = Mat!(T, 2, 4);
13 alias Mat3x4(T) = Mat!(T, 3, 4);
14 alias Mat3x2(T) = Mat!(T, 3, 2);
15 alias Mat4x2(T) = Mat!(T, 4, 2);
16 alias Mat4x3(T) = Mat!(T, 4, 3);
17 
18 alias FMat2x2 = Mat!(float, 2, 2);
19 alias FMat3x3 = Mat!(float, 3, 3);
20 alias FMat4x4 = Mat!(float, 4, 4);
21 alias FMat2x3 = Mat!(float, 2, 3);
22 alias FMat2x4 = Mat!(float, 2, 4);
23 alias FMat3x4 = Mat!(float, 3, 4);
24 alias FMat3x2 = Mat!(float, 3, 2);
25 alias FMat4x2 = Mat!(float, 4, 2);
26 alias FMat4x3 = Mat!(float, 4, 3);
27 
28 alias DMat2x2 = Mat!(double, 2, 2);
29 alias DMat3x3 = Mat!(double, 3, 3);
30 alias DMat4x4 = Mat!(double, 4, 4);
31 alias DMat2x3 = Mat!(double, 2, 3);
32 alias DMat2x4 = Mat!(double, 2, 4);
33 alias DMat3x4 = Mat!(double, 3, 4);
34 alias DMat3x2 = Mat!(double, 3, 2);
35 alias DMat4x2 = Mat!(double, 4, 2);
36 alias DMat4x3 = Mat!(double, 4, 3);
37 
38 alias IMat2x2 = Mat!(int, 2, 2);
39 alias IMat3x3 = Mat!(int, 3, 3);
40 alias IMat4x4 = Mat!(int, 4, 4);
41 alias IMat2x3 = Mat!(int, 2, 3);
42 alias IMat2x4 = Mat!(int, 2, 4);
43 alias IMat3x4 = Mat!(int, 3, 4);
44 alias IMat3x2 = Mat!(int, 3, 2);
45 alias IMat4x2 = Mat!(int, 4, 2);
46 alias IMat4x3 = Mat!(int, 4, 3);
47 
48 // further shortcuts
49 alias Mat2(T) = Mat2x2!T;
50 alias Mat3(T) = Mat3x3!T;
51 alias Mat4(T) = Mat4x4!T;
52 alias FMat2 = FMat2x2;
53 alias FMat3 = FMat3x3;
54 alias FMat4 = FMat4x4;
55 alias DMat2 = DMat2x2;
56 alias DMat3 = DMat3x3;
57 alias DMat4 = DMat4x4;
58 alias IMat2 = IMat2x2;
59 alias IMat3 = IMat3x3;
60 alias IMat4 = IMat4x4;
61 
62 /// Build a matrix whose component type and size is inferred from arguments.
63 /// Arguments must be rows or matrices with consistent column count.
64 auto mat(Rows...)(in Rows rows)
65 {
66     import gfx.math.vec : CompTup;
67     import std.traits : CommonType;
68 
69     static assert(hasConsistentLength!Rows, "All rows must have the same number of components");
70     immutable rt = rowTuple(rows);
71     alias CT = CompTup!(typeof(rt).Types);
72     alias Comp = CommonType!(CT.Types);
73     enum rowLength = rt.length;
74     enum colLength = rt[0].length;
75     return Mat!(Comp, rowLength, colLength)(rt.expand);
76 }
77 
78 ///
79 unittest
80 {
81     import gfx.math.vec : dvec;
82     import std.algorithm : equal;
83     import std.traits : Unqual;
84 
85     immutable r1 = dvec(1, 2, 3);
86     immutable r2 = dvec(4, 5, 6);
87     immutable m1 = mat(r1, r2);
88     static assert( is(Unqual!(typeof(m1)) == DMat2x3) );
89     assert(equal(m1.data, [ 1, 2, 3, 4, 5, 6 ]));
90 
91     immutable m2 = mat(r2, m1, r1);
92     static assert( is(Unqual!(typeof(m2)) == DMat4x3) );
93     assert(equal(m2.data, [ 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3]));
94 
95     // The following would yield an inconsistent column count.
96     static assert( !__traits(compiles, mat(r1, dvec(1, 2))) );
97 }
98 
99 /// Matrix type for linear algebra
100 struct Mat(T, size_t R, size_t C)
101 {
102     import gfx.math.vec : isVec, Vec;
103     import std.meta : allSatisfy, Repeat;
104     import std.traits : CommonType, isArray, isNumeric, isImplicitlyConvertible;
105     import std.typecons : Tuple;
106 
107     private T[R*C] _rep;
108 
109     /// The number of rows in the matrix.
110     enum rowLength = R;
111     /// The number of columns in the matrix.
112     enum columnLength = C;
113     /// The number of components in the matrix;
114     enum compLength = R*C;
115     /// The matrix rows type.
116     alias Row = Vec!(T, columnLength);
117     /// The matrix columns type.
118     alias Column = Vec!(T, rowLength);
119     /// The type of the componetypeof(rt.expand)nts.
120     alias Component = T;
121     /// row major container: length is the number of rows
122     enum length = rowLength;
123 
124     /// The identity matrix.
125     enum identity = mixin(identityCode);
126 
127 
128     /// Build a matrix from its elements.
129     /// To be provided row major.
130     this (Args...)(in Args args)
131     if (Args.length == R*C && allSatisfy!(isNumeric, Args) &&
132         isImplicitlyConvertible!(CommonType!Args, T))
133     {
134         _rep = [ args ];
135     }
136 
137     /// Build a matrix from the provided rows.
138     /// Each row must be an array (static or dynamic) and have the correct number
139     /// of elements.
140     this (Args...)(in Args args)
141     if (Args.length == rowLength && allSatisfy!(isArray, Args))
142     {
143         import std.traits : isStaticArray;
144 
145         static if (allSatisfy!(isStaticArray, Args)) {
146             static foreach (r, arg; args) {
147                 static assert(arg.length == columnLength);
148                 _rep[ r*columnLength .. (r+1)*columnLength ] = arg;
149             }
150         }
151         else {
152             foreach (r, arg; args) {
153                 assert(arg.length == columnLength);
154                 _rep[ r*columnLength .. (r+1)*columnLength ] = arg;
155             }
156         }
157     }
158 
159     /// ditto
160     this (Args...)(in Args args)
161     if (Args.length == rowLength && allSatisfy!(isVec, Args))
162     {
163         static foreach(r, arg; args)
164         {
165             static assert(arg.length == columnLength, "incorrect row size");
166             static if (is(typeof(arg[0]) == T))
167             {
168                 _rep[r*columnLength .. (r+1)*columnLength] = arg._rep;
169             }
170             else
171             {
172                 static foreach (c; 0 .. columnLength)
173                 {
174                     _rep[r*columnLength + c] = cast(T)arg[c];
175                 }
176             }
177         }
178     }
179 
180     // representation in tuple
181 
182     /// Alias to a type sequence holding all rows
183     alias RowSeq = Repeat!(rowLength, Row);
184     /// Alias to a type sequence holding all components
185     alias CompSeq = Repeat!(rowLength*columnLength, T);
186 
187     /// All rows in a tuple
188     @property Tuple!RowSeq rowTup() const
189     {
190         return Tuple!RowSeq(cast(Row[rowLength])_rep);
191     }
192     /// All components in a tuple
193     @property Tuple!CompSeq compTup() const
194     {
195         return Tuple!CompSeq(_rep);
196     }
197 
198     // array representation
199 
200     /// Direct access to underlying data.
201     /// Reminder: row major order!
202     @property inout(Component)[] data() inout
203     {
204         return _rep[];
205     }
206 
207     /// Cast a matrix to another type
208     Mat!(U, rowLength, columnLength) opCast(V : Mat!(U, rowLength, columnLength), U)() const
209     if (__traits(compiles, cast(U)T.init))
210     {
211         Mat!(U, rowLength, columnLength) res = void;
212         static foreach (i; 0 .. compLength) {
213             res._rep[i] = cast(U)_rep[i];
214         }
215         return res;
216     }
217 
218     // compile time indexing
219 
220     /// Index a component at compile time
221     @property Component ct(size_t r, size_t c)() const
222     {
223         static assert(r < rowLength && c < columnLength);
224         enum ind = r*columnLength + c;
225         return _rep[ind];
226     }
227 
228     /// Assign a compile time indexed component
229     void ct(size_t r, size_t c)(in Component comp)
230     {
231         static assert(r < rowLength && c < columnLength);
232         enum ind = r*columnLength + c;
233         _rep[ind] = comp;
234     }
235 
236     /// Index a row at compile time
237     @property Row ct(size_t r)() const
238     {
239         static assert(r < rowLength);
240         enum start = r*columnLength;
241         enum end = (r+1)*columnLength;
242         return Row(_rep[start .. end]);
243     }
244 
245     /// Assign a row indexed at compile time
246     void ct(size_t r)(in Row row)
247     {
248         static assert(r < rowLength);
249         enum start = r*columnLength;
250         enum end = (r+1)*columnLength;
251         _rep[start .. end] = row.array;
252     }
253 
254     /// Index a column at compile time
255     @property Column ctCol(size_t c)() const
256     {
257         static assert(c < columnLength);
258         Column col = void;
259         static foreach (r; 0 .. rowLength) {{
260             enum ind = r*columnLength + c;
261             col[r] = _rep[ind];
262         }}
263         return col;
264     }
265 
266     /// Assign a column indexed at compile time
267     void ctCol(size_t c)(in Column col)
268     {
269         static assert(r < columnLength);
270         static foreach (r; 0 .. rowLength) {{
271             enum ind = r*columnLength + c;
272             _rep[ind] = col[r];
273         }}
274     }
275 
276     // runtime indexing
277 
278     /// Index a matrix row.
279     Row row(in size_t r) const
280     {
281         assert(r < rowLength);
282         return Row(_rep[r*columnLength .. (r+1)*columnLength]);
283     }
284 
285     /// Index a matrix column.
286     Column column(in size_t c) const
287     {
288         assert(c < columnLength);
289         Column res=void;
290         static foreach (r; 0 .. rowLength)
291         {
292             res[r] = _rep[index(r, c)];
293         }
294         return res;
295     }
296 
297     /// Index a matrix component
298     T comp(in size_t r, in size_t c) const
299     {
300         return _rep[index(r, c)];
301     }
302 
303     /// Return a slice of the matrix
304     @property Mat!(T, RE-RS, CE-CS) slice(size_t RS, size_t RE, size_t CS, size_t CE)() const
305     if (RE > RS && RE <= rowLength && CE > CS && CE <= columnLength)
306     {
307         Mat!(T, RE-RS, CE-CS) res = void;
308         static foreach (r; RS .. RE)
309         {
310             static foreach (c; CS .. CE)
311             {
312                 res[r-RS, c-CS] = comp(r, c);
313             }
314         }
315         return res;
316     }
317 
318     /// Assign a slice of this matrix
319     /// e.g: $(D_CODE mat.slice!(0, 2) = otherMat;)
320     @property void slice(size_t RS, size_t CS, U, size_t UR, size_t UC)(in Mat!(U, UR, UC) mat)
321     if (RS+UR <= rowLength && CS+UC <= columnLength && isImplicitlyConvertible!(U, T))
322     {
323         static foreach (r; 0 .. UR)
324         {
325             static foreach (c; 0 .. UC)
326             {
327                 _rep[index(r+RS, c+CS)] = mat[r, c];
328             }
329         }
330     }
331 
332     /// Build an augmented matrix (add oth to the right of this matrix)
333     /// ---
334     /// immutable m = IMat2(4, 5, 6, 8);
335     /// assert( m ~ IMat2.identity == IMat2x4(4, 5, 1, 0, 6, 8, 0, 1));
336     /// ---
337     auto opBinary(string op, U, size_t UC)(in Mat!(U, rowLength, UC) mat) const
338     if (op == "~")
339     {
340         alias ResMat = Mat!(CommonType!(T, U), rowLength, columnLength+UC);
341         ResMat res = void;
342         static foreach (r; 0 .. rowLength) {
343             static foreach (c; 0 .. columnLength) {
344                 res[r, c] = _rep[index(r, c)];
345             }
346             static foreach (c; columnLength .. columnLength+UC) {
347                 res[r, c] = mat[r, c-columnLength];
348             }
349         }
350         return res;
351     }
352 
353     /// Index a matrix component.
354     T opIndex(in size_t r, in size_t c) const
355     {
356         return _rep[index(r, c)];
357     }
358 
359     /// Assign a matrix component.
360     void opIndexAssign(in T val, in size_t r, in size_t c)
361     {
362         _rep[index(r, c)] = val;
363     }
364 
365     /// Apply op on a matrix component.
366     void opIndexOpAssign(string op)(in T val, in size_t r, in size_t c)
367     if (op == "+" || op == "-" || op == "*" || op == "/")
368     {
369         mixin("_rep[index(r, c)] "~op~"= val;");
370     }
371 
372     /// Index a matrix row
373     Row opIndex(in size_t r) const
374     {
375         return row(r);
376     }
377 
378     /// Assign a row
379     void opIndexAssign(in Row row, in size_t r) {
380         _rep[r*columnLength .. (r+1)*columnLength] = row.data;
381     }
382 
383     /// Number of components per direction.
384     size_t opDollar(size_t i)() const
385     {
386         static assert(i < 2, "A matrix only has 2 dimensions.");
387         static if(i == 0)
388         {
389             return rowLength;
390         }
391         else
392         {
393             return columnLength;
394         }
395     }
396 
397     /// Add/Subtract by a matrix to its right.
398     auto opBinary(string op, U)(in Mat!(U, rowLength, columnLength) oth) const
399     if ((op == "+" || op == "-") && !is(CommonType!(T, U) == void))
400     {
401         alias ResMat = Mat!(CommonType!(T, U), rowLength, columnLength);
402         ResMat res = void;
403         static foreach (i; 0 .. rowLength*columnLength)
404         {
405             mixin("res._rep[i] = _rep[i] "~op~" oth._rep[i];");
406         }
407         return res;
408     }
409 
410     /// Multiply by a matrix to its right.
411     auto opBinary(string op, U, size_t UR, size_t UC)(in Mat!(U, UR, UC) oth) const
412     if (op == "*" && columnLength == UR && !is(CommonType!(T, U) == void))
413     {
414         // multiply the rows of this by the columns of oth.
415         //  1 2 3     7 8     1*7 + 2*9 + 3*3   1*8 + 2*1 + 3*5
416         //  4 5 6  x  9 1  =  4*7 + 5*9 + 6*3   4*8 + 5*9 + 6*3
417         //            3 5
418 
419         alias ResMat = Mat!(CommonType!(T, U), rowLength, UC);
420         ResMat res = void;
421 
422         // following is tested but reduces performance by an order of magnitude
423         // it vectorizes row * col multiplication but has more indexing and moving of data
424         // import core.simd : float4;
425         // static if (is(T==float) && is(U==float) && R==4 && C==4 && __traits(compiles, float4.init * float4.init)) {
426         //     static foreach (r; 0 .. 4)
427         //     {
428         //         static foreach (c; 0 .. 4)
429         //         {{
430         //             float4 thisRow;
431         //             float4 othCol;
432         //             float4 vecRes;
433         //             thisRow.array[0..4] = (ct!r).array;
434         //             othCol.array[0..4] = (oth.ctCol!c).array;
435         //             vecRes = thisRow * othCol;
436 
437         //             float[4] arr = vecRes.array;
438         //             ResMat.Component resComp = 0;
439         //             static foreach (rc; 0 .. columnLength)
440         //             {
441         //                 resComp += arr[rc];
442         //             }
443         //             res.ct!(r, c) = resComp;
444         //         }}
445         //     }
446         // }
447         // else {
448             static foreach(r; 0 .. rowLength)
449             {
450                 static foreach (c; 0 .. UC)
451                 {{
452                     ResMat.Component resComp = 0;
453                     static foreach (rc; 0 .. columnLength)
454                     {
455                         resComp += ct!(r, rc) * oth.ct!(rc, c);
456                     }
457                     res.ct!(r, c) = resComp;
458                 }}
459             }
460         // }
461 
462         return res;
463     }
464 
465     /// Multiply a matrix by a vector to its right.
466     auto opBinary(string op, U, size_t N)(in Vec!(U, N) vec) const
467     if (op == "*" && N == columnLength && !is(CommonType!(T, U) == void))
468     {
469         // same as matrix with one column
470         alias ResVec = Vec!(CommonType!(T, U), rowLength);
471         ResVec res = void;
472         static foreach (r; 0 .. rowLength)
473         {{
474             ResVec.Component resComp = 0;
475             static foreach (c; 0 .. columnLength)
476             {
477                 resComp += ct!(r, c) * vec[c];
478             }
479             res[r] = resComp;
480         }}
481         return res;
482     }
483 
484     /// Multiply a matrix by a vector to its left.
485     auto opBinaryRight(string op, U, size_t N)(in Vec!(U, N) vec) const
486     if (op == "*" && N == rowLength && !is(CommonType!(T, U) == void))
487     {
488         // same as matrix with one row
489         alias ResVec = Vec!(CommonType!(T, U), columnLength);
490         ResVec res = void;
491         static foreach (c; 0 .. columnLength)
492         {{
493             ResVec.Component resComp = 0;
494             static foreach (r; 0 .. rowLength)
495             {
496                 resComp += vec[r] * ct!(r, c);
497             }
498             res[c] = resComp;
499         }}
500         return res;
501     }
502 
503 
504     /// Operation of a matrix with a scalar on its right.
505     auto opBinary(string op, U)(in U val) const
506     if ((op == "+" || op == "-" || op == "*" || op == "/") &&
507         !is(CommonType!(T, U) == void))
508     {
509         // import core.simd;
510 
511         alias ResT = CommonType!(T, U);
512         alias ResMat = Mat!(ResT, rowLength, columnLength);
513         ResMat res = void;
514 
515         // static if ((compLength%8)==0 && hasSIMD && hasAVX && is(ResT == float) && is(typeof(mixin("float8.init"~op~"val")))) {
516         //     simdScalarOp!(ResMat, U, float8, 8, "vec"~op~"val")(res, val);
517         // }
518         // else static if ((compLength%4)==0 && hasSIMD && is(ResT == float) && is(typeof(mixin("float4.init"~op~"val")))) {
519         //     simdScalarOp!(ResMat, U, float4, 4, "vec"~op~"val")(res, val);
520         // }
521         // else {
522             static foreach (i; 0 .. compLength) {
523                 mixin("res._rep[i] = _rep[i] "~op~" val;");
524             }
525         // }
526         return res;
527     }
528 
529     /// Operation of a matrix with a scalar on its left.
530     auto opBinaryRight(string op, U)(in U val) const
531     if ((op == "+" || op == "-" || op == "*" || op == "/") &&
532         !is(CommonType!(T, U) == void))
533     {
534         // import core.simd;
535 
536         alias ResT = CommonType!(T, U);
537         alias ResMat = Mat!(ResT, rowLength, columnLength);
538         ResMat res = void;
539 
540         // static if ((compLength%8)==0 && hasSIMD && hasAVX && is(ResT == float) && is(typeof(mixin("val"~op~"float8.init")))) {
541         //     simdScalarOp!(ResMat, U, float8, 8, "val"~op~"vec")(res, val);
542         // }
543         // else static if ((compLength%4)==0 && hasSIMD && is(ResT == float) && is(typeof(mixin("val"~op~"float4.init")))) {
544         //     simdScalarOp!(ResMat, U, float4, 4, "val"~op~"vec")(res, val);
545         // }
546         // else {
547             static foreach (i; 0 .. compLength) {
548                 mixin("res._rep[i] = val "~op~" _rep[i];");
549             }
550         // }
551         return res;
552     }
553 
554     /// Assign operation of a matrix with a scalar on its right.
555     auto opOpAssign(string op, U)(in U val)
556     if ((op == "+" || op == "-" || op == "*" || op == "/") &&
557         !is(CommonType!(T, U) == void))
558     {
559         static foreach (i; 0 .. compLength) {
560             mixin("_rep[i] "~op~"= val;");
561         }
562         return this;
563     }
564 
565     /// Assign operation of a matrix with a matrix on its right.
566     auto opOpAssign(string op, M)(in M mat)
567     if ((op == "+" || op == "-" || op == "*") && is(typeof(mixin("this"~op~"mat"))))
568     {
569         const newThis = mixin("this "~op~" mat");
570         _rep = newThis._rep;
571         return this;
572     }
573 
574     string toString() const
575     {
576         /// [
577         ///     [      1.0000,       2.0000 ],
578         ///     [      3.0000,       4.0000 ]
579         /// ]
580         import std.format : format;
581 
582         string res = "[\n";
583         static foreach (r; 0 .. rowLength)
584         {{
585             import std.traits : isFloatingPoint;
586 
587             static if (isFloatingPoint!T) {
588                 enum fmt = "   [ %(%#10.4f%|, %) ]";
589             }
590             else {
591                 enum fmt = "   [ %(% 10s%|, %) ]";
592             }
593             res ~= format(fmt, _rep[r*columnLength .. (r+1)*columnLength]);
594             if (r != rowLength-1) res ~= ",\n";
595             else res ~= "\n";
596         }}
597         return res ~ "]";
598     }
599 
600     private static @property ctIndex(size_t r, size_t c)()
601     {
602         static assert(r < rowLength && c < columnLength);
603         return r * columnLength + c;
604     }
605 
606     private static size_t index(in size_t r, in size_t c)
607     {
608         assert(r < rowLength && c < columnLength);
609         return r * columnLength + c;
610     }
611 
612     private static @property string identityCode()
613     {
614         string code = "Mat (";
615         foreach (r; 0 .. rowLength)
616         {
617             foreach (c; 0 .. columnLength)
618             {
619                 code ~= r == c ? "1, " : "0, ";
620             }
621         }
622         return code ~ ")";
623     }
624 
625     private void simdScalarOp(M, U, SimdVecT, size_t size, string expr)(ref M res, in U val) const
626     {
627         static assert( (compLength % size) == 0 );
628         SimdVecT vec = void;
629         static foreach (i; 0 .. compLength/size) {{
630             enum start = i*size; enum end = start+size;
631             vec.array[0 .. size] = _rep[start .. end];
632             vec = mixin(expr);
633             res._rep[start .. end] = vec.array[0 .. size];
634         }}
635     }
636 }
637 
638 ///
639 unittest
640 {
641     import gfx.math.approx : approxUlp;
642 
643     assert(approxUlp(FMat2x2.identity, FMat2x2(
644         1, 0,
645         0, 1
646     )));
647 }
648 
649 ///
650 unittest
651 {
652     import gfx.math.approx : approxUlp;
653 
654     immutable ml = Mat!(float, 2, 3)(
655         1, 2, 3,
656         4, 5, 6,
657     );
658     immutable mr = Mat!(float, 3, 2)(
659         1, 2,
660         3, 4,
661         5, 6,
662     );
663     immutable exp = Mat!(float, 2, 2)(
664         1+6+15,  2+8+18,
665         4+15+30, 8+20+36
666     );
667     assert(approxUlp(ml * mr, exp));
668 }
669 
670 /// Give the transposed form of a matrix.
671 auto transpose(M)(in M mat) if (isMat!M)
672 {
673     alias T = M.Component;
674     enum R = M.rowLength;
675     enum C = M.columnLength;
676 
677     Mat!(T, C, R) res = void;
678     static foreach (r; 0 .. R) {
679         static foreach (c; 0 .. C) {
680             res[c, r] = mat[r, c];
681         }
682     }
683     return res;
684 }
685 
686 /// Compute the inverse of a matrix with Gaussian elimination method.
687 /// Complexity O(n3).
688 @property M gaussianInverse(M)(in M mat)
689 if (isMat!M)
690 {
691     import std.traits : isFloatingPoint;
692 
693     static assert(isFloatingPoint!(M.Component), "gaussianInverse only works with floating point matrices");
694     static assert(M.rowLength == M.columnLength, "gaussianInverse only works with square matrices");
695 
696     alias T = M.Component;
697     enum N = M.rowLength;
698 
699     auto pivot = mat ~ Mat!(real, N, N).identity;
700     static assert(is(pivot.Component == real));
701     ptrdiff_t pivotR = -1;
702     static foreach (c; 0 .. N)
703     {{
704         // find the max row of column c.
705         auto colMax = pivot[pivotR+1, c];
706         ptrdiff_t maxR = pivotR+1;
707         foreach (r; pivotR+2 .. N)
708         {
709             const val = pivot[r, c];
710             if (val > colMax)
711             {
712                 maxR = r;
713                 colMax = val;
714             }
715         }
716         if (colMax != 0)
717         {
718             pivotR += 1;
719             // normalizing the row where the max was found
720             static foreach (cc; 0 .. 2*N)
721             {
722                 pivot[maxR, cc] /= colMax;
723             }
724             // switching pivot row with the max row
725             if (pivotR != maxR)
726             {
727                 static foreach (cc; 0 .. 2*N)
728                 {{
729                     const swapTmp = pivot[maxR, cc];
730                     pivot[maxR, cc] = pivot[pivotR, cc];
731                     pivot[pivotR, cc] = swapTmp;
732                 }}
733             }
734             static foreach (r; 0 .. N)
735             {
736                 if (r != pivotR)
737                 {
738                     const fact = pivot.ct!(r, c);
739                     if (fact != 0)
740                     {
741                         static foreach (cc; 0 .. 2*N)
742                         {
743                             pivot.ct!(r, cc) = pivot.ct!(r, cc) - fact * pivot[pivotR, cc];
744                         }
745                     }
746                 }
747             }
748         }
749     }}
750     return cast(Mat!(T, N, N)) pivot.slice!(0, N, N, 2*N);
751 }
752 
753 ///
754 unittest
755 {
756     /// Example from https://en.wikipedia.org/wiki/Gaussian_elimination
757     const m = FMat3(
758         2, -1, 0,
759         -1, 2, -1,
760         0, -1, 2
761     );
762     const invM = gaussianInverse(m);
763 
764     import gfx.math.approx : approxUlp;
765     assert(approxUlp(invM, FMat3(
766         0.75f, 0.5f, 0.25f,
767         0.5f,  1f,   0.5f,
768         0.25f, 0.5f, 0.75f
769     )));
770     assert(approxUlp(gaussianInverse(invM), m));
771 }
772 
773 
774 /// Check whether MatT is a Mat
775 template isMat(MatT)
776 {
777     import std.traits : TemplateOf;
778     static if (is(typeof(__traits(isSame, TemplateOf!MatT, Mat))))
779     {
780         enum isMat = __traits(isSame, TemplateOf!MatT, Mat);
781     }
782     else
783     {
784         enum isMat = false;
785     }
786 }
787 
788 /// Check whether MatT is a Mat with R rows and C columns
789 template isMat(size_t R, size_t C, MatT)
790 {
791     static if (isMat!MatT)
792     {
793         enum isMat = MatT.rowLength == R && MatT.columnLength == C;
794     }
795     else
796     {
797         enum isMat = false;
798     }
799 }
800 
801 /// Check whether M is 2x2 matrix
802 enum isMat2(M) = isMat!(2, 2, M);
803 /// Check whether M is 3x3 matrix
804 enum isMat3(M) = isMat!(3, 3, M);
805 /// Check whether M is 4x4 matrix
806 enum isMat4(M) = isMat!(4, 4, M);
807 
808 /// Check if all types of MatSeq are instantiation of Mat
809 template areMat(MatSeq...)
810 {
811     enum areMat = allSatisfy!(isMat, MatSeq);
812 }
813 
814 /// Check if all types of MatSeq are instantiation of Mat with R rows and C columns
815 template areMat(size_t R, size_t C, MatSeq...)
816 {
817     static assert(MatSeq.length > 0);
818     static if (MatSeq.length == 1)
819     {
820         enum areMat = isMat!(R, C, MatSeq[0]);
821     }
822     else
823     {
824         enum areMat = isMat!(R, C, MatSeq[0]) && areMat!(R, C, MatSeq[1 .. $]);
825     }
826 }
827 
828 private:
829 
830 
831 version (D_SIMD) { enum hasSIMD = true; } else { enum hasSIMD = false; }
832 version (D_AVX) { enum hasAVX = true; } else { enum hasAVX = false; }
833 version (D_AVX2) { enum hasAVX2 = true; } else { enum hasAVX2 = false; }
834 
835 
836 /// Return a sequence of vectors and matrices as a flat tuple of rows.
837 template rowTuple(Rows...)
838 {
839     auto rowTuple(Rows rows)
840     {
841         import gfx.math.vec : isVec;
842         import std.traits : isStaticArray;
843         import std.typecons : tuple;
844 
845         static if (Rows.length == 0) {
846             return tuple();
847         }
848         else static if (Rows.length == 1) {
849             static if (isStaticArray!(Rows[0])) {
850                 return tuple(vec(rows[0]));
851             }
852             else static if (isVec!(Rows[0])) {
853                 return tuple(rows[0]);
854             }
855             else static if (isMat!(Rows[0])) {
856                 return rows[0].rowTup;
857             }
858             else {
859                 static assert(false, "only vectors and matrices allowed in rowTuple");
860             }
861         }
862         else static if (Rows.length > 1) {
863             return tuple(
864                 rowTuple(rows[0]).expand, rowTuple(rows[1 .. $]).expand
865             );
866         }
867         else {
868             static assert(false, "wrong arguments to rowTuple");
869         }
870     }
871 }
872 
873 
874 /// The type of row tuple from a sequence of vectors and tuples
875 template RowTuple (Rows...)
876 {
877     alias RowTuple = typeof(rowTuple(Rows.init));
878 }
879 
880 /// The number of rows in a row sequence
881 template rowLength (Rows...)
882 {
883     enum rowLength = RowTuple!(Rows).length;
884 }
885 
886 /// Check whether a row sequence has a consistent length.
887 /// (i.e. all rows have the same length).
888 template hasConsistentLength (Rows...)
889 {
890     import std.meta : allSatisfy;
891 
892     alias Tup = RowTuple!Rows;
893     template lengthOK(R)
894     {
895         enum lengthOK = R.length == Tup[0].length;
896     }
897     enum hasConsistentLength = allSatisfy!(lengthOK, Tup.Types[1 .. $]);
898 }