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