View Javadoc
1   package org.djunits.value.vdouble.matrix.data;
2   
3   import java.util.Arrays;
4   import java.util.Collection;
5   import java.util.concurrent.atomic.AtomicInteger;
6   import java.util.stream.IntStream;
7   
8   import org.djunits.unit.Unit;
9   import org.djunits.unit.scale.Scale;
10  import org.djunits.value.ValueRuntimeException;
11  import org.djunits.value.storage.StorageType;
12  import org.djunits.value.vdouble.function.DoubleFunction;
13  import org.djunits.value.vdouble.function.DoubleFunction2;
14  import org.djunits.value.vdouble.matrix.base.DoubleSparseValue;
15  import org.djunits.value.vdouble.scalar.base.DoubleScalarInterface;
16  import org.djutils.exceptions.Throw;
17  
18  /**
19   * Stores sparse data for a DoubleMatrix and carries out basic operations. The index in the sparse matrix data is calculated as
20   * <code>r * columns + c</code>, where r is the row number, cols is the total number of columns, and c is the column number.
21   * <p>
22   * Copyright (c) 2013-2023 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 class DoubleMatrixDataSparse extends DoubleMatrixData
29  {
30      /** */
31      private static final long serialVersionUID = 1L;
32  
33      /** the index values of the Matrix. The index is calculated as r * columns + c. */
34      private long[] indices;
35  
36      /**
37       * Create a matrix with sparse data.
38       * @param matrixSI double[]; the data to store
39       * @param indices long[]; the index values of the Matrix, with &lt;tt&gt;index = row * cols + col&lt;/tt&gt;
40       * @param rows int; the number of rows
41       * @param cols int; the number of columns
42       */
43      public DoubleMatrixDataSparse(final double[] matrixSI, final long[] indices, final int rows, final int cols)
44      {
45          super(StorageType.SPARSE);
46          this.matrixSI = matrixSI;
47          this.indices = indices;
48          this.rows = rows;
49          this.cols = cols;
50      }
51  
52      /**
53       * Create a matrix with sparse data.
54       * @param dataSI Collection&lt;DoubleSparseValue&lt;U, S&gt;&gt;; the sparse [X, Y, SI] values to store
55       * @param rows int; the number of rows of the matrix
56       * @param cols int; the number of columns of the matrix
57       * @throws NullPointerException when storageType is null or dataSI is null
58       * @param <U> the unit
59       * @param <S> the corresponding scalar type
60       */
61      public <U extends Unit<U>, S extends DoubleScalarInterface<U, S>> DoubleMatrixDataSparse(
62              final Collection<DoubleSparseValue<U, S>> dataSI, final int rows, final int cols) throws NullPointerException
63      {
64          super(StorageType.SPARSE);
65          Throw.whenNull(dataSI, "matrixSI is null");
66  
67          int length = (int) dataSI.stream().parallel().filter(d -> d.getValueSI() != 0.0).count();
68          this.rows = rows;
69          this.cols = cols;
70          this.matrixSI = new double[length];
71          this.indices = new long[length];
72          int index = 0;
73          for (DoubleSparseValue<U, S> data : dataSI)
74          {
75              if (data.getValueSI() != 0.0)
76              {
77                  this.indices[index] = data.getRow() * this.cols + data.getColumn();
78                  this.matrixSI[index] = data.getValueSI();
79                  index++;
80              }
81          }
82      }
83  
84      /** {@inheritDoc} */
85      @Override
86      public final int cardinality()
87      {
88          return this.indices.length;
89      }
90  
91      /**
92       * Fill the sparse data structures matrixSI[] and indices[]. Note: output vectors have to be initialized at the right size.
93       * Cannot be parallelized because of stateful and sequence-sensitive count.
94       * @param data double[][]; the input data
95       * @param matrixSI double[]; the matrix data to write
96       * @param indices long[]; the indices to write
97       * @throws ValueRuntimeException in case matrix is ragged
98       */
99      @SuppressWarnings("checkstyle:finalparameters")
100     private static void fill(final double[][] data, double[] matrixSI, long[] indices) throws ValueRuntimeException
101     {
102         int rows = data.length;
103         int cols = rows == 0 ? 0 : data[0].length;
104         if (cols == 0)
105         {
106             rows = 0;
107         }
108         int count = 0;
109         for (int r = 0; r < rows; r++)
110         {
111             double[] row = data[r];
112             // Row length check has been done by checkRectangularAndNonEmpty
113             for (int c = 0; c < cols; c++)
114             {
115                 int index = r * cols + c;
116                 if (row[c] != 0.0)
117                 {
118                     matrixSI[count] = row[c];
119                     indices[count] = index;
120                     count++;
121                 }
122             }
123         }
124     }
125 
126     /**
127      * Fill the sparse data structures matrixSI[] and indices[]. Note: output vectors have to be initialized at the right size.
128      * Cannot be parallelized because of stateful and sequence-sensitive count.
129      * @param data double[][]; the input data
130      * @param matrixSI double[]; the matrix data to write
131      * @param indices long[]; the indices to write
132      * @param scale Scale; Scale, the scale that will convert the data to SI
133      * @throws ValueRuntimeException in case matrix is ragged
134      */
135     @SuppressWarnings("checkstyle:finalparameters")
136     private static void fill(final double[][] data, double[] matrixSI, long[] indices, final Scale scale)
137             throws ValueRuntimeException
138     {
139         int rows = data.length;
140         int cols = rows == 0 ? 0 : data[0].length;
141         if (cols == 0)
142         {
143             rows = 0;
144         }
145         int count = 0;
146         for (int r = 0; r < rows; r++)
147         {
148             double[] row = data[r];
149             // Row length check has been done by checkRectangularAndNonEmpty
150             for (int c = 0; c < cols; c++)
151             {
152                 int index = r * cols + c;
153                 double value = scale.toStandardUnit(row[c]);
154                 if (value != 0.0)
155                 {
156                     matrixSI[count] = value;
157                     indices[count] = index;
158                     count++;
159                 }
160             }
161         }
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public DoubleMatrixData assign(final DoubleFunction doubleFunction)
167     {
168         if (doubleFunction.apply(0d) != 0d)
169         {
170             // It is most unlikely that the result AND the left and right operands are efficiently stored in Sparse format
171             DoubleMatrixDataSparse result = toDense().assign(doubleFunction).toSparse();
172             this.indices = result.indices;
173             this.matrixSI = result.matrixSI;
174             return this;
175         }
176         // The code below relies on the fact that doubleFunction.apply(0d) yields 0d
177         int currentSize = rows() * cols();
178         if (currentSize > 16)
179         {
180             currentSize = 16;
181         }
182         long[] newIndices = new long[currentSize];
183         double[] newValues = new double[currentSize];
184         int nonZeroValues = 0;
185         int ownIndex = 0;
186         while (ownIndex < this.indices.length)
187         {
188             long index = this.indices[ownIndex];
189             double value = doubleFunction.apply(this.matrixSI[ownIndex]);
190             ownIndex++;
191             if (value != 0d)
192             {
193                 if (nonZeroValues >= currentSize)
194                 {
195                     // increase size of arrays
196                     currentSize *= 2;
197                     if (currentSize > rows() * cols())
198                     {
199                         currentSize = rows() * cols();
200                     }
201                     long[] newNewIndices = new long[currentSize];
202                     System.arraycopy(newIndices, 0, newNewIndices, 0, newIndices.length);
203                     newIndices = newNewIndices;
204                     double[] newNewValues = new double[currentSize];
205                     System.arraycopy(newValues, 0, newNewValues, 0, newValues.length);
206                     newValues = newNewValues;
207                 }
208                 newIndices[nonZeroValues] = index;
209                 newValues[nonZeroValues] = value;
210                 nonZeroValues++;
211             }
212         }
213         if (nonZeroValues < currentSize)
214         {
215             // reduce size of arrays
216             long[] newNewIndices = new long[nonZeroValues];
217             System.arraycopy(newIndices, 0, newNewIndices, 0, nonZeroValues);
218             newIndices = newNewIndices;
219             double[] newNewValues = new double[nonZeroValues];
220             System.arraycopy(newValues, 0, newNewValues, 0, nonZeroValues);
221             newValues = newNewValues;
222         }
223         this.indices = newIndices;
224         this.matrixSI = newValues;
225         return this;
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public final DoubleMatrixDataSparse assign(final DoubleFunction2 doubleFunction, final DoubleMatrixData right)
231     {
232         if (doubleFunction.apply(0d, 0d) != 0d)
233         {
234             // It is most unlikely that the result AND the left and right operands are efficiently stored in Sparse format
235             DoubleMatrixDataSparse result = toDense().assign(doubleFunction, right).toSparse();
236             this.indices = result.indices;
237             this.matrixSI = result.matrixSI;
238             return this;
239         }
240         // The code below relies on the fact that doubleFunction.apply(0d, 0d) yields 0d
241         checkSizes(right);
242         int currentSize = rows() * cols();
243         if (currentSize > 16)
244         {
245             currentSize = 16;
246         }
247         long[] newIndices = new long[currentSize];
248         double[] newValues = new double[currentSize];
249         int nonZeroValues = 0;
250         int ownIndex = 0;
251         int otherIndex = 0;
252         if (right.isSparse())
253         { // both are sparse, result must be sparse
254             DoubleMatrixDataSparse other = (DoubleMatrixDataSparse) right;
255             while (ownIndex < this.indices.length || otherIndex < other.indices.length)
256             {
257                 double value;
258                 long index;
259                 if (ownIndex < this.indices.length && otherIndex < other.indices.length)
260                 { // neither we nor right has run out of values
261                     if (this.indices[ownIndex] == other.indices[otherIndex])
262                     {
263                         value = doubleFunction.apply(this.matrixSI[ownIndex], other.matrixSI[otherIndex]);
264                         index = this.indices[ownIndex];
265                         ownIndex++;
266                         otherIndex++;
267                     }
268                     else if (this.indices[ownIndex] < other.indices[otherIndex])
269                     {
270                         // we have a non-zero; right has a zero
271                         value = doubleFunction.apply(this.matrixSI[ownIndex], 0d);
272                         index = this.indices[ownIndex];
273                         ownIndex++;
274                     }
275                     else
276                     {
277                         // we have a zero; right has a non-zero
278                         value = doubleFunction.apply(0d, other.matrixSI[otherIndex]);
279                         index = other.indices[otherIndex];
280                         otherIndex++;
281                     }
282                 }
283                 else if (ownIndex < this.indices.length)
284                 { // right has run out of values; we have not
285                     value = doubleFunction.apply(this.matrixSI[ownIndex], 0d);
286                     index = this.indices[ownIndex];
287                     ownIndex++;
288                 }
289                 else
290                 { // we have run out of values; right has not
291                     value = doubleFunction.apply(0d, other.matrixSI[otherIndex]);
292                     index = other.indices[otherIndex];
293                     otherIndex++;
294                 }
295                 if (value != 0d)
296                 {
297                     if (nonZeroValues >= currentSize)
298                     {
299                         // increase size of arrays
300                         currentSize *= 2;
301                         if (currentSize > rows() * cols())
302                         {
303                             currentSize = rows() * cols();
304                         }
305                         long[] newNewIndices = new long[currentSize];
306                         System.arraycopy(newIndices, 0, newNewIndices, 0, newIndices.length);
307                         newIndices = newNewIndices;
308                         double[] newNewValues = new double[currentSize];
309                         System.arraycopy(newValues, 0, newNewValues, 0, newValues.length);
310                         newValues = newNewValues;
311                     }
312                     newIndices[nonZeroValues] = index;
313                     newValues[nonZeroValues] = value;
314                     nonZeroValues++;
315                 }
316             }
317         }
318         else
319         { // we are sparse; other is dense, result must be sparse
320             DoubleMatrixDataDense other = (DoubleMatrixDataDense) right;
321             while (otherIndex < right.matrixSI.length)
322             {
323                 double value;
324                 int index = otherIndex;
325                 if (ownIndex < this.indices.length)
326                 { // neither we nor right has run out of values
327                     if (this.indices[ownIndex] == otherIndex)
328                     {
329                         value = doubleFunction.apply(this.matrixSI[ownIndex], other.matrixSI[otherIndex]);
330                         ownIndex++;
331                     }
332                     else
333                     {
334                         // we have a zero; other has a value
335                         value = doubleFunction.apply(0d, other.matrixSI[otherIndex]);
336                     }
337                     otherIndex++;
338                 }
339                 else
340                 { // we have run out of values; right has not
341                     value = doubleFunction.apply(0d, other.matrixSI[otherIndex]);
342                     otherIndex++;
343                 }
344                 if (value != 0d)
345                 {
346                     if (nonZeroValues >= currentSize)
347                     {
348                         // increase size of arrays
349                         currentSize *= 2;
350                         if (currentSize > rows() * cols())
351                         {
352                             currentSize = rows() * cols();
353                         }
354                         long[] newNewIndices = new long[currentSize];
355                         System.arraycopy(newIndices, 0, newNewIndices, 0, newIndices.length);
356                         newIndices = newNewIndices;
357                         double[] newNewValues = new double[currentSize];
358                         System.arraycopy(newValues, 0, newNewValues, 0, newValues.length);
359                         newValues = newNewValues;
360                     }
361                     newIndices[nonZeroValues] = index;
362                     newValues[nonZeroValues] = value;
363                     nonZeroValues++;
364                 }
365             }
366         }
367         if (nonZeroValues < currentSize)
368         {
369             // reduce size of arrays
370             long[] newNewIndices = new long[nonZeroValues];
371             System.arraycopy(newIndices, 0, newNewIndices, 0, nonZeroValues);
372             newIndices = newNewIndices;
373             double[] newNewValues = new double[nonZeroValues];
374             System.arraycopy(newValues, 0, newNewValues, 0, nonZeroValues);
375             newValues = newNewValues;
376         }
377         this.indices = newIndices;
378         this.matrixSI = newValues;
379         return this;
380     }
381 
382     /** {@inheritDoc} */
383     @Override
384     public final DoubleMatrixDataDense toDense()
385     {
386         double[] denseSI = new double[this.rows * this.cols];
387         for (int index = 0; index < this.matrixSI.length; index++)
388         {
389             denseSI[(int) this.indices[index]] = this.matrixSI[index];
390         }
391         try
392         {
393             return new DoubleMatrixDataDense(denseSI, this.rows, this.cols);
394         }
395         catch (ValueRuntimeException exception)
396         {
397             throw new RuntimeException(exception); // cannot happen -- denseSI has the right size
398         }
399     }
400 
401     /** {@inheritDoc} */
402     @Override
403     public final DoubleMatrixDataSparse toSparse()
404     {
405         return this;
406     }
407 
408     /** {@inheritDoc} */
409     @Override
410     public final double getSI(final int row, final int col)
411     {
412         long index = row * this.cols + col;
413         int internalIndex = Arrays.binarySearch(this.indices, index);
414         return internalIndex < 0 ? 0.0 : this.matrixSI[internalIndex];
415     }
416 
417     /** {@inheritDoc} */
418     @Override
419     public final void setSI(final int row, final int col, final double valueSI)
420     {
421         long index = row * this.cols + col;
422         int internalIndex = Arrays.binarySearch(this.indices, index);
423         if (internalIndex >= 0)
424         {
425             this.matrixSI[internalIndex] = valueSI;
426             return;
427         }
428 
429         // make room. TODO increase size in chunks
430         internalIndex = -internalIndex - 1;
431         long[] indicesNew = new long[this.indices.length + 1];
432         double[] matrixSINew = new double[this.matrixSI.length + 1];
433         System.arraycopy(this.indices, 0, indicesNew, 0, internalIndex);
434         System.arraycopy(this.matrixSI, 0, matrixSINew, 0, internalIndex);
435         System.arraycopy(this.indices, internalIndex, indicesNew, internalIndex + 1, this.indices.length - internalIndex);
436         System.arraycopy(this.matrixSI, internalIndex, matrixSINew, internalIndex + 1, this.indices.length - internalIndex);
437         indicesNew[internalIndex] = index;
438         matrixSINew[internalIndex] = valueSI;
439         this.indices = indicesNew;
440         this.matrixSI = matrixSINew;
441     }
442 
443     /** {@inheritDoc} */
444     @Override
445     public final double[][] getDenseMatrixSI()
446     {
447         return toDense().getDenseMatrixSI();
448     }
449 
450     /** {@inheritDoc} */
451     @Override
452     public final DoubleMatrixDataSparse copy()
453     {
454         double[] vCopy = new double[this.matrixSI.length];
455         System.arraycopy(this.matrixSI, 0, vCopy, 0, this.matrixSI.length);
456         long[] iCopy = new long[this.indices.length];
457         System.arraycopy(this.indices, 0, iCopy, 0, this.indices.length);
458         return new DoubleMatrixDataSparse(vCopy, iCopy, this.rows, this.cols);
459     }
460 
461     /**
462      * Instantiate a DoubleMatrixDataSparse from an array.
463      * @param valuesSI double[][]; the (SI) values to store
464      * @return the DoubleMatrixDataSparse
465      * @throws ValueRuntimeException in case matrix is ragged
466      */
467     public static DoubleMatrixDataSparse instantiate(final double[][] valuesSI) throws ValueRuntimeException
468     {
469         checkRectangularAndNonNull(valuesSI);
470         int length = nonZero(valuesSI);
471         int rows = valuesSI.length;
472         final int cols = rows == 0 ? 0 : valuesSI[0].length;
473         if (cols == 0)
474         {
475             rows = 0;
476         }
477         double[] sparseSI = new double[length];
478         long[] indices = new long[length];
479         fill(valuesSI, sparseSI, indices);
480         return new DoubleMatrixDataSparse(sparseSI, indices, rows, cols);
481     }
482 
483     /**
484      * Instantiate a DoubleMatrixDataSparse from an array.
485      * @param values double[][]; the values to store
486      * @param scale Scale; the scale that will convert values to SI
487      * @return the DoubleMatrixDataSparse
488      * @throws ValueRuntimeException in case matrix is ragged
489      */
490     public static DoubleMatrixDataSparse instantiate(final double[][] values, final Scale scale) throws ValueRuntimeException
491     {
492         checkRectangularAndNonNull(values);
493         int length = nonZero(values);
494         int rows = values.length;
495         final int cols = rows == 0 ? 0 : values[0].length;
496         if (cols == 0)
497         {
498             rows = 0;
499         }
500         double[] sparseSI = new double[length];
501         long[] indices = new long[length];
502         fill(values, sparseSI, indices, scale);
503         return new DoubleMatrixDataSparse(sparseSI, indices, rows, cols);
504     }
505 
506     /**
507      * Calculate the number of non-zero values in a double[][] matrix.
508      * @param valuesSI double[][]; the double[][] matrix
509      * @return the number of non-zero values in the double[][] matrix
510      */
511     private static int nonZero(final double[][] valuesSI)
512     {
513         // determine number of non-null cells
514         AtomicInteger atomicLength = new AtomicInteger(0);
515         IntStream.range(0, valuesSI.length).parallel().forEach(r -> IntStream.range(0, valuesSI[0].length).forEach(c ->
516         {
517             if (valuesSI[r][c] != 0.0)
518             {
519                 atomicLength.incrementAndGet();
520             }
521         }));
522 
523         return atomicLength.get();
524     }
525 
526     /** {@inheritDoc} */
527     @Override
528     public DoubleMatrixData plus(final DoubleMatrixData right) throws ValueRuntimeException
529     {
530         if (right.isDense())
531         {
532             return right.copy().incrementBy(this);
533         }
534         return this.copy().incrementBy(right);
535     }
536 
537     /** {@inheritDoc} */
538     @Override
539     public final DoubleMatrixData minus(final DoubleMatrixData right)
540     {
541         if (right.isDense())
542         {
543             return this.toDense().decrementBy(right);
544         }
545         return this.copy().decrementBy(right);
546     }
547 
548     /** {@inheritDoc} */
549     @Override
550     public DoubleMatrixDataSparse times(final DoubleMatrixData right) throws ValueRuntimeException
551     {
552         checkSizes(right);
553         DoubleMatrixDataSparse result = this.copy();
554         result.multiplyBy(right);
555         return result;
556     }
557 
558     /** {@inheritDoc} */
559     @Override
560     public DoubleMatrixData divide(final DoubleMatrixData right) throws ValueRuntimeException
561     {
562         if (right.isSparse())
563         {
564             // Sparse divided by sparse makes a dense
565             return this.toDense().divide(right);
566         }
567         // Sparse divided by dense makes a sparse
568         checkSizes(right);
569         return this.copy().divideBy(right);
570     }
571 
572     /** {@inheritDoc} */
573     @Override
574     public int hashCode()
575     {
576         return super.hashCode();
577     }
578 
579     /** {@inheritDoc} */
580     @Override
581     @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
582     public boolean equals(final Object obj)
583     {
584         if (this == obj)
585             return true;
586         if (obj == null)
587             return false;
588         if (!(obj instanceof DoubleMatrixData))
589             return false;
590         DoubleMatrixData other = (DoubleMatrixData) obj;
591         if (this.rows != other.rows)
592             return false;
593         if (this.cols != other.cols)
594             return false;
595         if (other instanceof DoubleMatrixDataDense)
596             return super.equals(other);
597         if (getClass() != obj.getClass())
598             return false;
599         // Both are sparse
600         if (!Arrays.equals(this.indices, ((DoubleMatrixDataSparse) other).indices))
601             return false;
602         return Arrays.equals(this.matrixSI, ((DoubleMatrixDataSparse) other).matrixSI);
603     }
604 
605     /** {@inheritDoc} */
606     @Override
607     public String toString()
608     {
609         return "DoubleMatrixDataSparse [storageType=" + getStorageType() + ", indices=" + Arrays.toString(this.indices)
610                 + ", matrixSI=" + Arrays.toString(this.matrixSI) + "]";
611     }
612 
613 }