View Javadoc
1   package org.djunits.value.vdouble.matrix;
2   
3   import java.io.Serializable;
4   import java.util.Arrays;
5   import java.util.stream.IntStream;
6   
7   import org.djunits.unit.scale.Scale;
8   import org.djunits.value.StorageType;
9   import org.djunits.value.ValueException;
10  import org.djunits.value.vdouble.scalar.DoubleScalarInterface;
11  
12  /**
13   * Stores the data for a DoubleMatrix and carries out basic operations.
14   * <p>
15   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
17   * </p>
18   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
19   * initial version Oct 3, 2015 <br>
20   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   */
23  abstract class DoubleMatrixData implements Serializable
24  {
25      /** */
26      private static final long serialVersionUID = 1L;
27  
28      /** the internal storage of the Matrix; can be sparse or dense. */
29      @SuppressWarnings("checkstyle:visibilitymodifier")
30      protected double[] matrixSI;
31  
32      /** the number of rows of the matrix. */
33      @SuppressWarnings("checkstyle:visibilitymodifier")
34      protected int rows;
35  
36      /** the number of columns of the matrix. */
37      @SuppressWarnings("checkstyle:visibilitymodifier")
38      protected int cols;
39  
40      /** the data type. */
41      private final StorageType storageType;
42  
43      /**
44       * @param storageType StorageType; the data type.
45       */
46      DoubleMatrixData(final StorageType storageType)
47      {
48          super();
49          this.storageType = storageType;
50      }
51  
52      /* ============================================================================================ */
53      /* ====================================== INSTANTIATION ======================================= */
54      /* ============================================================================================ */
55  
56      /**
57       * Instantiate a DoubleMatrixData with the right data type.
58       * @param values double[][]; the (SI) values to store
59       * @param scale Scale; the scale of the unit to use for conversion to SI
60       * @param storageType StorageType; the data type to use
61       * @return the DoubleMatrixData with the right data type
62       * @throws ValueException when values are null, or storageType is null
63       */
64      public static DoubleMatrixData instantiate(final double[][] values, final Scale scale, final StorageType storageType)
65              throws ValueException
66      {
67          if (values == null || values.length == 0 || values[0].length == 0)
68          {
69              throw new ValueException("DoubleMatrixData.instantiate: double[][] values is null or "
70                      + "values.length == 0 or values[0].length == 0");
71          }
72  
73          final int rows = values.length;
74          final int cols = values[0].length;
75  
76          switch (storageType)
77          {
78              case DENSE:
79                  double[] valuesSI = new double[rows * cols];
80                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
81                          .forEach(c -> valuesSI[r * cols + c] = scale.toStandardUnit(values[r][c])));
82                  return new DoubleMatrixDataDense(valuesSI, rows, cols);
83  
84              case SPARSE:
85                  double[][] matrixSI = new double[rows][cols];
86                  IntStream.range(0, values.length).parallel().forEach(
87                          r -> IntStream.range(0, cols).forEach(c -> matrixSI[r][c] = scale.toStandardUnit(values[r][c])));
88                  return DoubleMatrixDataSparse.instantiate(matrixSI);
89  
90              default:
91                  throw new ValueException("Unknown data type in DoubleMatrixData.instantiate: " + storageType);
92          }
93      }
94  
95      /**
96       * Instantiate a DoubleMatrixData with the right data type.
97       * @param values DoubleScalarInterface[][]; the values to store
98       * @param storageType StorageType; the data type to use
99       * @return the DoubleMatrixData with the right data type
100      * @throws ValueException when values is null, or storageType is null
101      */
102     public static DoubleMatrixData instantiate(final DoubleScalarInterface[][] values, final StorageType storageType)
103             throws ValueException
104     {
105         if (values == null)
106         {
107             throw new ValueException("DoubleMatrixData.instantiate: DoubleScalar[] values is null");
108         }
109 
110         if (values == null || values.length == 0 || values[0].length == 0)
111         {
112             throw new ValueException("DoubleMatrixData.instantiate: DoubleScalar[][] values is null or "
113                     + "values.length == 0 or values[0].length == 0");
114         }
115 
116         final int rows = values.length;
117         final int cols = values[0].length;
118 
119         switch (storageType)
120         {
121             case DENSE:
122                 double[] valuesSI = new double[rows * cols];
123                 IntStream.range(0, rows).parallel()
124                         .forEach(r -> IntStream.range(0, cols).forEach(c -> valuesSI[r * cols + c] = values[r][c].getSI()));
125                 return new DoubleMatrixDataDense(valuesSI, rows, cols);
126 
127             case SPARSE:
128                 double[][] matrixSI = new double[rows][cols];
129                 IntStream.range(0, values.length).parallel()
130                         .forEach(r -> IntStream.range(0, cols).forEach(c -> matrixSI[r][c] = values[r][c].getSI()));
131                 return DoubleMatrixDataSparse.instantiate(matrixSI);
132 
133             default:
134                 throw new ValueException("Unknown data type in DoubleMatrixData.instantiate: " + storageType);
135         }
136     }
137 
138     /* ============================================================================================ */
139     /* ==================================== UTILITY FUNCTIONS ===================================== */
140     /* ============================================================================================ */
141 
142     /**
143      * Return the StorageType (DENSE, SPARSE, etc.) for the stored Matrix.
144      * @return the StorageType (DENSE, SPARSE, etc.) for the stored Matrix
145      */
146     public final StorageType getStorageType()
147     {
148         return this.storageType;
149     }
150 
151     /**
152      * @return the number of rows of the matrix
153      */
154     public int rows()
155     {
156         return this.rows;
157     }
158 
159     /**
160      * @return the number of columns of the matrix
161      */
162     public int cols()
163     {
164         return this.cols;
165     }
166 
167     /**
168      * @return whether data type is sparse.
169      */
170     public boolean isSparse()
171     {
172         return this.storageType.equals(StorageType.SPARSE);
173     }
174 
175     /**
176      * @return the sparse transformation of this data
177      */
178     public DoubleMatrixDataSparse toSparse()
179     {
180         return isSparse() ? (DoubleMatrixDataSparse) this : ((DoubleMatrixDataDense) this).toSparse();
181     }
182 
183     /**
184      * @return whether data type is dense.
185      */
186     public boolean isDense()
187     {
188         return this.storageType.equals(StorageType.DENSE);
189     }
190 
191     /**
192      * @return the dense transformation of this data
193      */
194     public DoubleMatrixDataDense toDense()
195     {
196         return isDense() ? (DoubleMatrixDataDense) this : ((DoubleMatrixDataSparse) this).toDense();
197     }
198 
199     /**
200      * @param row int; the row number to get the value for
201      * @param col int; the column number to get the value for
202      * @return the value at the [row, col] point
203      */
204     public abstract double getSI(int row, int col);
205 
206     /**
207      * Sets a value at the [row, col] point in the matrix.
208      * @param row int; the row number to set the value for
209      * @param col int; the column number to set the value for
210      * @param valueSI double; the value at the index
211      */
212     public abstract void setSI(int row, int col, double valueSI);
213 
214     /**
215      * @return the number of non-zero cells.
216      */
217     public final int cardinality()
218     {
219         return (int) Arrays.stream(this.matrixSI).parallel().filter(d -> d != 0.0).count();
220     }
221 
222     /**
223      * @return the sum of the values of all cells.
224      */
225     public final double zSum()
226     {
227         return Arrays.stream(this.matrixSI).parallel().sum();
228     }
229 
230     /**
231      * @return a deep copy of the data.
232      */
233     public abstract DoubleMatrixData copy();
234 
235     /**
236      * @return a safe dense copy of matrixSI as a matrix
237      */
238     public abstract double[][] getDenseMatrixSI();
239 
240     /**
241      * Check the sizes of this data object and the other data object.
242      * @param other DoubleMatrixData; the other data object
243      * @throws ValueException if matrices have different lengths
244      */
245     private void checkSizes(final DoubleMatrixData other) throws ValueException
246     {
247         if (this.rows() != other.rows() || this.cols() != other.cols())
248         {
249             throw new ValueException("Two data objects used in a DoubleMatrix operation do not have the same size");
250         }
251     }
252 
253     /* ============================================================================================ */
254     /* ================================== CALCULATION FUNCTIONS =================================== */
255     /* ============================================================================================ */
256 
257     /**
258      * Add two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a dense
259      * matrix is returned.
260      * @param right DoubleMatrixData; the other data object to add
261      * @return the sum of this data object and the other data object
262      * @throws ValueException if matrices have different lengths
263      */
264     public DoubleMatrixData plus(final DoubleMatrixData right) throws ValueException
265     {
266         checkSizes(right);
267         double[] dm = new double[this.rows * this.cols];
268         IntStream.range(0, this.rows).parallel().forEach(
269                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) + right.getSI(r, c)));
270         if (this instanceof DoubleMatrixDataSparse && right instanceof DoubleMatrixDataSparse)
271         {
272             return new DoubleMatrixDataDense(dm, this.rows, this.cols).toSparse();
273         }
274         return new DoubleMatrixDataDense(dm, this.rows, this.cols);
275     }
276 
277     /**
278      * Add a matrix to this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
279      * @param right DoubleMatrixData; the other data object to add
280      * @throws ValueException if matrices have different lengths
281      */
282     public abstract void incrementBy(DoubleMatrixData right) throws ValueException;
283 
284     /**
285      * Add a number to this matrix on a cell-by-cell basis.
286      * @param valueSI double; the value to add
287      */
288     public void incrementBy(final double valueSI)
289     {
290         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] += valueSI);
291     }
292 
293     /**
294      * Subtract two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a
295      * dense matrix is returned.
296      * @param right DoubleMatrixData; the other data object to subtract
297      * @return the sum of this data object and the other data object
298      * @throws ValueException if matrices have different lengths
299      */
300     public DoubleMatrixData minus(final DoubleMatrixData right) throws ValueException
301     {
302         checkSizes(right);
303         double[] dm = new double[this.rows * this.cols];
304         IntStream.range(0, this.rows).parallel().forEach(
305                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) - right.getSI(r, c)));
306         if (this instanceof DoubleMatrixDataSparse && right instanceof DoubleMatrixDataSparse)
307         {
308             return new DoubleMatrixDataDense(dm, this.rows, this.cols).toSparse();
309         }
310         return new DoubleMatrixDataDense(dm, this.rows, this.cols);
311     }
312 
313     /**
314      * Subtract a matrix from this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
315      * @param right DoubleMatrixData; the other data object to subtract
316      * @throws ValueException if matrices have different lengths
317      */
318     public abstract void decrementBy(DoubleMatrixData right) throws ValueException;
319 
320     /**
321      * Subtract a number from this matrix on a cell-by-cell basis.
322      * @param valueSI double; the value to subtract
323      */
324     public void decrementBy(final double valueSI)
325     {
326         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] -= valueSI);
327     }
328 
329     /**
330      * Multiply two matrix on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
331      * matrix is returned.
332      * @param right DoubleMatrixData; the other data object to multiply with
333      * @return the sum of this data object and the other data object
334      * @throws ValueException if matrices have different lengths
335      */
336     public DoubleMatrixData times(final DoubleMatrixData right) throws ValueException
337     {
338         checkSizes(right);
339         double[] dm = new double[this.rows * this.cols];
340         IntStream.range(0, this.rows).parallel().forEach(
341                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) * right.getSI(r, c)));
342         if (this instanceof DoubleMatrixDataDense && right instanceof DoubleMatrixDataDense)
343         {
344             return new DoubleMatrixDataDense(dm, this.rows, this.cols);
345         }
346         return new DoubleMatrixDataDense(dm, this.rows, this.cols).toSparse();
347     }
348 
349     /**
350      * Multiply a matrix with the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the
351      * same.
352      * @param right DoubleMatrixData; the other data object to multiply with
353      * @throws ValueException if matrices have different lengths
354      */
355     public abstract void multiplyBy(DoubleMatrixData right) throws ValueException;
356 
357     /**
358      * Multiply the values of this matrix with a number on a cell-by-cell basis.
359      * @param valueSI double; the value to multiply with
360      */
361     public void multiplyBy(final double valueSI)
362     {
363         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] *= valueSI);
364     }
365 
366     /**
367      * Divide two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
368      * matrix is returned.
369      * @param right DoubleMatrixData; the other data object to divide by
370      * @return the sum of this data object and the other data object
371      * @throws ValueException if matrices have different lengths
372      */
373     public DoubleMatrixData divide(final DoubleMatrixData right) throws ValueException
374     {
375         checkSizes(right);
376         double[] dm = new double[this.rows * this.cols];
377         IntStream.range(0, this.rows).parallel().forEach(
378                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) / right.getSI(r, c)));
379         if (this instanceof DoubleMatrixDataDense && right instanceof DoubleMatrixDataDense)
380         {
381             return new DoubleMatrixDataDense(dm, this.rows, this.cols);
382         }
383         return new DoubleMatrixDataDense(dm, this.rows, this.cols).toSparse();
384     }
385 
386     /**
387      * Divide the values of a matrix by the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense)
388      * stays the same.
389      * @param right DoubleMatrixData; the other data object to divide by
390      * @throws ValueException if matrices have different lengths
391      */
392     public abstract void divideBy(DoubleMatrixData right) throws ValueException;
393 
394     /**
395      * Divide the values of this matrix by a number on a cell-by-cell basis.
396      * @param valueSI double; the value to multiply with
397      */
398     public void divideBy(final double valueSI)
399     {
400         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] /= valueSI);
401     }
402 
403     /* ============================================================================================ */
404     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
405     /* ============================================================================================ */
406 
407     /** {@inheritDoc} */
408     @Override
409     public int hashCode()
410     {
411         final int prime = 31;
412         int result = 1;
413         result = prime * result + this.rows;
414         result = prime * result + this.cols;
415         result = prime * result + ((this.storageType == null) ? 0 : this.storageType.hashCode());
416         result = prime * result + Arrays.hashCode(this.matrixSI);
417         return result;
418     }
419 
420     /** {@inheritDoc} */
421     @Override
422     @SuppressWarnings("checkstyle:needbraces")
423     public boolean equals(final Object obj)
424     {
425         if (this == obj)
426             return true;
427         if (obj == null)
428             return false;
429         if (getClass() != obj.getClass())
430             return false;
431         DoubleMatrixData other = (DoubleMatrixData) obj;
432         if (this.rows != other.rows)
433             return false;
434         if (this.cols != other.cols)
435             return false;
436         if (this.storageType != other.storageType)
437             return false;
438         if (!Arrays.equals(this.matrixSI, other.matrixSI))
439             return false;
440         return true;
441     }
442 
443     /** {@inheritDoc} */
444     @Override
445     public String toString()
446     {
447         return "DoubleMatrixData [storageType=" + this.storageType + ", matrixSI=" + Arrays.toString(this.matrixSI) + "]";
448     }
449 }