View Javadoc
1   package org.djunits.value.vfloat.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.vfloat.function.FloatFunction;
15  import org.djunits.value.vfloat.function.FloatFunction2;
16  import org.djunits.value.vfloat.matrix.base.FloatSparseValue;
17  import org.djunits.value.vfloat.scalar.base.FloatScalarInterface;
18  
19  /**
20   * Stores the data for a FloatMatrix and carries out basic operations.
21   * <p>
22   * Copyright (c) 2013-2020 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 FloatMatrixData extends AbstractStorage<FloatMatrixData> implements Serializable
29  {
30      /** */
31      private static final long serialVersionUID = 1L;
32  
33      /** the internal storage of the Matrix; can be sparse or dense. The data is stored in an array. */
34      @SuppressWarnings("checkstyle:visibilitymodifier")
35      protected float[] 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      public FloatMatrixData(final StorageType storageType)
50      {
51          super(storageType);
52      }
53  
54      /* ============================================================================================ */
55      /* ====================================== INSTANTIATION ======================================= */
56      /* ============================================================================================ */
57  
58      /**
59       * Instantiate a FloatMatrixData with the right data type. The float array is of the form f[rows][columns] so each value can
60       * be found with f[row][column].
61       * @param values float[][]; 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 FloatMatrixData with the right data type
65       * @throws ValueRuntimeException when values are null, or storageType is null
66       */
67      public static FloatMatrixData instantiate(final float[][] values, final Scale scale, final StorageType storageType)
68              throws ValueRuntimeException
69      {
70          Throw.whenNull(scale, "FloatMatrixData.instantiate: scale is null");
71          Throw.whenNull(storageType, "FloatMatrixData.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                  float[] valuesSI = new float[rows * cols];
81                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
82                          .forEach(c -> valuesSI[r * cols + c] = (float) scale.toStandardUnit(values[r][c])));
83                  return new FloatMatrixDataDense(valuesSI, rows, cols);
84  
85              case SPARSE:
86                  return FloatMatrixDataSparse.instantiate(values, scale);
87  
88              default:
89                  throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.instantiate: " + storageType);
90          }
91      }
92  
93      /**
94       * Instantiate a FloatMatrixData with the right data type.
95       * @param values Collection&lt;FloatSparseValue&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 FloatMatrixData 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 FloatScalarInterface<U, S>> FloatMatrixData instantiate(
105             final Collection<FloatSparseValue<U, S>> values, final int rows, final int cols, final StorageType storageType)
106             throws ValueRuntimeException
107     {
108         Throw.whenNull(values, "FloatMatrixData.instantiate: values is null");
109         Throw.whenNull(storageType, "FloatMatrixData.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 (FloatSparseValue<U, S> fsp : values)
113         {
114             Throw.whenNull(fsp, "null value in values");
115             Throw.when(fsp.getRow() < 0 || fsp.getRow() >= rows, ValueRuntimeException.class, "row out of range");
116             Throw.when(fsp.getColumn() < 0 || fsp.getColumn() >= cols, ValueRuntimeException.class, "column out of range");
117         }
118 
119         switch (storageType)
120         {
121             case DENSE:
122                 float[] valuesSI = new float[rows * cols];
123                 values.stream().parallel().forEach(v -> valuesSI[v.getRow() * cols + v.getColumn()] = v.getValueSI());
124                 return new FloatMatrixDataDense(valuesSI, rows, cols);
125 
126             case SPARSE:
127                 return new FloatMatrixDataSparse(values, rows, cols);
128 
129             default:
130                 throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.instantiate: " + storageType);
131         }
132     }
133 
134     /**
135      * Instantiate a FloatMatrixData with the right data type. The FloatScalar array is of the form fs[rows][columns] so each
136      * value can be found with fs[row][column].
137      * @param values S[][]; the values to store
138      * @param storageType StorageType; the data type to use
139      * @return the FloatMatrixData 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 FloatScalarInterface<U, S>> FloatMatrixData instantiate(final S[][] values,
145             final StorageType storageType) throws ValueRuntimeException
146     {
147         Throw.whenNull(storageType, "FloatMatrixData.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                 float[] valuesSI = new float[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 FloatMatrixDataDense(valuesSI, rows, cols);
160 
161             case SPARSE:
162                 float[][] matrixSI = new float[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 FloatMatrixDataSparse.instantiate(matrixSI);
166 
167             default:
168                 throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.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 FloatMatrixDataDense; the dense transformation of this data
197      */
198     public abstract FloatMatrixDataDense toDense();
199 
200     /**
201      * Return the data of this matrix in sparse storage format.
202      * @return FloatMatrixDataSparse; the sparse transformation of this data
203      */
204     public abstract FloatMatrixDataSparse 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 float 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 float; the value at the index
219      */
220     public abstract void setSI(int row, int col, float valueSI);
221 
222     /**
223      * Compute and return the sum of the values of all cells of this matrix.
224      * @return float; the sum of the values of all cells
225      */
226     public final float zSum()
227     {
228         // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
229         return (float) IntStream.range(0, this.matrixSI.length).parallel().mapToDouble(i -> this.matrixSI[i]).sum();
230     }
231 
232     /**
233      * Create and return a deep copy of the data in dense format. The float array is of the form f[rows][columns] so each value
234      * can be found with f[row][column].
235      * @return float[][]; a safe, dense copy of matrixSI as a matrix
236      */
237     public abstract float[][] getDenseMatrixSI();
238 
239     /**
240      * 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
241      * can be found with d[row][column].
242      * @return double[][]; a safe, dense copy of matrixSI as a matrix
243      */
244     public abstract double[][] getDoubleDenseMatrixSI();
245 
246     /**
247      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
248      * @param values float[][]; the 2D array to check
249      * @return the values in case the method is used in a constructor
250      * @throws NullPointerException when <code>values</code> is null
251      * @throws ValueRuntimeException when <code>values</code> is empty, or jagged
252      */
253     protected static float[][] checkRectangularAndNonEmpty(final float[][] values) throws ValueRuntimeException
254     {
255         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null float[][]");
256         Throw.when(0 == values.length, ValueRuntimeException.class, "Cannot create a matrix from zero length float[][]");
257         for (int row = 0; row < values.length; row++)
258         {
259             Throw.when(null == values[row], ValueRuntimeException.class,
260                     "Cannot create a matrix from float[][] containing null row(s)");
261             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
262                     "Cannot create a matrix from a jagged float[][]");
263         }
264         Throw.when(0 == values[0].length, ValueRuntimeException.class,
265                 "Cannot create a matrix from a float[][] with zero values");
266         return values;
267     }
268 
269     /**
270      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
271      * @param values S[][]; the 2D array to check
272      * @return the values in case the method is used in a constructor
273      * @throws NullPointerException when <code>values</code> is null
274      * @throws ValueRuntimeException when <code>values</code> is empty, or jagged
275      * @param <U> the unit type
276      * @param <S> the corresponding scalar type
277      */
278     protected static <U extends Unit<U>, S extends FloatScalarInterface<U, S>> S[][] checkRectangularAndNonEmpty(
279             final S[][] values) throws ValueRuntimeException
280     {
281         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null Scalar[][]");
282         Throw.when(0 == values.length, ValueRuntimeException.class, "Cannot create a matrix from zero length Scalar[][]");
283         for (int row = 0; row < values.length; row++)
284         {
285             Throw.when(null == values[row], ValueRuntimeException.class,
286                     "Cannot create a matrix from Scalar[][] containing null row(s)");
287             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
288                     "Cannot create a matrix from a jagged Scalar[][]");
289             for (int col = 0; col < values[row].length; col++)
290             {
291                 Throw.whenNull(values[row][col], "Cannot create a matrix from Scalar[][] containing null(s)");
292             }
293         }
294         Throw.when(0 == values[0].length, ValueRuntimeException.class,
295                 "Cannot create a matrix from a Scalar[][] with zero values");
296         return values;
297     }
298 
299     /**
300      * Check the sizes of this data object and the other data object.
301      * @param other FloatMatrixData; the other data object
302      * @throws ValueRuntimeException if matrices have different lengths
303      */
304     protected void checkSizes(final FloatMatrixData other) throws ValueRuntimeException
305     {
306         if (this.rows() != other.rows() || this.cols() != other.cols())
307         {
308             throw new ValueRuntimeException("Two data objects used in a FloatMatrix operation do not have the same size");
309         }
310     }
311 
312     /* ============================================================================================ */
313     /* ================================== CALCULATION FUNCTIONS =================================== */
314     /* ============================================================================================ */
315 
316     /**
317      * Apply an operation to each cell.
318      * @param doubleFunction FloatFunction; the operation to apply
319      * @return FloatMatrixData; this (modified) double vector data object
320      */
321     public abstract FloatMatrixData assign(FloatFunction doubleFunction);
322 
323     /**
324      * Apply a binary operation on a cell by cell basis.
325      * @param floatFunction FloatFunction2; the binary operation to apply
326      * @param right FloatMatrixData; the right operand for the binary operation
327      * @return FloatMatrixData; this (modified) double matrix data object
328      * @throws ValueRuntimeException when the sizes of the vectors do not match
329      */
330     abstract FloatMatrixDatavfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData assign(FloatFunction2 floatFunction, FloatMatrixData right) throws ValueRuntimeException;
331 
332     /**
333      * Add two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a dense
334      * matrix is returned.
335      * @param right FloatMatrixData; the other data object to add
336      * @return the sum of this data object and the other data object
337      * @throws ValueRuntimeException if matrices have different lengths
338      */
339     public abstract FloatMatrixData./../../../../org/djunits/value/vfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData plus(FloatMatrixData right) throws ValueRuntimeException;
340 
341     /**
342      * Add a matrix to this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
343      * @param right FloatMatrixData; the other data object to add
344      * @return FloatMatrixData; this modified float matrix data object
345      * @throws ValueRuntimeException if matrices have different lengths
346      */
347     public final FloatMatrixData/FloatMatrixData.html#FloatMatrixData">FloatMatrixData incrementBy(final FloatMatrixData right) throws ValueRuntimeException
348     {
349         return assign(new FloatFunction2()
350         {
351             @Override
352             public float apply(final float leftValue, final float rightValue)
353             {
354                 return leftValue + rightValue;
355             }
356         }, right);
357     }
358 
359     /**
360      * Subtract two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a
361      * dense matrix is returned.
362      * @param right FloatMatrixData; the other data object to subtract
363      * @return the sum of this data object and the other data object
364      * @throws ValueRuntimeException if matrices have different lengths
365      */
366     public abstract FloatMatrixData/../../../../org/djunits/value/vfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData minus(FloatMatrixData right) throws ValueRuntimeException;
367 
368     /**
369      * Subtract a matrix from this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
370      * @param decrement FloatMatrixData; the other data object to subtract
371      * @return FloatMatrixData; this modified float matrix data object
372      * @throws ValueRuntimeException if matrices have different lengths
373      */
374     public final FloatMatrixData/FloatMatrixData.html#FloatMatrixData">FloatMatrixData decrementBy(final FloatMatrixData decrement) throws ValueRuntimeException
375     {
376         return assign(new FloatFunction2()
377         {
378             @Override
379             public float apply(final float leftValue, final float rightValue)
380             {
381                 return leftValue - rightValue;
382             }
383         }, decrement);
384     }
385 
386     /**
387      * Multiply two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
388      * matrix is returned.
389      * @param right FloatMatrixData; the other data object to multiply with
390      * @return FloatMatrixData; a new double matrix data store holding the result of the multiplications
391      * @throws ValueRuntimeException if matrices have different sizes
392      */
393     public abstract FloatMatrixData/../../../../org/djunits/value/vfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData times(FloatMatrixData right) throws ValueRuntimeException;
394 
395     /**
396      * Multiply a matrix with the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the
397      * same.
398      * @param right FloatMatrixData; the other data object to multiply with
399      * @return FloatMatrixData; this modified data store
400      * @throws ValueRuntimeException if matrices have different sizes
401      */
402     public final FloatMatrixDataa/FloatMatrixData.html#FloatMatrixData">FloatMatrixData multiplyBy(final FloatMatrixData right) throws ValueRuntimeException
403     {
404         return assign(new FloatFunction2()
405         {
406             @Override
407             public float apply(final float leftValue, final float rightValue)
408             {
409                 return leftValue * rightValue;
410             }
411         }, right);
412     }
413 
414     /**
415      * Divide two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
416      * matrix is returned.
417      * @param right FloatMatrixData; the other data object to divide by
418      * @return the sum of this data object and the other data object
419      * @throws ValueRuntimeException if matrices have different sizes
420      */
421     public abstract FloatMatrixData../../../../org/djunits/value/vfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData divide(FloatMatrixData right) throws ValueRuntimeException;
422 
423     /**
424      * Divide the values of a matrix by the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense)
425      * stays the same.
426      * @param right FloatMatrixData; the other data object to divide by
427      * @return FloatMatrixData; this modified data store
428      * @throws ValueRuntimeException if matrices have different sizes
429      */
430     public final FloatMatrixDataata/FloatMatrixData.html#FloatMatrixData">FloatMatrixData divideBy(final FloatMatrixData right) throws ValueRuntimeException
431     {
432         return assign(new FloatFunction2()
433         {
434             @Override
435             public float apply(final float leftValue, final float rightValue)
436             {
437                 return leftValue / rightValue;
438             }
439         }, right);
440     }
441 
442     /* ============================================================================================ */
443     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
444     /* ============================================================================================ */
445 
446     /** {@inheritDoc} */
447     @Override
448     public int hashCode()
449     {
450         final int prime = 31;
451         int result = 1;
452         result = prime * result + this.rows;
453         result = prime * result + this.cols;
454         for (int row = 0; row < this.rows; row++)
455         {
456             for (int col = 0; col < this.cols; col++)
457             {
458                 result = 31 * result + Float.floatToIntBits(getSI(row, col));
459             }
460         }
461         return result;
462     }
463 
464     /**
465      * Compare contents of a dense and a sparse matrix.
466      * @param dm FloatMatrixDataDense; the dense matrix
467      * @param sm FloatMatrixDataSparse; the sparse matrix
468      * @return boolean; true if the contents are equal
469      */
470     protected boolean compareDenseMatrixWithSparseMatrix(final FloatMatrixDataDense dm, final FloatMatrixDataSparse sm)
471     {
472         for (int row = 0; row < dm.rows; row++)
473         {
474             for (int col = 0; col < dm.cols; col++)
475             {
476                 if (dm.getSI(row, col) != sm.getSI(row, col))
477                 {
478                     return false;
479                 }
480             }
481         }
482         return true;
483     }
484 
485     /** {@inheritDoc} */
486     @Override
487     @SuppressWarnings("checkstyle:needbraces")
488     public boolean equals(final Object obj)
489     {
490         if (this == obj)
491             return true;
492         if (obj == null)
493             return false;
494         if (!(obj instanceof FloatMatrixData))
495             return false;
496         FloatMatrixData/../../../org/djunits/value/vfloat/matrix/data/FloatMatrixData.html#FloatMatrixData">FloatMatrixData other = (FloatMatrixData) obj;
497         if (this.rows != other.rows)
498             return false;
499         if (this.cols != other.cols)
500             return false;
501         if (other instanceof FloatMatrixDataSparse && this instanceof FloatMatrixDataDense)
502         {
503             return compareDenseMatrixWithSparseMatrix((FloatMatrixDataDense) this, (FloatMatrixDataSparse) other);
504         }
505         else if (other instanceof FloatMatrixDataDense && this instanceof FloatMatrixDataSparse)
506         {
507             return compareDenseMatrixWithSparseMatrix((FloatMatrixDataDense) other, (FloatMatrixDataSparse) this);
508         }
509         // Both are dense (both sparse is handled in FloatMatrixDataSparse class)
510         return Arrays.equals(this.matrixSI, other.matrixSI);
511     }
512 
513     /** {@inheritDoc} */
514     @Override
515     public String toString()
516     {
517         return "FloatMatrixData [storageType=" + getStorageType() + ", matrixSI=" + Arrays.toString(this.matrixSI) + "]";
518     }
519 
520 }