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.UnitInterface;
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   * @param <U> the unit type
30   */
31  public class MatrixNxN<Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>>
32          extends SquareMatrix<Q, U, MatrixNxN<Q, U>, MatrixNxN<SIQuantity, SIUnit>, MatrixNxN<?, ?>>
33  {
34      /** */
35      private static final long serialVersionUID = 600L;
36  
37      /** The data of the matrix, in SI unit. */
38      private final DataGridSi<?> dataSi;
39  
40      /**
41       * Create a new NxN Matrix with a unit, based on a DataGrid storage object that contains SI data.
42       * @param dataSi the data of the matrix, in SI unit.
43       * @param displayUnit the display unit to use
44       * @throws IllegalArgumentException when the number of rows or columns does not have a positive value
45       */
46      public MatrixNxN(final DataGridSi<?> dataSi, final U displayUnit)
47      {
48          super(displayUnit);
49          Throw.whenNull(dataSi, "dataSi");
50          Throw.when(dataSi.rows() != dataSi.cols(), IllegalArgumentException.class, "Data for the NxN matrix is not square");
51          this.dataSi = dataSi;
52      }
53  
54      /**
55       * Create a new MatrixNxN with a unit, based on a 1-dimensional array.
56       * @param valueArrayInUnit the matrix values {a11, a12, ..., aN1, aN32, ..., aNN} expressed in the display unit
57       * @param displayUnit the display unit to use
58       * @param <Q> the quantity type
59       * @param <U> the unit type
60       * @return a new MatrixNxN with a unit
61       * @throws IllegalArgumentException when the number of entries in the valueArray is not a perfect square
62       */
63      @SuppressWarnings("checkstyle:needbraces")
64      public static <Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>> MatrixNxN<Q, U> of(final double[] valueArrayInUnit,
65              final U displayUnit)
66      {
67          Throw.whenNull(valueArrayInUnit, "valueArrayInUnit");
68          Throw.whenNull(displayUnit, "displayUnit");
69          int len = valueArrayInUnit.length;
70          // This check works for any (positive) int. The largest perfect square fitting in an int is 46340^2 = 2,147,395,600,
71          // slightly below Integer.MAX_VALUE = 2,147,483,647. All integers up to that value have square roots <= 46340, which
72          // easily fit in the IEEE 754 double mantissa (53 bits), so perfect squares have exact square roots in double.
73          // Therefore, (int) Math.sqrt(n) is guaranteed correct for any 32-bit integer when checking perfect squares. The
74          // complexity of this check is O(1).
75          int n = (int) Math.sqrt(len);
76          Throw.when(len != n * n, IllegalArgumentException.class,
77                  "valueArrayInUnit does not contain a square number of entries (%d)", len);
78          double[] aSi = new double[valueArrayInUnit.length];
79          for (int i = 0; i < valueArrayInUnit.length; i++)
80              aSi[i] = displayUnit.toBaseValue(valueArrayInUnit[i]);
81          return new MatrixNxN<Q, U>(new DenseDoubleDataSi(aSi, n, n), displayUnit);
82      }
83  
84      /**
85       * Create a new MatrixNxN with a unit, based on a 2-dimensional grid.
86       * @param valueGridInUnit the matrix values {{a11, a12, a13}, ..., {a31, a32, a33}} expressed in the display unit
87       * @param displayUnit the display unit to use
88       * @param <Q> the quantity type
89       * @param <U> the unit type
90       * @return a new MatrixNxN with a unit
91       * @throws IllegalArgumentException when valueGrid has 0 rows, or when the number of columns for one of the rows is not
92       *             equal to the number of rows
93       */
94      @SuppressWarnings("checkstyle:needbraces")
95      public static <Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>> MatrixNxN<Q, U> of(final double[][] valueGridInUnit,
96              final U displayUnit)
97      {
98          Throw.whenNull(valueGridInUnit, "valueGridInUnit");
99          Throw.whenNull(displayUnit, "displayUnit");
100         int n = valueGridInUnit.length;
101         Throw.when(n == 0, IllegalArgumentException.class, "valueGridInUnit has 0 rows");
102         double[] aSi = new double[n * n];
103         for (int r = 0; r < valueGridInUnit.length; r++)
104         {
105             Throw.when(valueGridInUnit[r].length != n, IllegalArgumentException.class,
106                     "valueGridInUnit is not a NxN array; row %d has a length of %d, not %d", r, valueGridInUnit[r].length, n);
107             for (int c = 0; c < n; c++)
108                 aSi[n * r + c] = displayUnit.toBaseValue(valueGridInUnit[r][c]);
109         }
110         return new MatrixNxN<Q, U>(new DenseDoubleDataSi(aSi, n, n), displayUnit);
111     }
112 
113     @Override
114     public MatrixNxN<Q, U> instantiateSi(final double[] siNew)
115     {
116         return new MatrixNxN<Q, U>(this.dataSi.instantiateNew(siNew), getDisplayUnit().getBaseUnit())
117                 .setDisplayUnit(getDisplayUnit());
118     }
119 
120     @Override
121     public double[] si()
122     {
123         return this.dataSi.getDataArray();
124     }
125 
126     @Override
127     public double si(final int row, final int col)
128     {
129         return this.dataSi.get(row, col);
130     }
131 
132     @Override
133     public int rows()
134     {
135         return this.dataSi.rows();
136     }
137 
138     @Override
139     public int cols()
140     {
141         return this.dataSi.cols();
142     }
143 
144     /**
145      * Return the data grid in SI units.
146      * @return the data grid in SI units
147      */
148     public DataGridSi<?> getDataGrid()
149     {
150         return this.dataSi;
151     }
152 
153     @Override
154     public MatrixNxN<SIQuantity, SIUnit> instantiateSi(final double[] siNew, final SIUnit siUnit)
155     {
156         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(siNew), siUnit);
157     }
158 
159     @Override
160     public VectorN.Row<Q, U> getRowVector(final int row)
161     {
162         checkRow(row);
163         return VectorN.Row.ofSi(this.dataSi.getRowArray(row), getDisplayUnit());
164     }
165 
166     @Override
167     public VectorN.Row<Q, U> mgetRowVector(final int mRow)
168     {
169         mcheckRow(mRow);
170         return VectorN.Row.ofSi(this.dataSi.getRowArray(mRow - 1), getDisplayUnit());
171     }
172 
173     @Override
174     public VectorN.Col<Q, U> getColumnVector(final int col)
175     {
176         checkCol(col);
177         return VectorN.Col.ofSi(this.dataSi.getColArray(col), getDisplayUnit());
178     }
179 
180     @Override
181     public VectorN.Col<Q, U> mgetColumnVector(final int mCol)
182     {
183         mcheckCol(mCol);
184         return VectorN.Col.ofSi(this.dataSi.getColArray(mCol - 1), getDisplayUnit());
185     }
186 
187     @Override
188     public VectorN.Col<Q, U> getDiagonalVector() throws IllegalStateException
189     {
190         final int n = rows();
191         final double[] data = new double[n];
192         for (int i = 0; i < n; i++)
193         {
194             data[i] = si(i, i);
195         }
196         // n x 1 column-shape
197         return VectorN.Col.ofSi(data, getDisplayUnit());
198     }
199 
200     @Override
201     public double[] getRowSi(final int row)
202     {
203         checkRow(row);
204         return this.dataSi.getRowArray(row);
205     }
206 
207     @Override
208     public double[] getColumnSi(final int col)
209     {
210         checkCol(col);
211         return this.dataSi.getColArray(col);
212     }
213 
214     @Override
215     public MatrixNxN<SIQuantity, SIUnit> inverse() throws NonInvertibleMatrixException
216     {
217         double[] invData = MatrixMath.inverse(si(), order());
218         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(invData), getDisplayUnit().siUnit().invert());
219     }
220 
221     @Override
222     public MatrixNxN<SIQuantity, SIUnit> adjugate()
223     {
224         double[] invData = MatrixMath.adjugate(si(), order());
225         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(invData),
226                 getDisplayUnit().siUnit().pow(order() - 1));
227     }
228 
229     @Override
230     public MatrixNxN<SIQuantity, SIUnit> invertElements()
231     {
232         SIUnit siUnit = getDisplayUnit().siUnit().invert();
233         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(ArrayMath.reciprocal(si())), siUnit);
234     }
235 
236     @Override
237     public MatrixNxN<SIQuantity, SIUnit> multiplyElements(final MatrixNxN<?, ?> other)
238     {
239         SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
240         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(ArrayMath.multiply(si(), other.si())), siUnit);
241     }
242 
243     @Override
244     public MatrixNxN<SIQuantity, SIUnit> divideElements(final MatrixNxN<?, ?> other)
245     {
246         SIUnit siUnit = SIUnit.subtract(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
247         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(ArrayMath.divide(si(), other.si())), siUnit);
248     }
249 
250     @Override
251     public MatrixNxN<SIQuantity, SIUnit> multiplyElements(final Quantity<?, ?> quantity)
252     {
253         SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), quantity.getDisplayUnit().siUnit());
254         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(ArrayMath.scaleBy(si(), quantity.si())), siUnit);
255     }
256 
257     @Override
258     public int hashCode()
259     {
260         return Objects.hash(this.dataSi);
261     }
262 
263     @SuppressWarnings("checkstyle:needbraces")
264     @Override
265     public boolean equals(final Object obj)
266     {
267         if (this == obj)
268             return true;
269         if (obj == null)
270             return false;
271         if (getClass() != obj.getClass())
272             return false;
273         MatrixNxN<?, ?> other = (MatrixNxN<?, ?>) obj;
274         return Objects.equals(this.dataSi, other.dataSi);
275     }
276 
277     // ------------------------------ MATRIX MULTIPLICATION AND AS() --------------------------
278 
279     /**
280      * Multiply this matrix with another matrix using matrix multiplication and return the result.
281      * <p>
282      * 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
283      * underlying {@link SIUnit}s).
284      * @param otherMat the right-hand matrix to multiply with
285      * @return the product matrix with the correct SI unit
286      */
287     public MatrixNxN<SIQuantity, SIUnit> multiply(final MatrixNxN<?, ?> otherMat)
288     {
289         checkMultiply(otherMat);
290         final int n = order();
291         final double[] resultData = MatrixMath.multiply(si(), otherMat.si(), n, n, n);
292         final SIUnit resultUnit = getDisplayUnit().siUnit().plus(otherMat.getDisplayUnit().siUnit());
293         return new MatrixNxN<SIQuantity, SIUnit>(this.dataSi.instantiateNew(resultData), resultUnit);
294     }
295 
296     /**
297      * Multiply this matrix with a column vector, resulting in a column vector.
298      * <p>
299      * 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
300      * {@link SIUnit}s).
301      * @param otherVec the column vector to multiply with (size {@code N})
302      * @return the resulting column vector from the multiplication
303      * @throws IllegalArgumentException if the vector size does not equal {@code order()}
304      */
305     public VectorN.Col<SIQuantity, SIUnit> multiply(final VectorN.Col<?, ?> otherVec)
306     {
307         checkMultiply(otherVec);
308         final int n = order();
309         final double[] resultData = MatrixMath.multiply(si(), otherVec.si(), n, n, 1);
310         final SIUnit resultUnit = getDisplayUnit().siUnit().plus(otherVec.getDisplayUnit().siUnit());
311         return VectorN.Col.ofSi(resultData, resultUnit);
312     }
313 
314     /**
315      * Return the matrix "as" a matrix with a known quantity, using a unit to express the result in.
316      * <p>
317      * The SI units of this matrix and the target unit must match; otherwise an {@link IllegalArgumentException} is thrown. The
318      * returned matrix shares the SI values but has the specified display unit.
319      * @param <TQ> target quantity type
320      * @param <TU> target unit type
321      * @param targetUnit the unit to convert the matrix to
322      * @return a matrix typed in the target quantity with the specified display unit
323      * @throws IllegalArgumentException when the units do not match
324      */
325     public <TQ extends Quantity<TQ, TU>, TU extends UnitInterface<TU, TQ>> MatrixNxN<TQ, TU> as(final TU targetUnit)
326     {
327         Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
328                 "MatrixNxN.as(%s) called, but units do not match: %s <> %s", targetUnit,
329                 getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
330         return new MatrixNxN<TQ, TU>(this.dataSi.instantiateNew(si()), targetUnit.getBaseUnit()).setDisplayUnit(targetUnit);
331     }
332 
333     /**
334      * Convert this matrix to a {@link Matrix1x1}. The shape must be 1 x 1.
335      * @return a {@code Matrix1x1} with identical SI data and display unit
336      * @throws IllegalStateException if this matrix is not 1 x 1
337      */
338     public Matrix1x1<Q, U> asMatrix1x1()
339     {
340         Throw.when(order() != 1, IllegalStateException.class, "asMatrix1x1() called, but matrix is no 1x1 but %dx%d", rows(),
341                 cols());
342         return Matrix1x1.of(si(), getDisplayUnit().getBaseUnit()).setDisplayUnit(getDisplayUnit());
343     }
344 
345     /**
346      * Convert this matrix to a {@link Matrix2x2}. The shape must be 2 x 2.
347      * @return a {@code Matrix2x2} with identical SI data and display unit
348      * @throws IllegalStateException if this matrix is not 2 x 2
349      */
350     public Matrix2x2<Q, U> asMatrix2x2()
351     {
352         Throw.when(order() != 2, IllegalStateException.class, "asMatrix2x2() called, but matrix is no 2x2 but %dx%d", rows(),
353                 cols());
354         return Matrix2x2.of(si(), getDisplayUnit().getBaseUnit()).setDisplayUnit(getDisplayUnit());
355     }
356 
357     /**
358      * Convert this matrix to a {@link Matrix3x3}. The shape must be 3 x 3.
359      * @return a {@code Matrix3x3} with identical SI data and display unit
360      * @throws IllegalStateException if this matrix is not 3 x 3
361      */
362     public Matrix3x3<Q, U> asMatrix3x3()
363     {
364         Throw.when(order() != 3, IllegalStateException.class, "asMatrix3x3() called, but matrix is no 3x3 but %dx%d", rows(),
365                 cols());
366         return Matrix3x3.of(si(), getDisplayUnit().getBaseUnit()).setDisplayUnit(getDisplayUnit());
367     }
368 
369 }