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.Entry;
7   import java.util.SortedMap;
8   import java.util.stream.IntStream;
9   
10  import org.djunits.unit.Unit;
11  import org.djunits.unit.scale.Scale;
12  import org.djunits.value.ValueRuntimeException;
13  import org.djunits.value.storage.AbstractStorage;
14  import org.djunits.value.storage.StorageType;
15  import org.djunits.value.vfloat.function.FloatFunction;
16  import org.djunits.value.vfloat.function.FloatFunction2;
17  import org.djunits.value.vfloat.scalar.base.FloatScalarInterface;
18  import org.djutils.exceptions.Throw;
19  
20  /**
21   * Stores the data for a FloatVector and carries out basic operations.
22   * <p>
23   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
24   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
25   * </p>
26   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
27   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
28   */
29  public abstract class FloatVectorData extends AbstractStorage<FloatVectorData> implements Serializable
30  {
31      /** */
32      private static final long serialVersionUID = 1L;
33  
34      /** The internal storage of the Vector; can be sparse or dense. */
35      @SuppressWarnings("checkstyle:visibilitymodifier")
36      protected float[] vectorSI;
37  
38      /**
39       * Construct a new FloatVectorData object.
40       * @param storageType StorageType; the data type.
41       */
42      FloatVectorData(final StorageType storageType)
43      {
44          super(storageType);
45      }
46  
47      /* ============================================================================================ */
48      /* ====================================== INSTANTIATION ======================================= */
49      /* ============================================================================================ */
50  
51      /**
52       * Instantiate a FloatVectorData with the right data type.
53       * @param values float[]; the (SI) values to store
54       * @param scale Scale; the scale of the unit to use for conversion to SI
55       * @param storageType StorageType; the data type to use
56       * @return FloatVectorData; the FloatVectorData with the right data type
57       * @throws NullPointerException when values are null, or storageType is null
58       */
59      public static FloatVectorData instantiate(final float[] values, final Scale scale, final StorageType storageType)
60      {
61          Throw.whenNull(values, "FloatVectorData.instantiate: float[] values is null");
62          Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
63          Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
64  
65          float[] valuesSI = new float[values.length];
66          IntStream.range(0, values.length).parallel().forEach(i -> valuesSI[i] = (float) scale.toStandardUnit(values[i]));
67  
68          switch (storageType)
69          {
70              case DENSE:
71                  return new FloatVectorDataDense(valuesSI);
72  
73              case SPARSE:
74                  return FloatVectorDataSparse.instantiate(valuesSI);
75  
76              default:
77                  throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
78          }
79      }
80  
81      /**
82       * Instantiate a FloatVectorData with the right data type.
83       * @param values List&lt;Float&gt;; the values to store
84       * @param scale Scale; the scale of the unit to use for conversion to SI
85       * @param storageType StorageType; the data type to use
86       * @return FloatVectorData; the FloatVectorData with the right data type
87       * @throws NullPointerException when list is null, or storageType is null
88       */
89      public static FloatVectorData instantiate(final List<Float> values, final Scale scale, final StorageType storageType)
90      {
91          Throw.whenNull(values, "FloatVectorData.instantiate: float[] values is null");
92          Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
93          Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
94          Throw.when(values.parallelStream().filter(d -> d == null).count() > 0, NullPointerException.class,
95                  "values contains one or more null values");
96  
97          float[] valuesSI = new float[values.size()];
98          IntStream.range(0, values.size()).parallel().forEach(i -> valuesSI[i] = (float) scale.toStandardUnit(values.get(i)));
99  
100         switch (storageType)
101         {
102             case DENSE:
103                 return new FloatVectorDataDense(valuesSI);
104 
105             case SPARSE:
106                 return FloatVectorDataSparse.instantiate(valuesSI);
107 
108             default:
109                 throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
110         }
111     }
112 
113     /**
114      * Instantiate a FloatVectorData with the right data type.
115      * @param values S[]; the values to store
116      * @param storageType StorageType; the data type to use
117      * @return FloatVectorData; the FloatVectorData with the right data type
118      * @throws NullPointerException when values is null, or storageType is null
119      * @param <U> the unit type
120      * @param <S> the corresponding scalar type
121      */
122     public static <U extends Unit<U>, S extends FloatScalarInterface<U, S>> FloatVectorData instantiate(final S[] values,
123             final StorageType storageType)
124     {
125         Throw.whenNull(values, "FloatVectorData.instantiate: double[] values is null");
126         Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
127 
128         for (S s : values)
129         {
130             Throw.whenNull(s, "null value in values");
131         }
132 
133         float[] valuesSI = new float[values.length];
134         IntStream.range(0, values.length).parallel().forEach(i -> valuesSI[i] = values[i].getSI());
135 
136         switch (storageType)
137         {
138             case DENSE:
139                 return new FloatVectorDataDense(valuesSI);
140 
141             case SPARSE:
142                 return FloatVectorDataSparse.instantiate(valuesSI);
143 
144             default:
145                 throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
146         }
147     }
148 
149     /**
150      * Instantiate a FloatVectorData with the right data type.
151      * @param valueList List&lt;? extends FloatScalarInterface&gt;; the FloatScalar values to store
152      * @param storageType StorageType; the data type to use
153      * @return FloatVectorData; the FloatVectorData with the right data type
154      * @throws NullPointerException when values is null, or storageType is null
155      * @param <U> the unit type
156      * @param <S> the corresponding scalar type
157      */
158     public static <U extends Unit<U>, S extends FloatScalarInterface<U, S>> FloatVectorData instantiateList(
159             final List<S> valueList, final StorageType storageType)
160     {
161         Throw.whenNull(valueList, "FloatVectorData.instantiate: valueList is null");
162         Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
163 
164         for (S s : valueList)
165         {
166             Throw.whenNull(s, "null value in valueList");
167         }
168 
169         float[] valuesSI = new float[valueList.size()];
170         IntStream.range(0, valueList.size()).parallel().forEach(i -> valuesSI[i] = valueList.get(i).getSI());
171 
172         switch (storageType)
173         {
174             case DENSE:
175                 return new FloatVectorDataDense(valuesSI);
176 
177             case SPARSE:
178                 return FloatVectorDataSparse.instantiate(valuesSI);
179 
180             default:
181                 throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
182         }
183     }
184 
185     /**
186      * Instantiate a FloatVectorData with the right data type.
187      * @param valueMap SortedMap&lt;Integer,Float&gt;; the FloatScalar values to store
188      * @param length int; the length of the vector to pad with 0 after last entry in map
189      * @param scale Scale; the scale of the unit to use for conversion to SI
190      * @param storageType StorageType; the data type to use
191      * @return FloatVectorData; the FloatVectorData with the right data type
192      * @throws ValueRuntimeException when length &lt; 0
193      * @throws NullPointerException when values is null, or storageType is null
194      */
195     public static FloatVectorData instantiate(final SortedMap<Integer, Float> valueMap, final int length, final Scale scale,
196             final StorageType storageType) throws ValueRuntimeException
197     {
198         Throw.whenNull(valueMap, "FloatVectorData.instantiate: values is null");
199         Throw.when(length < 0, ValueRuntimeException.class, "Length must be >= 0");
200         Throw.whenNull(scale, "FloatVectorData.instantiate: scale is null");
201         Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
202         for (Integer key : valueMap.keySet())
203         {
204             Throw.when(key < 0 || key >= length, ValueRuntimeException.class, "Key in values out of range");
205         }
206 
207         switch (storageType)
208         {
209             case DENSE:
210             {
211                 float[] valuesSI = new float[length];
212                 if (scale.isBaseSIScale())
213                 {
214                     valueMap.entrySet().parallelStream().forEach(entry -> valuesSI[entry.getKey()] = entry.getValue());
215                 }
216                 else
217                 {
218                     Arrays.fill(valuesSI, (float) scale.toStandardUnit(0.0));
219                     valueMap.entrySet().parallelStream()
220                             .forEach(entry -> valuesSI[entry.getKey()] = (float) scale.toStandardUnit(entry.getValue()));
221                 }
222                 return new FloatVectorDataDense(valuesSI);
223             }
224 
225             case SPARSE:
226             {
227                 int nonZeroCount;
228                 if (scale.isBaseSIScale())
229                 {
230                     nonZeroCount = (int) valueMap.keySet().parallelStream().filter(d -> d != 0d).count();
231                 }
232                 else
233                 {
234                     // Much harder, and the result is unlikely to be very sparse
235                     nonZeroCount = length
236                             - (int) valueMap.values().parallelStream().filter(d -> scale.toStandardUnit(d) == 0d).count();
237                 }
238                 int[] indices = new int[nonZeroCount];
239                 float[] valuesSI = new float[nonZeroCount];
240                 if (scale.isBaseSIScale())
241                 {
242                     int index = 0;
243                     for (Integer key : valueMap.keySet())
244                     {
245                         float value = valueMap.get(key);
246                         if (0.0 != value)
247                         {
248                             indices[index] = key;
249                             valuesSI[index] = value;
250                             index++;
251                         }
252                     }
253                 }
254                 else
255                 {
256                     Arrays.fill(valuesSI, (float) scale.toStandardUnit(0.0));
257                     int index = 0;
258                     int lastKey = 0;
259                     for (Integer key : valueMap.keySet())
260                     {
261                         for (int i = lastKey; i < key; i++)
262                         {
263                             indices[index++] = i;
264                         }
265                         lastKey = key;
266                         float value = (float) scale.toStandardUnit(valueMap.get(key));
267                         if (0.0 != value)
268                         {
269                             indices[index] = key;
270                             valuesSI[index] = value;
271                             index++;
272                         }
273                         lastKey = key + 1;
274                     }
275                     while (index < indices.length)
276                     {
277                         indices[index++] = lastKey++;
278                     }
279                 }
280                 return new FloatVectorDataSparse(valuesSI, indices, length);
281             }
282 
283             default:
284                 throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
285         }
286     }
287 
288     /**
289      * Instantiate a FloatVectorData with the right data type.
290      * @param values SortedMap&lt;Integer,S&gt;; the FloatScalar values to store
291      * @param length int; the length of the vector to pad with 0 after last entry in map
292      * @param storageType StorageType; the data type to use
293      * @return FloatVectorData; the FloatVectorData with the right data type
294      * @throws NullPointerException when values is null, or storageType is null
295      * @throws ValueRuntimeException when length &lt; 0
296      * @param <U> the unit type
297      * @param <S> the scalar type to use
298      */
299     public static <U extends Unit<U>, S extends FloatScalarInterface<U, S>> FloatVectorData instantiateMap(
300             final SortedMap<Integer, S> values, final int length, final StorageType storageType) throws ValueRuntimeException
301     {
302         Throw.whenNull(values, "FloatVectorData.instantiate: values is null");
303         Throw.when(length < 0, ValueRuntimeException.class, "Length must be >= 0");
304         Throw.whenNull(storageType, "FloatVectorData.instantiate: storageType is null");
305         for (Entry<Integer, S> e : values.entrySet())
306         {
307             Throw.when(e.getKey() < 0 || e.getKey() >= length, ValueRuntimeException.class, "Key in values out of range");
308             Throw.whenNull(e.getValue(), "null value in map");
309         }
310 
311         switch (storageType)
312         {
313             case DENSE:
314             {
315                 float[] valuesSI = new float[length];
316                 values.entrySet().parallelStream().forEach(entry -> valuesSI[entry.getKey()] = entry.getValue().getSI());
317                 return new FloatVectorDataDense(valuesSI);
318             }
319 
320             case SPARSE:
321             {
322                 int nonZeroCount = (int) values.values().parallelStream().filter(s -> s.getSI() != 0f).count();
323                 int[] indices = new int[nonZeroCount];
324                 float[] valuesSI = new float[nonZeroCount];
325                 int index = 0;
326                 for (Integer key : values.keySet())
327                 {
328                     float value = values.get(key).getSI();
329                     if (0.0f != value)
330                     {
331                         indices[index] = key;
332                         valuesSI[index] = value;
333                         index++;
334                     }
335                 }
336                 return new FloatVectorDataSparse(valuesSI, indices, length);
337             }
338 
339             default:
340                 throw new ValueRuntimeException("Unknown storage type in FloatVectorData.instantiate: " + storageType);
341         }
342     }
343 
344     /* ============================================================================================ */
345     /* ==================================== UTILITY FUNCTIONS ===================================== */
346     /* ============================================================================================ */
347 
348     /**
349      * Retrieve the size of the vector.
350      * @return int; the size of the vector
351      */
352     public abstract int size();
353 
354     /**
355      * Return the densely stored equivalent of this data.
356      * @return FloatVectorDataDense; the dense transformation of this data
357      */
358     public abstract FloatVectorDataDense toDense();
359 
360     /**
361      * Return the sparsely stored equivalent of this data.
362      * @return FloatVectorDataSparse; the sparse transformation of this data
363      */
364     public abstract FloatVectorDataSparse toSparse();
365 
366     /**
367      * Retrieve the SI value of one element of this data.
368      * @param index int; the index to get the value for
369      * @return the value at the index
370      */
371     public abstract float getSI(int index);
372 
373     /**
374      * Sets a value at the index in the vector.
375      * @param index int; the index to set the value for
376      * @param valueSI float; the value at the index
377      */
378     public abstract void setSI(int index, float valueSI);
379 
380     /**
381      * Compute and return the sum of all values.
382      * @return double; the sum of the values of all cells
383      */
384     public final float zSum()
385     {
386         // this does not copy the data. See http://stackoverflow.com/questions/23106093/how-to-get-a-stream-from-a-float
387         return (float) IntStream.range(0, this.vectorSI.length).parallel().mapToDouble(i -> this.vectorSI[i]).sum();
388     }
389 
390     /**
391      * Create and return a dense copy of the data.
392      * @return float[]; a safe copy of VectorSI
393      */
394     public abstract float[] getDenseVectorSI();
395 
396     /**
397      * Check the sizes of this data object and the other data object.
398      * @param other FloatVectorData; the other data object
399      * @throws ValueRuntimeException if vectors have different lengths
400      */
401     protected void checkSizes(final FloatVectorData other) throws ValueRuntimeException
402     {
403         if (this.size() != other.size())
404         {
405             throw new ValueRuntimeException("Two data objects used in a FloatVector operation do not have the same size");
406         }
407     }
408 
409     /* ============================================================================================ */
410     /* ================================== CALCULATION FUNCTIONS =================================== */
411     /* ============================================================================================ */
412 
413     /**
414      * Apply an operation to each cell.
415      * @param floatFunction FloatFunction; the operation to apply
416      * @return FloatVectorData; this (modified) float vector data object
417      */
418     public abstract FloatVectorData assign(FloatFunction floatFunction);
419 
420     /**
421      * Apply a binary operation on a cell by cell basis.
422      * @param floatFunction2 FloatFunction2; the binary operation to apply
423      * @param right FloatVectorData; the right operand for the binary operation
424      * @return DoubleMatrixData; this (modified) float vector data object
425      * @throws ValueRuntimeException when the sizes of the vectors do not match
426      */
427     abstract FloatVectorData assign(FloatFunction2 floatFunction2, FloatVectorData right) throws ValueRuntimeException;
428 
429     /**
430      * Add two vectors on a cell-by-cell basis. If both vectors are sparse, a sparse vector is returned, otherwise a dense
431      * vector is returned.
432      * @param right FloatVectorData; the other data object to add
433      * @return FloatVectorData; the sum of this data object and the other data object
434      * @throws ValueRuntimeException if vectors have different lengths
435      */
436     public abstract FloatVectorData plus(FloatVectorData right) throws ValueRuntimeException;
437 
438     /**
439      * Add a vector to this vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the same.
440      * @param right FloatVectorData; the other data object to add
441      * @return FloatVectorData; this modified float vector data object
442      * @throws ValueRuntimeException if vectors have different lengths
443      */
444     public final FloatVectorData incrementBy(final FloatVectorData right) throws ValueRuntimeException
445     {
446         return assign(new FloatFunction2()
447         {
448             @Override
449             public float apply(final float leftValue, final float rightValue)
450             {
451                 return leftValue + rightValue;
452             }
453         }, right);
454     }
455 
456     /**
457      * Subtract two vectors on a cell-by-cell basis. If both vectors are sparse, a sparse vector is returned, otherwise a dense
458      * vector is returned.
459      * @param right FloatVectorData; the other data object to subtract
460      * @return FloatVectorData; the difference of this data object and the other data object
461      * @throws ValueRuntimeException if vectors have different lengths
462      */
463     public abstract FloatVectorData minus(FloatVectorData right) throws ValueRuntimeException;
464 
465     /**
466      * Subtract a vector from this vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the same.
467      * @param right FloatVectorData; the other data object to subtract
468      * @return FloatVectorData; this modified float vector data object
469      * @throws ValueRuntimeException if vectors have different lengths
470      */
471     public final FloatVectorData decrementBy(final FloatVectorData right) throws ValueRuntimeException
472     {
473         return assign(new FloatFunction2()
474         {
475             @Override
476             public float apply(final float leftValue, final float rightValue)
477             {
478                 return leftValue - rightValue;
479             }
480         }, right);
481     }
482 
483     /**
484      * Multiply two vector on a cell-by-cell basis. If both vectors are dense, a dense vector is returned, otherwise a sparse
485      * vector is returned.
486      * @param right FloatVectorData; the other data object to multiply with
487      * @return FloatVectorData; a new double vector data store holding the result of the multiplications
488      * @throws ValueRuntimeException if vectors have different lengths
489      */
490     public abstract FloatVectorData times(FloatVectorData right) throws ValueRuntimeException;
491 
492     /**
493      * Multiply a vector with the values of another vector on a cell-by-cell basis. The type of vector (sparse, dense) stays the
494      * same.
495      * @param right FloatVectorData; the other data object to multiply with
496      * @return FloatVectorData; this modified float vector data store
497      * @throws ValueRuntimeException if vectors have different lengths
498      */
499     public final FloatVectorData multiplyBy(final FloatVectorData right) throws ValueRuntimeException
500     {
501         assign(new FloatFunction2()
502         {
503             @Override
504             public float apply(final float leftValue, final float rightValue)
505             {
506                 return leftValue * rightValue;
507             }
508         }, right);
509         return this;
510     }
511 
512     /**
513      * Divide two vectors on a cell-by-cell basis. If this vector is sparse and <code>right</code> is dense, a sparse vector is
514      * returned, otherwise a dense vector is returned.
515      * @param right FloatVectorData; the other data object to divide by
516      * @return FloatVectorData; the ratios of the values of this data object and the other data object
517      * @throws ValueRuntimeException if vectors have different lengths
518      */
519     public abstract FloatVectorData divide(FloatVectorData right) throws ValueRuntimeException;
520 
521     /**
522      * Divide the values of a vector by the values of another vector on a cell-by-cell basis. The type of vector (sparse, dense)
523      * stays the same.
524      * @param right FloatVectorData; the other data object to divide by
525      * @return FloatVectorData; this modified float vector data store
526      * @throws ValueRuntimeException if vectors have different lengths
527      */
528     public final FloatVectorData divideBy(final FloatVectorData right) throws ValueRuntimeException
529     {
530         return assign(new FloatFunction2()
531         {
532             @Override
533             public float apply(final float leftValue, final float rightValue)
534             {
535                 return leftValue / rightValue;
536             }
537         }, right);
538     }
539 
540     /* ============================================================================================ */
541     /* =============================== EQUALS, HASHCODE, TOSTRING ================================= */
542     /* ============================================================================================ */
543 
544     /** {@inheritDoc} */
545     @Override
546     public int hashCode()
547     {
548         final int prime = 31;
549         int result = 1;
550         result = prime * result + this.size();
551         for (int index = 0; index < this.size(); index++)
552         {
553             result = 31 * result + Float.floatToIntBits(getSI(index));
554         }
555         return result;
556     }
557 
558     /**
559      * Compare contents of a dense and a sparse vector.
560      * @param dm FloatVectorDataDense; the dense vector
561      * @param sm FloatVectorDataSparse; the sparse vector
562      * @return boolean; true if the contents are equal
563      */
564     protected boolean compareDenseVectorWithSparseVector(final FloatVectorDataDense dm, final FloatVectorDataSparse sm)
565     {
566         for (int index = 0; index < dm.size(); index++)
567         {
568             if (dm.getSI(index) != sm.getSI(index))
569             {
570                 return false;
571             }
572         }
573         return true;
574     }
575 
576     /** {@inheritDoc} */
577     @Override
578     @SuppressWarnings("checkstyle:needbraces")
579     public boolean equals(final Object obj)
580     {
581         if (this == obj)
582             return true;
583         if (obj == null)
584             return false;
585         if (!(obj instanceof FloatVectorData))
586             return false;
587         FloatVectorData other = (FloatVectorData) obj;
588         if (this.size() != other.size())
589             return false;
590         if (other instanceof FloatVectorDataSparse && this instanceof FloatVectorDataDense)
591         {
592             return compareDenseVectorWithSparseVector((FloatVectorDataDense) this, (FloatVectorDataSparse) other);
593         }
594         else if (other instanceof FloatVectorDataDense && this instanceof FloatVectorDataSparse)
595         {
596             return compareDenseVectorWithSparseVector((FloatVectorDataDense) other, (FloatVectorDataSparse) this);
597         }
598         // Both are dense (both sparse is handled in FloatVectorDataSparse class)
599         return Arrays.equals(this.vectorSI, other.vectorSI);
600     }
601 
602     /** {@inheritDoc} */
603     @Override
604     public String toString()
605     {
606         return "FloatVectorData [storageType=" + getStorageType() + ", vectorSI=" + Arrays.toString(this.vectorSI) + "]";
607     }
608 
609 }