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