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