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