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