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.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.vfloat.function.FloatFunction;
13  import org.djunits.value.vfloat.function.FloatFunction2;
14  import org.djunits.value.vfloat.matrix.base.FloatSparseValue;
15  import org.djunits.value.vfloat.scalar.base.FloatScalarInterface;
16  import org.djutils.exceptions.Throw;
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-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 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 = 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             float[] 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 float[][]; the input data
130      * @param matrixSI float[]; 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 float[][] data, float[] 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             float[] 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                 float value = (float) 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 FloatMatrixData assign(final FloatFunction floatFunction)
167     {
168         if (floatFunction.apply(0f) != 0f)
169         {
170             // It is most unlikely that the result AND the left and right operands are efficiently stored in Sparse format
171             FloatMatrixDataSparse result = toDense().assign(floatFunction).toSparse();
172             this.indices = result.indices;
173             this.matrixSI = result.matrixSI;
174             return this;
175         }
176         // The code below relies on the fact that floatFunction.apply(0f) yields 0f
177         int currentSize = rows() * cols();
178         if (currentSize > 16)
179         {
180             currentSize = 16;
181         }
182         long[] newIndices = new long[currentSize];
183         float[] newValues = new float[currentSize];
184         int nonZeroValues = 0;
185         int ownIndex = 0;
186         while (ownIndex < this.indices.length)
187         {
188             long index = this.indices[ownIndex];
189             float value = floatFunction.apply(this.matrixSI[ownIndex]);
190             ownIndex++;
191             if (value != 0f)
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                     float[] newNewValues = new float[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             float[] newNewValues = new float[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 FloatMatrixDataSparse assign(final FloatFunction2 floatFunction, final FloatMatrixData right)
231     {
232         checkSizes(right);
233         int currentSize = rows() * cols();
234         if (currentSize > 16)
235         {
236             currentSize = 16;
237         }
238         long[] newIndices = new long[currentSize];
239         float[] newValues = new float[currentSize];
240         int nonZeroValues = 0;
241         int ownIndex = 0;
242         int otherIndex = 0;
243         if (right.isSparse())
244         { // both are sparse, result must be sparse
245             FloatMatrixDataSparse other = (FloatMatrixDataSparse) right;
246             while (ownIndex < this.indices.length || otherIndex < other.indices.length)
247             {
248                 float value;
249                 long index;
250                 if (ownIndex < this.indices.length && otherIndex < other.indices.length)
251                 { // neither we nor right has run out of values
252                     if (this.indices[ownIndex] == other.indices[otherIndex])
253                     {
254                         value = floatFunction.apply(this.matrixSI[ownIndex], other.matrixSI[otherIndex]);
255                         index = this.indices[ownIndex];
256                         ownIndex++;
257                         otherIndex++;
258                     }
259                     else if (this.indices[ownIndex] < other.indices[otherIndex])
260                     {
261                         // we have a non-zero; right has a zero
262                         value = floatFunction.apply(this.matrixSI[ownIndex], 0.0f);
263                         index = this.indices[ownIndex];
264                         ownIndex++;
265                     }
266                     else
267                     {
268                         // we have a zero; right has a non-zero
269                         value = floatFunction.apply(0.0f, other.matrixSI[otherIndex]);
270                         index = other.indices[otherIndex];
271                         otherIndex++;
272                     }
273                 }
274                 else if (ownIndex < this.indices.length)
275                 { // right has run out of values; we have not
276                     value = floatFunction.apply(this.matrixSI[ownIndex], 0f);
277                     index = this.indices[ownIndex];
278                     ownIndex++;
279                 }
280                 else
281                 { // we have run out of values; right has not
282                     value = floatFunction.apply(0.0f, other.matrixSI[otherIndex]);
283                     index = other.indices[otherIndex];
284                     otherIndex++;
285                 }
286                 if (value != 0f)
287                 {
288                     if (nonZeroValues >= currentSize)
289                     {
290                         // increase size of arrays
291                         currentSize *= 2;
292                         if (currentSize > rows() * cols())
293                         {
294                             currentSize = rows() * cols();
295                         }
296                         long[] newNewIndices = new long[currentSize];
297                         System.arraycopy(newIndices, 0, newNewIndices, 0, newIndices.length);
298                         newIndices = newNewIndices;
299                         float[] newNewValues = new float[currentSize];
300                         System.arraycopy(newValues, 0, newNewValues, 0, newValues.length);
301                         newValues = newNewValues;
302                     }
303                     newIndices[nonZeroValues] = index;
304                     newValues[nonZeroValues] = value;
305                     nonZeroValues++;
306                 }
307             }
308         }
309         else
310         { // we are sparse; other is dense, result must be sparse
311             FloatMatrixDataDense other = (FloatMatrixDataDense) right;
312             while (otherIndex < right.matrixSI.length)
313             {
314                 float value;
315                 int index = otherIndex;
316                 if (ownIndex < this.indices.length)
317                 { // neither we nor right has run out of values
318                     if (this.indices[ownIndex] == otherIndex)
319                     {
320                         value = floatFunction.apply(this.matrixSI[ownIndex], other.matrixSI[otherIndex]);
321                         ownIndex++;
322                     }
323                     else
324                     {
325                         // we have a zero; other has a value
326                         value = floatFunction.apply(0.0f, other.matrixSI[otherIndex]);
327                     }
328                     otherIndex++;
329                 }
330                 else
331                 { // we have run out of values; right has not
332                     value = floatFunction.apply(0.0f, other.matrixSI[otherIndex]);
333                     otherIndex++;
334                 }
335                 if (value != 0f)
336                 {
337                     if (nonZeroValues >= currentSize)
338                     {
339                         // increase size of arrays
340                         currentSize *= 2;
341                         if (currentSize > rows() * cols())
342                         {
343                             currentSize = rows() * cols();
344                         }
345                         long[] newNewIndices = new long[currentSize];
346                         System.arraycopy(newIndices, 0, newNewIndices, 0, newIndices.length);
347                         newIndices = newNewIndices;
348                         float[] newNewValues = new float[currentSize];
349                         System.arraycopy(newValues, 0, newNewValues, 0, newValues.length);
350                         newValues = newNewValues;
351                     }
352                     newIndices[nonZeroValues] = index;
353                     newValues[nonZeroValues] = value;
354                     nonZeroValues++;
355                 }
356             }
357         }
358         if (nonZeroValues < currentSize)
359         {
360             // reduce size of arrays
361             long[] newNewIndices = new long[nonZeroValues];
362             System.arraycopy(newIndices, 0, newNewIndices, 0, nonZeroValues);
363             newIndices = newNewIndices;
364             float[] newNewValues = new float[nonZeroValues];
365             System.arraycopy(newValues, 0, newNewValues, 0, nonZeroValues);
366             newValues = newNewValues;
367         }
368         this.indices = newIndices;
369         this.matrixSI = newValues;
370         return this;
371     }
372 
373     /** {@inheritDoc} */
374     @Override
375     public final FloatMatrixDataDense toDense()
376     {
377         float[] denseSI = new float[this.rows * this.cols];
378         for (int index = 0; index < this.matrixSI.length; index++)
379         {
380             denseSI[(int) this.indices[index]] = this.matrixSI[index];
381         }
382         try
383         {
384             return new FloatMatrixDataDense(denseSI, this.rows, this.cols);
385         }
386         catch (ValueRuntimeException exception)
387         {
388             throw new RuntimeException(exception); // cannot happen -- denseSI has the right size
389         }
390     }
391 
392     /** {@inheritDoc} */
393     @Override
394     public final FloatMatrixDataSparse toSparse()
395     {
396         return this;
397     }
398 
399     /** {@inheritDoc} */
400     @Override
401     public final float getSI(final int row, final int col)
402     {
403         long index = row * this.cols + col;
404         int internalIndex = Arrays.binarySearch(this.indices, index);
405         return internalIndex < 0 ? 0.0f : this.matrixSI[internalIndex];
406     }
407 
408     /** {@inheritDoc} */
409     @Override
410     public final void setSI(final int row, final int col, final float valueSI)
411     {
412         long index = row * this.cols + col;
413         int internalIndex = Arrays.binarySearch(this.indices, index);
414         if (internalIndex >= 0)
415         {
416             this.matrixSI[internalIndex] = valueSI;
417             return;
418         }
419 
420         // make room. TODO increase size in chunks
421         internalIndex = -internalIndex - 1;
422         long[] indicesNew = new long[this.indices.length + 1];
423         float[] matrixSINew = new float[this.matrixSI.length + 1];
424         System.arraycopy(this.indices, 0, indicesNew, 0, internalIndex);
425         System.arraycopy(this.matrixSI, 0, matrixSINew, 0, internalIndex);
426         System.arraycopy(this.indices, internalIndex, indicesNew, internalIndex + 1, this.indices.length - internalIndex);
427         System.arraycopy(this.matrixSI, internalIndex, matrixSINew, internalIndex + 1, this.indices.length - internalIndex);
428         indicesNew[internalIndex] = index;
429         matrixSINew[internalIndex] = valueSI;
430         this.indices = indicesNew;
431         this.matrixSI = matrixSINew;
432     }
433 
434     /** {@inheritDoc} */
435     @Override
436     public final float[][] getDenseMatrixSI()
437     {
438         return toDense().getDenseMatrixSI();
439     }
440 
441     /** {@inheritDoc} */
442     @Override
443     public final double[][] getDoubleDenseMatrixSI()
444     {
445         return toDense().getDoubleDenseMatrixSI();
446     }
447 
448     /** {@inheritDoc} */
449     @Override
450     public final FloatMatrixDataSparse copy()
451     {
452         float[] vCopy = new float[this.matrixSI.length];
453         System.arraycopy(this.matrixSI, 0, vCopy, 0, this.matrixSI.length);
454         long[] iCopy = new long[this.indices.length];
455         System.arraycopy(this.indices, 0, iCopy, 0, this.indices.length);
456         return new FloatMatrixDataSparse(vCopy, iCopy, this.rows, this.cols);
457     }
458 
459     /**
460      * Instantiate a FloatMatrixDataSparse from an array.
461      * @param valuesSI float[][]; the (SI) values to store
462      * @return the FloatMatrixDataSparse
463      * @throws ValueRuntimeException in case matrix is ragged
464      */
465     public static FloatMatrixDataSparse instantiate(final float[][] valuesSI) throws ValueRuntimeException
466     {
467         checkRectangularAndNonNull(valuesSI);
468         int length = nonZero(valuesSI);
469         int rows = valuesSI.length;
470         final int cols = rows == 0 ? 0 : valuesSI[0].length;
471         if (cols == 0)
472         {
473             rows = 0;
474         }
475         float[] sparseSI = new float[length];
476         long[] indices = new long[length];
477         fill(valuesSI, sparseSI, indices);
478         return new FloatMatrixDataSparse(sparseSI, indices, rows, cols);
479     }
480 
481     /**
482      * Instantiate a FloatMatrixDataSparse from an array.
483      * @param values float[][]; the values to store
484      * @param scale Scale; the scale that will convert values to SI
485      * @return the DoubleMatrixDataSparse
486      * @throws ValueRuntimeException in case matrix is ragged
487      */
488     public static FloatMatrixDataSparse instantiate(final float[][] values, final Scale scale) throws ValueRuntimeException
489     {
490         checkRectangularAndNonNull(values);
491         int length = nonZero(values);
492         int rows = values.length;
493         final int cols = rows == 0 ? 0 : values[0].length;
494         if (cols == 0)
495         {
496             rows = 0;
497         }
498         float[] sparseSI = new float[length];
499         long[] indices = new long[length];
500         fill(values, sparseSI, indices, scale);
501         return new FloatMatrixDataSparse(sparseSI, indices, rows, cols);
502     }
503 
504     /**
505      * Calculate the number of non-zero values in a float[][] matrix.
506      * @param valuesSI float[][]; the float[][] matrix
507      * @return the number of non-zero values in the float[][] matrix
508      */
509     private static int nonZero(final float[][] valuesSI)
510     {
511         // determine number of non-null cells
512         AtomicInteger atomicLength = new AtomicInteger(0);
513         IntStream.range(0, valuesSI.length).parallel().forEach(r -> IntStream.range(0, valuesSI[0].length).forEach(c ->
514         {
515             if (valuesSI[r][c] != 0.0f)
516             {
517                 atomicLength.incrementAndGet();
518             }
519         }));
520 
521         return atomicLength.get();
522     }
523 
524     /** {@inheritDoc} */
525     @Override
526     public FloatMatrixData plus(final FloatMatrixData right) throws ValueRuntimeException
527     {
528         if (right.isDense())
529         {
530             return right.copy().incrementBy(this);
531         }
532         return this.copy().incrementBy(right);
533     }
534 
535     /** {@inheritDoc} */
536     @Override
537     public final FloatMatrixData minus(final FloatMatrixData right)
538     {
539         if (right.isDense())
540         {
541             return this.toDense().decrementBy(right);
542         }
543         return this.copy().decrementBy(right);
544     }
545 
546     /** {@inheritDoc} */
547     @Override
548     public FloatMatrixData times(final FloatMatrixData right) throws ValueRuntimeException
549     {
550         return this.copy().multiplyBy(right);
551     }
552 
553     /** {@inheritDoc} */
554     @Override
555     public FloatMatrixData divide(final FloatMatrixData right) throws ValueRuntimeException
556     {
557         if (right.isSparse())
558         {
559             // Sparse divided by sparse makes a dense
560             return this.toDense().divide(right);
561         }
562         // Sparse divided by dense makes a sparse
563         return this.copy().divideBy(right);
564     }
565 
566     /** {@inheritDoc} */
567     @Override
568     public int hashCode()
569     {
570         return super.hashCode();
571     }
572 
573     /** {@inheritDoc} */
574     @Override
575     @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
576     public boolean equals(final Object obj)
577     {
578         if (this == obj)
579             return true;
580         if (obj == null)
581             return false;
582         if (!(obj instanceof FloatMatrixData))
583             return false;
584         FloatMatrixData other = (FloatMatrixData) obj;
585         if (this.rows != other.rows)
586             return false;
587         if (this.cols != other.cols)
588             return false;
589         if (other instanceof FloatMatrixDataDense)
590             return super.equals(other);
591         if (getClass() != obj.getClass())
592             return false;
593         // Both are sparse
594         if (!Arrays.equals(this.indices, ((FloatMatrixDataSparse) other).indices))
595             return false;
596         return Arrays.equals(this.matrixSI, ((FloatMatrixDataSparse) other).matrixSI);
597     }
598 
599 }