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 }