View Javadoc
1   package org.djunits.value.vfloat.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.vfloat.scalar.FloatScalarInterface;
10  
11  /**
12   * Stores the data for a FloatMatrix 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 FloatMatrixData
23  {
24      /** the internal storage of the Matrix; can be sparse or dense. */
25      @SuppressWarnings("checkstyle:visibilitymodifier")
26      protected float[] 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      FloatMatrixData(final StorageType storageType)
43      {
44          super();
45          this.storageType = storageType;
46      }
47  
48      /* ============================================================================================ */
49      /* ====================================== INSTANTIATION ======================================= */
50      /* ============================================================================================ */
51  
52      /**
53       * Instantiate a FloatMatrixData 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 FloatMatrixData with the right data type
58       * @throws ValueException when values are null, or storageType is null
59       */
60      public static FloatMatrixData instantiate(final float[][] 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("FloatMatrixData.instantiate: float[][] 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                  float[] valuesSI = new float[rows * cols];
76                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
77                          .forEach(c -> valuesSI[r * cols + c] = (float) scale.toStandardUnit(values[r][c])));
78                  return new FloatMatrixDataDense(valuesSI, rows, cols);
79  
80              case SPARSE:
81                  float[][] matrixSI = new float[rows][cols];
82                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
83                          .forEach(c -> matrixSI[r][c] = (float) scale.toStandardUnit(values[r][c])));
84                  return FloatMatrixDataSparse.instantiate(matrixSI);
85  
86              default:
87                  throw new ValueException("Unknown data type in FloatMatrixData.instantiate: " + storageType);
88          }
89      }
90  
91      /**
92       * Instantiate a FloatMatrixData with the right data type.
93       * @param values the values to store
94       * @param storageType the data type to use
95       * @return the FloatMatrixData with the right data type
96       * @throws ValueException when values is null, or storageType is null
97       */
98      public static FloatMatrixData instantiate(final FloatScalarInterface[][] values, final StorageType storageType)
99              throws ValueException
100     {
101         if (values == null)
102         {
103             throw new ValueException("FloatMatrixData.instantiate: FloatScalar[] values is null");
104         }
105 
106         if (values == null || values.length == 0 || values[0].length == 0)
107         {
108             throw new ValueException("FloatMatrixData.instantiate: FloatScalar[][] 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                 float[] valuesSI = new float[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 FloatMatrixDataDense(valuesSI, rows, cols);
122 
123             case SPARSE:
124                 float[][] matrixSI = new float[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 FloatMatrixDataSparse.instantiate(matrixSI);
128 
129             default:
130                 throw new ValueException("Unknown data type in FloatMatrixData.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 FloatMatrixDataSparse toSparse()
175     {
176         return isSparse() ? (FloatMatrixDataSparse) this : ((FloatMatrixDataDense) 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 FloatMatrixDataDense toDense()
191     {
192         return isDense() ? (FloatMatrixDataDense) this : ((FloatMatrixDataSparse) 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 float 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, float valueSI);
209 
210     /**
211      * @return the number of non-zero cells.
212      */
213     public final int cardinality()
214     {
215         // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
216         return (int) IntStream.range(0, this.matrixSI.length).parallel().mapToDouble(i -> this.matrixSI[i])
217                 .filter(d -> d != 0.0).count();
218     }
219 
220     /**
221      * @return the sum of the values of all cells.
222      */
223     public final float zSum()
224     {
225         // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
226         return (float) IntStream.range(0, this.matrixSI.length).parallel().mapToDouble(i -> this.matrixSI[i]).sum();
227     }
228 
229     /**
230      * @return a deep copy of the data.
231      */
232     public abstract FloatMatrixData copy();
233 
234     /**
235      * @return a safe dense copy of matrixSI as a matrix
236      */
237     public abstract float[][] getDenseMatrixSI();
238 
239     /**
240      * @return a safe dense copy of matrixSI as a double matrix
241      */
242     public abstract double[][] getDoubleDenseMatrixSI();
243 
244     /**
245      * Check the sizes of this data object and the other data object.
246      * @param other the other data object
247      * @throws ValueException if matrices have different lengths
248      */
249     private void checkSizes(final FloatMatrixData other) throws ValueException
250     {
251         if (this.rows() != other.rows() || this.cols() != other.cols())
252         {
253             throw new ValueException("Two data objects used in a FloatMatrix operation do not have the same size");
254         }
255     }
256 
257     /* ============================================================================================ */
258     /* ================================== CALCULATION FUNCTIONS =================================== */
259     /* ============================================================================================ */
260 
261     /**
262      * Add two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a dense
263      * matrix is returned.
264      * @param right the other data object to add
265      * @return the sum of this data object and the other data object
266      * @throws ValueException if matrices have different lengths
267      */
268     public FloatMatrixData plus(final FloatMatrixData right) throws ValueException
269     {
270         checkSizes(right);
271         float[] dm = new float[this.rows * this.cols];
272         IntStream.range(0, this.rows).parallel().forEach(
273                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) + right.getSI(r, c)));
274         if (this instanceof FloatMatrixDataSparse && right instanceof FloatMatrixDataSparse)
275         {
276             return new FloatMatrixDataDense(dm, this.rows, this.cols).toSparse();
277         }
278         return new FloatMatrixDataDense(dm, this.rows, this.cols);
279     }
280 
281     /**
282      * Add a matrix to this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
283      * @param right the other data object to add
284      * @throws ValueException if matrices have different lengths
285      */
286     public abstract void incrementBy(FloatMatrixData right) throws ValueException;
287 
288     /**
289      * Add a number to this matrix on a cell-by-cell basis.
290      * @param valueSI the value to add
291      */
292     public void incrementBy(final float valueSI)
293     {
294         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] += valueSI);
295     }
296 
297     /**
298      * Subtract two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a
299      * dense matrix is returned.
300      * @param right the other data object to subtract
301      * @return the sum of this data object and the other data object
302      * @throws ValueException if matrices have different lengths
303      */
304     public FloatMatrixData minus(final FloatMatrixData right) throws ValueException
305     {
306         checkSizes(right);
307         float[] dm = new float[this.rows * this.cols];
308         IntStream.range(0, this.rows).parallel().forEach(
309                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) - right.getSI(r, c)));
310         if (this instanceof FloatMatrixDataSparse && right instanceof FloatMatrixDataSparse)
311         {
312             return new FloatMatrixDataDense(dm, this.rows, this.cols).toSparse();
313         }
314         return new FloatMatrixDataDense(dm, this.rows, this.cols);
315     }
316 
317     /**
318      * Subtract a matrix from this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
319      * @param right the other data object to subtract
320      * @throws ValueException if matrices have different lengths
321      */
322     public abstract void decrementBy(FloatMatrixData right) throws ValueException;
323 
324     /**
325      * Subtract a number from this matrix on a cell-by-cell basis.
326      * @param valueSI the value to subtract
327      */
328     public void decrementBy(final float valueSI)
329     {
330         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] -= valueSI);
331     }
332 
333     /**
334      * Multiply two matrix on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
335      * matrix is returned.
336      * @param right the other data object to multiply with
337      * @return the sum of this data object and the other data object
338      * @throws ValueException if matrices have different lengths
339      */
340     public FloatMatrixData times(final FloatMatrixData right) throws ValueException
341     {
342         checkSizes(right);
343         float[] dm = new float[this.rows * this.cols];
344         IntStream.range(0, this.rows).parallel().forEach(
345                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) * right.getSI(r, c)));
346         if (this instanceof FloatMatrixDataDense && right instanceof FloatMatrixDataDense)
347         {
348             return new FloatMatrixDataDense(dm, this.rows, this.cols);
349         }
350         return new FloatMatrixDataDense(dm, this.rows, this.cols).toSparse();
351     }
352 
353     /**
354      * Multiply a matrix with the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the
355      * same.
356      * @param right the other data object to multiply with
357      * @throws ValueException if matrices have different lengths
358      */
359     public abstract void multiplyBy(FloatMatrixData right) throws ValueException;
360 
361     /**
362      * Multiply the values of this matrix with a number on a cell-by-cell basis.
363      * @param valueSI the value to multiply with
364      */
365     public void multiplyBy(final float valueSI)
366     {
367         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] *= valueSI);
368     }
369 
370     /**
371      * Divide two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
372      * matrix is returned.
373      * @param right the other data object to divide by
374      * @return the sum of this data object and the other data object
375      * @throws ValueException if matrices have different lengths
376      */
377     public FloatMatrixData divide(final FloatMatrixData right) throws ValueException
378     {
379         checkSizes(right);
380         float[] dm = new float[this.rows * this.cols];
381         IntStream.range(0, this.rows).parallel().forEach(
382                 r -> IntStream.range(0, this.cols).forEach(c -> dm[r * this.cols + c] = getSI(r, c) / right.getSI(r, c)));
383         if (this instanceof FloatMatrixDataDense && right instanceof FloatMatrixDataDense)
384         {
385             return new FloatMatrixDataDense(dm, this.rows, this.cols);
386         }
387         return new FloatMatrixDataDense(dm, this.rows, this.cols).toSparse();
388     }
389 
390     /**
391      * Divide the values of a matrix by the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense)
392      * stays the same.
393      * @param right the other data object to divide by
394      * @throws ValueException if matrices have different lengths
395      */
396     public abstract void divideBy(FloatMatrixData right) throws ValueException;
397 
398     /**
399      * Divide the values of this matrix by a number on a cell-by-cell basis.
400      * @param valueSI the value to multiply with
401      */
402     public void divideBy(final float valueSI)
403     {
404         IntStream.range(0, this.matrixSI.length).parallel().forEach(i -> this.matrixSI[i] /= valueSI);
405     }
406 
407     /* ============================================================================================ */
408     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
409     /* ============================================================================================ */
410 
411     /** {@inheritDoc} */
412     @Override
413     public int hashCode()
414     {
415         final int prime = 31;
416         int result = 1;
417         result = prime * result + this.rows;
418         result = prime * result + this.cols;
419         result = prime * result + ((this.storageType == null) ? 0 : this.storageType.hashCode());
420         result = prime * result + Arrays.hashCode(this.matrixSI);
421         return result;
422     }
423 
424     /** {@inheritDoc} */
425     @Override
426     @SuppressWarnings("checkstyle:needbraces")
427     public boolean equals(final Object obj)
428     {
429         if (this == obj)
430             return true;
431         if (obj == null)
432             return false;
433         if (getClass() != obj.getClass())
434             return false;
435         FloatMatrixData other = (FloatMatrixData) obj;
436         if (this.rows != other.rows)
437             return false;
438         if (this.cols != other.cols)
439             return false;
440         if (this.storageType != other.storageType)
441             return false;
442         if (!Arrays.equals(this.matrixSI, other.matrixSI))
443             return false;
444         return true;
445     }
446 
447     /** {@inheritDoc} */
448     @Override
449     public String toString()
450     {
451         return "FloatMatrixData [storageType=" + this.storageType + ", matrixSI=" + Arrays.toString(this.matrixSI) + "]";
452     }
453 }