1 package org.djunits.vecmat.def;
2
3 import java.util.Objects;
4
5 import org.djunits.quantity.def.AbsQuantity;
6 import org.djunits.quantity.def.Quantity;
7 import org.djunits.quantity.def.Reference;
8 import org.djunits.unit.UnitInterface;
9 import org.djunits.util.ArrayMath;
10 import org.djunits.util.SuppressFBWarnings;
11 import org.djunits.value.Value;
12 import org.djunits.vecmat.d1.AbsMatrix1x1;
13 import org.djunits.vecmat.d1.AbsVector1;
14 import org.djunits.vecmat.d1.Matrix1x1;
15 import org.djunits.vecmat.d1.Vector1;
16 import org.djunits.vecmat.d2.AbsMatrix2x2;
17 import org.djunits.vecmat.d2.AbsVector2;
18 import org.djunits.vecmat.d2.Matrix2x2;
19 import org.djunits.vecmat.d2.Vector2;
20 import org.djunits.vecmat.d3.AbsMatrix3x3;
21 import org.djunits.vecmat.d3.AbsVector3;
22 import org.djunits.vecmat.d3.Matrix3x3;
23 import org.djunits.vecmat.d3.Vector3;
24 import org.djunits.vecmat.dn.AbsMatrixNxN;
25 import org.djunits.vecmat.dn.AbsVectorN;
26 import org.djunits.vecmat.dn.MatrixNxN;
27 import org.djunits.vecmat.dn.VectorN;
28 import org.djunits.vecmat.dnxm.AbsMatrixNxM;
29 import org.djunits.vecmat.dnxm.MatrixNxM;
30 import org.djunits.vecmat.table.AbsQuantityTable;
31 import org.djunits.vecmat.table.QuantityTable;
32 import org.djutils.exceptions.Throw;
33
34 /**
35 * AbsVectorMatrix contains a number of standard operations on vectors and matrices of absolute quantities.
36 * <p>
37 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
38 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
39 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
40 * @author Alexander Verbraeck
41 * @param <A> the absolute quantity type
42 * @param <Q> the quantity type
43 * @param <VMA> the absolute vector or matrix type
44 * @param <VMQ> the relative vector or matrix type
45 * @param <VMAT> the type of the transposed version of the absolute vector or matrix
46 */
47 public abstract class AbsVectorMatrix<A extends AbsQuantity<A, Q, ?>, Q extends Quantity<Q>,
48 VMA extends AbsVectorMatrix<A, Q, VMA, VMQ, VMAT>, VMQ extends VectorMatrix<Q, VMQ, ?, ?, ?>,
49 VMAT extends AbsVectorMatrix<A, Q, VMAT, ?, VMA>> implements Value<Q>
50 {
51 /** */
52 private static final long serialVersionUID = 600L;
53
54 /** The underlying relative vector or matrix with SI values relative to the reference point. */
55 private final VMQ relativeVecMat;
56
57 /** The reference point for the absolute values. */
58 private final Reference<?, A, Q> reference;
59
60 /**
61 * Create a new vector or matrix of absolute values with a reference point.
62 * @param relativeVecMat the underlying relative vector or matrix with SI values relative to the reference point
63 * @param reference the reference point for the absolute values
64 */
65 @SuppressFBWarnings("EI_EXPOSE_REP2") // relative vector is immutable
66 public AbsVectorMatrix(final VMQ relativeVecMat, final Reference<?, A, Q> reference)
67 {
68 Throw.whenNull(relativeVecMat, "relativeVecMat");
69 Throw.whenNull(reference, "reference");
70 this.relativeVecMat = relativeVecMat;
71 this.reference = reference;
72 }
73
74 @Override
75 public UnitInterface<Q> getDisplayUnit()
76 {
77 return this.relativeVecMat.getDisplayUnit();
78 }
79
80 /**
81 * Return the number of rows.
82 * @return the number of rows
83 */
84 public int rows()
85 {
86 return this.relativeVecMat.rows();
87 }
88
89 /**
90 * Return the number of columns.
91 * @return the number of columns
92 */
93 public int cols()
94 {
95 return this.relativeVecMat.cols();
96 }
97
98 /**
99 * Return a new vector or matrix with the given SI or BASE values for the relative vector or matrix.
100 * @param siNew the values for the new vector or matrix in row-major format
101 * @param newReference the reference point for the relative SI values
102 * @param displayUnit the display unit of the data in the absolute vector or matrix
103 * @return a new matrix with the provided SI or BASE values
104 */
105 public VMA instantiateSi(final double[] siNew, final Reference<?, A, Q> newReference, final UnitInterface<Q> displayUnit)
106 {
107 VMQ rel = this.relativeVecMat.instantiateSi(siNew, displayUnit);
108 return instantiate(rel, newReference);
109 }
110
111 /**
112 * Return a new vector or matrix with the given SI or BASE values for the relative vector or matrix. The absolute matrix
113 * uses the display unit of the relative vector or matrix.
114 * @param relVecMat the underlying relative vector or matrix with SI values relative to the reference point
115 * @param newReference the reference point for the relative SI values
116 * @return a new matrix with the provided SI or BASE values, and the display unit of the relative vector or matrix
117 */
118 public abstract VMA instantiate(VMQ relVecMat, Reference<?, A, Q> newReference);
119
120 /**
121 * Return the underlying relative vector or matrix with SI values relative to the reference point.
122 * @return the underlying relative vector or matrix with SI values relative to the reference point
123 */
124 @SuppressFBWarnings("EI_EXPOSE_REP") // relative vector is immutable
125 public VMQ getRelativeVecMat()
126 {
127 return this.relativeVecMat;
128 }
129
130 /**
131 * Return the reference point for the absolute values.
132 * @return the reference point for the absolute values
133 */
134 public Reference<?, A, Q> getReference()
135 {
136 return this.reference;
137 }
138
139 /**
140 * Return a transposed absolute vector or matrix, where rows and columns have been swapped.
141 * @return a transposed absolute vector or matrix, where rows and columns have been swapped
142 */
143 public abstract VMAT transpose();
144
145 @Override
146 public boolean isRelative()
147 {
148 return false;
149 }
150
151 /**
152 * Check if the 0-based row is within bounds.
153 * @param row the 0-based row to check
154 * @throws IndexOutOfBoundsException when row is out of bounds
155 */
156 protected void checkRow(final int row)
157 {
158 Throw.when(row < 0 || row >= rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [0..%d]", row, rows() - 1);
159 }
160
161 /**
162 * Check if the 0-based column is within bounds.
163 * @param col the 0-based column to check
164 * @throws IndexOutOfBoundsException when column is out of bounds
165 */
166 protected void checkCol(final int col)
167 {
168 Throw.when(col < 0 || col >= cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [0..%d]", col,
169 cols() - 1);
170 }
171
172 /**
173 * Check if the 1-based row is within bounds.
174 * @param mRow the 1-based row to check
175 * @throws IndexOutOfBoundsException when row is out of bounds
176 */
177 protected void mcheckRow(final int mRow)
178 {
179 Throw.when(mRow < 1 || mRow > rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [1..%d]", mRow, rows());
180 }
181
182 /**
183 * Check if the 1-based column is within bounds.
184 * @param mCol the 1-based column to check
185 * @throws IndexOutOfBoundsException when column is out of bounds
186 */
187 protected void mcheckCol(final int mCol)
188 {
189 Throw.when(mCol < 1 || mCol > cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [1..%d]", mCol, cols());
190 }
191
192 /**
193 * Return the minimum value of the entries of the vector or matrix.
194 * @return the minimum value of the entries of the vector or matrix
195 */
196 public A min()
197 {
198 return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.min().si(), getDisplayUnit()));
199 }
200
201 /**
202 * Return the maximum value of the entries of the vector or matrix.
203 * @return the maximum value of the entries of the vector or matrix
204 */
205 public A max()
206 {
207 return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.max().si(), getDisplayUnit()));
208 }
209
210 /**
211 * Return the median value of the entries of the vector or matrix.
212 * @return the median value of the entries of the vector or matrix
213 */
214 public A median()
215 {
216 return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.median().si(), getDisplayUnit()));
217 }
218
219 /**
220 * Return a vector or matrix with entries that contain the sum of the element and the increment.
221 * @param increment the quantity by which to increase the values of the vector or matrix
222 * @return a vector or matrix with entries that are incremented by the given quantity
223 */
224 public VMA add(final Q increment)
225 {
226 return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), increment.si()), getReference(),
227 getDisplayUnit());
228 }
229
230 /**
231 * Return a vector or matrix with entries that contain the value minus the decrement.
232 * @param decrement the quantity by which to decrease the values of the vector or matrix
233 * @return a vector or matrix with entries that are decremented by the given quantity
234 */
235 public VMA subtract(final Q decrement)
236 {
237 return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), -decrement.si()), getReference(),
238 getDisplayUnit());
239 }
240
241 /**
242 * Return a vector or matrix with entries that contain the sum of the element and the increment.
243 * @param other the vector or matrix that contains the values by which to increase the values of the vector or matrix
244 * @return a vector or matrix with entries that are decremented by the given quantity
245 */
246 public VMA add(final VMQ other)
247 {
248 return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), other.unsafeSiArray()), getReference(),
249 getDisplayUnit());
250 }
251
252 /**
253 * Return a vector or matrix with entries that contain the value minus the decrement.
254 * @param other the vector or matrix that contains the values by which to decrease the values of the vector or matrix
255 * @return a vector or matrix with entries that are decremented by the given vector or matrix values
256 */
257 public VMA subtract(final VMQ other)
258 {
259 return instantiateSi(ArrayMath.subtract(this.relativeVecMat.unsafeSiArray(), other.unsafeSiArray()), getReference(),
260 getDisplayUnit());
261 }
262
263 /**
264 * Return a relative vector or matrix with entries that contain the absolute value minus the absolute decrement.
265 * @param other the vector or matrix that contains the values by which to decrease the values of the vector or matrix
266 * @return a vector or matrix with entries that are decremented by the given vector or matrix values
267 */
268 public VMQ subtract(final VMA other)
269 {
270 return this.relativeVecMat.instantiateSi(
271 ArrayMath.subtract(this.relativeVecMat.unsafeSiArray(), other.getRelativeVecMat().unsafeSiArray()),
272 getDisplayUnit());
273 }
274
275 /**
276 * Return a relative vector or matrix with entries that contain the absolute value minus the absolute decrement.
277 * @param decrement the absolute quantity by which to decrease the values of the vector or matrix
278 * @return a vector or matrix with entries that are decremented by the given decrement
279 */
280 public VMQ subtract(final A decrement)
281 {
282 return this.relativeVecMat.instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), -decrement.si()),
283 getDisplayUnit());
284 }
285
286 // ------------------------------------ AS() METHODS ------------------------------------
287
288 /**
289 * Convert this absolute vector or matrix to a {@link AbsMatrixNxM}. The underlying data MIGHT be shared between this object
290 * and the new AbsMatrixNxM.
291 * @return a {@code AbsMatrixNxN} with identical SI data, display unit, and reference point
292 */
293 public AbsMatrixNxM<A, Q> asAbsMatrixNxM()
294 {
295 return new AbsMatrixNxM<A, Q>(MatrixNxM.ofSi(getRelativeVecMat().unsafeSiArray(), rows(), cols(), getDisplayUnit()),
296 getReference());
297 }
298
299 /**
300 * Convert this absolute vector or matrix to a {@link AbsQuantityTable}. The underlying data MIGHT be shared between this
301 * object and the new AbsQuantityTable.
302 * @return a {@code AbsQuantityTable} with identical SI data, display unit, and reference point
303 */
304 public AbsQuantityTable<A, Q> asAbsQuantityTable()
305 {
306 return new AbsQuantityTable<A, Q>(
307 QuantityTable.ofSi(getRelativeVecMat().unsafeSiArray(), rows(), cols(), getDisplayUnit()), getReference());
308 }
309
310 /**
311 * Return this absolute vector, matrix or table as a 1-element column vector. Shape must be 1 x 1.
312 * @return a {@code AbsVector1} with identical SI data and display unit
313 * @throws IllegalStateException if shape is not 1 x 1
314 */
315 public AbsVector1<A, Q> asAbsVector1()
316 {
317 Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class, "Matrix is not 1x1");
318 final double[] data = getRelativeVecMat().unsafeSiArray();
319 return new AbsVector1<A, Q>(new Vector1<Q>(data[0], getDisplayUnit()), getReference());
320 }
321
322 /**
323 * Return this absolute vector, matrix or table as a 2-element column vector. Shape must be 2 x 1.
324 * @return a {@code AbsVector2.Col} with identical SI data and display unit
325 * @throws IllegalStateException if shape is not 2 x 1
326 */
327 public AbsVector2.Col<A, Q> asAbsVector2Col()
328 {
329 Throw.when(rows() != 2 || cols() != 1, IllegalStateException.class, "Matrix is not 2x1");
330 final double[] data = getRelativeVecMat().unsafeSiArray();
331 return new AbsVector2.Col<A, Q>(new Vector2.Col<Q>(data[0], data[1], getDisplayUnit()), getReference());
332 }
333
334 /**
335 * Return this absolute vector, matrix or table as a 3-element column vector. Shape must be 3 x 1.
336 * @return a {@code AbsVector3.Col} with identical SI data and display unit
337 * @throws IllegalStateException if shape is not 3 x 1
338 */
339 public AbsVector3.Col<A, Q> asAbsVector3Col()
340 {
341 Throw.when(rows() != 3 || cols() != 1, IllegalStateException.class, "Matrix is not 3x1");
342 final double[] data = getRelativeVecMat().unsafeSiArray();
343 return new AbsVector3.Col<A, Q>(new Vector3.Col<Q>(data[0], data[1], data[2], getDisplayUnit()), getReference());
344 }
345
346 /**
347 * Convert this absolute vector, matrix or table to an N-element column vector. Shape must be N x 1. The underlying data
348 * MIGHT be shared between this object and the AbsVectorN.Col.
349 * @return a {@code AbsVectorN.Col} with identical SI data and display unit
350 * @throws IllegalStateException if {@code cols() != 1}
351 */
352 public AbsVectorN.Col<A, Q> asAbsVectorNCol()
353 {
354 Throw.when(cols() != 1, IllegalStateException.class, "Matrix is not Nx1");
355 return new AbsVectorN.Col<A, Q>(VectorN.Col.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()),
356 getReference());
357 }
358
359 /**
360 * Return this absolute vector, matrix or table as a 2-element row vector. Shape must be 1 x 2.
361 * @return a {@code AbsVector2.Row} with identical SI data and display unit
362 * @throws IllegalStateException if shape is not 1 x 2
363 */
364 public AbsVector2.Row<A, Q> asAbsVector2Row()
365 {
366 Throw.when(rows() != 1 || cols() != 2, IllegalStateException.class, "Matrix is not 1x2");
367 final double[] data = getRelativeVecMat().unsafeSiArray();
368 return new AbsVector2.Row<A, Q>(new Vector2.Row<Q>(data[0], data[1], getDisplayUnit()), getReference());
369 }
370
371 /**
372 * Return this absolute vector, matrix or table as a 3-element row vector. Shape must be 1 x 3.
373 * @return a {@code AbsVector3.Row} with identical SI data and display unit
374 * @throws IllegalStateException if shape is not 1 x 3
375 */
376 public AbsVector3.Row<A, Q> asAbsVector3Row()
377 {
378 Throw.when(rows() != 1 || cols() != 3, IllegalStateException.class, "Matrix is not 1x3");
379 final double[] data = getRelativeVecMat().unsafeSiArray();
380 return new AbsVector3.Row<A, Q>(new Vector3.Row<Q>(data[0], data[1], data[2], getDisplayUnit()), getReference());
381 }
382
383 /**
384 * Convert this absolute vector, matrix or table to an N-element row vector. Shape must be 1 x N. The underlying data MIGHT
385 * be shared between this object and the AbsVectorN.Row.
386 * @return a {@code AbsVectorN.Row} with identical SI data and display unit
387 * @throws IllegalStateException if {@code rows() != 1}
388 */
389 public AbsVectorN.Row<A, Q> asAbsVectorNRow()
390 {
391 Throw.when(rows() != 1, IllegalStateException.class, "Matrix is not 1xN");
392 return new AbsVectorN.Row<A, Q>(VectorN.Row.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()),
393 getReference());
394 }
395
396 /**
397 * Convert this absolute vector, matrix or table to a {@link AbsMatrix1x1}. The shape must be 1 x 1.
398 * @return a {@code AbsMatrix1x1} with identical SI data and display unit
399 * @throws IllegalStateException if this matrix is not 1 x 1
400 */
401 public AbsMatrix1x1<A, Q> asAbsMatrix1x1()
402 {
403 Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class,
404 "asAbsMatrix1x1() called, but matrix is no 1x1 but %dx%d", rows(), cols());
405 return new AbsMatrix1x1<A, Q>(Matrix1x1.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
406 }
407
408 /**
409 * Convert this absolute vector, matrix or table to a {@link AbsMatrix2x2}. The shape must be 2 x 2.
410 * @return a {@code AbsMatrix2x2} with identical SI data and display unit
411 * @throws IllegalStateException if this matrix is not 2 x 2
412 */
413 public AbsMatrix2x2<A, Q> asAbsMatrix2x2()
414 {
415 Throw.when(rows() != 2 || cols() != 2, IllegalStateException.class,
416 "asAbsMatrix2x2() called, but matrix is no 2x2 but %dx%d", rows(), cols());
417 return new AbsMatrix2x2<A, Q>(Matrix2x2.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
418 }
419
420 /**
421 * Convert this absolute vector, matrix or table to a {@link AbsMatrix3x3}. The shape must be 3 x 3.
422 * @return a {@code AbsMatrix3x3} with identical SI data and display unit
423 * @throws IllegalStateException if this matrix is not 3 x 3
424 */
425 public AbsMatrix3x3<A, Q> asAbsMatrix3x3()
426 {
427 Throw.when(rows() != 3 || cols() != 3, IllegalStateException.class,
428 "asAbsMatrix3x3() called, but matrix is no 3x3 but %dx%d", rows(), cols());
429 return new AbsMatrix3x3<A, Q>(Matrix3x3.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
430 }
431
432 /**
433 * Convert this absolute vector, matrix or table to a {@link AbsMatrixNxN}. The shape must be square. The underlying data
434 * MIGHT be shared between this object and the AbsMatrixNxN.
435 * @return a {@code AbsMatrixNxN} with identical SI data and display unit
436 * @throws IllegalStateException if this matrix is not square
437 */
438 public AbsMatrixNxN<A, Q> asAbsMatrixNxN()
439 {
440 Throw.when(rows() != cols(), IllegalStateException.class, "asAbsMatrixNxN() called, but matrix is no square but %dx%d",
441 rows(), cols());
442 return new AbsMatrixNxN<A, Q>(MatrixNxN.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
443 }
444
445 // ======================================= hashCode() and equals() ===============================================
446
447 @Override
448 public int hashCode()
449 {
450 return Objects.hash(this.reference, this.relativeVecMat);
451 }
452
453 @Override
454 @SuppressWarnings("checkstyle:needbraces")
455 public boolean equals(final Object obj)
456 {
457 if (this == obj)
458 return true;
459 if (obj == null)
460 return false;
461 if (getClass() != obj.getClass())
462 return false;
463 AbsVectorMatrix<?, ?, ?, ?, ?> other = (AbsVectorMatrix<?, ?, ?, ?, ?>) obj;
464 return Objects.equals(this.reference, other.reference) && Objects.equals(this.relativeVecMat, other.relativeVecMat);
465 }
466
467 // -------------------------------- TOSTRING / FORMAT METHODS -------------------------------
468
469 @Override
470 public String toString()
471 {
472 return format();
473 }
474
475 }