View Javadoc
1   package org.djunits.vecmat.dnxm;
2   
3   import java.util.Objects;
4   
5   import org.djunits.quantity.SIQuantity;
6   import org.djunits.quantity.def.Quantity;
7   import org.djunits.unit.Unit;
8   import org.djunits.unit.si.SIUnit;
9   import org.djunits.util.MatrixMath;
10  import org.djunits.vecmat.d1.Matrix1x1;
11  import org.djunits.vecmat.d1.Vector1;
12  import org.djunits.vecmat.d2.Matrix2x2;
13  import org.djunits.vecmat.d2.Vector2;
14  import org.djunits.vecmat.d3.Matrix3x3;
15  import org.djunits.vecmat.d3.Vector3;
16  import org.djunits.vecmat.def.Matrix;
17  import org.djunits.vecmat.dn.MatrixNxN;
18  import org.djunits.vecmat.dn.VectorN;
19  import org.djunits.vecmat.storage.DataGridSi;
20  import org.djunits.vecmat.storage.DenseDoubleDataSi;
21  import org.djunits.vecmat.storage.DenseFloatDataSi;
22  import org.djutils.exceptions.Throw;
23  
24  /**
25   * MatrixNxM implements a matrix with NxM real-valued entries. The matrix is immutable, except for the display unit, which can
26   * be changed. Internal storage can be float or double, and dense or sparse. MatrixNxN and VectorN extend from this class.
27   * <p>
28   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
29   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
30   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
31   * @author Alexander Verbraeck
32   * @param <Q> the quantity type
33   */
34  public class MatrixNxM<Q extends Quantity<Q>> extends Matrix<Q, MatrixNxM<Q>, MatrixNxM<SIQuantity>, MatrixNxM<?>, MatrixNxM<Q>>
35  {
36      /** */
37      private static final long serialVersionUID = 600L;
38  
39      /** The data of the table, in SI unit. */
40      private final DataGridSi<?> dataGridSi;
41  
42      /**
43       * Create a new NxM Matrix with a unit, based on a DataGrid storage object that contains SI data.
44       * @param dataGridSi the data of the matrix, in SI unit.
45       * @param displayUnit the display unit to use
46       * @throws IllegalArgumentException when the number of rows or columns does not have a positive value
47       */
48      public MatrixNxM(final DataGridSi<?> dataGridSi, final Unit<?, Q> displayUnit)
49      {
50          super(displayUnit);
51          Throw.whenNull(dataGridSi, "dataGridSi");
52          this.dataGridSi = dataGridSi;
53      }
54  
55      @Override
56      public MatrixNxM<Q> instantiateSi(final double[] siNew)
57      {
58          return new MatrixNxM<Q>(this.dataGridSi.instantiateNew(siNew), getDisplayUnit());
59      }
60  
61      @Override
62      public MatrixNxM<SIQuantity> instantiateSi(final double[] siNew, final SIUnit siUnit)
63      {
64          return new MatrixNxM<SIQuantity>(this.dataGridSi.instantiateNew(siNew), siUnit);
65      }
66  
67      /**
68       * Return the internal datagrid object, so we can retrieve data from it.
69       * @return the internal datagrid object
70       */
71      public DataGridSi<?> getDataGrid()
72      {
73          return this.dataGridSi;
74      }
75  
76      @Override
77      public double[] getSiArray()
78      {
79          return this.dataGridSi.getSiArray();
80      }
81  
82      @Override
83      public double[] unsafeSiArray()
84      {
85          return this.dataGridSi.unsafeSiArray();
86      }
87  
88      @Override
89      public double si(final int row, final int col) throws IndexOutOfBoundsException
90      {
91          checkRow(row);
92          checkCol(col);
93          return this.dataGridSi.get(row, col);
94      }
95  
96      @Override
97      public VectorN.Row<Q> getRowVector(final int row)
98      {
99          return VectorN.Row.ofSi(getRowSi(row), getDisplayUnit());
100     }
101 
102     @Override
103     public VectorN.Row<Q> mgetRowVector(final int mRow)
104     {
105         return VectorN.Row.ofSi(mgetRowSi(mRow), getDisplayUnit());
106     }
107 
108     @Override
109     public VectorN.Col<Q> getColumnVector(final int col)
110     {
111         return VectorN.Col.ofSi(getColumnSi(col), getDisplayUnit());
112     }
113 
114     @Override
115     public VectorN.Col<Q> mgetColumnVector(final int mCol)
116     {
117         return VectorN.Col.ofSi(mgetColumnSi(mCol), getDisplayUnit());
118     }
119 
120     @Override
121     public double[] getRowSi(final int row)
122     {
123         checkRow(row);
124         return this.dataGridSi.getRowArray(row);
125     }
126 
127     @Override
128     public double[] getColumnSi(final int col)
129     {
130         checkCol(col);
131         return this.dataGridSi.getColArray(col);
132     }
133 
134     @Override
135     public int rows()
136     {
137         return this.dataGridSi.rows();
138     }
139 
140     @Override
141     public int cols()
142     {
143         return this.dataGridSi.cols();
144     }
145 
146     @Override
147     public int nonZeroCount()
148     {
149         return this.dataGridSi.nonZeroCount();
150     }
151 
152     /**
153      * Return the transposed matrix. A transposed matrix has the same unit as the original one.
154      * @return the transposed matrix
155      */
156     @Override
157     @SuppressWarnings("checkstyle:needbraces")
158     public MatrixNxM<Q> transpose()
159     {
160         double[] data = unsafeSiArray();
161         double[] newSi = new double[data.length];
162         int rows = rows();
163         int cols = cols();
164         final Unit<?, Q> displayUnit = getDisplayUnit();
165         for (int r = 0; r < rows; r++)
166             for (int c = 0; c < cols; c++)
167                 newSi[c * rows + r] = data[r * cols + c];
168         return new MatrixNxM<Q>(this.dataGridSi.instantiateNew(newSi, cols, rows), displayUnit);
169     }
170 
171     @Override
172     public int hashCode()
173     {
174         return Objects.hash(this.dataGridSi);
175     }
176 
177     @SuppressWarnings("checkstyle:needbraces")
178     @Override
179     public boolean equals(final Object obj)
180     {
181         if (this == obj)
182             return true;
183         if (obj == null)
184             return false;
185         if (getClass() != obj.getClass())
186             return false;
187         MatrixNxM<?> other = (MatrixNxM<?>) obj;
188         return Objects.equals(this.dataGridSi, other.dataGridSi);
189     }
190 
191     // ---------------------------------------------- MULTIPLY() METHODS ----------------------------------------------
192 
193     /**
194      * Multiply this vector or matrix with a Matrix1x1, resulting in a MatrixNxM. The multiplication is a (Mx1) x (1x1) matrix
195      * multiplication resulting in an (Mx1) matrix.
196      * @param matrix the matrix to multiply with
197      * @return a MatrixNxM of an SIQuantity as the result of the matrix multiplication
198      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
199      *             matrix or vector to multiply with
200      */
201     public MatrixNxM<SIQuantity> multiply(final Matrix1x1<?> matrix)
202     {
203         checkMultiply(matrix);
204         double[] result = MatrixMath.multiply(unsafeSiArray(), matrix.unsafeSiArray(), rows(), cols(), matrix.cols());
205         SIUnit siUnit = getDisplayUnit().siUnit().plus(matrix.getDisplayUnit().siUnit());
206         return new MatrixNxM<SIQuantity>(new DenseDoubleDataSi(result, rows(), matrix.cols()), siUnit);
207     }
208 
209     /**
210      * Multiply this vector or matrix with a Matrix2x2, resulting in a MatrixNxM. The multiplication is a (Mx2) x (2x2) matrix
211      * multiplication resulting in an (Mx2) matrix.
212      * @param matrix the matrix to multiply with
213      * @return a MatrixNxM of an SIQuantity as the result of the matrix multiplication
214      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
215      *             matrix or vector to multiply with
216      */
217     public MatrixNxM<SIQuantity> multiply(final Matrix2x2<?> matrix)
218     {
219         checkMultiply(matrix);
220         double[] result = MatrixMath.multiply(unsafeSiArray(), matrix.unsafeSiArray(), rows(), cols(), matrix.cols());
221         SIUnit siUnit = getDisplayUnit().siUnit().plus(matrix.getDisplayUnit().siUnit());
222         return new MatrixNxM<SIQuantity>(new DenseDoubleDataSi(result, rows(), matrix.cols()), siUnit);
223     }
224 
225     /**
226      * Multiply this vector or matrix with a Matrix3x3, resulting in a MatrixNxM. The multiplication is a (Mx3) x (3x3) matrix
227      * multiplication resulting in an (Mx3) matrix.
228      * @param matrix the matrix to multiply with
229      * @return a MatrixNxM of an SIQuantity as the result of the matrix multiplication
230      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
231      *             matrix or vector to multiply with
232      */
233     public MatrixNxM<SIQuantity> multiply(final Matrix3x3<?> matrix)
234     {
235         checkMultiply(matrix);
236         double[] result = MatrixMath.multiply(unsafeSiArray(), matrix.unsafeSiArray(), rows(), cols(), matrix.cols());
237         SIUnit siUnit = getDisplayUnit().siUnit().plus(matrix.getDisplayUnit().siUnit());
238         return new MatrixNxM<SIQuantity>(new DenseDoubleDataSi(result, rows(), matrix.cols()), siUnit);
239     }
240 
241     /**
242      * Multiply this vector or matrix with a MatrixNxM, resulting in a MatrixNxM. The multiplication is a (NxM) x (MxP) matrix
243      * multiplication resulting in an (NxP) matrix.
244      * @param matrix the matrix to multiply with
245      * @return a MatrixNxM of an SIQuantity as the result of the matrix multiplication
246      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
247      *             matrix or vector to multiply with
248      */
249     public MatrixNxM<SIQuantity> multiply(final MatrixNxN<?> matrix)
250     {
251         checkMultiply(matrix);
252         double[] result = MatrixMath.multiply(unsafeSiArray(), matrix.unsafeSiArray(), rows(), cols(), matrix.cols());
253         SIUnit siUnit = getDisplayUnit().siUnit().plus(matrix.getDisplayUnit().siUnit());
254         if (matrix.getDataGrid().isDouble())
255         {
256             return new MatrixNxM<SIQuantity>(new DenseDoubleDataSi(result, rows(), matrix.cols()), siUnit);
257         }
258         return new MatrixNxM<SIQuantity>(new DenseFloatDataSi(result, rows(), matrix.cols()), siUnit);
259     }
260 
261     /**
262      * Multiply this vector or matrix with a Vector1, resulting in a MatrixNxM. The multiplication is a (Mx1) x (1x1) matrix
263      * multiplication resulting in an (Mx1) matrix.
264      * @param vector the vector to multiply with
265      * @return a MatrixNxM of an SIQuantity as the result of the matrix multiplication
266      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
267      *             vector to multiply with
268      */
269     public MatrixNxM<SIQuantity> multiply(final Vector1<?> vector)
270     {
271         checkMultiply(vector);
272         double[] result = MatrixMath.multiply(unsafeSiArray(), vector.unsafeSiArray(), rows(), cols(), vector.cols());
273         SIUnit siUnit = getDisplayUnit().siUnit().plus(vector.getDisplayUnit().siUnit());
274         return new MatrixNxM<SIQuantity>(new DenseDoubleDataSi(result, rows(), vector.cols()), siUnit);
275     }
276 
277     /**
278      * Multiply this vector or matrix with a Vector2.Col, resulting in a Vector2.Col. The multiplication is a (Mx2) x (2x1)
279      * matrix multiplication resulting in an (Mx1) column vector.
280      * @param vector the vector to multiply with
281      * @return a VectorN.Col of an SIQuantity as the result of the matrix multiplication
282      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
283      *             vector to multiply with
284      */
285     public VectorN.Col<SIQuantity> multiply(final Vector2.Col<?> vector)
286     {
287         checkMultiply(vector);
288         double[] result = MatrixMath.multiply(unsafeSiArray(), vector.unsafeSiArray(), rows(), cols(), vector.cols());
289         SIUnit siUnit = getDisplayUnit().siUnit().plus(vector.getDisplayUnit().siUnit());
290         return new VectorN.Col<SIQuantity>(new DenseDoubleDataSi(result, rows(), vector.cols()), siUnit);
291     }
292 
293     /**
294      * Multiply this vector or matrix with a Vector3.Col, resulting in a Vector3.Col. The multiplication is a (Mx3) x (3x1)
295      * matrix multiplication resulting in an (Mx1) column vector.
296      * @param vector the vector to multiply with
297      * @return a VectorN.Col of an SIQuantity as the result of the matrix multiplication
298      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
299      *             vector to multiply with
300      */
301     public VectorN.Col<SIQuantity> multiply(final Vector3.Col<?> vector)
302     {
303         checkMultiply(vector);
304         double[] result = MatrixMath.multiply(unsafeSiArray(), vector.unsafeSiArray(), rows(), cols(), vector.cols());
305         SIUnit siUnit = getDisplayUnit().siUnit().plus(vector.getDisplayUnit().siUnit());
306         return new VectorN.Col<SIQuantity>(new DenseDoubleDataSi(result, rows(), vector.cols()), siUnit);
307     }
308 
309     /**
310      * Multiply this vector or matrix with a VectorN.Col, resulting in a VectorN.Col. The multiplication is a (MxN) x (Nx1)
311      * matrix multiplication resulting in an (Mx1) column vector.
312      * @param vector the vector to multiply with
313      * @return a VectorN.Col of an SIQuantity as the result of the matrix multiplication
314      * @throws IllegalArgumentException when the number of columns of this matrix does not equal the number of rows of the
315      *             vector to multiply with
316      */
317     public VectorN.Col<SIQuantity> multiply(final VectorN.Col<?> vector)
318     {
319         checkMultiply(vector);
320         double[] result = MatrixMath.multiply(unsafeSiArray(), vector.unsafeSiArray(), rows(), cols(), vector.cols());
321         SIUnit siUnit = getDisplayUnit().siUnit().plus(vector.getDisplayUnit().siUnit());
322         return new VectorN.Col<SIQuantity>(new DenseDoubleDataSi(result, rows(), vector.cols()), siUnit);
323     }
324 
325     // ------------------------------------------ OF METHODS ------------------------------------------
326 
327     /**
328      * Create a new MatrixNxM with a unit, based on a row-major array with values in the given unit.
329      * @param dataInUnit the matrix values {a11, a12, ..., A1M, ..., aN1, aN2, ..., aNM} expressed in the unit
330      * @param rows the number of rows
331      * @param cols the number of columns
332      * @param unit the unit of the data, also used as the display unit
333      * @param <Q> the quantity type
334      * @return a new MatrixNxM with a unit
335      * @throws IllegalArgumentException when dataInUnit does not contain a square number of values
336      */
337     public static <Q extends Quantity<Q>> MatrixNxM<Q> of(final double[] dataInUnit, final int rows, final int cols,
338             final Unit<?, Q> unit)
339     {
340         return new MatrixNxM<Q>(DenseDoubleDataSi.of(dataInUnit, rows, cols, unit), unit);
341     }
342 
343     /**
344      * Create a MatrixNxM without needing generics, based on a row-major array with SI-values.
345      * @param dataSi the matrix values {a11, a12, ..., A1M, ..., aN1, aN2, ..., aNM} as an array using SI units
346      * @param rows the number of rows
347      * @param cols the number of columns
348      * @param displayUnit the display unit to use
349      * @return a new MatrixNxM with a unit
350      * @param <Q> the quantity type
351      * @throws IllegalArgumentException when dataSi does not contain a square number of values
352      */
353     public static <Q extends Quantity<Q>> MatrixNxM<Q> ofSi(final double[] dataSi, final int rows, final int cols,
354             final Unit<?, Q> displayUnit)
355     {
356         return new MatrixNxM<Q>(DenseDoubleDataSi.ofSi(dataSi, rows, cols), displayUnit);
357     }
358 
359     /**
360      * Create a MatrixNxM without needing generics, based on a row-major array of quantities. The unit is taken from the first
361      * quantity in the array.
362      * @param data the matrix values {a11, a12, ..., A1M, ..., aN1, aN2, ..., aNM} expressed as an array of quantities
363      * @param rows the number of rows
364      * @param cols the number of columns
365      * @return a new MatrixNxM with a unit
366      * @param <Q> the quantity type
367      * @throws IllegalArgumentException when data does not contain a square number of quantities
368      */
369     public static <Q extends Quantity<Q>> MatrixNxM<Q> of(final Q[] data, final int rows, final int cols)
370     {
371         Throw.whenNull(data, "data");
372         Throw.when(data.length == 0, IllegalArgumentException.class, "data.length = 0");
373         return new MatrixNxM<Q>(DenseDoubleDataSi.of(data, rows, cols), data[0].getDisplayUnit());
374     }
375 
376     /**
377      * Create a new MatrixNxM with a unit, based on a 2-dimensional grid with SI-values.
378      * @param gridSi the matrix values {{a11, a12, ..., A1M}, ..., {aN1, aN2, ..., aNM}} expressed in the SI or base unit
379      * @param displayUnit the unit of the data, which will also be used as the display unit
380      * @param <Q> the quantity type
381      * @return a new MatrixNxM with a unit
382      * @throws IllegalArgumentException when dataInUnit does not contain a square number of values
383      */
384     @SuppressWarnings("checkstyle:needbraces")
385     public static <Q extends Quantity<Q>> MatrixNxM<Q> ofSi(final double[][] gridSi, final Unit<?, Q> displayUnit)
386     {
387         return new MatrixNxM<>(DenseDoubleDataSi.ofSi(gridSi), displayUnit);
388     }
389 
390     /**
391      * Create a new MatrixNxM with a unit, based on a 2-dimensional grid with values in the given unit.
392      * @param gridInUnit the matrix values {{a11, a12, ..., A1M}, ..., {aN1, aN2, ..., aNM}} expressed in the unit
393      * @param unit the unit of the values, also used as the display unit
394      * @param <Q> the quantity type
395      * @return a new MatrixNxM with a unit
396      * @throws IllegalArgumentException when dataInUnit does not contain a square number of values
397      */
398     @SuppressWarnings("checkstyle:needbraces")
399     public static <Q extends Quantity<Q>> MatrixNxM<Q> of(final double[][] gridInUnit, final Unit<?, Q> unit)
400     {
401         return new MatrixNxM<>(DenseDoubleDataSi.of(gridInUnit, unit), unit);
402     }
403 
404     /**
405      * Create a MatrixNxM without needing generics, based on a 2-dimensional grid of quantities. The unit is taken from the
406      * first quantity in the grid.
407      * @param grid the matrix values {{a11, a12, ..., A1M}, ..., {aN1, aN2, ..., aNM}} expressed as a 2-dimensional array of
408      *            quantities
409      * @return a new MatrixNxM with a unit
410      * @param <Q> the quantity type
411      * @throws IllegalArgumentException when dataInUnit does not contain a square number of quantities
412      */
413     public static <Q extends Quantity<Q>> MatrixNxM<Q> of(final Q[][] grid)
414     {
415         Throw.whenNull(grid, "grid");
416         Throw.when(grid.length == 0, IllegalArgumentException.class, "grid.length = 0");
417         Throw.whenNull(grid[0], "grid[0] = null");
418         Throw.when(grid[0].length == 0, IllegalArgumentException.class, "grid[0].length = 0");
419         Throw.whenNull(grid[0][0], "grid[0][0] = null");
420         return new MatrixNxM<>(DenseDoubleDataSi.of(grid), grid[0][0].getDisplayUnit());
421     }
422 
423     // ------------------------------------------------- AS() METHODS -------------------------------------------------
424 
425     /**
426      * Return the matrix 'as' a matrix with a known quantity, using a unit to express the result in. Throw a Runtime exception
427      * when the SI units of this vector and the target vector do not match.
428      * @param targetUnit the unit to convert the matrix to
429      * @return a matrix typed in the target matrix class
430      * @throws IllegalArgumentException when the units do not match
431      * @param <TQ> target quantity type
432      */
433     public <TQ extends Quantity<TQ>> MatrixNxM<TQ> as(final Unit<?, TQ> targetUnit) throws IllegalArgumentException
434     {
435         Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
436                 "MatrixNxM.as(%s) called, but units do not match: %s <> %s", targetUnit,
437                 getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
438         return new MatrixNxM<TQ>(this.dataGridSi.instantiateNew(unsafeSiArray()), targetUnit);
439     }
440 
441 }