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