View Javadoc
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 &lt;SIQuantity, SIUnit&lt;
37   * @param <H> the generic vector or matrix type with generics &lt;?, ?&lt; 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 }