View Javadoc
1   package org.djunits.value.vdouble.matrix.data;
2   
3   import java.io.Serializable;
4   import java.util.Arrays;
5   import java.util.Collection;
6   import java.util.stream.IntStream;
7   
8   import org.djunits.unit.Unit;
9   import org.djunits.unit.scale.Scale;
10  import org.djunits.value.ValueRuntimeException;
11  import org.djunits.value.storage.Storage;
12  import org.djunits.value.storage.StorageType;
13  import org.djunits.value.vdouble.function.DoubleFunction;
14  import org.djunits.value.vdouble.function.DoubleFunction2;
15  import org.djunits.value.vdouble.matrix.base.DoubleSparseValue;
16  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
17  import org.djutils.exceptions.Throw;
18  
19  /**
20   * Stores the data for a DoubleMatrix and carries out basic operations.
21   * <p>
22   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
24   * </p>
25   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
27   */
28  public abstract class DoubleMatrixData extends Storage<DoubleMatrixData> implements Serializable
29  {
30      /** */
31      private static final long serialVersionUID = 1L;
32  
33      /** the internal storage of the Matrix; can be sparse or dense. */
34      @SuppressWarnings("checkstyle:visibilitymodifier")
35      protected double[] matrixSI;
36  
37      /** the number of rows of the matrix. */
38      @SuppressWarnings("checkstyle:visibilitymodifier")
39      protected int rows;
40  
41      /** the number of columns of the matrix. */
42      @SuppressWarnings("checkstyle:visibilitymodifier")
43      protected int cols;
44  
45      /**
46       * Construct a new DoubleMatrixData store.
47       * @param storageType StorageType; the data type
48       */
49      DoubleMatrixData(final StorageType storageType)
50      {
51          super(storageType);
52      }
53  
54      /* ============================================================================================ */
55      /* ====================================== INSTANTIATION ======================================= */
56      /* ============================================================================================ */
57  
58      /**
59       * Instantiate a DoubleMatrixData with the right data type. The double array is of the form d[rows][columns] so each value
60       * can be found with d[row][column].
61       * @param values double[][]; the (SI) values to store
62       * @param scale Scale; the scale of the unit to use for conversion to SI
63       * @param storageType StorageType; the data type to use
64       * @return the DoubleMatrixData with the right data type
65       * @throws NullPointerException when values are null, or storageType is null
66       * @throws ValueRuntimeException when values is ragged
67       */
68      public static DoubleMatrixData instantiate(final double[][] values, final Scale scale, final StorageType storageType)
69              throws ValueRuntimeException
70      {
71          Throw.whenNull(scale, "DoubleMatrixData.instantiate: scale is null");
72          Throw.whenNull(storageType, "DoubleMatrixData.instantiate: storageType is null");
73          checkRectangularAndNonNull(values);
74  
75          int rows = values.length;
76          final int cols = rows == 0 ? 0 : values[0].length;
77          if (cols == 0)
78          {
79              rows = 0;
80          }
81  
82          switch (storageType)
83          {
84              case DENSE:
85                  double[] valuesSI = new double[rows * cols];
86                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
87                          .forEach(c -> valuesSI[r * cols + c] = scale.toStandardUnit(values[r][c])));
88                  return new DoubleMatrixDataDense(valuesSI, rows, cols);
89  
90              case SPARSE:
91                  return DoubleMatrixDataSparse.instantiate(values, scale);
92  
93              default:
94                  throw new ValueRuntimeException("Unknown storage type in DoubleMatrixData.instantiate: " + storageType);
95          }
96      }
97  
98      /**
99       * Instantiate a DoubleMatrixData with the right data type.
100      * @param values Collection&lt;DoubleSparseValue&lt;U, S&gt;&gt;; the (sparse [X, Y, SI]) values to store
101      * @param rows int; the number of rows of the matrix
102      * @param cols int; the number of columns of the matrix
103      * @param storageType StorageType; the data type to use
104      * @return the DoubleMatrixData with the right data type
105      * @throws NullPointerException when values are null, or storageType is null
106      * @throws ValueRuntimeException when rows &lt; 0 or cols &lt; 0
107      * @param <U> the unit type
108      * @param <S> the corresponding scalar type
109      */
110     public static <U extends Unit<U>, S extends DoubleScalar<U, S>> DoubleMatrixData instantiate(
111             final Collection<DoubleSparseValue<U, S>> values, final int rows, final int cols, final StorageType storageType)
112             throws ValueRuntimeException
113     {
114         Throw.whenNull(values, "DoubleMatrixData.instantiate: values is null");
115         Throw.whenNull(storageType, "DoubleMatrixData.instantiate: storageType is null");
116         Throw.when(cols < 0, ValueRuntimeException.class, "cols must be >= 0");
117         Throw.when(rows < 0, ValueRuntimeException.class, "rows must be >= 0");
118         for (DoubleSparseValue<U, S> dsp : values)
119         {
120             Throw.whenNull(dsp, "null value in values");
121             Throw.when(dsp.getRow() < 0 || dsp.getRow() >= rows, ValueRuntimeException.class, "row out of range");
122             Throw.when(dsp.getColumn() < 0 || dsp.getColumn() >= cols, ValueRuntimeException.class, "column out of range");
123         }
124 
125         switch (storageType)
126         {
127             case DENSE:
128                 double[] valuesSI = new double[rows * cols];
129                 values.stream().parallel().forEach(v -> valuesSI[v.getRow() * cols + v.getColumn()] = v.getValueSI());
130                 return new DoubleMatrixDataDense(valuesSI, rows, cols);
131 
132             case SPARSE:
133                 return new DoubleMatrixDataSparse(values, rows, cols);
134 
135             default:
136                 throw new ValueRuntimeException("Unknown storage type in DoubleMatrixData.instantiate: " + storageType);
137         }
138     }
139 
140     /**
141      * Instantiate a DoubleMatrixData with the right data type. The double array is of the form d[rows][columns] so each value
142      * can be found with d[row][column].
143      * @param values S[][]; the values to store
144      * @param storageType StorageType; the data type to use
145      * @return the DoubleMatrixData with the right data type
146      * @throws NullPointerException when values is null, or storageType is null
147      * @throws ValueRuntimeException when values is ragged
148      * @param <U> the unit type
149      * @param <S> the corresponding scalar type
150      */
151     public static <U extends Unit<U>, S extends DoubleScalar<U, S>> DoubleMatrixData instantiate(final S[][] values,
152             final StorageType storageType) throws ValueRuntimeException
153     {
154         Throw.whenNull(storageType, "DoubleMatrixData.instantiate: storageType is null");
155         checkRectangularAndNonNull(values);
156 
157         int rows = values.length;
158         final int cols = rows == 0 ? 0 : values[0].length;
159         if (cols == 0)
160         {
161             rows = 0;
162         }
163 
164         switch (storageType)
165         {
166             case DENSE:
167                 double[] valuesSI = new double[rows * cols];
168                 IntStream.range(0, rows).parallel()
169                         .forEach(r -> IntStream.range(0, cols).forEach(c -> valuesSI[r * cols + c] = values[r][c].getSI()));
170                 return new DoubleMatrixDataDense(valuesSI, rows, cols);
171 
172             case SPARSE:
173                 double[][] matrixSI = new double[rows][cols];
174                 IntStream.range(0, values.length).parallel()
175                         .forEach(r -> IntStream.range(0, cols).forEach(c -> matrixSI[r][c] = values[r][c].getSI()));
176                 return DoubleMatrixDataSparse.instantiate(matrixSI);
177 
178             default:
179                 throw new ValueRuntimeException("Unknown storage type in DoubleMatrixData.instantiate: " + storageType);
180         }
181     }
182 
183     /* ============================================================================================ */
184     /* ==================================== UTILITY FUNCTIONS ===================================== */
185     /* ============================================================================================ */
186 
187     /**
188      * Retrieve the row count.
189      * @return int; the number of rows of the matrix
190      */
191     public int rows()
192     {
193         return this.rows;
194     }
195 
196     /**
197      * Retrieve the column count.
198      * @return int; the number of columns of the matrix
199      */
200     public int cols()
201     {
202         return this.cols;
203     }
204 
205     /**
206      * Return the data of this matrix in dense storage format.
207      * @return DoubleMatrixDataDense; the dense transformation of this data
208      */
209     public abstract DoubleMatrixDataDense toDense();
210 
211     /**
212      * Return the data of this matrix in sparse storage format.
213      * @return DoubleMatrixDataSparse; the sparse transformation of this data
214      */
215     public abstract DoubleMatrixDataSparse toSparse();
216 
217     /**
218      * Retrieve one value from this data.
219      * @param row int; the row number to get the value for
220      * @param col int; the column number to get the value for
221      * @return the value at the [row, col] point
222      */
223     public abstract double getSI(int row, int col);
224 
225     /**
226      * Sets a value at the [row, col] point in the matrix.
227      * @param row int; the row number to set the value for
228      * @param col int; the column number to set the value for
229      * @param valueSI double; the value at the index
230      */
231     public abstract void setSI(int row, int col, double valueSI);
232 
233     /**
234      * Compute and return the sum of the values of all cells of this matrix.
235      * @return double; the sum of the values of all cells
236      */
237     public final double zSum()
238     {
239         return Arrays.stream(this.matrixSI).parallel().sum();
240     }
241 
242     /**
243      * Create and return a deep copy of the data in dense format. The double array is of the form d[rows][columns] so each value
244      * can be found with d[row][column].
245      * @return double[][]; a safe, dense copy of matrixSI as a matrix
246      */
247     public abstract double[][] getDenseMatrixSI();
248 
249     /**
250      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
251      * @param values double[][]; the 2D array to check
252      * @return the values in case the method is used in a constructor
253      * @throws NullPointerException when <code>values</code> is null
254      * @throws ValueRuntimeException when <code>values</code> is jagged
255      */
256     protected static double[][] checkRectangularAndNonNull(final double[][] values) throws ValueRuntimeException
257     {
258         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null double[][]");
259         for (int row = 0; row < values.length; row++)
260         {
261             Throw.when(null == values[row], ValueRuntimeException.class,
262                     "Cannot create a matrix from double[][] containing null row(s)");
263             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
264                     "Cannot create a matrix from a jagged double[][]");
265         }
266         return values;
267     }
268 
269     /**
270      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
271      * @param values S[][]; the 2D array to check
272      * @return the values in case the method is used in a constructor
273      * @throws NullPointerException when <code>values</code> is null
274      * @throws ValueRuntimeException when <code>values</code> is jagged
275      * @param <U> the unit type
276      * @param <S> the corresponding scalar type
277      */
278     protected static <U extends Unit<U>, S extends DoubleScalar<U, S>> S[][] checkRectangularAndNonNull(
279             final S[][] values) throws ValueRuntimeException
280     {
281         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null Scalar[][]");
282         for (int row = 0; row < values.length; row++)
283         {
284             Throw.when(null == values[row], ValueRuntimeException.class,
285                     "Cannot create a matrix from Scalar[][] containing null row(s)");
286             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
287                     "Cannot create a matrix from a jagged Scalar[][]");
288             for (int col = 0; col < values[row].length; col++)
289             {
290                 Throw.whenNull(values[row][col], "Cannot create a matrix from Scalar[][] containing null(s)");
291             }
292         }
293         return values;
294     }
295 
296     /**
297      * Check the sizes of this data object and the other data object.
298      * @param other DoubleMatrixData; the other data object
299      * @throws ValueRuntimeException if matrices have different lengths
300      */
301     protected void checkSizes(final DoubleMatrixData other) throws ValueRuntimeException
302     {
303         if (this.rows() != other.rows() || this.cols() != other.cols())
304         {
305             throw new ValueRuntimeException("Two data objects used in a DoubleMatrix operation do not have the same size");
306         }
307     }
308 
309     /* ============================================================================================ */
310     /* ================================== CALCULATION FUNCTIONS =================================== */
311     /* ============================================================================================ */
312 
313     /**
314      * Apply an operation to each cell.
315      * @param doubleFunction DoubleFunction; the operation to apply
316      * @return DoubleMatrixData; this (modified) double matrix data object
317      */
318     public abstract DoubleMatrixData assign(DoubleFunction doubleFunction);
319 
320     /**
321      * Apply a binary operation on a cell by cell basis.
322      * @param doubleFunction2 DoubleFunction2; the binary operation to apply
323      * @param right DoubleMatrixData; the right operand for the binary operation
324      * @return DoubleMatrixData; this (modified) double matrix data object
325      * @throws ValueRuntimeException when the sizes of the matrices do not match
326      */
327     abstract DoubleMatrixData assign(DoubleFunction2 doubleFunction2, DoubleMatrixData right) throws ValueRuntimeException;
328 
329     /**
330      * Add two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a dense
331      * matrix is returned.
332      * @param right DoubleMatrixData; the other data object to add
333      * @return the sum of this data object and the other data object
334      * @throws ValueRuntimeException if matrices have different lengths
335      */
336     public abstract DoubleMatrixData plus(DoubleMatrixData right) throws ValueRuntimeException;
337 
338     /**
339      * Add a matrix to this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
340      * @param right DoubleMatrixData; the other data object to add
341      * @return DoubleMatrixData; this modified double matrix data object
342      * @throws ValueRuntimeException if matrices have different lengths
343      */
344     public final DoubleMatrixData incrementBy(final DoubleMatrixData right) throws ValueRuntimeException
345     {
346         return assign(new DoubleFunction2()
347         {
348             @Override
349             public double apply(final double leftValue, final double rightValue)
350             {
351                 return leftValue + rightValue;
352             }
353         }, right);
354     }
355 
356     /**
357      * Subtract two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a
358      * dense matrix is returned.
359      * @param right DoubleMatrixData; the other data object to subtract
360      * @return the sum of this data object and the other data object
361      * @throws ValueRuntimeException if matrices have different lengths
362      */
363     public abstract DoubleMatrixData minus(DoubleMatrixData right) throws ValueRuntimeException;
364 
365     /**
366      * Subtract a matrix from this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
367      * @param decrement DoubleMatrixData; the amount to subtract
368      * @return DoubleMatrixData; this modified double matrix data object
369      * @throws ValueRuntimeException if matrices have different sizes
370      */
371     public final DoubleMatrixData decrementBy(final DoubleMatrixData decrement) throws ValueRuntimeException
372     {
373         return assign(new DoubleFunction2()
374         {
375             @Override
376             public double apply(final double leftValue, final double rightValue)
377             {
378                 return leftValue - rightValue;
379             }
380         }, decrement);
381     }
382 
383     /**
384      * Multiply two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
385      * matrix is returned.
386      * @param right DoubleMatrixData; the other data object to multiply with
387      * @return DoubleVectorData; a new double matrix data store holding the result of the multiplications
388      * @throws ValueRuntimeException if matrices have different sizes
389      */
390     public abstract DoubleMatrixData times(DoubleMatrixData right) throws ValueRuntimeException;
391 
392     /**
393      * Multiply a matrix with the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the
394      * same.
395      * @param right DoubleMatrixData; the other data object to multiply with
396      * @return DoubleMatrixData; this modified data store
397      * @throws ValueRuntimeException if matrices have different lengths
398      */
399     public final DoubleMatrixData multiplyBy(final DoubleMatrixData right) throws ValueRuntimeException
400     {
401         return assign(new DoubleFunction2()
402         {
403             @Override
404             public double apply(final double leftValue, final double rightValue)
405             {
406                 return leftValue * rightValue;
407             }
408         }, right);
409     }
410 
411     /**
412      * Divide two matrices on a cell-by-cell basis. If this matrix is sparse and <code>right</code> is dense, a sparse matrix is
413      * returned, otherwise a dense matrix is returned.
414      * @param right DoubleMatrixData; the other data object to divide by
415      * @return DoubleMatrixData; the ratios of the values of this data object and the other data object
416      * @throws ValueRuntimeException if matrices have different sizes
417      */
418     public abstract DoubleMatrixData divide(DoubleMatrixData right) throws ValueRuntimeException;
419 
420     /**
421      * Divide the values of a matrix by the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense)
422      * stays the same.
423      * @param right DoubleMatrixData; the other data object to divide by
424      * @return DoubleMatrixData; this modified data store
425      * @throws ValueRuntimeException if matrices have different sizes
426      */
427     public final DoubleMatrixData divideBy(final DoubleMatrixData right) throws ValueRuntimeException
428     {
429         return assign(new DoubleFunction2()
430         {
431             @Override
432             public double apply(final double leftValue, final double rightValue)
433             {
434                 return leftValue / rightValue;
435             }
436         }, right);
437     }
438 
439     /* ============================================================================================ */
440     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
441     /* ============================================================================================ */
442 
443     @Override
444     public int hashCode()
445     {
446         final int prime = 31;
447         int result = 1;
448         result = prime * result + this.rows;
449         result = prime * result + this.cols;
450         for (int row = 0; row < this.rows; row++)
451         {
452             for (int col = 0; col < this.cols; col++)
453             {
454                 long bits = Double.doubleToLongBits(getSI(row, col));
455                 result = 31 * result + (int) (bits ^ (bits >>> 32));
456             }
457         }
458         return result;
459     }
460 
461     /**
462      * Compare contents of a dense and a sparse matrix.
463      * @param dm DoubleMatrixDataDense; the dense matrix
464      * @param sm DoubleMatrixDataSparse; the sparse matrix
465      * @return boolean; true if the contents are equal
466      */
467     protected boolean compareDenseMatrixWithSparseMatrix(final DoubleMatrixDataDense dm, final DoubleMatrixDataSparse sm)
468     {
469         for (int row = 0; row < dm.rows; row++)
470         {
471             for (int col = 0; col < dm.cols; col++)
472             {
473                 if (dm.getSI(row, col) != sm.getSI(row, col))
474                 {
475                     return false;
476                 }
477             }
478         }
479         return true;
480     }
481 
482     @Override
483     @SuppressWarnings("checkstyle:needbraces")
484     public boolean equals(final Object obj)
485     {
486         if (this == obj)
487             return true;
488         if (obj == null)
489             return false;
490         if (!(obj instanceof DoubleMatrixData))
491             return false;
492         DoubleMatrixData other = (DoubleMatrixData) obj;
493         if (this.rows != other.rows)
494             return false;
495         if (this.cols != other.cols)
496             return false;
497         if (other instanceof DoubleMatrixDataSparse && this instanceof DoubleMatrixDataDense)
498         {
499             return compareDenseMatrixWithSparseMatrix((DoubleMatrixDataDense) this, (DoubleMatrixDataSparse) other);
500         }
501         else if (other instanceof DoubleMatrixDataDense && this instanceof DoubleMatrixDataSparse)
502         {
503             return compareDenseMatrixWithSparseMatrix((DoubleMatrixDataDense) other, (DoubleMatrixDataSparse) this);
504         }
505         // Both are dense (both sparse is handled in DoubleMatrixDataSparse class)
506         return Arrays.equals(this.matrixSI, other.matrixSI);
507     }
508 
509 }