1 package org.djunits.value.vfloat.vector.data;
2
3 import java.io.Serializable;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.stream.IntStream;
8
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.Storage;
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.scalar.base.FloatScalar;
17 import org.djutils.exceptions.Throw;
18
19 /**
20 * Stores the data for a FloatVector 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 FloatVectorData extends Storage<FloatVectorData> implements Serializable
29 {
30 /** */
31 private static final long serialVersionUID = 1L;
32
33 /** The internal storage of the Vector; can be sparse or dense. */
34 @SuppressWarnings("checkstyle:visibilitymodifier")
35 protected float[] vectorSI;
36
37 /**
38 * Construct a new FloatVectorData object.
39 * @param storageType the data type.
40 */
41 FloatVectorData(final StorageType storageType)
42 {
43 super(storageType);
44 }
45
46 /* ============================================================================================ */
47 /* ====================================== INSTANTIATION ======================================= */
48 /* ============================================================================================ */
49
50 /**
51 * Instantiate a FloatVectorData with the right data type.
52 * @param values the (SI) values to store
53 * @param scale the scale of the unit to use for conversion to SI
54 * @param storageType the data type to use
55 * @return the FloatVectorData with the right data type
56 * @throws NullPointerException when values are null, or storageType is null
57 */
58 public static FloatVectorData instantiate(final float[] values, final Scale scale, final StorageType storageType)
59 {
60 Throw.whenNull(values, "FloatVectorData.instantiate: float[] values is null");
61 Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
62 Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
63
64 float[] valuesSI = new float[values.length];
65 IntStream.range(0, values.length).parallel().forEach(i -> valuesSI[i] = (float) scale.toStandardUnit(values[i]));
66
67 if (storageType.equals(StorageType.DENSE))
68 {
69 return new FloatVectorDataDense(valuesSI);
70 }
71 else
72 {
73 return FloatVectorDataSparse.instantiate(valuesSI);
74 }
75 }
76
77 /**
78 * Instantiate a FloatVectorData with the right data type.
79 * @param values the values to store, can either be a list of numbers, or a list of FloatScalars
80 * @param scale the scale of the unit to use for conversion to SI
81 * @param storageType the data type to use
82 * @return the FloatVectorData with the right data type
83 * @throws NullPointerException when list is null, or storageType is null
84 */
85 public static FloatVectorData instantiate(final List<? extends Number> values, final Scale scale,
86 final StorageType storageType)
87 {
88 Throw.whenNull(values, "FloatVectorData.instantiate: float[] values is null");
89 Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
90 Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
91 Throw.when(values.parallelStream().filter(d -> d == null).count() > 0, NullPointerException.class,
92 "values contains one or more null values");
93
94 float[] valuesSI = new float[values.size()];
95 IntStream.range(0, values.size()).parallel()
96 .forEach(i -> valuesSI[i] = (float) scale.toStandardUnit(values.get(i).floatValue()));
97
98 if (storageType.equals(StorageType.DENSE))
99 {
100 return new FloatVectorDataDense(valuesSI);
101 }
102 else
103 {
104 return FloatVectorDataSparse.instantiate(valuesSI);
105 }
106 }
107
108 /**
109 * Instantiate a FloatVectorData with the right data type.
110 * @param values the values to store
111 * @param storageType the data type to use
112 * @return the FloatVectorData with the right data type
113 * @throws NullPointerException when values is null, or storageType is null
114 * @param <U> the unit type
115 * @param <S> the corresponding scalar type
116 */
117 public static <U extends Unit<U>, S extends FloatScalar<U, S>> FloatVectorData instantiate(final S[] values,
118 final StorageType storageType)
119 {
120 Throw.whenNull(values, "FloatVectorData.instantiate: double[] values is null");
121 Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
122
123 for (S s : values)
124 {
125 Throw.whenNull(s, "null value in values");
126 }
127
128 float[] valuesSI = new float[values.length];
129 IntStream.range(0, values.length).parallel().forEach(i -> valuesSI[i] = values[i].getSI());
130
131 if (storageType.equals(StorageType.DENSE))
132 {
133 return new FloatVectorDataDense(valuesSI);
134 }
135 else
136 {
137 return FloatVectorDataSparse.instantiate(valuesSI);
138 }
139 }
140
141 /**
142 * Instantiate a FloatVectorData with the right data type.
143 * @param valueMap the values to store; either Numbers or FloatScalars
144 * @param size the size of the vector to pad with 0 after last entry in map
145 * @param scale the scale of the unit to use for conversion to SI
146 * @param storageType the data type to use
147 * @return the FloatVectorData with the right data type
148 * @throws IllegalArgumentException when length < 0
149 * @throws NullPointerException when values is null, or storageType is null
150 * @throws IndexOutOfBoundsException when one of the keys is out of range with the given size
151 */
152 public static FloatVectorData instantiate(final Map<Integer, ? extends Number> valueMap, final int size, final Scale scale,
153 final StorageType storageType) throws IllegalArgumentException, IndexOutOfBoundsException
154 {
155 Throw.whenNull(valueMap, "FloatVectorData.instantiate: values is null");
156 Throw.when(size < 0, IllegalArgumentException.class, "size must be >= 0");
157 Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
158 Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
159 for (Integer key : valueMap.keySet())
160 {
161 Throw.when(key < 0 || key >= size, IndexOutOfBoundsException.class, "Key in values out of range");
162 }
163
164 if (storageType.equals(StorageType.DENSE))
165 {
166 float[] valuesSI = new float[size];
167 if (scale.isBaseSIScale())
168 {
169 valueMap.entrySet().parallelStream().forEach(entry -> valuesSI[entry.getKey()] = entry.getValue().floatValue());
170 }
171 else
172 {
173 Arrays.fill(valuesSI, (float) scale.toStandardUnit(0.0));
174 valueMap.entrySet().parallelStream().forEach(
175 entry -> valuesSI[entry.getKey()] = (float) scale.toStandardUnit(entry.getValue().floatValue()));
176 }
177 return new FloatVectorDataDense(valuesSI);
178 }
179
180 else // StorageType.SPARSE
181
182 {
183 int nonZeroCount;
184 if (scale.isBaseSIScale())
185 {
186 nonZeroCount = (int) valueMap.values().parallelStream().filter(f -> f.floatValue() != 0f).count();
187 }
188 else
189 {
190 // Much harder, and the result is unlikely to be very sparse
191 nonZeroCount = size - (int) valueMap.values().parallelStream()
192 .filter(d -> scale.toStandardUnit(d.floatValue()) == 0d).count();
193 }
194 int[] indices = new int[nonZeroCount];
195 float[] valuesSI = new float[nonZeroCount];
196 if (scale.isBaseSIScale())
197 {
198 int index = 0;
199 for (Integer key : valueMap.keySet())
200 {
201 float value = valueMap.get(key).floatValue();
202 if (0.0 != value)
203 {
204 indices[index] = key;
205 valuesSI[index] = value;
206 index++;
207 }
208 }
209 }
210 else
211 {
212 Arrays.fill(valuesSI, (float) scale.toStandardUnit(0.0));
213 int index = 0;
214 int lastKey = 0;
215 for (Integer key : valueMap.keySet())
216 {
217 for (int i = lastKey; i < key; i++)
218 {
219 indices[index++] = i;
220 }
221 lastKey = key;
222 float value = (float) scale.toStandardUnit(valueMap.get(key).floatValue());
223 if (0.0 != value)
224 {
225 indices[index] = key;
226 valuesSI[index] = value;
227 index++;
228 }
229 lastKey = key + 1;
230 }
231 while (index < indices.length)
232 {
233 indices[index++] = lastKey++;
234 }
235 }
236 return new FloatVectorDataSparse(valuesSI, indices, size);
237 }
238 }
239
240 /* ============================================================================================ */
241 /* ==================================== UTILITY FUNCTIONS ===================================== */
242 /* ============================================================================================ */
243
244 /**
245 * Retrieve the size of the vector.
246 * @return the size of the vector
247 */
248 public abstract int size();
249
250 /**
251 * Return the densely stored equivalent of this data.
252 * @return the dense transformation of this data
253 */
254 public abstract FloatVectorDataDense toDense();
255
256 /**
257 * Return the sparsely stored equivalent of this data.
258 * @return the sparse transformation of this data
259 */
260 public abstract FloatVectorDataSparse toSparse();
261
262 /**
263 * Retrieve the SI value of one element of this data.
264 * @param index the index to get the value for
265 * @return the value at the index
266 */
267 public abstract float getSI(int index);
268
269 /**
270 * Sets a value at the index in the vector.
271 * @param index the index to set the value for
272 * @param valueSI the value at the index
273 */
274 public abstract void setSI(int index, float valueSI);
275
276 /**
277 * Compute and return the sum of all values.
278 * @return the sum of the values of all cells
279 */
280 public final float zSum()
281 {
282 // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
283 return (float) IntStream.range(0, this.vectorSI.length).parallel().mapToDouble(i -> this.vectorSI[i]).sum();
284 }
285
286 /**
287 * Create and return a dense copy of the data.
288 * @return a safe copy of VectorSI
289 */
290 public abstract float[] getDenseVectorSI();
291
292 /**
293 * Check the sizes of this data object and the other data object.
294 * @param other the other data object
295 * @throws ValueRuntimeException if vectors have different lengths
296 */
297 protected void checkSizes(final FloatVectorData other) throws ValueRuntimeException
298 {
299 if (this.size() != other.size())
300 {
301 throw new ValueRuntimeException("Two data objects used in a FloatVector operation do not have the same size");
302 }
303 }
304
305 /* ============================================================================================ */
306 /* ================================== CALCULATION FUNCTIONS =================================== */
307 /* ============================================================================================ */
308
309 /**
310 * Apply an operation to each cell.
311 * @param floatFunction the operation to apply
312 * @return this (modified) float vector data object
313 */
314 public abstract FloatVectorData assign(FloatFunction floatFunction);
315
316 /**
317 * Apply a binary operation on a cell by cell basis.
318 * @param floatFunction2 the binary operation to apply
319 * @param right the right operand for the binary operation
320 * @return this (modified) float vector data object
321 * @throws ValueRuntimeException when the sizes of the vectors do not match
322 */
323 abstract FloatVectorData assign(FloatFunction2 floatFunction2, FloatVectorData right) throws ValueRuntimeException;
324
325 /**
326 * Add two vectors on a cell-by-cell basis. If both vectors are sparse, a sparse vector is returned, otherwise a dense
327 * vector is returned.
328 * @param right the other data object to add
329 * @return the sum of this data object and the other data object
330 * @throws ValueRuntimeException if vectors have different lengths
331 */
332 public abstract FloatVectorData plus(FloatVectorData right) throws ValueRuntimeException;
333
334 /**
335 * Add a vector to this vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the same.
336 * @param right the other data object to add
337 * @return this modified float vector data object
338 * @throws ValueRuntimeException if vectors have different lengths
339 */
340 public final FloatVectorData incrementBy(final FloatVectorData right) throws ValueRuntimeException
341 {
342 return assign(new FloatFunction2()
343 {
344 @Override
345 public float apply(final float leftValue, final float rightValue)
346 {
347 return leftValue + rightValue;
348 }
349 }, right);
350 }
351
352 /**
353 * Subtract two vectors on a cell-by-cell basis. If both vectors are sparse, a sparse vector is returned, otherwise a dense
354 * vector is returned.
355 * @param right the other data object to subtract
356 * @return the difference of this data object and the other data object
357 * @throws ValueRuntimeException if vectors have different lengths
358 */
359 public abstract FloatVectorData minus(FloatVectorData right) throws ValueRuntimeException;
360
361 /**
362 * Subtract a vector from this vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the same.
363 * @param right the other data object to subtract
364 * @return this modified float vector data object
365 * @throws ValueRuntimeException if vectors have different lengths
366 */
367 public final FloatVectorData decrementBy(final FloatVectorData right) throws ValueRuntimeException
368 {
369 return assign(new FloatFunction2()
370 {
371 @Override
372 public float apply(final float leftValue, final float rightValue)
373 {
374 return leftValue - rightValue;
375 }
376 }, right);
377 }
378
379 /**
380 * Multiply two vector on a cell-by-cell basis. If both vectors are dense, a dense vector is returned, otherwise a sparse
381 * vector is returned.
382 * @param right the other data object to multiply with
383 * @return a new double vector data store holding the result of the multiplications
384 * @throws ValueRuntimeException if vectors have different lengths
385 */
386 public abstract FloatVectorData times(FloatVectorData right) throws ValueRuntimeException;
387
388 /**
389 * Multiply a vector with the values of another vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the
390 * same.
391 * @param right the other data object to multiply with
392 * @return this modified float vector data store
393 * @throws ValueRuntimeException if vectors have different lengths
394 */
395 public final FloatVectorData multiplyBy(final FloatVectorData right) throws ValueRuntimeException
396 {
397 assign(new FloatFunction2()
398 {
399 @Override
400 public float apply(final float leftValue, final float rightValue)
401 {
402 return leftValue * rightValue;
403 }
404 }, right);
405 return this;
406 }
407
408 /**
409 * Divide two vectors on a cell-by-cell basis. If this vector is sparse and <code>right</code> is dense, a sparse vector is
410 * returned, otherwise a dense vector is returned.
411 * @param right the other data object to divide by
412 * @return the ratios of the values of this data object and the other data object
413 * @throws ValueRuntimeException if vectors have different lengths
414 */
415 public abstract FloatVectorData divide(FloatVectorData right) throws ValueRuntimeException;
416
417 /**
418 * Divide the values of a vector by the values of another vector on a cell-by-cell basis. The type of vector (sparse, dense)
419 * stays the same.
420 * @param right the other data object to divide by
421 * @return this modified float vector data store
422 * @throws ValueRuntimeException if vectors have different lengths
423 */
424 public final FloatVectorData divideBy(final FloatVectorData right) throws ValueRuntimeException
425 {
426 return assign(new FloatFunction2()
427 {
428 @Override
429 public float apply(final float leftValue, final float rightValue)
430 {
431 return leftValue / rightValue;
432 }
433 }, right);
434 }
435
436 /* ============================================================================================ */
437 /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
438 /* ============================================================================================ */
439
440 @Override
441 public int hashCode()
442 {
443 final int prime = 31;
444 int result = 1;
445 result = prime * result + this.size();
446 for (int index = 0; index < this.size(); index++)
447 {
448 result = 31 * result + Float.floatToIntBits(getSI(index));
449 }
450 return result;
451 }
452
453 /**
454 * Compare contents of a dense and a sparse vector.
455 * @param dm the dense vector
456 * @param sm the sparse vector
457 * @return true if the contents are equal
458 */
459 protected boolean compareDenseVectorWithSparseVector(final FloatVectorDataDense dm, final FloatVectorDataSparse sm)
460 {
461 for (int index = 0; index < dm.size(); index++)
462 {
463 if (dm.getSI(index) != sm.getSI(index))
464 {
465 return false;
466 }
467 }
468 return true;
469 }
470
471 @Override
472 @SuppressWarnings("checkstyle:needbraces")
473 public boolean equals(final Object obj)
474 {
475 if (this == obj)
476 return true;
477 if (obj == null)
478 return false;
479 if (!(obj instanceof FloatVectorData))
480 return false;
481 FloatVectorData other = (FloatVectorData) obj;
482 if (this.size() != other.size())
483 return false;
484 if (other instanceof FloatVectorDataSparse && this instanceof FloatVectorDataDense)
485 {
486 return compareDenseVectorWithSparseVector((FloatVectorDataDense) this, (FloatVectorDataSparse) other);
487 }
488 else if (other instanceof FloatVectorDataDense && this instanceof FloatVectorDataSparse)
489 {
490 return compareDenseVectorWithSparseVector((FloatVectorDataDense) other, (FloatVectorDataSparse) this);
491 }
492 // Both are dense (both sparse is handled in FloatVectorDataSparse class)
493 return Arrays.equals(this.vectorSI, other.vectorSI);
494 }
495
496 @Override
497 public String toString()
498 {
499 return "FloatVectorData [storageType=" + getStorageType() + ", vectorSI=" + Arrays.toString(this.vectorSI) + "]";
500 }
501
502 }