View Javadoc
1   package org.djunits.vecmat.storage;
2   
3   import java.util.Arrays;
4   import java.util.List;
5   import java.util.Objects;
6   
7   import org.djunits.quantity.def.AbsQuantity;
8   import org.djunits.quantity.def.Quantity;
9   import org.djunits.quantity.def.Reference;
10  import org.djunits.unit.UnitInterface;
11  import org.djunits.util.SuppressFBWarnings;
12  import org.djutils.exceptions.Throw;
13  
14  /**
15   * DenseDoubleData implements a dense data grid for N x M matrices or N x 1 or 1 x M vectors with double values. DenseDoubleData
16   * always stores a safe copy of the data when using the of() or ofSi() methods.
17   * <p>
18   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
19   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
20   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
21   * @author Alexander Verbraeck
22   */
23  public class DenseDoubleDataSi implements DataGridSi<DenseDoubleDataSi>
24  {
25      /** */
26      private static final long serialVersionUID = 601L;
27  
28      /** The data stored in row-major format. */
29      private final double[] dataSi;
30  
31      /** The number of rows. */
32      private final int rows;
33  
34      /** the number of columns. */
35      private final int cols;
36  
37      /**
38       * Instantiate a data object with one array in row-major format. NO safe copy of the data is stored. The constructor is very
39       * useful to store data after a calculation that already made a new safe copy.
40       * @param dataSi the data with SI-values in row-major format
41       * @param rows the number of rows
42       * @param cols the number of columns
43       * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
44       *             or columns is not positive
45       */
46      @SuppressFBWarnings(justification = "explicitly documented that no safe copy is made")
47      public DenseDoubleDataSi(final double[] dataSi, final int rows, final int cols)
48      {
49          Throw.whenNull(dataSi, "dataSi");
50          Throw.when(rows <= 0, IllegalArgumentException.class, "Number of rows <= 0");
51          Throw.when(cols <= 0, IllegalArgumentException.class, "Number of columns <= 0");
52          Throw.when(dataSi.length != rows * cols, IllegalArgumentException.class,
53                  "Data object length != rows * cols, %d != %d * %d", dataSi.length, rows, cols);
54          this.dataSi = dataSi;
55          this.rows = rows;
56          this.cols = cols;
57      }
58  
59      /**
60       * Instantiate a data object with one array in row-major format. A safe copy of the data is stored.
61       * @param dataSi the data with SI-values in row-major format
62       * @param rows the number of rows
63       * @param cols the number of columns
64       * @return a dense double data object with SI values for vectors, matrices and tables
65       * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
66       *             or columns is not positive
67       */
68      public static DenseDoubleDataSi ofSi(final double[] dataSi, final int rows, final int cols)
69      {
70          return new DenseDoubleDataSi(dataSi.clone(), rows, cols);
71      }
72  
73      /**
74       * Instantiate a data object based on a row-major double[] array in a given unit. A safe copy of the data is stored.
75       * @param dataInUnit the data in row-major format, expressed in the given unit
76       * @param rows the number of rows
77       * @param cols the number of columns
78       * @param unit the unit of the data
79       * @return a dense double data object with SI values for vectors, matrices and tables
80       * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
81       *             or columns is not positive
82       * @param <Q> the quantity type
83       */
84      public static <Q extends Quantity<Q>> DenseDoubleDataSi of(final double[] dataInUnit, final int rows, final int cols,
85              final UnitInterface<Q> unit)
86      {
87          Throw.whenNull(dataInUnit, "dataInUnit");
88          Throw.whenNull(unit, "unit");
89          double[] dataSi = new double[dataInUnit.length];
90          for (int i = 0; i < dataInUnit.length; i++)
91          {
92              dataSi[i] = unit.toBaseValue(dataInUnit[i]);
93          }
94          return new DenseDoubleDataSi(dataSi, rows, cols);
95      }
96  
97      /**
98       * Instantiate a data object based on a row-major Q[] array. A safe copy of the data is stored.
99       * @param data the quantity data in row-major format
100      * @param rows the number of rows
101      * @param cols the number of columns
102      * @return a dense double data object with SI values for vectors, matrices and tables
103      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
104      *             or columns is not positive
105      * @param <Q> the quantity type
106      */
107     public static <Q extends Quantity<Q>> DenseDoubleDataSi of(final Q[] data, final int rows, final int cols)
108     {
109         Throw.whenNull(data, "data");
110         Throw.when(data.length != rows * cols, IllegalArgumentException.class, "Q[] length != rows * cols");
111         double[] dataSi = new double[data.length];
112         for (int i = 0; i < data.length; i++)
113         {
114             Throw.whenNull(data[i], "data[%d] = null", i);
115             dataSi[i] = data[i].si();
116         }
117         return new DenseDoubleDataSi(dataSi, rows, cols);
118     }
119 
120     /**
121      * Instantiate a data object based on a row-major A[] array. A safe copy of the data is stored.
122      * @param absData the absolute quantities as a array in row-major format
123      * @param rows the number of rows
124      * @param cols the number of columns
125      * @return a dense double data object with SI values for vectors, matrices and tables
126      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
127      *             or columns is not positive
128      * @param <A> the absolute quantity type
129      * @param <Q> the quantity type
130      */
131     public static <A extends AbsQuantity<A, Q, ?>, Q extends Quantity<Q>> DenseDoubleDataSi of(final A[] absData,
132             final int rows, final int cols)
133     {
134         Throw.whenNull(absData, "absData");
135         Throw.when(rows == 0, IllegalArgumentException.class, "rows = 0");
136         Throw.when(cols == 0, IllegalArgumentException.class, "cols = 0");
137         Throw.when(absData.length != rows * cols, IllegalArgumentException.class, "A[] length != rows * cols");
138         Throw.whenNull(absData[0], "absData[0] = null");
139         Reference<?, A, Q> reference = absData[0].getReference();
140         double[] dataSi = new double[rows * cols];
141         for (int i = 0; i < rows * cols; i++)
142         {
143             Throw.whenNull(absData[i], "absGrid[%d] = null", i);
144             Throw.when(!reference.equals(absData[i].getReference()), IllegalArgumentException.class,
145                     "Reference of absData[%d] != %s, but %s", i, reference.toString(), absData[i].getReference().toString());
146             dataSi[i] = absData[i].si();
147         }
148         return new DenseDoubleDataSi(dataSi, rows, cols);
149     }
150 
151     /**
152      * Instantiate a data object based on a row-major list of absolute quantities. A safe copy of the data is stored.
153      * @param absData the absolute quantities as a list in row-major format
154      * @param rows the number of rows
155      * @param cols the number of columns
156      * @return a dense double data object with SI values for vectors, matrices and tables
157      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols, or when the number of rows
158      *             or columns is not positive
159      * @param <A> the absolute quantity type
160      * @param <Q> the quantity type
161      */
162     public static <A extends AbsQuantity<A, Q, ?>, Q extends Quantity<Q>> DenseDoubleDataSi of(final List<A> absData,
163             final int rows, final int cols)
164     {
165         Throw.whenNull(absData, "absData");
166         Throw.when(rows == 0, IllegalArgumentException.class, "rows = 0");
167         Throw.when(cols == 0, IllegalArgumentException.class, "cols = 0");
168         Throw.when(absData.size() != rows * cols, IllegalArgumentException.class, "List size != rows * cols");
169         Throw.whenNull(absData.get(0), "absData[0] = null");
170         Reference<?, A, Q> reference = absData.get(0).getReference();
171         double[] dataSi = new double[rows * cols];
172         for (int i = 0; i < rows * cols; i++)
173         {
174             Throw.whenNull(absData.get(i), "absData[%d] = null", i);
175             Throw.when(!reference.equals(absData.get(i).getReference()), IllegalArgumentException.class,
176                     "Reference of absGrid[%d] != %s, but %s", i, reference.toString(),
177                     absData.get(i).getReference().toString());
178             dataSi[i] = absData.get(i).si();
179         }
180         return new DenseDoubleDataSi(dataSi, rows, cols);
181     }
182 
183     /**
184      * Instantiate a data object with a double[rows][cols]. A safe copy of the data is stored.
185      * @param gridSi the data as a double[][] array in row-major format, with SI-values
186      * @return a dense double data object with SI values for vectors, matrices and tables
187      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols
188      */
189     public static DenseDoubleDataSi ofSi(final double[][] gridSi)
190     {
191         Throw.whenNull(gridSi, "gridSi");
192         Throw.when(gridSi.length == 0, IllegalArgumentException.class, "Number of rows in the data grid = 0");
193         int rows = gridSi.length;
194         Throw.whenNull(gridSi[0], "gridSi[0] = null");
195         int cols = gridSi[0].length;
196         double[] dataSi = new double[rows * cols];
197         for (int r = 0; r < rows; r++)
198         {
199             Throw.whenNull(gridSi[r], "gridSi[%d] = null", r);
200             Throw.when(gridSi[r].length != cols, IllegalArgumentException.class,
201                     "Number of columns in row %d (%d) is not equal to number of columns in row 0 (%d)", r, gridSi[r].length,
202                     cols);
203             for (int c = 0; c < cols; c++)
204             {
205                 dataSi[r * cols + c] = gridSi[r][c];
206             }
207         }
208         return new DenseDoubleDataSi(dataSi, rows, cols);
209     }
210 
211     /**
212      * Instantiate a data object based on a row x column double[][] array in a given unit. A safe copy of the data is stored.
213      * @param gridInUnit the data as a double[][] array in row-major format, expressed in the given unit
214      * @param unit the unit of the data
215      * @return a dense double data object with SI values for vectors, matrices and tables
216      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols
217      * @param <Q> the quantity type
218      */
219     public static <Q extends Quantity<Q>> DenseDoubleDataSi of(final double[][] gridInUnit, final UnitInterface<Q> unit)
220     {
221         Throw.whenNull(gridInUnit, "gridInUnit");
222         Throw.whenNull(unit, "unit");
223         Throw.when(gridInUnit.length == 0, IllegalArgumentException.class, "Number of rows in the data grid = 0");
224         int rows = gridInUnit.length;
225         Throw.whenNull(gridInUnit[0], "gridInUnit[0] = null");
226         int cols = gridInUnit[0].length;
227         double[] dataSi = new double[rows * cols];
228         for (int r = 0; r < rows; r++)
229         {
230             Throw.whenNull(gridInUnit[r], "gridInUnit[%d] = null", r);
231             Throw.when(gridInUnit[r].length != cols, IllegalArgumentException.class,
232                     "Number of columns in row %d (%d) is not equal to number of columns in row 0 (%d)", r, gridInUnit[r].length,
233                     cols);
234             for (int c = 0; c < cols; c++)
235             {
236                 dataSi[r * cols + c] = unit.toBaseValue(gridInUnit[r][c]);
237             }
238         }
239         return new DenseDoubleDataSi(dataSi, rows, cols);
240     }
241 
242     /**
243      * Instantiate a data object with a Q[rows][cols]. A safe copy of the data is stored.
244      * @param grid the quantities as a [][] array in row-major format
245      * @return a dense double data object with SI values for vectors, matrices and tables
246      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols
247      * @param <Q> the quantity type
248      */
249     public static <Q extends Quantity<Q>> DenseDoubleDataSi of(final Q[][] grid)
250     {
251         Throw.whenNull(grid, "grid");
252         Throw.when(grid.length == 0, IllegalArgumentException.class, "Number of rows in the data grid = 0");
253         int rows = grid.length;
254         Throw.whenNull(grid[0], "grid[0] = null");
255         int cols = grid[0].length;
256         double[] dataSi = new double[rows * cols];
257         for (int r = 0; r < rows; r++)
258         {
259             Throw.whenNull(grid[r], "grid[%d] = null", r);
260             Throw.when(grid[r].length != cols, IllegalArgumentException.class,
261                     "Number of columns in row %d (%d) is not equal to number of columns in row 0 (%d)", r, grid[r].length,
262                     cols);
263             for (int c = 0; c < cols; c++)
264             {
265                 Throw.whenNull(grid[r][c], "grid[%d][%d] = null", r, c);
266                 dataSi[r * cols + c] = grid[r][c].si();
267             }
268         }
269         return new DenseDoubleDataSi(dataSi, rows, cols);
270     }
271 
272     /**
273      * Instantiate a data object with a A[rows][cols]. A safe copy of the data is stored.
274      * @param absGrid the quantities as a [][] array in row-major format
275      * @return a dense double data object with SI values for vectors, matrices and tables
276      * @throws IllegalArgumentException when the size of the data object is not equal to rows*cols
277      * @param <A> the absolute quantity type
278      * @param <Q> the quantity type
279      */
280     public static <A extends AbsQuantity<A, Q, ?>, Q extends Quantity<Q>> DenseDoubleDataSi of(final A[][] absGrid)
281     {
282         Throw.whenNull(absGrid, "absGrid");
283         int rows = absGrid.length;
284         Throw.when(rows == 0, IllegalArgumentException.class, "rows = 0");
285         Throw.whenNull(absGrid[0], "absGrid[0] = null");
286         int cols = absGrid[0].length;
287         Throw.when(cols == 0, IllegalArgumentException.class, "cols = 0");
288         Throw.whenNull(absGrid[0][0], "absGrid[0][0] = null");
289         Reference<?, A, Q> reference = absGrid[0][0].getReference();
290         double[] dataSi = new double[rows * cols];
291         for (int r = 0; r < rows; r++)
292         {
293             Throw.whenNull(absGrid[r], "absGrid[%d] = null", r);
294             Throw.when(absGrid[r].length != cols, IllegalArgumentException.class,
295                     "Number of columns in row %d (%d) is not equal to number of columns in row 0 (%d)", r, absGrid[r].length,
296                     cols);
297             for (int c = 0; c < cols; c++)
298             {
299                 Throw.whenNull(absGrid[r][c], "absGrid[%d][%d] = null", r, c);
300                 Throw.when(!reference.equals(absGrid[r][c].getReference()), IllegalArgumentException.class,
301                         "Reference of absGrid[%d][%d] != %s, but %s", r, c, reference.toString(),
302                         absGrid[r][c].getReference().toString());
303                 dataSi[r * cols + c] = absGrid[r][c].si();
304             }
305         }
306         return new DenseDoubleDataSi(dataSi, rows, cols);
307     }
308 
309     @Override
310     public int rows()
311     {
312         return this.rows;
313     }
314 
315     @Override
316     public int cols()
317     {
318         return this.cols;
319     }
320 
321     @Override
322     public boolean isDense()
323     {
324         return true;
325     }
326 
327     @Override
328     public boolean isDouble()
329     {
330         return true;
331     }
332 
333     /**
334      * Check whether the row and column are within bounds.
335      * @param row the row number
336      * @param col the column number
337      * @throws IndexOutOfBoundsException when row &gt; rows() or col &gt; cols() or row &lt; 0 or col &lt; 0
338      */
339     private void checkRowCol(final int row, final int col) throws IndexOutOfBoundsException
340     {
341         Throw.when(row < 0 || row >= this.rows, IndexOutOfBoundsException.class, "row %d not in range 0..%d", row, this.rows);
342         Throw.when(col < 0 || col >= this.cols, IndexOutOfBoundsException.class, "column %d not in range 0..%d", col,
343                 this.cols);
344     }
345 
346     @Override
347     public double get(final int row, final int col)
348     {
349         checkRowCol(row, col);
350         return this.dataSi[row * this.cols + col];
351     }
352 
353     @Override
354     @SuppressFBWarnings(justification = "name of the method indicates unsafe access")
355     public double[] unsafeSiArray()
356     {
357         return this.dataSi;
358     }
359 
360     @Override
361     public double[] getSiArray()
362     {
363         return this.dataSi.clone();
364     }
365 
366     @Override
367     public DenseDoubleDataSi copy()
368     {
369         return new DenseDoubleDataSi(this.dataSi.clone(), rows(), cols());
370     }
371 
372     @SuppressWarnings("checkstyle:needbraces")
373     @Override
374     public int nonZeroCount()
375     {
376         int result = 0;
377         for (int i = 0; i < this.dataSi.length; i++)
378             result += this.dataSi[i] == 0.0 ? 0 : 1;
379         return result;
380     }
381 
382     @Override
383     public DenseDoubleDataSi instantiateNew(final double[] newData)
384     {
385         Throw.when(newData.length != rows() * cols(), IllegalArgumentException.class,
386                 "Data object length != rows * cols, %d != %d * %d", newData.length, rows(), cols());
387         return new DenseDoubleDataSi(newData, rows(), cols());
388     }
389 
390     @Override
391     public DenseDoubleDataSi instantiateNew(final double[] newData, final int newRows, final int newCols)
392     {
393         Throw.when(newData.length != newRows * newCols, IllegalArgumentException.class,
394                 "Data object length != rows * cols, %d != %d * %d", newData.length, newRows, newCols);
395         return new DenseDoubleDataSi(newData, newRows, newCols);
396     }
397 
398     @Override
399     public int hashCode()
400     {
401         final int prime = 31;
402         int result = 1;
403         result = prime * result + Arrays.hashCode(this.dataSi);
404         result = prime * result + Objects.hash(this.cols, this.rows);
405         return result;
406     }
407 
408     @SuppressWarnings("checkstyle:needbraces")
409     @Override
410     public boolean equals(final Object obj)
411     {
412         if (this == obj)
413             return true;
414         if (obj == null)
415             return false;
416         if (getClass() != obj.getClass())
417         {
418             if (obj instanceof DataGridSi dg)
419                 return this.cols == dg.cols() && this.rows == dg.rows() && Arrays.equals(this.dataSi, dg.unsafeSiArray());
420             return false;
421         }
422         DenseDoubleDataSi other = (DenseDoubleDataSi) obj;
423         return this.cols == other.cols && this.rows == other.rows && Arrays.equals(this.dataSi, other.dataSi);
424     }
425 
426 }