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-2025 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 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 the (SI) values to store
62 * @param scale the scale of the unit to use for conversion to SI
63 * @param 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 the (sparse [X, Y, SI]) values to store
101 * @param rows the number of rows of the matrix
102 * @param cols the number of columns of the matrix
103 * @param 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 < 0 or cols < 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 the values to store
144 * @param 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 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 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 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 the sparse transformation of this data
214 */
215 public abstract FloatMatrixDataSparse toSparse();
216
217 /**
218 * Retrieve one value from this data.
219 * @param row the row number to get the value for
220 * @param col 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 the row number to set the value for
228 * @param col the column number to set the value for
229 * @param valueSI 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 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 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 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 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 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(final S[][] values)
287 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 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 the operation to apply
324 * @return 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 the binary operation to apply
331 * @param right the right operand for the binary operation
332 * @return 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 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 the other data object to add
349 * @return 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 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 the other data object to subtract
376 * @return 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 the other data object to multiply with
395 * @return 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 the other data object to multiply with
404 * @return 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 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 the other data object to divide by
432 * @return 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 the dense matrix
471 * @param sm the sparse matrix
472 * @return 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 }