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.unit.Unit;
9   import org.djunits.unit.scale.Scale;
10  import org.djunits.value.ValueRuntimeException;
11  import org.djunits.value.storage.Storage;
12  import org.djunits.value.storage.StorageType;
13  import org.djunits.value.vfloat.function.FloatFunction;
14  import org.djunits.value.vfloat.function.FloatFunction2;
15  import org.djunits.value.vfloat.matrix.base.FloatSparseValue;
16  import org.djunits.value.vfloat.scalar.base.FloatScalar;
17  import org.djutils.exceptions.Throw;
18  
19  /**
20   * Stores the data for a FloatMatrix and carries out basic operations.
21   * <p>
22   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS 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 Storage<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 is ragged
66       * @throws NullPointerException when values are null, or storageType is null
67       */
68      public static FloatMatrixData instantiate(final float[][] values, final Scale scale, final StorageType storageType)
69              throws ValueRuntimeException
70      {
71          Throw.whenNull(scale, "FloatMatrixData.instantiate: scale is null");
72          Throw.whenNull(storageType, "FloatMatrixData.instantiate: storageType is null");
73          checkRectangularAndNonNull(values);
74  
75          int rows = values.length;
76          final int cols = rows == 0 ? 0 : values[0].length;
77          if (cols == 0)
78          {
79              rows = 0;
80          }
81  
82          switch (storageType)
83          {
84              case DENSE:
85                  float[] valuesSI = new float[rows * cols];
86                  IntStream.range(0, values.length).parallel().forEach(r -> IntStream.range(0, cols)
87                          .forEach(c -> valuesSI[r * cols + c] = (float) scale.toStandardUnit(values[r][c])));
88                  return new FloatMatrixDataDense(valuesSI, rows, cols);
89  
90              case SPARSE:
91                  return FloatMatrixDataSparse.instantiate(values, scale);
92  
93              default:
94                  throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.instantiate: " + storageType);
95          }
96      }
97  
98      /**
99       * Instantiate a FloatMatrixData with the right data type.
100      * @param values Collection&lt;FloatSparseValue&lt;U, S&gt;&gt;; the (sparse [X, Y, SI]) values to store
101      * @param rows int; the number of rows of the matrix
102      * @param cols int; the number of columns of the matrix
103      * @param storageType StorageType; the data type to use
104      * @return the FloatMatrixData with the right data type
105      * @throws NullPointerException when values are null, or storageType is null
106      * @throws ValueRuntimeException when rows &lt; 0 or cols &lt; 0
107      * @param <U> the unit type
108      * @param <S> the corresponding scalar type
109      */
110     public static <U extends Unit<U>, S extends FloatScalar<U, S>> FloatMatrixData instantiate(
111             final Collection<FloatSparseValue<U, S>> values, final int rows, final int cols, final StorageType storageType)
112             throws NullPointerException
113     {
114         Throw.whenNull(values, "FloatMatrixData.instantiate: values is null");
115         Throw.whenNull(storageType, "FloatMatrixData.instantiate: storageType is null");
116         Throw.when(cols < 0, ValueRuntimeException.class, "cols must be >= 0");
117         Throw.when(rows < 0, ValueRuntimeException.class, "rows must be >= 0");
118         for (FloatSparseValue<U, S> fsp : values)
119         {
120             Throw.whenNull(fsp, "null value in values");
121             Throw.when(fsp.getRow() < 0 || fsp.getRow() >= rows, ValueRuntimeException.class, "row out of range");
122             Throw.when(fsp.getColumn() < 0 || fsp.getColumn() >= cols, ValueRuntimeException.class, "column out of range");
123         }
124 
125         switch (storageType)
126         {
127             case DENSE:
128                 float[] valuesSI = new float[rows * cols];
129                 values.stream().parallel().forEach(v -> valuesSI[v.getRow() * cols + v.getColumn()] = v.getValueSI());
130                 return new FloatMatrixDataDense(valuesSI, rows, cols);
131 
132             case SPARSE:
133                 return new FloatMatrixDataSparse(values, rows, cols);
134 
135             default:
136                 throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.instantiate: " + storageType);
137         }
138     }
139 
140     /**
141      * Instantiate a FloatMatrixData with the right data type. The FloatScalar array is of the form fs[rows][columns] so each
142      * value can be found with fs[row][column].
143      * @param values S[][]; the values to store
144      * @param storageType StorageType; the data type to use
145      * @return the FloatMatrixData with the right data type
146      * @throws NullPointerException when values is null, or storageType is null
147      * @throws ValueRuntimeException when values is ragged
148      * @param <U> the unit type
149      * @param <S> the corresponding scalar type
150      */
151     public static <U extends Unit<U>, S extends FloatScalar<U, S>> FloatMatrixData instantiate(final S[][] values,
152             final StorageType storageType) throws ValueRuntimeException
153     {
154         Throw.whenNull(storageType, "FloatMatrixData.instantiate: storageType is null");
155         checkRectangularAndNonNull(values);
156 
157         int rows = values.length;
158         final int cols = rows == 0 ? 0 : values[0].length;
159         if (cols == 0)
160         {
161             rows = 0;
162         }
163 
164         switch (storageType)
165         {
166             case DENSE:
167                 float[] valuesSI = new float[rows * cols];
168                 IntStream.range(0, rows).parallel()
169                         .forEach(r -> IntStream.range(0, cols).forEach(c -> valuesSI[r * cols + c] = values[r][c].getSI()));
170                 return new FloatMatrixDataDense(valuesSI, rows, cols);
171 
172             case SPARSE:
173                 float[][] matrixSI = new float[rows][cols];
174                 IntStream.range(0, values.length).parallel()
175                         .forEach(r -> IntStream.range(0, cols).forEach(c -> matrixSI[r][c] = values[r][c].getSI()));
176                 return FloatMatrixDataSparse.instantiate(matrixSI);
177 
178             default:
179                 throw new ValueRuntimeException("Unknown storage type in FloatMatrixData.instantiate: " + storageType);
180         }
181     }
182 
183     /* ============================================================================================ */
184     /* ==================================== UTILITY FUNCTIONS ===================================== */
185     /* ============================================================================================ */
186 
187     /**
188      * Retrieve the row count.
189      * @return int; the number of rows of the matrix
190      */
191     public int rows()
192     {
193         return this.rows;
194     }
195 
196     /**
197      * Retrieve the column count.
198      * @return int; the number of columns of the matrix
199      */
200     public int cols()
201     {
202         return this.cols;
203     }
204 
205     /**
206      * Return the data of this matrix in dense storage format.
207      * @return FloatMatrixDataDense; the dense transformation of this data
208      */
209     public abstract FloatMatrixDataDense toDense();
210 
211     /**
212      * Return the data of this matrix in sparse storage format.
213      * @return FloatMatrixDataSparse; the sparse transformation of this data
214      */
215     public abstract FloatMatrixDataSparse toSparse();
216 
217     /**
218      * Retrieve one value from this data.
219      * @param row int; the row number to get the value for
220      * @param col int; the column number to get the value for
221      * @return the value at the [row, col] point
222      */
223     public abstract float getSI(int row, int col);
224 
225     /**
226      * Sets a value at the [row, col] point in the matrix.
227      * @param row int; the row number to set the value for
228      * @param col int; the column number to set the value for
229      * @param valueSI float; the value at the index
230      */
231     public abstract void setSI(int row, int col, float valueSI);
232 
233     /**
234      * Compute and return the sum of the values of all cells of this matrix.
235      * @return float; the sum of the values of all cells
236      */
237     public final float zSum()
238     {
239         // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
240         return (float) IntStream.range(0, this.matrixSI.length).parallel().mapToDouble(i -> this.matrixSI[i]).sum();
241     }
242 
243     /**
244      * 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
245      * can be found with f[row][column].
246      * @return float[][]; a safe, dense copy of matrixSI as a matrix
247      */
248     public abstract float[][] getDenseMatrixSI();
249 
250     /**
251      * 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
252      * can be found with d[row][column].
253      * @return double[][]; a safe, dense copy of matrixSI as a matrix
254      */
255     public abstract double[][] getDoubleDenseMatrixSI();
256 
257     /**
258      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
259      * @param values float[][]; the 2D array to check
260      * @return the values in case the method is used in a constructor
261      * @throws NullPointerException when <code>values</code> is null
262      * @throws ValueRuntimeException when <code>values</code> is jagged
263      */
264     protected static float[][] checkRectangularAndNonNull(final float[][] values) throws ValueRuntimeException
265     {
266         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null float[][]");
267         for (int row = 0; row < values.length; row++)
268         {
269             Throw.when(null == values[row], ValueRuntimeException.class,
270                     "Cannot create a matrix from float[][] containing null row(s)");
271             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
272                     "Cannot create a matrix from a jagged float[][]");
273         }
274         return values;
275     }
276 
277     /**
278      * Check that a 2D array of float is not null, not empty and not jagged; i.e. all rows have the same length.
279      * @param values S[][]; the 2D array to check
280      * @return the values in case the method is used in a constructor
281      * @throws NullPointerException when <code>values</code> is null
282      * @throws ValueRuntimeException when <code>values</code> is jagged
283      * @param <U> the unit type
284      * @param <S> the corresponding scalar type
285      */
286     protected static <U extends Unit<U>, S extends FloatScalar<U, S>> S[][] checkRectangularAndNonNull(
287             final S[][] values) throws ValueRuntimeException
288     {
289         Throw.when(null == values, NullPointerException.class, "Cannot create a matrix from a null Scalar[][]");
290         for (int row = 0; row < values.length; row++)
291         {
292             Throw.when(null == values[row], ValueRuntimeException.class,
293                     "Cannot create a matrix from Scalar[][] containing null row(s)");
294             Throw.when(values[row].length != values[0].length, ValueRuntimeException.class,
295                     "Cannot create a matrix from a jagged Scalar[][]");
296             for (int col = 0; col < values[row].length; col++)
297             {
298                 Throw.whenNull(values[row][col], "Cannot create a matrix from Scalar[][] containing null(s)");
299             }
300         }
301         return values;
302     }
303 
304     /**
305      * Check the sizes of this data object and the other data object.
306      * @param other FloatMatrixData; the other data object
307      * @throws ValueRuntimeException if matrices have different lengths
308      */
309     protected void checkSizes(final FloatMatrixData other) throws ValueRuntimeException
310     {
311         if (this.rows() != other.rows() || this.cols() != other.cols())
312         {
313             throw new ValueRuntimeException("Two data objects used in a FloatMatrix operation do not have the same size");
314         }
315     }
316 
317     /* ============================================================================================ */
318     /* ================================== CALCULATION FUNCTIONS =================================== */
319     /* ============================================================================================ */
320 
321     /**
322      * Apply an operation to each cell.
323      * @param doubleFunction FloatFunction; the operation to apply
324      * @return FloatMatrixData; this (modified) double vector data object
325      */
326     public abstract FloatMatrixData assign(FloatFunction doubleFunction);
327 
328     /**
329      * Apply a binary operation on a cell by cell basis.
330      * @param floatFunction FloatFunction2; the binary operation to apply
331      * @param right FloatMatrixData; the right operand for the binary operation
332      * @return FloatMatrixData; this (modified) double matrix data object
333      * @throws ValueRuntimeException when the sizes of the vectors do not match
334      */
335     abstract FloatMatrixData assign(FloatFunction2 floatFunction, FloatMatrixData right) throws ValueRuntimeException;
336 
337     /**
338      * Add two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a dense
339      * matrix is returned.
340      * @param right FloatMatrixData; the other data object to add
341      * @return the sum of this data object and the other data object
342      * @throws ValueRuntimeException if matrices have different lengths
343      */
344     public abstract FloatMatrixData plus(FloatMatrixData right) throws ValueRuntimeException;
345 
346     /**
347      * Add a matrix to this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
348      * @param right FloatMatrixData; the other data object to add
349      * @return FloatMatrixData; this modified float matrix data object
350      * @throws ValueRuntimeException if matrices have different lengths
351      */
352     public final FloatMatrixData incrementBy(final FloatMatrixData right) throws ValueRuntimeException
353     {
354         return assign(new FloatFunction2()
355         {
356             @Override
357             public float apply(final float leftValue, final float rightValue)
358             {
359                 return leftValue + rightValue;
360             }
361         }, right);
362     }
363 
364     /**
365      * Subtract two matrices on a cell-by-cell basis. If both matrices are sparse, a sparse matrix is returned, otherwise a
366      * dense matrix is returned.
367      * @param right FloatMatrixData; the other data object to subtract
368      * @return the sum of this data object and the other data object
369      * @throws ValueRuntimeException if matrices have different lengths
370      */
371     public abstract FloatMatrixData minus(FloatMatrixData right) throws ValueRuntimeException;
372 
373     /**
374      * Subtract a matrix from this matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the same.
375      * @param decrement FloatMatrixData; the other data object to subtract
376      * @return FloatMatrixData; this modified float matrix data object
377      * @throws ValueRuntimeException if matrices have different lengths
378      */
379     public final FloatMatrixData decrementBy(final FloatMatrixData decrement) throws ValueRuntimeException
380     {
381         return assign(new FloatFunction2()
382         {
383             @Override
384             public float apply(final float leftValue, final float rightValue)
385             {
386                 return leftValue - rightValue;
387             }
388         }, decrement);
389     }
390 
391     /**
392      * Multiply two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
393      * matrix is returned.
394      * @param right FloatMatrixData; the other data object to multiply with
395      * @return FloatMatrixData; a new double matrix data store holding the result of the multiplications
396      * @throws ValueRuntimeException if matrices have different sizes
397      */
398     public abstract FloatMatrixData times(FloatMatrixData right) throws ValueRuntimeException;
399 
400     /**
401      * Multiply a matrix with the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense) stays the
402      * same.
403      * @param right FloatMatrixData; the other data object to multiply with
404      * @return FloatMatrixData; this modified data store
405      * @throws ValueRuntimeException if matrices have different sizes
406      */
407     public final FloatMatrixData multiplyBy(final FloatMatrixData right) throws ValueRuntimeException
408     {
409         return assign(new FloatFunction2()
410         {
411             @Override
412             public float apply(final float leftValue, final float rightValue)
413             {
414                 return leftValue * rightValue;
415             }
416         }, right);
417     }
418 
419     /**
420      * Divide two matrices on a cell-by-cell basis. If both matrices are dense, a dense matrix is returned, otherwise a sparse
421      * matrix is returned.
422      * @param right FloatMatrixData; the other data object to divide by
423      * @return the sum of this data object and the other data object
424      * @throws ValueRuntimeException if matrices have different sizes
425      */
426     public abstract FloatMatrixData divide(FloatMatrixData right) throws ValueRuntimeException;
427 
428     /**
429      * Divide the values of a matrix by the values of another matrix on a cell-by-cell basis. The type of matrix (sparse, dense)
430      * stays the same.
431      * @param right FloatMatrixData; the other data object to divide by
432      * @return FloatMatrixData; this modified data store
433      * @throws ValueRuntimeException if matrices have different sizes
434      */
435     public final FloatMatrixData divideBy(final FloatMatrixData right) throws ValueRuntimeException
436     {
437         return assign(new FloatFunction2()
438         {
439             @Override
440             public float apply(final float leftValue, final float rightValue)
441             {
442                 return leftValue / rightValue;
443             }
444         }, right);
445     }
446 
447     /* ============================================================================================ */
448     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
449     /* ============================================================================================ */
450 
451     @Override
452     public int hashCode()
453     {
454         final int prime = 31;
455         int result = 1;
456         result = prime * result + this.rows;
457         result = prime * result + this.cols;
458         for (int row = 0; row < this.rows; row++)
459         {
460             for (int col = 0; col < this.cols; col++)
461             {
462                 result = 31 * result + Float.floatToIntBits(getSI(row, col));
463             }
464         }
465         return result;
466     }
467 
468     /**
469      * Compare contents of a dense and a sparse matrix.
470      * @param dm FloatMatrixDataDense; the dense matrix
471      * @param sm FloatMatrixDataSparse; the sparse matrix
472      * @return boolean; true if the contents are equal
473      */
474     protected boolean compareDenseMatrixWithSparseMatrix(final FloatMatrixDataDense dm, final FloatMatrixDataSparse sm)
475     {
476         for (int row = 0; row < dm.rows; row++)
477         {
478             for (int col = 0; col < dm.cols; col++)
479             {
480                 if (dm.getSI(row, col) != sm.getSI(row, col))
481                 {
482                     return false;
483                 }
484             }
485         }
486         return true;
487     }
488 
489     @Override
490     @SuppressWarnings("checkstyle:needbraces")
491     public boolean equals(final Object obj)
492     {
493         if (this == obj)
494             return true;
495         if (obj == null)
496             return false;
497         if (!(obj instanceof FloatMatrixData))
498             return false;
499         FloatMatrixData other = (FloatMatrixData) obj;
500         if (this.rows != other.rows)
501             return false;
502         if (this.cols != other.cols)
503             return false;
504         if (other instanceof FloatMatrixDataSparse && this instanceof FloatMatrixDataDense)
505         {
506             return compareDenseMatrixWithSparseMatrix((FloatMatrixDataDense) this, (FloatMatrixDataSparse) other);
507         }
508         else if (other instanceof FloatMatrixDataDense && this instanceof FloatMatrixDataSparse)
509         {
510             return compareDenseMatrixWithSparseMatrix((FloatMatrixDataDense) other, (FloatMatrixDataSparse) this);
511         }
512         // Both are dense (both sparse is handled in FloatMatrixDataSparse class)
513         return Arrays.equals(this.matrixSI, other.matrixSI);
514     }
515 
516     @Override
517     public String toString()
518     {
519         return "FloatMatrixData [storageType=" + getStorageType() + ", matrixSI=" + Arrays.toString(this.matrixSI) + "]";
520     }
521 
522 }