1 // Written in the D programming language
2 /**
3  * Implementation of matrix in mathematical definition.
4  * Features:
5  * $(OL
6  *      $(LI All matrix actions is checked at compile time)
7  *      $(LI Matrix contains only it's data, and nothing additional)
8  *      $(LI Almost all matrix actions is `pure` and `nothrow`)
9  * )
10  * 
11  * Usage:
12  * Just create matrix as in documentation below and treat it like a simple
13  * two-dimensional array (if you want to get some data from matrix or put it in)
14  * or number (if you need to do some mathematical action with matrix).
15  * 
16  * Copyright:
17  *      Copyright Vlad Rindevich, 2014.
18  * 
19  * License:
20  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
21  * 
22  * Authors:
23  *      Vlad Rindevich (rindevich.vs@gmail.com).
24  */
25 module mathed.types.matrix;
26 
27 public import mathed.utils.traits : isMatrix;
28 
29 private
30 {
31     import std.traits : isNumeric;
32     import std.conv : to;
33     import std.array : Appender, appender;
34 }
35 
36 alias Matrix!(1, 1) Matrix1f;
37 alias Matrix!(2, 2) Matrix2f;
38 alias Matrix!(3, 3) Matrix3f;
39 alias Matrix!(4, 4) Matrix4f;
40 
41 alias Matrix!(1, 1, int) Matrix1i;
42 alias Matrix!(2, 2, int) Matrix2i;
43 alias Matrix!(3, 3, int) Matrix3i;
44 alias Matrix!(4, 4, int) Matrix4i;
45 
46 /**
47  * Main matrix interface.
48  */
49 struct Matrix (size_t Lines, size_t Cols, Type = float)
50     if (Lines > 0 && Cols > 0)
51 {
52     private
53     {
54         alias Matrix!(Lines, Cols, Type) Self;
55 
56         /*
57          * Matrix core array.
58          */
59         static if (isNumeric!Type)
60             Type[Lines * Cols] data = mixin (DefaultInit (Lines * Cols));
61         else
62             Type[Lines * Cols] data;
63     }
64 
65     /// Gets quantity of matrix lines.
66     alias Lines lines;
67 
68     /// Gets quantity of matrix columns.
69     alias Cols cols;
70 
71     /// Gets type of matrix data
72     alias Type type;
73 
74     unittest
75     {
76         auto m = Matrix!(2, 3, int)
77         (
78              3, -1, 6,
79              2,  1, 5,
80         );
81 
82         assert (m.cols == 3);
83         assert (m.lines == 2);
84     }
85 
86 @trusted:
87 
88     /**
89      * Matrix default constructor. It receives a bunch of values in amount
90      * of product of matrix lines and columns.
91      */
92     this (in Type[Cols * Lines] values...) pure nothrow
93     {
94         set (values);
95     }
96 
97     /**
98      * Sets all matrix values in one action. It receives bunch of values.
99      */
100     void set (in Type[Cols * Lines] values...) pure nothrow
101     {
102         foreach (size_t index, ref element; data)
103             element = values[index];
104     }
105 
106     ///
107     unittest
108     {
109         // Creating matrix
110         auto m = Matrix3i
111         (
112              3, -1, 6,
113              2,  1, 5,
114             -3,  1, 0
115         );
116 
117         // Testing some value to be the same as added
118         assert (m[2][0] == -3);
119 
120         // Setting all values to zero.
121         m.set
122         (
123             0, 0, 0,
124             0, 0, 0,
125             0, 0, 0
126         );
127 
128         // Testing some values to be zero. 
129         assert (m[0][0] == 0);
130     }
131 
132     /**
133      * Stringifies matrix data. 
134      */
135     string toString ()
136     {
137         Appender!string result = appender ("[");
138 
139         foreach (size_t index; 0..Lines)
140             result.put (this[index].to!string ()
141                         ~ (index != Lines - 1 ? ", " : ""));
142 
143         result.put ("]");
144 
145         return result.data;
146     }
147 
148     unittest
149     {
150         auto m = Matrix3i
151         (
152              3, -1, 6,
153              2,  1, 5,
154             -3,  1, 0
155         );
156 
157         auto str = m.to!string ();
158         assert (str == "[[3, -1, 6], [2, 1, 5], [-3, 1, 0]]");
159     }
160 
161     /**
162      * Gets matrix line.
163      */
164     ref auto opIndex (size_t line) pure nothrow
165     {
166         return data[Cols * line .. Cols + Cols * line];
167     }
168 
169     unittest
170     {
171         auto m = Matrix3i
172         (
173              3, -1, 6,
174              2,  1, 5,
175             -3,  1, 0
176         );
177 
178         assert (m[2][0] == -3);
179     }
180 
181     /**
182      * Iterates matrix without returning any element number.
183      */
184     int opApply (int delegate (ref Type) foreach_)
185     {
186         int result;
187         
188         foreach (ref element; data)
189         {
190             result = foreach_ (element);
191             if (result) break;
192         }
193         
194         return result;
195     }
196 
197     /**
198      * Iterates matrix by element number. E.g. returned number of m[1][2]
199      * element in Matrix3x3 iteration will be `5`.
200      */
201     int opApply (int delegate (ref size_t, ref Type) foreach_)
202     {
203         int result;
204 
205         foreach (size_t index, ref element; data)
206         {
207             result = foreach_ (index, element);
208             if (result) break;
209         }
210         
211         return result;
212     }
213 
214     /**
215      * Iterates matrix returning line and column number with element.
216      */
217     int opApply (int delegate (ref size_t, ref size_t, ref Type) foreach_)
218     {
219         int result;
220         
221         size_t line, col;
222         foreach (size_t index, ref element; data)
223         {
224             result = foreach_ (line, col, element);
225             col++;
226 
227             if (index == Cols - 1 + Cols * line)
228             {
229                 line++;
230                 col = 0;
231             }
232 
233             if (result) break;
234         }
235         
236         return result;
237     }
238 
239     ///
240     unittest
241     {
242         auto m = Matrix3i
243         (
244              3, -1, 6,
245              2,  1, 5,
246             -3,  1, 0
247         );
248 
249         // Let's iterate matrix by element number
250         size_t index;
251         foreach (i, ref element; m)
252         {
253             if (element == 5)
254             {
255                 index = i;
256                 break;
257             }
258         }
259         assert (index == 5);
260 
261         // Let's iterate matrix with line and column number
262         size_t line, col;
263         foreach (i, j, ref element; m)
264         {
265             if (element == 5)
266             {
267                 line = i;
268                 col = j;
269                 break;
270             }
271         }
272         assert (line == 1 && col == 2);
273     }
274 
275     /**
276      * Inverses matrix sign
277      */ 
278     auto opUnary(string op)() pure nothrow
279         if( op == "-" )
280     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
281     body
282     {
283         return opBinary!"*" (-1);
284     }
285 
286     /**
287      * Processes matrix addition and subtraction.
288      */
289     Self opBinary (string op, SumType)(in Matrix!(Lines, Cols, SumType) summand) pure nothrow
290         if ((op == "+" || op == "-") && is (SumType : Type))
291     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
292     body
293     {
294         Self newMatrix;
295         foreach (size_t index, ref element; newMatrix.data)
296             mixin ("element = data[index] " ~ op ~ " cast(Type) summand.data[index];");
297         return newMatrix;
298     }
299 
300     /// ditto
301     void opOpAssign (string op, SumType)(in Matrix!(Lines, Cols, SumType) summand) pure nothrow
302         if ((op == "+" || op == "-") && is (SumType : Type))
303     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
304     body
305     {
306         this = opBinary!op (summand);
307     }
308 
309     unittest
310     {
311         auto m = Matrix3i
312         (
313              3, -1, 6,
314              2,  1, 5,
315             -3,  1, 0
316         );
317 
318         auto x = m + m;
319         m += m;
320 
321         auto equalX = Matrix3i
322         (
323              6, -2, 12,
324              4,  2, 10,
325             -6,  2,  0
326         );
327 
328         assert (x == equalX);
329         assert (m == equalX);
330     }
331 
332     /**
333      * Processes matrix multiplication and division with number.
334      */
335     Self opBinary (string op, Number)(in Number num) pure nothrow
336         if ((op == "*" || op == "/") && isNumeric!Number)
337     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
338     body
339     {
340         Self newMatrix;
341         foreach (size_t index, ref element; newMatrix.data)
342             mixin ("element = data[index] " ~ op ~ " num;");
343         return newMatrix;
344     }
345 
346     /// ditto
347     Self opBinaryRight (string op, Number)(in Number num) pure nothrow
348         if ((op == "*" || op == "/") && isNumeric!Number)
349     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
350     body
351     {
352         return opBinary!op (num);
353     }
354 
355     /// ditto
356     void opOpAssign (string op, Number)(in Number num) pure nothrow
357         if ((op == "*" || op == "/") && isNumeric!Number)
358     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
359     body
360     {
361         this = opBinary!op (num);
362     }
363 
364     unittest
365     {
366         auto m = Matrix3i
367         (
368              3, -1, 6,
369              2,  1, 5,
370             -3,  1, 0
371         );
372         
373         auto y = m * 5;
374         auto yy = 5 * m;
375         m *= 5;
376 
377         auto equalY = Matrix3i
378         (
379              15, -5, 30,
380              10,  5, 25,
381             -15,  5,  0
382         );
383 
384         assert (y == equalY);
385         assert (yy == equalY);
386         assert (m == equalY);
387     }
388 
389     /**
390      * Processes matrix multiplication with another matrix.
391      */
392     auto opBinary (string op, Factor)(in Factor factor) pure nothrow
393         if (op == "*" && isMatrix!Factor && Cols == Factor.lines
394             && is (Factor.type : Type))
395     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
396     body
397     {
398         Matrix!(Lines, Factor.cols, Type) newMatrix;
399 
400         size_t line, col;
401         foreach (ref element; newMatrix.data)
402         {
403             if (col == Factor.cols)
404             {
405                 line++;
406                 col = 0;
407             }
408 
409             foreach (k; 0 .. Cols)
410                 element += data[Cols * line + k] 
411                            * factor.data[Factor.cols * k + col];
412 
413             col++;
414         }
415 
416         return newMatrix;
417     }
418 
419     /// ditto
420     void opOpAssign (string op, Factor)(in Factor factor) pure nothrow
421         if (op == "*" && isMatrix!Factor && Cols == Factor.lines
422             && is (Factor.type : Type))
423     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
424     body
425     {
426         this = opBinary!op (factor);
427     }
428 
429     unittest
430     {
431         auto m = Matrix!(3, 3, int)
432         (
433              3, -1, 6,
434              2,  1, 5,
435             -3,  1, 0
436         );
437         
438         auto n = Matrix!(3, 2, int)
439         (
440             3, 4,
441             1, 0,
442             5, 2
443         );
444         
445         auto z = m * n;
446 
447         auto equalZ = Matrix!(3, 2, int)
448         (
449             38,  24,
450             32,  18,
451             -8, -12
452         );
453 
454         assert (z == equalZ);
455     }
456 
457     /**
458      * Casts matrix to a new type. It should be matrix type too, with equal
459      * quantity of lines and cols.
460      */
461     NewType opCast (NewType)() pure nothrow
462         if (isMatrix!NewType && NewType.lines == Lines
463             && NewType.cols == Cols && (is (NewType.type : Type)
464                                     || is (Type : NewType.type)))
465     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
466     body
467     {
468         NewType newMatrix;
469 
470         foreach (size_t index, ref element; newMatrix.data)
471             element = cast(NewType.type) data[index];
472 
473         return newMatrix;
474     }
475 
476     /// ditto
477     Matrix!(Lines, Cols, NewType) castTo (NewType)()
478         if (is (NewType : Type) || is (Type : NewType))
479     in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); }
480     body
481     {
482         return cast(Matrix!(Lines, Cols, NewType)) this;
483     }
484 
485     unittest
486     {
487         auto m = Matrix!(3, 3, int)
488         (
489              3, -1, 6,
490              2,  1, 5,
491             -3,  1, 0
492         );
493 
494         auto n = cast(Matrix!(3, 3)) m;
495         auto l = m.castTo!double ();
496 
497         assert (is (n.type == float));
498         assert (is (l.type == double));
499 
500         assert (is (typeof (n[0][0]) == float));
501         assert (is (typeof (l[0][0]) == double));
502 
503         assert (isMatrix!n);
504         assert (isMatrix!l);
505     }
506 
507     /**
508      * Casts matrix to a string.
509      */
510     string opCast (NewType)()
511         if (is (NewType == string))
512     {
513         return toString ();
514     }
515 
516     /**
517      * Transposes matrix.
518      */
519     @property auto t () pure nothrow
520     {
521         Matrix!(Cols, Lines, Type) newMatrix;
522 
523         size_t line, col;
524         foreach (ref element; newMatrix.data)
525         {
526             if (line == Lines)
527             {
528                 col++;
529                 line = 0;
530             }
531 
532             element = data[Cols * line + col];
533 
534             line++;
535         }
536 
537         return newMatrix;
538     }
539 
540     unittest
541     {
542         auto m = Matrix3i
543         (
544              3, -1, 6,
545              2,  1, 5,
546             -3,  1, 0
547         );
548 
549         assert (m.t.t == m);
550     }
551 
552     static if (Lines == Cols && isNumeric!Type)
553     {
554         /**
555          * Returns indentity matrix instead of zero.
556          */
557         @trusted static @property Self identity () pure nothrow
558         {
559             Self newMatrix;
560             newMatrix.data = mixin (ConstructIdentity ());
561             return newMatrix;
562         }
563 
564         /**
565          * Makes matrix with diagonal consists of received values.
566          */
567         @trusted static Self diag (Type[Lines] values...) pure nothrow
568         {
569             Self newMatrix;
570 
571             size_t line, col, index;
572             foreach (ref element; newMatrix.data)
573             {
574                 if (col == Cols)
575                 {
576                     line++;
577                     col = 0;
578                 }
579 
580                 if (line == col)
581                 {
582                     element = values[index];
583                     index++;
584                 }
585                 else
586                     element = 0;
587 
588                 col++;
589             }
590 
591             return newMatrix;
592         }
593     }
594 
595 private:
596 
597     static if (isNumeric!Type)
598     {
599         @trusted static string ConstructIdentity () pure nothrow
600         {
601             string buffer = "[ ";
602 
603             size_t line, col;
604             foreach (size_t index; 0 .. Lines * Cols)
605             {
606                 if (col == Cols)
607                 {
608                     line++;
609                     col = 0;
610                 }
611                 
612                 if (line == col)
613                     buffer ~= "1, ";
614                 else
615                     buffer ~= "0, ";
616                 
617                 col++;
618             }
619 
620             return buffer ~= " ]";
621         }
622     }
623 }
624 
625 package static @trusted string DefaultInit (size_t Size) pure nothrow
626 {
627     string buffer = "[ ";
628     foreach (size_t index; 0 .. Size)
629         buffer ~= "0, ";
630     return buffer ~ " ]";
631 }
632 
633 private:
634 
635 enum NOT_NUMERIC_FORBIDDEN = "Impossible to apply mathematical action to "
636     ~ "not-numeric matrix";