1 package org.djunits.vecmat.def;
2
3 import java.lang.reflect.Array;
4
5 import org.djunits.quantity.SIQuantity;
6 import org.djunits.quantity.def.Quantity;
7 import org.djunits.unit.Unit;
8 import org.djutils.exceptions.Throw;
9
10 /**
11 * Table contains a number of standard operations on 2-dimensional tables of relative quantities. The Matrix and QuantityTable
12 * are examples of classes that extends Table.
13 * <p>
14 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
15 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
16 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
17 * @author Alexander Verbraeck
18 * @param <Q> the quantity type
19 * @param <T> the 'SELF' table type
20 * @param <SI> the table type with generics <SIQuantity, SIUnit<
21 * @param <H> the generic table type with generics <?, ?< for Hadamard operations
22 * @param <TT> the type of the transposed version of the table
23 */
24 public abstract class Table<Q extends Quantity<Q>, T extends Table<Q, T, SI, H, TT>, SI extends Table<SIQuantity, SI, ?, ?, ?>,
25 H extends Table<?, ?, ?, ?, ?>, TT extends Table<Q, TT, ?, ?, T>> extends VectorMatrix<Q, T, SI, H, TT>
26 {
27 /** */
28 private static final long serialVersionUID = 600L;
29
30 /**
31 * Create a new Table with a unit.
32 * @param displayUnit the display unit to use
33 */
34 public Table(final Unit<?, Q> displayUnit)
35 {
36 super(displayUnit);
37 }
38
39 /**
40 * Return the si-value at position (row, col), where both row and col are 0-based values.
41 * @param row the row (0-based)
42 * @param col the column (0-based)
43 * @return the si-value at position (row, col)
44 * @throws IndexOutOfBoundsException when row or col < 0 or larger than number of rows/columns - 1.
45 */
46 public abstract double si(int row, int col) throws IndexOutOfBoundsException;
47
48 /**
49 * Return the si-value at position (row, col), where both row and col are 1-based values.
50 * @param mRow the row (1-based)
51 * @param mCol the column (1-based)
52 * @return the si-value at position (row, col)
53 * @throws IndexOutOfBoundsException when row or col < 1 or larger than number of rows/columns.
54 */
55 public double msi(final int mRow, final int mCol) throws IndexOutOfBoundsException
56 {
57 return si(mRow - 1, mCol - 1);
58 }
59
60 /**
61 * Return the quantity at position (row, col), where both row and col are 0-based values.
62 * @param row the row (0-based)
63 * @param col the column (0-based)
64 * @return the quantity at position (row, col)
65 * @throws IndexOutOfBoundsException when row or col < 0 or larger than number of rows/columns - 1.
66 */
67 public Q get(final int row, final int col) throws IndexOutOfBoundsException
68 {
69 return getDisplayUnit().ofSi(si(row, col)).setDisplayUnit(getDisplayUnit());
70 }
71
72 /**
73 * Return the quantity at position (row, col), where both row and col are 1-based values.
74 * @param mRow the row (1-based)
75 * @param mCol the column (1-based)
76 * @return the quantity at position (row, col)
77 * @throws IndexOutOfBoundsException when row or col < 1 or larger than number of rows/columns.
78 */
79 public Q mget(final int mRow, final int mCol) throws IndexOutOfBoundsException
80 {
81 return getDisplayUnit().ofSi(msi(mRow, mCol)).setDisplayUnit(getDisplayUnit());
82 }
83
84 /**
85 * Return a row-major array of quantities for this QuantityTable.
86 * @return a row-major array of quantities for this QuantityTable
87 */
88 @SuppressWarnings("unchecked")
89 public Q[] getScalarArray()
90 {
91 // Determine the runtime type of Q using the first cell; constructors guarantee rows, cols >= 0.
92 final Q first = get(0, 0);
93 final Class<?> qClass = first.getClass();
94
95 // Allocate a Q[rows() * cols()] array and fill it.
96 final Q[] out = (Q[]) Array.newInstance(qClass, rows() * cols());
97 double[] dataSi = unsafeSiArray();
98 var unit = getDisplayUnit();
99 for (int i = 0; i < rows() * cols(); i++)
100 {
101 out[i] = unit.ofSi(dataSi[i]).setDisplayUnit(unit);
102 }
103 return out;
104 }
105
106 /**
107 * Return the table or matrix as a 2D array of scalars.
108 * @return a new Q[rows()][cols()] array; entry [i][j] contains get(i, j).
109 */
110 @SuppressWarnings("unchecked") // cast from Array.newInstance(...) to Q[][]
111 public Q[][] getScalarGrid()
112 {
113 // Determine the runtime type of Q using the first cell; constructors guarantee rows, cols >= 0.
114 final Q first = get(0, 0);
115 final Class<?> qClass = first.getClass();
116
117 // Allocate a Q[rows()][cols()] array and fill it.
118 final Q[][] out = (Q[][]) Array.newInstance(qClass, rows(), cols());
119 for (int i = 0; i < rows(); i++)
120 {
121 for (int j = 0; j < cols(); j++)
122 {
123 out[i][j] = get(i, j);
124 }
125 }
126 return out;
127 }
128
129 /**
130 * Return the table or matrix as a 2D array of double SI values.
131 * @return a new double[rows()][cols()] array; entry [i][j] contains si(i, j).
132 */
133 public double[][] getSiGrid()
134 {
135 // Allocate a double[rows()][cols()] array and fill it.
136 final double[][] out = (double[][]) Array.newInstance(double.class, rows(), cols());
137 for (int i = 0; i < rows(); i++)
138 {
139 for (int j = 0; j < cols(); j++)
140 {
141 out[i][j] = si(i, j);
142 }
143 }
144 return out;
145 }
146
147 /**
148 * Return a quantity row (0-based) from the table or matrix. Note that the specific vector to return can be tightened by the
149 * implementing class.
150 * @param row the row number to retrieve (0-based)
151 * @return a row vector with the data at the given row
152 */
153 public abstract Vector<Q, ?, ?, ?, ?> getRowVector(int row);
154
155 /**
156 * Return a quantity row (1-based) from the table or matrix. Note that the specific vector to return can be tightened by the
157 * implementing class.
158 * @param mRow the row number to retrieve (1-based)
159 * @return a row vector with the data at the given row
160 */
161 public abstract Vector<Q, ?, ?, ?, ?> mgetRowVector(int mRow);
162
163 /**
164 * Return a quantity column (0-based) from the table or matrix. Note that the specific vector to return can be tightened by
165 * the implementing class.
166 * @param col the column number to retrieve (0-based)
167 * @return a column vector with the data at the given column
168 */
169 public abstract Vector<Q, ?, ?, ?, ?> getColumnVector(int col);
170
171 /**
172 * Return a quantity column (1-based) from the table or matrix. Note that the specific vector to return can be tightened by
173 * the implementing class.
174 * @param mCol the column number to retrieve (1-based)
175 * @return a column vector with the data at the given column
176 */
177 public abstract Vector<Q, ?, ?, ?, ?> mgetColumnVector(int mCol);
178
179 /**
180 * Return an array with SI-values for the given row (0-based) from the table or matrix.
181 * @param row the row number to retrieve (0-based)
182 * @return an array with SI-values with the data at the given row
183 */
184 public abstract double[] getRowSi(int row);
185
186 /**
187 * Return an array with SI-values for the given row (1-based) from the table or matrix.
188 * @param mRow the row number to retrieve (1-based)
189 * @return an array with SI-values with the data at the given row
190 */
191 public double[] mgetRowSi(final int mRow)
192 {
193 mcheckRow(mRow);
194 return getRowSi(mRow - 1);
195 }
196
197 /**
198 * Return an array with SI-values for the given column (0-based) from the table or matrix.
199 * @param col the column number to retrieve (0-based)
200 * @return an array with SI-values with the data at the given column
201 */
202 public abstract double[] getColumnSi(int col);
203
204 /**
205 * Return an array with SI-values for the given column (1-based) from the table or matrix.
206 * @param mCol the column number to retrieve (1-based)
207 * @return an array with SI-values with the data at the given column
208 */
209 public double[] mgetColumnSi(final int mCol)
210 {
211 mcheckCol(mCol);
212 return getColumnSi(mCol - 1);
213 }
214
215 /**
216 * Retrieve a row (0-based) from the table or matrix as an array of scalars.
217 * @param row row of the values to retrieve (0-based)
218 * @return the row as a Scalar array
219 * @throws IndexOutOfBoundsException in case row is out of bounds
220 */
221 @SuppressWarnings("unchecked")
222 public Q[] getRowScalars(final int row) throws IndexOutOfBoundsException
223 {
224 checkRow(row);
225
226 // Build a Q[] of length cols() using the runtime class of the first element
227 Q first = get(row, 0);
228 Q[] out = (Q[]) Array.newInstance(first.getClass(), cols());
229 for (int c = 0; c < cols(); c++)
230 {
231 out[c] = get(row, c);
232 }
233 return out;
234 }
235
236 /**
237 * Retrieve a row (1-based) from the table or matrix as an array of scalars.
238 * @param mRow row of the values to retrieve (1-based)
239 * @return the row as a Scalar array
240 * @throws IndexOutOfBoundsException in case row is out of bounds
241 */
242 public Q[] mgetRowScalars(final int mRow) throws IndexOutOfBoundsException
243 {
244 mcheckRow(mRow);
245 return getRowScalars(mRow - 1);
246 }
247
248 /**
249 * Retrieve a column (0-based) from the table or matrix as an array of scalars.
250 * @param col column of the values to retrieve (0-based)
251 * @return the column as a Scalar array
252 * @throws IndexOutOfBoundsException in case column is out of bounds
253 */
254 @SuppressWarnings("unchecked")
255 public Q[] getColumnScalars(final int col) throws IndexOutOfBoundsException
256 {
257 checkCol(col);
258
259 Q first = get(0, col);
260 Q[] out = (Q[]) Array.newInstance(first.getClass(), rows());
261 for (int r = 0; r < rows(); r++)
262 {
263 out[r] = get(r, col);
264 }
265 return out;
266 }
267
268 /**
269 * Retrieve a column (1-based) from the table or matrix as an array of scalars.
270 * @param mCol column of the values to retrieve (1-based)
271 * @return the column as a Scalar array
272 * @throws IndexOutOfBoundsException in case column is out of bounds
273 */
274 public Q[] mgetColumnScalars(final int mCol) throws IndexOutOfBoundsException
275 {
276 mcheckCol(mCol);
277 return getColumnScalars(mCol - 1);
278 }
279
280 // ------------------------------------ HELPER METHODS ------------------------------------
281
282 /**
283 * Check if the 0-based row is within bounds.
284 * @param row the 0-based row to check
285 * @throws IndexOutOfBoundsException when row is out of bounds
286 */
287 protected void checkRow(final int row)
288 {
289 Throw.when(row < 0 || row >= rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [0..%d]", row, rows() - 1);
290 }
291
292 /**
293 * Check if the 0-based column is within bounds.
294 * @param col the 0-based column to check
295 * @throws IndexOutOfBoundsException when column is out of bounds
296 */
297 protected void checkCol(final int col)
298 {
299 Throw.when(col < 0 || col >= cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [0..%d]", col,
300 cols() - 1);
301 }
302
303 /**
304 * Check if the 1-based row is within bounds.
305 * @param mRow the 1-based row to check
306 * @throws IndexOutOfBoundsException when row is out of bounds
307 */
308 protected void mcheckRow(final int mRow)
309 {
310 Throw.when(mRow < 1 || mRow > rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [1..%d]", mRow, rows());
311 }
312
313 /**
314 * Check if the 1-based column is within bounds.
315 * @param mCol the 1-based column to check
316 * @throws IndexOutOfBoundsException when column is out of bounds
317 */
318 protected void mcheckCol(final int mCol)
319 {
320 Throw.when(mCol < 1 || mCol > cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [1..%d]", mCol, cols());
321 }
322
323 }