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