View Javadoc
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 &lt; 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 }