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