1 /******************************************************************************************************
2  * (C) 2016 markummitchell@github.com. This file is part of Engauge Digitizer, which is released      *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission.     *
5  ******************************************************************************************************/
6 
7 #include "EngaugeAssert.h"
8 #include "Matrix.h"
9 #include <qmath.h>
10 #include <QTextStream>
11 
Matrix(int N)12 Matrix::Matrix (int N)
13 {
14   initialize (N, N);
15 }
16 
Matrix(int rows,int cols)17 Matrix::Matrix (int rows,
18                 int cols)
19 {
20   initialize (rows, cols);
21 }
22 
Matrix(const Matrix & other)23 Matrix::Matrix (const Matrix &other)
24 {
25   m_rows = other.rows();
26   m_cols = other.cols();
27   m_vector.resize (m_rows * m_cols);
28   for (int row = 0; row < m_rows; row++) {
29     for (int col = 0; col < m_cols; col++) {
30       set (row, col, other.get (row, col));
31     }
32   }
33 }
34 
operator =(const Matrix & other)35 Matrix &Matrix::operator= (const Matrix &other)
36 {
37   m_rows = other.rows();
38   m_cols = other.cols();
39   m_vector.resize (m_rows * m_cols);
40   for (int row = 0; row < m_rows; row++) {
41     for (int col = 0; col < m_cols; col++) {
42       set (row, col, other.get (row, col));
43     }
44   }
45 
46   return *this;
47 }
48 
addRowToAnotherWithScaling(int rowFrom,int rowTo,double factor)49 void Matrix::addRowToAnotherWithScaling (int rowFrom,
50                                          int rowTo,
51                                          double factor)
52 {
53   for (int col = 0; col < cols (); col++) {
54     double oldValueFrom = get (rowFrom, col);
55     double oldValueTo = get (rowTo, col);
56     double newValueTo = oldValueFrom * factor + oldValueTo;
57     set (rowTo, col, newValueTo);
58   }
59 }
60 
cols() const61 int Matrix::cols () const
62 {
63   return m_cols;
64 }
65 
determinant() const66 double Matrix::determinant () const
67 {
68   ENGAUGE_ASSERT (m_rows == m_cols);
69 
70   double rtn;
71 
72   if (m_rows == 1) {
73 
74     rtn = m_vector [0];
75 
76   } else {
77 
78     const int COL = 0; // We arbitrarily iterate through the first column
79 
80     // This is a recursive algorithm
81     rtn = 0.0;
82     double multiplier = +1;
83     for (int row = 0; row < m_rows; row++) {
84       Matrix min = minorReduced (row, COL);
85       rtn += multiplier * get (row, COL) * min.determinant ();
86       multiplier *= -1.0;
87     }
88   }
89 
90   return rtn;
91 }
92 
fold2dIndexes(int row,int col) const93 int Matrix::fold2dIndexes (int row, int col) const
94 {
95   return row * m_cols + col;
96 }
97 
get(int row,int col) const98 double Matrix::get (int row, int col) const
99 {
100   int foldedIndex = fold2dIndexes (row, col);
101   return m_vector [foldedIndex];
102 }
103 
initialize(int rows,int cols)104 void Matrix::initialize (int rows,
105                          int cols)
106 {
107   m_rows = rows;
108   m_cols = cols;
109   m_vector.resize (rows * cols);
110   for (int row = 0; row < m_rows; row++) {
111     for (int col = 0; col < m_cols; col++) {
112 
113       // Identity matrix for square matrix, otherwise zero matrix
114       if (row == col && m_rows == m_cols) {
115         set (row, col, 1.0);
116       } else {
117         set (row, col, 0.0);
118       }
119     }
120   }
121 }
122 
inverse(int significantDigits,MatrixConsistent & matrixConsistent) const123 Matrix Matrix::inverse (int significantDigits,
124                         MatrixConsistent &matrixConsistent) const
125 {
126   // Set epsilon threshold for valueFailsEpsilonTest
127   double maxValue = 0;
128   for (int row = 0; row < m_rows; row++) {
129     for (int col = 0; col < m_cols; col++) {
130       double value = qAbs (get (row, col));
131       if (value > maxValue) {
132         maxValue = value;
133       }
134     }
135   }
136 
137   // Available algorithms are inverseCramersRule and inverseGaussianElimination
138   matrixConsistent = MATRIX_CONSISTENT;
139   return inverseGaussianElimination (significantDigits,
140                                      matrixConsistent);
141 }
142 
inverseCramersRule(MatrixConsistent & matrixConsistent,double epsilonThreshold) const143 Matrix Matrix::inverseCramersRule (MatrixConsistent & matrixConsistent,
144                                    double epsilonThreshold) const
145 {
146   ENGAUGE_ASSERT (m_rows == m_cols);
147 
148   Matrix inv (m_rows);
149   int row, col;
150 
151   if (m_rows > 1) {
152 
153     // Compute cofactor matrix
154     double multiplierStartForRow = 1.0;
155     Matrix cofactor (m_rows);
156     for (row = 0; row < m_rows; row++) {
157       double multiplier = multiplierStartForRow;
158       for (col = 0; col < m_cols; col++) {
159         Matrix min = minorReduced (row, col);
160         double element = multiplier * min.determinant ();
161         multiplier *= -1.0;
162         cofactor.set (row, col, element);
163       }
164       multiplierStartForRow *= -1.0;
165     }
166 
167     // Compute adjoint
168     Matrix adjoint = cofactor.transpose ();
169 
170     double determ = determinant ();
171     if (valueFailsEpsilonTest (determ,
172                                epsilonThreshold)) {
173       matrixConsistent = MATRIX_INCONSISTENT;
174       return inv;
175     }
176 
177     // Inverse is the adjoint divided by the determinant
178     for (row = 0; row < m_rows; row++) {
179       for (col = 0; col < m_cols; col++) {
180         inv.set (row, col, adjoint.get (row, col) / determ);
181       }
182     }
183   } else {
184     double denominator = get (0, 0);
185     if (valueFailsEpsilonTest (denominator,
186                                epsilonThreshold)) {
187       matrixConsistent = MATRIX_INCONSISTENT;
188       return inv;
189     }
190     inv.set (0, 0, 1.0 / denominator);
191   }
192 
193   return inv;
194 }
195 
inverseGaussianElimination(int significantDigits,MatrixConsistent & matrixConsistent) const196 Matrix Matrix::inverseGaussianElimination (int significantDigits,
197                                            MatrixConsistent &matrixConsistent) const
198 {
199   // From https://en.wikipedia.org/wiki/Gaussian_elimination
200 
201   int row, col, rowFrom, rowTo;
202   Matrix inv (rows ());
203 
204   // Track the row switches that may or may not be performed below
205   QVector<int> rowIndexes (rows ());
206   for (row = 0; row < rows (); row++) {
207     rowIndexes [row] = row;
208   }
209 
210   // Create the working matrix and populate the left half. Number of columns in the working matrix is twice the number
211   // of cols in this matrix, but we will not populate the right half until after the bubble sort below
212   Matrix working (rows (), 2 * cols ());
213   for (row = 0; row < rows (); row++) {
214     for (col = 0; col < cols (); col++) {
215       double value = get (row, col);
216       working.set (row, col, value);
217     }
218   }
219 
220   // Simple bubble sort to rearrange the rows with 0 leading zeros at start, followed by rows with 1 leading zero at start, ...
221   for (int row1 = 0; row1 < rows (); row1++) {
222     for (int row2 = row1 + 1; row2 < rows (); row2++) {
223       if (working.leadingZeros (row1) > working.leadingZeros (row2)) {
224         working.switchRows (row1, row2);
225 
226         // Remember this switch
227         int row1Index = rowIndexes [row1];
228         int row2Index = rowIndexes [row2];
229         rowIndexes [row1] = row2Index;
230         rowIndexes [row2] = row1Index;
231       }
232     }
233   }
234 
235   // Populate the right half now
236   for (row = 0; row < rows (); row++) {
237     for (col = cols (); col < 2 * cols (); col++) {
238       int colIdentitySubmatrix = col - cols ();
239       double value  = (row == colIdentitySubmatrix) ? 1 : 0;
240       working.set (row, col, value);
241     }
242   }
243 
244   // Loop through the "from" row going down. This results in the lower off-diagonal terms becoming zero, in the left half
245   for (rowFrom = 0; rowFrom < rows (); rowFrom++) {
246     int colFirstWithNonZero = rowFrom;
247 
248     // In pathological situations we have (rowFrom, colFirstWithNonzero) = 0 in which case the solution cannot be obtained
249     // so we exit
250     if (qAbs (working.get (rowFrom, colFirstWithNonZero)) <= 0) {
251       matrixConsistent = MATRIX_INCONSISTENT;
252       return inv;
253     }
254 
255     // Normalize the 'from' row with first nonzero term set to 1
256     working.normalizeRow (rowFrom, colFirstWithNonZero, significantDigits, matrixConsistent);
257     if (matrixConsistent == MATRIX_INCONSISTENT) {
258       return inv;
259     }
260 
261     // Apply the 'from' row to all the 'to' rows
262     for (rowTo = rowFrom + 1; rowTo < rows (); rowTo++) {
263 
264       if (qAbs (working.get (rowTo, colFirstWithNonZero)) > 0) {
265 
266         // We need to merge rowFrom and rowTo into rowTo
267         double denominator = working.get (rowFrom, colFirstWithNonZero);
268         double factor = -1.0 * working.get (rowTo, colFirstWithNonZero) / denominator;
269         working.addRowToAnotherWithScaling (rowFrom, rowTo, factor);
270       }
271     }
272   }
273 
274   // Loop through the "from" row doing up. This results in the upper off-diagonal terms becoming zero, in the left half
275   for (rowFrom = rows () - 1; rowFrom >= 0; rowFrom--) {
276     int colFirstWithNonZero = rowFrom; // This is true since we should have 1s all down the diagonal at this point
277 
278     // Normalize the 'from' row with diagonal term set to 1. The first term should be like 0.9999 or 1.0001 but we want exactly one
279     MatrixConsistent matrixConsistentIter;
280     working.normalizeRow (rowFrom, colFirstWithNonZero, significantDigits, matrixConsistentIter);
281     if (matrixConsistentIter == MATRIX_INCONSISTENT) {
282       return inv;
283     }
284 
285     // Apply the 'from' row to all the 'to' rows
286     for (rowTo = rowFrom - 1; rowTo >= 0; rowTo--) {
287 
288       if (qAbs (working.get (rowTo, colFirstWithNonZero)) > 0) {
289 
290         // We need to merge rowFro and rowTo into rowTo
291         double denominator = working.get (rowFrom, colFirstWithNonZero);
292         double factor = -1.0 * working.get (rowTo, colFirstWithNonZero) / denominator;
293         working.addRowToAnotherWithScaling (rowFrom, rowTo, factor);
294       }
295     }
296   }
297 
298   // Extract right half of rectangular matrix which is the inverse
299 
300   for (row = 0; row < working.rows (); row++) {
301 
302     int rowBeforeSort = rowIndexes [row];
303 
304     for (col = 0; col < working.rows (); col++) {
305       int colRightHalf = col + inv.cols ();
306       double value = working.get (rowBeforeSort, colRightHalf);
307       inv.set (row, col, value);
308     }
309   }
310 
311   return inv;
312 }
313 
leadingZeros(int row) const314 unsigned int Matrix::leadingZeros (int row) const
315 {
316   unsigned int sum = 0;
317   for (int col = 0; col < cols (); col++) {
318     if (qAbs (get (row, col)) > 0) {
319       ++sum;
320     }
321   }
322 
323   return sum;
324 }
325 
minorReduced(int rowOmit,int colOmit) const326 Matrix Matrix::minorReduced (int rowOmit, int colOmit) const
327 {
328   ENGAUGE_ASSERT (m_rows == m_cols);
329 
330   Matrix outMinor (m_rows - 1);
331   int rowMinor = 0;
332   for (int row = 0; row < m_rows; row++) {
333 
334     if (row != rowOmit) {
335 
336       int colMinor = 0;
337       for (int col = 0; col < m_cols; col++) {
338 
339         if (col != colOmit) {
340 
341           outMinor.set (rowMinor, colMinor, get (row, col));
342           ++colMinor;
343         }
344       }
345       ++rowMinor;
346     }
347   }
348 
349   return outMinor;
350 }
351 
normalizeRow(int rowToNormalize,int colToNormalize,int significantDigits,MatrixConsistent & matrixConsistent)352 void Matrix::normalizeRow (int rowToNormalize,
353                            int colToNormalize,
354                            int significantDigits,
355                            MatrixConsistent &matrixConsistent)
356 {
357   double denominator = get (rowToNormalize, colToNormalize);
358 
359   // Epsilon is computed from smallest value in row
360   double smallestAbsValue = 0;
361   for (int col = 0; col < cols (); col++) {
362     double absValue = qAbs (get (rowToNormalize, 0));
363     if (col == 0 || absValue < smallestAbsValue) {
364       smallestAbsValue = absValue;
365     }
366   }
367   double epsilonThreshold = smallestAbsValue / qPow (10.0, significantDigits);
368 
369   if (valueFailsEpsilonTest (denominator,
370                              epsilonThreshold)) {
371 
372     matrixConsistent = MATRIX_INCONSISTENT;
373 
374   } else {
375 
376     matrixConsistent = MATRIX_CONSISTENT;
377 
378     double factor = 1.0 / denominator;
379     for (int col = 0; col < cols (); col++) {
380       double value = get (rowToNormalize, col);
381       set (rowToNormalize, col, factor * value);
382     }
383   }
384 }
385 
operator *(const Matrix & other) const386 Matrix Matrix::operator* (const Matrix &other) const
387 {
388   ENGAUGE_ASSERT (m_cols == other.rows ());
389 
390   Matrix out (m_rows, other.cols ());
391 
392   for (int row = 0; row < m_rows; row++) {
393     for (int col = 0; col < other.cols (); col++) {
394       double sum = 0;
395       for (int index = 0; index < m_cols; index++) {
396         sum += get (row, index) * other.get (index, col);
397       }
398       out.set (row, col, sum);
399     }
400   }
401 
402   return out;
403 }
404 
operator *(const QVector<double> other) const405 QVector<double> Matrix::operator* (const QVector<double> other) const
406 {
407   ENGAUGE_ASSERT (m_cols == other.size ());
408 
409   QVector<double> out;
410   out.resize (m_rows);
411   for (int row = 0; row < m_rows; row++) {
412     double sum = 0;
413     for (int col = 0; col < m_cols; col++) {
414       sum += get (row, col) * other [col];
415     }
416 
417     out [row] = sum;
418   }
419 
420   return out;
421 }
422 
rows() const423 int Matrix::rows () const
424 {
425   return m_rows;
426 }
427 
set(int row,int col,double value)428 void Matrix::set (int row, int col, double value)
429 {
430   m_vector [fold2dIndexes (row, col)] = value;
431 }
432 
switchRows(int row1,int row2)433 void Matrix::switchRows (int row1,
434                          int row2)
435 {
436   // Switch rows
437   for (int col = 0; col < cols (); col++) {
438     double temp1 = get (row1, col);
439     double temp2 = get (row2, col);
440     set (row2, col, temp2);
441     set (row1, col, temp1);
442   }
443 }
444 
toString() const445 QString Matrix::toString () const
446 {
447   QString out;
448   QTextStream str (&out);
449 
450   str << "(";
451   for (int row = 0; row < rows (); row++) {
452     if (row > 0) {
453       str << ", ";
454     }
455     str << "(";
456     for (int col = 0; col < cols (); col++) {
457       if (col > 0) {
458         str << ", ";
459       }
460       str << get (row, col);
461     }
462     str << ")";
463   }
464   str << ")";
465 
466   return out;
467 }
468 
transpose() const469 Matrix Matrix::transpose () const
470 {
471   Matrix out (m_cols, m_rows);
472 
473   for (int row = 0; row < m_rows; row++) {
474     for (int col = 0; col < m_cols; col++) {
475       out.set (col, row, get (row, col));
476     }
477   }
478 
479   return out;
480 }
481 
valueFailsEpsilonTest(double value,double epsilonThreshold) const482 bool Matrix::valueFailsEpsilonTest (double value,
483                                     double epsilonThreshold) const
484 {
485   return qAbs (value) < qAbs (epsilonThreshold);
486 }
487