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