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