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