1 package org.djunits.vecmat.def;
2
3 import java.lang.reflect.Array;
4
5 import org.djunits.formatter.Format;
6 import org.djunits.quantity.Dimensionless;
7 import org.djunits.quantity.SIQuantity;
8 import org.djunits.quantity.def.Quantity;
9 import org.djunits.unit.UnitInterface;
10 import org.djunits.unit.si.SIUnit;
11 import org.djunits.util.ArrayMath;
12 import org.djunits.util.Math2;
13 import org.djunits.value.Additive;
14 import org.djunits.value.Scalable;
15 import org.djunits.value.Value;
16 import org.djunits.vecmat.dnxm.MatrixNxM;
17 import org.djunits.vecmat.operations.Hadamard;
18 import org.djunits.vecmat.storage.DenseDoubleDataSi;
19 import org.djunits.vecmat.table.QuantityTable;
20 import org.djutils.exceptions.Throw;
21
22 /**
23 * VectorMatrix contains a number of standard operations on vectors and matrices of relative quantities.
24 * <p>
25 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
26 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
27 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
28 * @author Alexander Verbraeck
29 * @param <Q> the quantity type
30 * @param <U> the unit type
31 * @param <VM> the 'SELF' vector or matrix type
32 * @param <SI> the vector or matrix type with generics <SIQuantity, SIUnit<
33 * @param <H> the generic vector or matrix type with generics <?, ?< for Hadamard operations
34 */
35 public abstract class VectorMatrix<Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
36 VM extends VectorMatrix<Q, U, VM, SI, H>, SI extends VectorMatrix<SIQuantity, SIUnit, SI, ?, ?>,
37 H extends VectorMatrix<?, ?, ?, ?, ?>> implements Value<U, VM>, Scalable<VM>, Additive<VM>, Hadamard<H, SI>
38 {
39 /** */
40 private static final long serialVersionUID = 600L;
41
42 /** The display unit. */
43 private U displayUnit;
44
45 /**
46 * Create a new vector or matrix with a unit.
47 * @param displayUnit the display unit to use
48 */
49 public VectorMatrix(final U displayUnit)
50 {
51 Throw.whenNull(displayUnit, "displayUnit");
52 this.displayUnit = displayUnit;
53 }
54
55 @Override
56 public U getDisplayUnit()
57 {
58 return this.displayUnit;
59 }
60
61 @SuppressWarnings("unchecked")
62 @Override
63 public VM setDisplayUnit(final U newUnit)
64 {
65 Throw.whenNull(this.displayUnit, "displayUnit");
66 this.displayUnit = newUnit;
67 return (VM) this;
68 }
69
70 /**
71 * Return the number of rows.
72 * @return the number of rows
73 */
74 public abstract int rows();
75
76 /**
77 * Return the number of columns.
78 * @return the number of columns
79 */
80 public abstract int cols();
81
82 /**
83 * Return a new vector or matrix with the given SI or BASE values.
84 * @param siNew the values for the new vector or matrix in row-major format
85 * @return a new matrix with the provided SI or BASE values
86 */
87 public abstract VM instantiateSi(double[] siNew);
88
89 /**
90 * Return a new vector or matrix in SI-units with the given SI or BASE values.
91 * @param siNew the values for the new vector or matrix in row-major format
92 * @param siUnit the new unit for the new vector or matrix
93 * @return a new matrix with the provided SI or BASE values
94 */
95 public abstract SI instantiateSi(double[] siNew, SIUnit siUnit);
96
97 /**
98 * Return a row-major array of SI-values for this matrix or vector. Note that this is NOT a safe copy.
99 * @return the row-major array of SI-values
100 */
101 public abstract double[] si();
102
103 /**
104 * Return the si-value at position (row, col), where both row and col are 0-based values.
105 * @param row the row (0-based)
106 * @param col the column (0-based)
107 * @return the si-value at position (row, col)
108 * @throws IndexOutOfBoundsException when row or col < 0 or larger than number of rows/columns - 1.
109 */
110 public abstract double si(int row, int col) throws IndexOutOfBoundsException;
111
112 /**
113 * Return the si-value at position (row, col), where both row and col are 1-based values.
114 * @param mRow the row (1-based)
115 * @param mCol the column (1-based)
116 * @return the si-value at position (row, col)
117 * @throws IndexOutOfBoundsException when row or col < 1 or larger than number of rows/columns.
118 */
119 public double msi(final int mRow, final int mCol) throws IndexOutOfBoundsException
120 {
121 return si(mRow - 1, mCol - 1);
122 }
123
124 /**
125 * Return the quantity at position (row, col), where both row and col are 0-based values.
126 * @param row the row (0-based)
127 * @param col the column (0-based)
128 * @return the quantity at position (row, col)
129 * @throws IndexOutOfBoundsException when row or col < 0 or larger than number of rows/columns - 1.
130 */
131 public Q get(final int row, final int col) throws IndexOutOfBoundsException
132 {
133 return getDisplayUnit().ofSi(si(row, col)).setDisplayUnit(getDisplayUnit());
134 }
135
136 /**
137 * Return the quantity at position (row, col), where both row and col are 1-based values.
138 * @param mRow the row (1-based)
139 * @param mCol the column (1-based)
140 * @return the quantity at position (row, col)
141 * @throws IndexOutOfBoundsException when row or col < 1 or larger than number of rows/columns.
142 */
143 public Q mget(final int mRow, final int mCol) throws IndexOutOfBoundsException
144 {
145 return getDisplayUnit().ofSi(msi(mRow, mCol)).setDisplayUnit(getDisplayUnit());
146 }
147
148 /**
149 * Return the vector or matrix as a 2D array of scalars.
150 * @return a new Q[rows()][cols()] array; entry [i-1][j-1] contains get(i, j).
151 */
152 @SuppressWarnings("unchecked") // cast from Array.newInstance(...) to Q[][]
153 public Q[][] getScalarGrid()
154 {
155 // Determine the runtime type of Q using the first cell; constructors guarantee rows, cols >= 0.
156 final Q first = get(0, 0);
157 final Class<?> qClass = first.getClass();
158
159 // Allocate a Q[rows()][cols()] array and fill it.
160 final Q[][] out = (Q[][]) Array.newInstance(qClass, rows(), cols());
161 for (int i = 0; i < rows(); i++)
162 {
163 for (int j = 0; j < cols(); j++)
164 {
165 out[i][j] = get(i, j);
166 }
167 }
168 return out;
169 }
170
171 /**
172 * Return a quantity row (0-based) from the vector or matrix. Note that the specific vector to return can be tightened by
173 * the implementing class.
174 * @param row the row number to retrieve (0-based)
175 * @return a row vector with the data at the given row
176 */
177 public abstract Vector<Q, U, ?, ?, ?> getRowVector(int row);
178
179 /**
180 * Return a quantity row (1-based) from the vector or matrix. Note that the specific vector to return can be tightened by
181 * the implementing class.
182 * @param mRow the row number to retrieve (1-based)
183 * @return a row vector with the data at the given row
184 */
185 public abstract Vector<Q, U, ?, ?, ?> mgetRowVector(int mRow);
186
187 /**
188 * Return a quantity column (0-based) from the vector or matrix. Note that the specific vector to return can be tightened by
189 * the implementing class.
190 * @param col the column number to retrieve (0-based)
191 * @return a column vector with the data at the given column
192 */
193 public abstract Vector<Q, U, ?, ?, ?> getColumnVector(int col);
194
195 /**
196 * Return a quantity column (1-based) from the vector or matrix. Note that the specific vector to return can be tightened by
197 * the implementing class.
198 * @param mCol the column number to retrieve (1-based)
199 * @return a column vector with the data at the given column
200 */
201 public abstract Vector<Q, U, ?, ?, ?> mgetColumnVector(int mCol);
202
203 /**
204 * Return an array with SI-values for the given row (0-based) from the vector or matrix.
205 * @param row the row number to retrieve (0-based)
206 * @return an array with SI-values with the data at the given row
207 */
208 public abstract double[] getRowSi(int row);
209
210 /**
211 * Return an array with SI-values for the given row (1-based) from the vector or matrix.
212 * @param mRow the row number to retrieve (1-based)
213 * @return an array with SI-values with the data at the given row
214 */
215 public double[] mgetRowSi(final int mRow)
216 {
217 mcheckRow(mRow);
218 return getRowSi(mRow - 1);
219 }
220
221 /**
222 * Return an array with SI-values for the given column (0-based) from the vector or matrix.
223 * @param col the column number to retrieve (0-based)
224 * @return an array with SI-values with the data at the given column
225 */
226 public abstract double[] getColumnSi(int col);
227
228 /**
229 * Return an array with SI-values for the given column (1-based) from the vector or matrix.
230 * @param mCol the column number to retrieve (1-based)
231 * @return an array with SI-values with the data at the given column
232 */
233 public double[] mgetColumnSi(final int mCol)
234 {
235 mcheckCol(mCol);
236 return getColumnSi(mCol - 1);
237 }
238
239 @Override
240 public boolean isRelative()
241 {
242 return get(0, 0).isRelative();
243 }
244
245 /**
246 * Check if the 0-based row is within bounds.
247 * @param row the 0-based row to check
248 * @throws IndexOutOfBoundsException when row is out of bounds
249 */
250 protected void checkRow(final int row)
251 {
252 Throw.when(row < 0 || row >= rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [0..%d]", row, rows() - 1);
253 }
254
255 /**
256 * Check if the 0-based column is within bounds.
257 * @param col the 0-based column to check
258 * @throws IndexOutOfBoundsException when column is out of bounds
259 */
260 protected void checkCol(final int col)
261 {
262 Throw.when(col < 0 || col >= cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [0..%d]", col,
263 cols() - 1);
264 }
265
266 /**
267 * Check if the 1-based row is within bounds.
268 * @param mRow the 1-based row to check
269 * @throws IndexOutOfBoundsException when row is out of bounds
270 */
271 protected void mcheckRow(final int mRow)
272 {
273 Throw.when(mRow < 1 || mRow > rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [1..%d]", mRow, rows());
274 }
275
276 /**
277 * Check if the 1-based column is within bounds.
278 * @param mCol the 1-based column to check
279 * @throws IndexOutOfBoundsException when column is out of bounds
280 */
281 protected void mcheckCol(final int mCol)
282 {
283 Throw.when(mCol < 1 || mCol > cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [1..%d]", mCol, cols());
284 }
285
286 /**
287 * Retrieve a row (0-based) from the matrix as an array of scalars.
288 * @param row row of the values to retrieve (0-based)
289 * @return the row as a Scalar array
290 * @throws IndexOutOfBoundsException in case row is out of bounds
291 */
292 @SuppressWarnings("unchecked")
293 public Q[] getRowScalars(final int row) throws IndexOutOfBoundsException
294 {
295 checkRow(row);
296
297 // Build a Q[] of length cols() using the runtime class of the first element
298 Q first = get(row, 0);
299 Q[] out = (Q[]) Array.newInstance(first.getClass(), cols());
300 for (int c = 0; c < cols(); c++)
301 {
302 out[c] = get(row, c);
303 }
304 return out;
305 }
306
307 /**
308 * Retrieve a row (1-based) from the matrix as an array of scalars.
309 * @param mRow row of the values to retrieve (1-based)
310 * @return the row as a Scalar array
311 * @throws IndexOutOfBoundsException in case row is out of bounds
312 */
313 public Q[] mgetRowScalars(final int mRow) throws IndexOutOfBoundsException
314 {
315 mcheckRow(mRow);
316 return getRowScalars(mRow - 1);
317 }
318
319 /**
320 * Retrieve a column (0-based) from the matrix as an array of scalars.
321 * @param col column of the values to retrieve (0-based)
322 * @return the column as a Scalar array
323 * @throws IndexOutOfBoundsException in case column is out of bounds
324 */
325 @SuppressWarnings("unchecked")
326 public Q[] getColumnScalars(final int col) throws IndexOutOfBoundsException
327 {
328 checkCol(col);
329
330 Q first = get(0, col);
331 Q[] out = (Q[]) Array.newInstance(first.getClass(), rows());
332 for (int r = 0; r < rows(); r++)
333 {
334 out[r] = get(r, col);
335 }
336 return out;
337 }
338
339 /**
340 * Retrieve a column (1-based) from the matrix as an array of scalars.
341 * @param mCol column of the values to retrieve (1-based)
342 * @return the column as a Scalar array
343 * @throws IndexOutOfBoundsException in case column is out of bounds
344 */
345 public Q[] mgetColumnScalars(final int mCol) throws IndexOutOfBoundsException
346 {
347 mcheckCol(mCol);
348 return getColumnScalars(mCol - 1);
349 }
350
351 /**
352 * Return the mean value of the elements of the vector or matrix.
353 * @return the mean value of the elements of the vector or matrix
354 */
355 public Q mean()
356 {
357 return getDisplayUnit().ofSi(sum().si() / si().length).setDisplayUnit(getDisplayUnit());
358 }
359
360 /**
361 * Return the minimum value of the elements of the vector or matrix.
362 * @return the minimum value of the elements of the vector or matrix
363 */
364 public Q min()
365 {
366 return getDisplayUnit().ofSi(Math2.min(si())).setDisplayUnit(getDisplayUnit());
367 }
368
369 /**
370 * Return the maximum value of the elements of the vector or matrix.
371 * @return the maximum value of the elements of the vector or matrix
372 */
373 public Q max()
374 {
375 return getDisplayUnit().ofSi(Math2.max(si())).setDisplayUnit(getDisplayUnit());
376 }
377
378 /**
379 * Return the largest value of the elements of the vector or matrix.
380 * @return the largest value of the elements of the vector or matrix
381 */
382 public Q mode()
383 {
384 return max();
385 }
386
387 /**
388 * Return the median value of the elements of the vector or matrix.
389 * @return the median value of the elements of the vector or matrix
390 */
391 public Q median()
392 {
393 return getDisplayUnit().ofSi(Math2.median(si())).setDisplayUnit(getDisplayUnit());
394 }
395
396 /**
397 * Return the sum of the values of the elements of the vector or matrix.
398 * @return the sum of the values of the elements of the vector or matrix
399 */
400 public Q sum()
401 {
402 return getDisplayUnit().ofSi(Math2.sum(si())).setDisplayUnit(getDisplayUnit());
403 }
404
405 /**
406 * Return the a vector or matrix with entries that contain the sum of the element and the increment.
407 * @param increment the quantity by which to increase the values of the vector or matrix
408 * @return a vector or matrix with elements that are incremented by the given quantity
409 */
410 public VM add(final Q increment)
411 {
412 return instantiateSi(ArrayMath.add(si(), increment.si()));
413 }
414
415 /**
416 * Return the a vector or matrix with entries that contain the value minus the decrement.
417 * @param decrement the quantity by which to decrease the values of the vector or matrix
418 * @return a vector or matrix with elements that are decremented by the given quantity
419 */
420 public VM subtract(final Q decrement)
421 {
422 return instantiateSi(ArrayMath.add(si(), -decrement.si()));
423 }
424
425 @Override
426 public VM add(final VM other)
427 {
428 return instantiateSi(ArrayMath.add(si(), other.si()));
429 }
430
431 @Override
432 public VM subtract(final VM other)
433 {
434 return instantiateSi(ArrayMath.subtract(si(), other.si()));
435 }
436
437 @Override
438 public VM negate()
439 {
440 return scaleBy(-1.0);
441 }
442
443 @Override
444 public VM abs()
445 {
446 return instantiateSi(ArrayMath.abs(si()));
447 }
448
449 @Override
450 public VM scaleBy(final double factor)
451 {
452 return instantiateSi(ArrayMath.scaleBy(si(), factor));
453 }
454
455 /**
456 * Multiply the elements of this vector, matrix or table by the given quantity. This is actually a Hadamard operation, but
457 * since it is equivalent to a scaleBy operation, it is included in this interface.
458 * @param quantity the scalar quantity to multiply by
459 * @return a vector, matrix or table where the elements have been multiplied by the given quantity
460 */
461 public VM multiplyElements(final Dimensionless quantity)
462 {
463 return scaleBy(quantity.si());
464 }
465
466 /**
467 * Multiply the elements of this vector, matrix or table by the given quantity. This is actually a Hadamard operation, but
468 * since it is equivalent to a scaleBy operation, it is included in this interface.
469 * @param quantity the scalar quantity to multiply by
470 * @return a vector, matrix or table where the elements have been multiplied by the given quantity
471 */
472 public VM divideElements(final Dimensionless quantity)
473 {
474 return scaleBy(1.0 / quantity.si());
475 }
476
477 @Override
478 public SI invertElements()
479 {
480 return (SI) instantiateSi(ArrayMath.reciprocal(si()), getDisplayUnit().siUnit().invert());
481 }
482
483 @Override
484 public SI multiplyElements(final H other)
485 {
486 return (SI) instantiateSi(ArrayMath.multiply(si(), other.si()),
487 getDisplayUnit().siUnit().plus(other.getDisplayUnit().siUnit()));
488 }
489
490 @Override
491 public SI divideElements(final H other)
492 {
493 return (SI) instantiateSi(ArrayMath.divide(si(), other.si()),
494 getDisplayUnit().siUnit().minus(other.getDisplayUnit().siUnit()));
495 }
496
497 @Override
498 public SI multiplyElements(final Quantity<?, ?> quantity)
499 {
500 return (SI) instantiateSi(ArrayMath.scaleBy(si(), quantity.si()),
501 getDisplayUnit().siUnit().plus(quantity.getDisplayUnit().siUnit()));
502 }
503
504 /**
505 * Convert this vector or matrix to a {@link MatrixNxM}.
506 * @return a {@code MatrixNxN} with identical SI data and display unit
507 */
508 public MatrixNxM<Q, U> asMatrixNxM()
509 {
510 return new MatrixNxM<Q, U>(new DenseDoubleDataSi(si(), rows(), cols()), getDisplayUnit().getBaseUnit())
511 .setDisplayUnit(getDisplayUnit());
512 }
513
514 /**
515 * Convert this vector or matrix to a {@link QuantityTable}.
516 * @return a {@code QuantityTable} with identical SI data and display unit
517 */
518 public QuantityTable<Q, U> asQuantityTable()
519 {
520 return new QuantityTable<Q, U>(new DenseDoubleDataSi(si(), rows(), cols()), getDisplayUnit().getBaseUnit())
521 .setDisplayUnit(getDisplayUnit());
522 }
523
524 @SuppressWarnings("checkstyle:needbraces")
525 @Override
526 public String toString(final U withUnit)
527 {
528 var s = new StringBuilder();
529 for (int r = 0; r < rows(); r++)
530 {
531 s.append(r == 0 ? "[" : "\n ");
532 for (int c = 0; c < cols(); c++)
533 {
534 if (c > 0)
535 s.append(", ");
536 s.append(Format.format(withUnit.fromBaseValue(si(r, c))));
537 }
538 }
539 s.append("] ");
540 s.append(withUnit.getDisplayAbbreviation());
541 return s.toString();
542 }
543
544 @Override
545 public String toString()
546 {
547 return toString(getDisplayUnit());
548 }
549
550 }