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