1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5 
6 #include "gtest/gtest.h"
7 
8 #include "axom/core/numerics/Matrix.hpp"
9 
10 // C/C++ includes
11 #include <sstream>  // for std::ostringstream
12 
13 using IndexType = axom::IndexType;
14 
15 //-----------------------------------------------------------------------------
16 // HELPER ROUTINES
17 //-----------------------------------------------------------------------------
18 namespace
19 {
testConstAccess(const axom::numerics::Matrix<double> & A)20 void testConstAccess(const axom::numerics::Matrix<double>& A)
21 {
22   const int MROWS = 10;
23   const int NCOLS = 10;
24   EXPECT_EQ(MROWS, A.getNumRows());
25   EXPECT_EQ(NCOLS, A.getNumColumns());
26 
27   for(int irow = 0; irow < MROWS; ++irow)
28   {
29     for(int jcol = 0; jcol < NCOLS; ++jcol)
30     {
31       double expval = static_cast<double>(irow * NCOLS + jcol);
32       EXPECT_EQ(expval, A(irow, jcol));
33     }  // END for all columns
34   }    // END for all rows
35 }
36 
37 //------------------------------------------------------------------------------
testCopyConstructor(axom::numerics::Matrix<double> A)38 void testCopyConstructor(axom::numerics::Matrix<double> A)
39 {
40   const int MROWS = 10;
41   const int NCOLS = 10;
42   EXPECT_EQ(MROWS, A.getNumRows());
43   EXPECT_EQ(NCOLS, A.getNumColumns());
44 
45   for(int irow = 0; irow < MROWS; ++irow)
46   {
47     for(int jcol = 0; jcol < NCOLS; ++jcol)
48     {
49       double expval = static_cast<double>(irow * NCOLS + jcol);
50       EXPECT_EQ(expval, A(irow, jcol));
51     }  // END for all columns
52   }    // END for all rows
53 }
54 
55 //------------------------------------------------------------------------------
testExternalBufferPassByValue(axom::numerics::Matrix<int> A)56 void testExternalBufferPassByValue(axom::numerics::Matrix<int> A)
57 {
58   const int FILL_VAL = 42;
59 
60   EXPECT_FALSE(A.usesExternalBuffer());
61   A.fill(FILL_VAL);
62 
63   const int nrows = A.getNumRows();
64   const int ncols = A.getNumColumns();
65 
66   for(int i = 0; i < nrows; ++i)
67   {
68     for(int j = 0; j < ncols; ++j)
69     {
70       EXPECT_EQ(FILL_VAL, A(i, j));
71     }  // END for all columns
72   }    // END for all rows
73 }
74 
75 } /* end unnamed namespace */
76 
77 //-----------------------------------------------------------------------------
78 // TEST ROUTINES
79 //------------------------------------------------------------------------------
TEST(numerics_matrix,basic_constructor)80 TEST(numerics_matrix, basic_constructor)
81 {
82   const int MROWS = 5;
83   const int NCOLS = 10;
84 
85   axom::numerics::Matrix<double> A(MROWS, NCOLS);
86   EXPECT_EQ(MROWS, A.getNumRows());
87   EXPECT_EQ(NCOLS, A.getNumColumns());
88   EXPECT_FALSE(A.isSquare());
89 
90   for(int irow = 0; irow < MROWS; ++irow)
91   {
92     for(int jcol = 0; jcol < NCOLS; ++jcol)
93     {
94       EXPECT_DOUBLE_EQ(0.0, A(irow, jcol));
95     }
96   }
97 }
98 
99 //------------------------------------------------------------------------------
TEST(numerics_matrix,basic_constructor_with_value)100 TEST(numerics_matrix, basic_constructor_with_value)
101 {
102   const int MROWS = 5;
103   const int NCOLS = 10;
104   const double FILL_VAL = 2.5;
105 
106   axom::numerics::Matrix<double> A(MROWS, NCOLS, FILL_VAL);
107   EXPECT_EQ(MROWS, A.getNumRows());
108   EXPECT_EQ(NCOLS, A.getNumColumns());
109   EXPECT_FALSE(A.isSquare());
110 
111   for(int irow = 0; irow < MROWS; ++irow)
112   {
113     for(int jcol = 0; jcol < NCOLS; ++jcol)
114     {
115       EXPECT_DOUBLE_EQ(FILL_VAL, A(irow, jcol));
116     }
117   }
118 }
119 
120 //------------------------------------------------------------------------------
TEST(numerics_matrix,array_constructor)121 TEST(numerics_matrix, array_constructor)
122 {
123   const int MROWS = 2;
124   const int NCOLS = 2;
125   int data[4] = {1, 2, 3, 4};
126 
127   axom::numerics::Matrix<int> A(MROWS, NCOLS, data);
128   EXPECT_EQ(MROWS, A.getNumRows());
129   EXPECT_EQ(NCOLS, A.getNumColumns());
130 
131   int idx = 0;
132   for(int j = 0; j < NCOLS; ++j)
133   {
134     for(int i = 0; i < MROWS; ++i)
135     {
136       ++idx;
137       EXPECT_EQ(idx, A(i, j));
138     }
139   }
140 }
141 
142 //------------------------------------------------------------------------------
TEST(numerics_matrix,array_constructor_with_external_buffer)143 TEST(numerics_matrix, array_constructor_with_external_buffer)
144 {
145   const int MROWS = 4;
146   const int NCOLS = 2;
147   int data[8] = {1, 1, 1, 1, 2, 2, 2, 2};
148   // BEGIN SCOPE
149   {
150     axom::numerics::Matrix<int> A(MROWS, NCOLS, data, true);
151     EXPECT_TRUE(A.usesExternalBuffer());
152 
153     for(int i = 0; i < NCOLS; ++i)
154     {
155       int* col = A.getColumn(i);
156       for(int j = 0; j < MROWS; ++j)
157       {
158         EXPECT_EQ(i + 1, col[j]);
159       }  // END for all rows
160     }    // END for all cols
161 
162     A.swapColumns(0, 1);
163   }
164   // END SCOPE
165 
166   // ensure the data is still there when the matrix goes out-of-scope
167   for(int i = 0; i < 8; ++i)
168   {
169     int expected = (i < 4) ? 2 : 1;
170     EXPECT_EQ(expected, data[i]);
171   }
172 
173   // test assignment
174   // BEGIN SCOPE
175   {
176     axom::numerics::Matrix<int> A(MROWS, NCOLS, data, true);
177     EXPECT_TRUE(A.usesExternalBuffer());
178     A.swapColumns(0, 1);
179 
180     // deep copy A into B
181     axom::numerics::Matrix<int> B = A;
182     EXPECT_FALSE(B.usesExternalBuffer());
183 
184     const int nrows = B.getNumRows();
185     const int ncols = B.getNumColumns();
186     EXPECT_EQ(nrows, A.getNumRows());
187     EXPECT_EQ(ncols, A.getNumColumns());
188 
189     for(int i = 0; i < nrows; ++i)
190     {
191       for(int j = 0; j < ncols; ++j)
192       {
193         EXPECT_EQ(A(i, j), B(i, j));
194       }
195     }
196 
197     const int FILL_VAL = 3;
198     B.fill(FILL_VAL);
199     for(int i = 0; i < nrows; ++i)
200     {
201       for(int j = 0; j < ncols; ++j)
202       {
203         EXPECT_EQ(FILL_VAL, B(i, j));
204         EXPECT_FALSE(A(i, j) == B(i, j));
205       }
206     }
207 
208     for(int i = 0; i < NCOLS; ++i)
209     {
210       int* col = A.getColumn(i);
211       for(int j = 0; j < MROWS; ++j)
212       {
213         EXPECT_EQ(i + 1, col[j]);
214       }  // END for all rows
215     }    // END for all cols
216   }
217   // END SCOPE
218 
219   // test copy constructor
220   // BEGIN SCOPE
221   {
222     axom::numerics::Matrix<int> A(MROWS, NCOLS, data, true);
223     EXPECT_TRUE(A.usesExternalBuffer());
224 
225     testExternalBufferPassByValue(A);
226 
227     for(int i = 0; i < NCOLS; ++i)
228     {
229       int* col = A.getColumn(i);
230       for(int j = 0; j < MROWS; ++j)
231       {
232         EXPECT_EQ(i + 1, col[j]);
233       }  // END for all rows
234     }    // END for all cols
235   }
236   // END SCOPE
237 }
238 
239 //------------------------------------------------------------------------------
TEST(numerics_matrix,is_square)240 TEST(numerics_matrix, is_square)
241 {
242   const int MROWS = 10;
243   const int NCOLS = 10;
244 
245   axom::numerics::Matrix<double> A(MROWS, NCOLS);
246   EXPECT_TRUE(A.isSquare());
247 }
248 
249 //------------------------------------------------------------------------------
TEST(numerics_matrix,random_access_operators)250 TEST(numerics_matrix, random_access_operators)
251 {
252   const int MROWS = 10;
253   const int NCOLS = 10;
254 
255   axom::numerics::Matrix<double> A(MROWS, NCOLS);
256 
257   for(int irow = 0; irow < MROWS; ++irow)
258   {
259     for(int jcol = 0; jcol < NCOLS; ++jcol)
260     {
261       double val = static_cast<double>(irow * NCOLS + jcol);
262       A(irow, jcol) = val;
263       EXPECT_EQ(val, A(irow, jcol));
264     }  // END for all columns
265   }    // END for all rows
266 
267   testConstAccess(A);
268 }
269 
270 //------------------------------------------------------------------------------
TEST(numerics_matrix,copy_constructor)271 TEST(numerics_matrix, copy_constructor)
272 {
273   const int MROWS = 10;
274   const int NCOLS = 10;
275 
276   axom::numerics::Matrix<double> A(MROWS, NCOLS);
277 
278   for(int irow = 0; irow < MROWS; ++irow)
279   {
280     for(int jcol = 0; jcol < NCOLS; ++jcol)
281     {
282       double val = static_cast<double>(irow * NCOLS + jcol);
283       A(irow, jcol) = val;
284       EXPECT_EQ(val, A(irow, jcol));
285     }  // END for all columns
286   }    // END for all rows
287 
288   testCopyConstructor(A);
289 }
290 
291 //------------------------------------------------------------------------------
TEST(numerics_matrix,assignment)292 TEST(numerics_matrix, assignment)
293 {
294   const int MROWS = 3;
295   const int NCOLS = 3;
296 
297   axom::numerics::Matrix<int> A(MROWS, NCOLS);
298   A.fill(3);
299 
300   axom::numerics::Matrix<int> B(2, 2);
301   B.fill(1);
302 
303   B = A;
304 
305   EXPECT_EQ(MROWS, B.getNumRows());
306   EXPECT_EQ(NCOLS, B.getNumColumns());
307 
308   for(int i = 0; i < MROWS; ++i)
309   {
310     for(int j = 0; j < NCOLS; ++j)
311     {
312       EXPECT_EQ(A(i, j), B(i, j));
313     }
314   }
315 }
316 
317 //------------------------------------------------------------------------------
TEST(numerics_matrix,getColumn)318 TEST(numerics_matrix, getColumn)
319 {
320   const int N = 3;
321   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
322   EXPECT_EQ(N, M.getNumRows());
323   EXPECT_EQ(N, M.getNumColumns());
324 
325   int expected[] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
326 
327   for(int j = 0; j < N; ++j)
328   {
329     const int* column = M.getColumn(j);
330     for(int i = 0; i < N; ++i)
331     {
332       EXPECT_EQ(expected[j * N + i], column[i]);
333     }  // END for all i
334   }    // END for all j
335 }
336 
337 //------------------------------------------------------------------------------
TEST(numerics_matrix,getRow)338 TEST(numerics_matrix, getRow)
339 {
340   const int N = 3;
341   axom::numerics::Matrix<int> A(N, N);
342 
343   int row_sums[] = {0, 0, 0};
344 
345   for(IndexType i = 0; i < N; ++i)
346   {
347     A.fillRow(i, i + 1);
348     row_sums[i] = N * (i + 1);
349   }
350 
351   IndexType p = 0;
352   IndexType size = 0;
353   for(IndexType i = 0; i < N; ++i)
354   {
355     int* row = A.getRow(i, p, size);
356     for(IndexType j = 0; j < size; j += p)
357     {
358       EXPECT_EQ(i + 1, row[j]);
359     }
360 
361     double sum = 0.0;
362     for(IndexType j = 0; j < size; j += p)
363     {
364       sum += row[j];
365     }
366 
367     EXPECT_EQ(row_sums[i], sum);
368   }
369 }
370 
371 //------------------------------------------------------------------------------
TEST(numerics_matrix,getDiagonal)372 TEST(numerics_matrix, getDiagonal)
373 {
374   const int N = 3;
375   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
376   EXPECT_EQ(N, M.getNumRows());
377   EXPECT_EQ(N, M.getNumColumns());
378   EXPECT_EQ(N, M.getDiagonalSize());
379 
380   const int EXPECTED_SUM = N;
381 
382   int* diagonal = new int[N];
383   M.getDiagonal(diagonal);
384 
385   int sum = 0;
386   for(int i = 0; i < N; ++i)
387   {
388     EXPECT_EQ(1, diagonal[i]);
389     sum += diagonal[i];
390   }
391 
392   EXPECT_EQ(EXPECTED_SUM, sum);
393   delete[] diagonal;
394 
395   IndexType p = 0;
396   IndexType size = 0;
397   const int* diag = M.getDiagonal(p, size);
398   EXPECT_EQ(N + 1, p);
399   EXPECT_EQ(M.getDiagonalSize() * N, size);
400 
401   int sum2 = 0;
402   for(IndexType i = 0; i < size; i += p)
403   {
404     EXPECT_EQ(1, diag[i]);
405     sum2 += diag[i];
406   }
407   EXPECT_EQ(EXPECTED_SUM, sum2);
408 }
409 
410 //------------------------------------------------------------------------------
TEST(numerics_matrix,fillDiagonal)411 TEST(numerics_matrix, fillDiagonal)
412 {
413   const int N = 3;
414   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
415   EXPECT_EQ(N, M.getNumRows());
416   EXPECT_EQ(N, M.getNumColumns());
417   EXPECT_EQ(N, M.getDiagonalSize());
418 
419   M.fillDiagonal(3);
420 
421   int* diagonal = new int[N];
422   M.getDiagonal(diagonal);
423 
424   for(int i = 0; i < N; ++i)
425   {
426     EXPECT_EQ(3, diagonal[i]);
427   }
428 
429   delete[] diagonal;
430 }
431 
432 //------------------------------------------------------------------------------
TEST(numerics_matrix,fillRow)433 TEST(numerics_matrix, fillRow)
434 {
435   const int FILL_VAL = 3;
436   const int TARGET_ROW = 1;
437 
438   const int N = 3;
439   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
440   EXPECT_EQ(N, M.getNumRows());
441   EXPECT_EQ(N, M.getNumColumns());
442 
443   M.fillRow(TARGET_ROW, FILL_VAL);
444 
445   for(int i = 0; i < N; ++i)
446   {
447     EXPECT_EQ(FILL_VAL, M(TARGET_ROW, i));
448   }
449 }
450 
451 //------------------------------------------------------------------------------
TEST(numerics_matrix,fillColumn)452 TEST(numerics_matrix, fillColumn)
453 {
454   const int FILL_VAL = 3;
455   const int TARGET_COL = 1;
456 
457   const int N = 3;
458   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
459   EXPECT_EQ(N, M.getNumRows());
460   EXPECT_EQ(N, M.getNumColumns());
461 
462   M.fillColumn(TARGET_COL, FILL_VAL);
463 
464   for(int i = 0; i < N; ++i)
465   {
466     EXPECT_EQ(FILL_VAL, M(i, TARGET_COL));
467   }
468 }
469 
470 //------------------------------------------------------------------------------
TEST(numerics_matrix,fill)471 TEST(numerics_matrix, fill)
472 {
473   const int FILL_VAL = 3;
474 
475   const int N = 3;
476   axom::numerics::Matrix<int> M = axom::numerics::Matrix<int>::identity(N);
477   EXPECT_EQ(N, M.getNumRows());
478   EXPECT_EQ(N, M.getNumColumns());
479 
480   M.fill(FILL_VAL);
481 
482   for(int i = 0; i < N; ++i)
483   {
484     for(int j = 0; j < N; ++j)
485     {
486       EXPECT_EQ(FILL_VAL, M(i, j));
487     }
488   }
489 }
490 
491 //------------------------------------------------------------------------------
TEST(numerics_matrix,swapRows)492 TEST(numerics_matrix, swapRows)
493 {
494   const int M = 2;
495   const int N = 3;
496   axom::numerics::Matrix<int> A = axom::numerics::Matrix<int>::zeros(M, N);
497 
498   EXPECT_EQ(2, M);
499   int FILL_VAL[2] = {3, 9};
500 
501   A.fillRow(0, FILL_VAL[0]);
502   A.fillRow(1, FILL_VAL[1]);
503 
504   A.swapRows(0, 1);
505 
506   for(IndexType i = 0; i < N; ++i)
507   {
508     int* column = A.getColumn(i);
509     for(IndexType j = 0; j < M; ++j)
510     {
511       EXPECT_EQ(FILL_VAL[(j + 1) % M], column[j]);
512     }
513   }
514 }
515 
516 //------------------------------------------------------------------------------
TEST(numerics_matrix,swapColumns)517 TEST(numerics_matrix, swapColumns)
518 {
519   const int M = 3;
520   const int N = 4;
521 
522   // setup a test matrix
523   axom::numerics::Matrix<int> A(M, N);
524   for(IndexType i = 0; i < N; ++i)
525   {
526     A.fillColumn(i, i + 1);
527   }
528 
529   int first_column = 0;
530   int last_column = N - 1;
531   A.swapColumns(first_column, last_column);
532 
533   int* first_column_data = A.getColumn(first_column);
534   int* last_column_data = A.getColumn(last_column);
535   for(IndexType i = 0; i < M; ++i)
536   {
537     EXPECT_EQ(last_column + 1, first_column_data[i]);
538     EXPECT_EQ(first_column + 1, last_column_data[i]);
539   }
540 }
541 
542 //------------------------------------------------------------------------------
TEST(numerics_matrix,output_stream)543 TEST(numerics_matrix, output_stream)
544 {
545   axom::numerics::Matrix<int> A(2, 3);
546   A(0, 0) = 1;
547   A(0, 1) = 2;
548   A(0, 2) = 3;
549   A(1, 0) = 4;
550   A(1, 1) = 5;
551   A(1, 2) = 6;
552 
553   std::ostringstream expected_stream;
554   expected_stream << "[ 1 2 3 ]\n"
555                   << "[ 4 5 6 ]\n";
556 
557   std::ostringstream actual_stream;
558   actual_stream << A;
559 
560   EXPECT_EQ(expected_stream.str(), actual_stream.str());
561 }
562