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