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