View Javadoc
1   package org.djunits.value.vdouble.matrix.base;
2   
3   import java.lang.reflect.Array;
4   
5   import org.djunits.unit.Unit;
6   import org.djunits.value.Absolute;
7   import org.djunits.value.ValueRuntimeException;
8   import org.djunits.value.base.Matrix;
9   import org.djunits.value.formatter.Format;
10  import org.djunits.value.storage.StorageType;
11  import org.djunits.value.util.ValueUtil;
12  import org.djunits.value.vdouble.function.DoubleFunction;
13  import org.djunits.value.vdouble.function.DoubleMathFunctions;
14  import org.djunits.value.vdouble.matrix.data.DoubleMatrixData;
15  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
16  import org.djunits.value.vdouble.vector.base.DoubleVector;
17  import org.djunits.value.vdouble.vector.data.DoubleVectorData;
18  import org.djutils.exceptions.Throw;
19  
20  /**
21   * DoubleMatrix utility methods, e.g., for creating DoubleMatrixs from different types of data.
22   * <p>
23   * Copyright (c) 2015-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
24   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
25   * </p>
26   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
27   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
28   * @param <U> the unit
29   * @param <S> the scalar with unit U
30   * @param <V> the vector type belonging to the matrix type
31   * @param <M> the generic matrix type
32   */
33  public abstract class DoubleMatrix<U extends Unit<U>, S extends DoubleScalar<U, S>, V extends DoubleVector<U, S, V>,
34          M extends DoubleMatrix<U, S, V, M>> extends Matrix<U, S, V, DoubleVectorData, M, DoubleMatrixData>
35  {
36      /** */
37      private static final long serialVersionUID = 20161015L;
38  
39      /** The stored data as an object, can be sparse or dense. */
40      @SuppressWarnings("checkstyle:visibilitymodifier")
41      protected DoubleMatrixData data;
42  
43      /**
44       * Construct a new DoubleMatrix.
45       * @param data DoubleMatrixData; an internal data object
46       * @param unit U; the unit
47       */
48      public DoubleMatrix(final DoubleMatrixData data, final U unit)
49      {
50          super(unit);
51          Throw.whenNull(data, "data cannot be null");
52          this.data = data;
53      }
54  
55      /**
56       * Instantiate a new matrix of the class of this matrix. This can be used instead of the DoubleMatrix.instiantiate() methods
57       * in case another matrix of this class is known. The method is faster than DoubleMatrix.instantiate, and it will also work
58       * if the matrix is user-defined.
59       * @param dmd DoubleMatrixData; the data used to instantiate the matrix
60       * @param displayUnit U; the display unit of the matrix
61       * @return V; a matrix of the correct type
62       */
63      public abstract M instantiateMatrix(DoubleMatrixData dmd, U displayUnit);
64  
65      /**
66       * Instantiate a new vector of the class of this matrix. This can be used instead of the DoubleVector.instiantiate() methods
67       * in case another matrix of this class is known. The method is faster than DoubleVector.instantiate, and it will also work
68       * if the matrix and/or vector are user-defined.
69       * @param dvd DoubleVectorData; the data used to instantiate the vector
70       * @param displayUnit U; the display unit of the vector
71       * @return V; a vector of the correct type
72       */
73      public abstract V instantiateVector(DoubleVectorData dvd, U displayUnit);
74  
75      /**
76       * Instantiate a new scalar for the class of this matrix. This can be used instead of the DoubleScalar.instiantiate()
77       * methods in case a matrix of this class is known. The method is faster than DoubleScalar.instantiate, and it will also
78       * work if the matrix and/or scalar are user-defined.
79       * @param valueSI double; the SI value of the scalar
80       * @param displayUnit U; the unit in which the value will be displayed
81       * @return S; a scalar of the correct type, belonging to the matrix type
82       */
83      public abstract S instantiateScalarSI(double valueSI, U displayUnit);
84  
85      @Override
86      protected final DoubleMatrixData getData()
87      {
88          return this.data;
89      }
90  
91      @Override
92      protected void setData(final DoubleMatrixData data)
93      {
94          this.data = data;
95      }
96  
97      /**
98       * Retrieve the value stored at a specified row and column in the standard SI unit.
99       * @param row int; row of the value to retrieve
100      * @param column int; column of the value to retrieve
101      * @return double; value at position row, column in the standard SI unit
102      * @throws IndexOutOfBoundsException when row or column out of range (row &lt; 0 or row &gt;= rows() or column &lt; 0 or
103      *             column &gt;= columns())
104      */
105     public double getSI(final int row, final int column) throws IndexOutOfBoundsException
106     {
107         checkIndex(row, column);
108         return this.data.getSI(row, column);
109     }
110 
111     /**
112      * Retrieve the value stored at a specified row and column in the original unit.
113      * @param row int; row of the value to retrieve
114      * @param column int; column of the value to retrieve
115      * @return double; value at position row, column in the original unit
116      * @throws IndexOutOfBoundsException when row or column out of range (row &lt; 0 or row &gt;= rows() or column &lt; 0 or
117      *             column &gt;= columns())
118      */
119     public double getInUnit(final int row, final int column) throws IndexOutOfBoundsException
120     {
121         checkIndex(row, column);
122         return ValueUtil.expressAsUnit(this.data.getSI(row, column), getDisplayUnit());
123     }
124 
125     /**
126      * Retrieve the value stored at a specified row and column converted into a specified unit.
127      * @param row int; row of the value to retrieve
128      * @param column int; column of the value to retrieve
129      * @param targetUnit U; the unit for the result
130      * @return double; value at position row, column converted into the specified unit
131      * @throws IndexOutOfBoundsException when row or column out of range (row &lt; 0 or row &gt;= rows() or column &lt; 0 or
132      *             column &gt;= columns())
133      */
134     public double getInUnit(final int row, final int column, final U targetUnit) throws IndexOutOfBoundsException
135     {
136         checkIndex(row, column);
137         return ValueUtil.expressAsUnit(this.data.getSI(row, column), targetUnit);
138     }
139 
140     /**
141      * Set the value, specified in the standard SI unit, at the specified position.
142      * @param row int; row of the value to set
143      * @param column int; column of the value to set
144      * @param valueSI double; the value, specified in the standard SI unit
145      * @throws IndexOutOfBoundsException when index out of range (index &lt; 0 or index &gt;= size())
146      */
147     public void setSI(final int row, final int column, final double valueSI) throws IndexOutOfBoundsException
148     {
149         checkIndex(row, column);
150         checkCopyOnWrite();
151         this.data.setSI(row, column, valueSI);
152     }
153 
154     /**
155      * Set the value, specified in the (current) display unit, at the specified position.
156      * @param row int; row of the value to set
157      * @param column int; column of the value to set
158      * @param valueInUnit double; the value, specified in the (current) display unit
159      * @throws IndexOutOfBoundsException when index out of range (index &lt; 0 or index &gt;= size())
160      */
161     public void setInUnit(final int row, final int column, final double valueInUnit) throws IndexOutOfBoundsException
162     {
163         setSI(row, column, ValueUtil.expressAsSIUnit(valueInUnit, getDisplayUnit()));
164     }
165 
166     /**
167      * Set the value, specified in the <code>valueUnit</code>, at the specified position.
168      * @param row int; row of the value to set
169      * @param column int; column of the value to set
170      * @param valueInUnit double; the value, specified in the (current) display unit
171      * @param valueUnit U; the unit in which the <code>valueInUnit</code> is expressed
172      * @throws IndexOutOfBoundsException when index out of range (index &lt; 0 or index &gt;= size())
173      */
174     public void setInUnit(final int row, final int column, final double valueInUnit, final U valueUnit)
175             throws IndexOutOfBoundsException
176     {
177         setSI(row, column, ValueUtil.expressAsSIUnit(valueInUnit, valueUnit));
178     }
179 
180     /**
181      * Set the scalar value at the specified position.
182      * @param row int; row of the value to set
183      * @param column int; column of the value to set
184      * @param value S; the value to set
185      * @throws IndexOutOfBoundsException when index out of range (index &lt; 0 or index &gt;= size())
186      */
187     public void set(final int row, final int column, final S value) throws IndexOutOfBoundsException
188     {
189         setSI(row, column, value.si);
190     }
191 
192     /**
193      * Retrieve a row from the matrix as an array of double.
194      * @param row int; row of the values to retrieve
195      * @return S[]; the row as a double array
196      * @throws IndexOutOfBoundsException in case row is out of bounds
197      */
198     public double[] getRowSI(final int row) throws IndexOutOfBoundsException
199     {
200         checkRowIndex(row);
201         double[] result = new double[this.data.cols()];
202         for (int col = 0; col < result.length; col++)
203         {
204             result[col] = this.data.getSI(row, col);
205         }
206         return result;
207     }
208 
209     /**
210      * Retrieve a column from the matrix as an array of double.
211      * @param column int; column of the values to retrieve
212      * @return S[]; the column as a double array
213      * @throws IndexOutOfBoundsException in case column is out of bounds
214      */
215     public double[] getColumnSI(final int column) throws IndexOutOfBoundsException
216     {
217         checkColumnIndex(column);
218         double[] result = new double[this.data.rows()];
219         for (int row = 0; row < result.length; row++)
220         {
221             result[row] = this.data.getSI(row, column);
222         }
223         return result;
224     }
225 
226     /**
227      * Retrieve the main diagonal of the matrix as an array of double.
228      * @return V; the main diagonal as a double array
229      * @throws ValueRuntimeException in case the matrix is not square
230      */
231     public double[] getDiagonalSI() throws ValueRuntimeException
232     {
233         checkSquare();
234         double[] result = new double[this.data.rows()];
235         for (int row = 0; row < result.length; row++)
236         {
237             result[row] = this.data.getSI(row, row);
238         }
239         return result;
240     }
241 
242     /**
243      * Create a dense double[][] array filled with the values in the standard SI unit.
244      * @return double[][]; array of values in the standard SI unit
245      */
246     public final double[][] getValuesSI()
247     {
248         return this.data.getDenseMatrixSI();
249     }
250 
251     /**
252      * Create a dense double[][] array filled with the values in the original unit.
253      * @return double[][]; the values in the original unit
254      */
255     public final double[][] getValuesInUnit()
256     {
257         return getValuesInUnit(getDisplayUnit());
258     }
259 
260     /**
261      * Create a dense double[][] array filled with the values converted into a specified unit.
262      * @param targetUnit U; the unit into which the values are converted for use
263      * @return double[][]; the values converted into the specified unit
264      */
265     public final double[][] getValuesInUnit(final U targetUnit)
266     {
267         double[][] values = getValuesSI();
268         for (int i = values.length; --i >= 0;)
269         {
270             for (int j = values[i].length; --j >= 0;)
271             {
272                 values[i][j] = ValueUtil.expressAsUnit(values[i][j], targetUnit);
273             }
274         }
275         return values;
276     }
277 
278     @Override
279     public int rows()
280     {
281         return this.data.rows();
282     }
283 
284     @Override
285     public int cols()
286     {
287         return this.data.cols();
288     }
289 
290     @SuppressWarnings("unchecked")
291     @Override
292     public S[][] getScalars()
293     {
294         S[][] array = (S[][]) Array.newInstance(getScalarClass(), rows(), cols());
295         for (int i = 0; i < rows(); i++)
296         {
297             S[] row = (S[]) Array.newInstance(getScalarClass(), cols());
298             array[i] = row;
299             for (int j = 0; j < cols(); j++)
300             {
301                 row[j] = get(i, j);
302             }
303         }
304         return array;
305     }
306 
307     @Override
308     public S get(final int row, final int column) throws IndexOutOfBoundsException
309     {
310         checkIndex(row, column);
311         return DoubleScalar.instantiateSI(getSI(row, column), getDisplayUnit());
312     }
313 
314     @Override
315     public V getRow(final int row) throws IndexOutOfBoundsException
316     {
317         checkRowIndex(row);
318         DoubleVectorData dvd =
319                 DoubleVectorData.instantiate(getRowSI(row), getDisplayUnit().getStandardUnit().getScale(), getStorageType());
320         return instantiateVector(dvd, getDisplayUnit());
321     }
322 
323     @Override
324     public V getColumn(final int column) throws IndexOutOfBoundsException
325     {
326         checkColumnIndex(column);
327         DoubleVectorData dvd = DoubleVectorData.instantiate(getColumnSI(column), getDisplayUnit().getStandardUnit().getScale(),
328                 getStorageType());
329         return instantiateVector(dvd, getDisplayUnit());
330     }
331 
332     @Override
333     public V getDiagonal() throws ValueRuntimeException
334     {
335         checkSquare();
336         DoubleVectorData dvd =
337                 DoubleVectorData.instantiate(getDiagonalSI(), getDisplayUnit().getStandardUnit().getScale(), getStorageType());
338         return instantiateVector(dvd, getDisplayUnit());
339     }
340 
341     @SuppressWarnings("unchecked")
342     @Override
343     public S[] getRowScalars(final int row) throws IndexOutOfBoundsException
344     {
345         checkRowIndex(row);
346         S[] array = (S[]) Array.newInstance(getScalarClass(), cols());
347         for (int col = 0; col < cols(); col++)
348         {
349             array[col] = get(row, col);
350         }
351         return array;
352     }
353 
354     @SuppressWarnings("unchecked")
355     @Override
356     public S[] getColumnScalars(final int col) throws IndexOutOfBoundsException
357     {
358         checkColumnIndex(col);
359         S[] array = (S[]) Array.newInstance(getScalarClass(), rows());
360         for (int row = 0; row < rows(); row++)
361         {
362             array[row] = get(row, col);
363         }
364         return array;
365     }
366 
367     @SuppressWarnings("unchecked")
368     @Override
369     public S[] getDiagonalScalars() throws ValueRuntimeException
370     {
371         checkSquare();
372         S[] array = (S[]) Array.newInstance(getScalarClass(), rows());
373         for (int row = 0; row < rows(); row++)
374         {
375             array[row] = get(row, row);
376         }
377         return array;
378     }
379 
380     @SuppressWarnings("unchecked")
381     @Override
382     public M toSparse()
383     {
384         M result;
385         if (getStorageType().equals(StorageType.SPARSE))
386         {
387             result = (M) this;
388             result.setDisplayUnit(getDisplayUnit());
389         }
390         else
391         {
392             result = instantiateMatrix(this.data.toSparse(), getDisplayUnit());
393         }
394         result.setDisplayUnit(getDisplayUnit());
395         return result;
396     }
397 
398     @SuppressWarnings("unchecked")
399     @Override
400     public M toDense()
401     {
402         M result;
403         if (getStorageType().equals(StorageType.DENSE))
404         {
405             result = (M) this;
406             result.setDisplayUnit(getDisplayUnit());
407         }
408         else
409         {
410             result = instantiateMatrix(this.data.toDense(), getDisplayUnit());
411         }
412         return result;
413     }
414 
415     /**
416      * Execute a function on a cell by cell basis. Note: May be expensive when used on sparse data.
417      * @param doubleFunction DoubleFunction; the function to apply
418      * @return M; this updated matrix
419      */
420     @SuppressWarnings("unchecked")
421     public final M assign(final DoubleFunction doubleFunction)
422     {
423         checkCopyOnWrite();
424         this.data.assign(doubleFunction);
425         return (M) this;
426     }
427 
428     @Override
429     public final M abs()
430     {
431         return assign(DoubleMathFunctions.ABS);
432     }
433 
434     @Override
435     public final M ceil()
436     {
437         return assign(DoubleMathFunctions.CEIL);
438     }
439 
440     @Override
441     public final M floor()
442     {
443         return assign(DoubleMathFunctions.FLOOR);
444     }
445 
446     @Override
447     public final M neg()
448     {
449         return assign(DoubleMathFunctions.NEG);
450     }
451 
452     @Override
453     public final M rint()
454     {
455         return assign(DoubleMathFunctions.RINT);
456     }
457 
458     @Override
459     public String toString()
460     {
461         return toString(getDisplayUnit(), false, true);
462     }
463 
464     @Override
465     public String toString(final U displayUnit)
466     {
467         return toString(displayUnit, false, true);
468     }
469 
470     @Override
471     public String toString(final boolean verbose, final boolean withUnit)
472     {
473         return toString(getDisplayUnit(), verbose, withUnit);
474     }
475 
476     @Override
477     public String toString(final U displayUnit, final boolean verbose, final boolean withUnit)
478     {
479         StringBuffer buf = new StringBuffer();
480         if (verbose)
481         {
482             String ab = this instanceof Absolute ? "Abs " : "Rel ";
483             String ds = this.data.isDense() ? "Dense  " : this.data.isSparse() ? "Sparse " : "?????? ";
484             if (isMutable())
485             {
486                 buf.append("Mutable   " + ab + ds);
487             }
488             else
489             {
490                 buf.append("Immutable " + ab + ds);
491             }
492         }
493         for (int row = 0; row < rows(); row++)
494         {
495             buf.append("\r\n\t");
496             for (int col = 0; col < cols(); col++)
497             {
498                 try
499                 {
500                     double d = ValueUtil.expressAsUnit(getSI(row, col), displayUnit);
501                     buf.append(" " + Format.format(d));
502                 }
503                 catch (IndexOutOfBoundsException ve)
504                 {
505                     buf.append(" " + "********************".substring(0, Format.DEFAULTSIZE));
506                 }
507             }
508         }
509         buf.append("\n");
510         if (withUnit)
511         {
512             buf.append(displayUnit.getLocalizedDisplayAbbreviation());
513         }
514         return buf.toString();
515     }
516 
517     /**
518      * Check that provided row and column indices are valid.
519      * @param row int; the row value to check
520      * @param col int; the column value to check
521      * @throws IndexOutOfBoundsException when row or column is invalid
522      */
523     protected final void checkIndex(final int row, final int col) throws IndexOutOfBoundsException
524     {
525         if (row < 0 || row >= rows() || col < 0 || col >= cols())
526         {
527             throw new IndexOutOfBoundsException("index out of range (valid range is 0.." + (rows() - 1) + ", 0.." + (cols() - 1)
528                     + ", got " + row + ", " + col + ")");
529         }
530     }
531 
532     /**
533      * Check that provided row index is valid.
534      * @param row int; the row value to check
535      * @throws IndexOutOfBoundsException when row is invalid
536      */
537     protected final void checkRowIndex(final int row) throws IndexOutOfBoundsException
538     {
539         if (row < 0 || row >= rows())
540         {
541             throw new IndexOutOfBoundsException(
542                     "row index out of range (valid range is 0.." + (rows() - 1) + ", got " + row + ")");
543         }
544     }
545 
546     /**
547      * Check that provided column index is valid.
548      * @param col int; the column value to check
549      * @throws IndexOutOfBoundsException when row is invalid
550      */
551     protected final void checkColumnIndex(final int col) throws IndexOutOfBoundsException
552     {
553         if (col < 0 || col >= cols())
554         {
555             throw new IndexOutOfBoundsException(
556                     "column index out of range (valid range is 0.." + (cols() - 1) + ", got " + col + ")");
557         }
558     }
559 
560     /**
561      * Check that the matrix is square.
562      * @throws ValueRuntimeException when matrix is not square
563      */
564     protected final void checkSquare() throws ValueRuntimeException
565     {
566         Throw.when(rows() != cols(), ValueRuntimeException.class, "Matrix is not square, rows=%d, cols=%d", rows(), cols());
567     }
568 
569     /**
570      * Compute the determinant of the matrix, based on the SI values in the matrix.
571      * @return double; the determinant of the matrix
572      * @throws ValueRuntimeException when matrix is neither sparse, nor dense, or not square
573      */
574     public final double determinantSI() throws ValueRuntimeException
575     {
576         checkSquare();
577         return det(getValuesSI());
578     }
579 
580     /**
581      * Calculate the determinant of an n x n matrix.
582      * @param mat the matrix
583      * @return the determinant using the co-factor formula
584      */
585     private static double det(final double[][] mat)
586     {
587         if (mat.length == 1)
588         {
589             return mat[0][0];
590         }
591         // det(A) = sum(j=1:n) (-1)^(i+j).a_ij.A_ij where A_ij is the matrix with row i and column j removed
592         double det = 0.0;
593         // possible optimization: pick the row or column with most zeros; here: pick row 0
594         for (int col = 0; col < mat.length; col++)
595         {
596             double sgn = (col % 2 == 0) ? 1 : -1;
597             double aij = mat[0][col];
598             double[][] matAij = new double[mat.length - 1][];
599             int r = 0;
600             for (int row = 1; row < mat.length; row++)
601             {
602                 matAij[r] = new double[matAij.length];
603                 int c = 0;
604                 for (int j = 0; j < mat.length; j++)
605                 {
606                     if (j != col)
607                     {
608                         matAij[r][c++] = mat[row][j];
609                     }
610                 }
611                 r++;
612             }
613             det += sgn * aij * det(matAij);
614         }
615         return det;
616     }
617 
618     @Override
619     @SuppressWarnings("checkstyle:designforextension")
620     public int hashCode()
621     {
622         final int prime = 31;
623         int result = getDisplayUnit().getStandardUnit().hashCode();
624         result = prime * result + ((this.data == null) ? 0 : this.data.hashCode());
625         return result;
626     }
627 
628     @Override
629     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
630     public boolean equals(final Object obj)
631     {
632         if (this == obj)
633             return true;
634         if (obj == null)
635             return false;
636         if (getClass() != obj.getClass())
637             return false;
638         DoubleMatrix<?, ?, ?, ?> other = (DoubleMatrix<?, ?, ?, ?>) obj;
639         if (!getDisplayUnit().getStandardUnit().equals(other.getDisplayUnit().getStandardUnit()))
640             return false;
641         if (this.data == null)
642         {
643             if (other.data != null)
644                 return false;
645         }
646         else if (!this.data.equals(other.data))
647             return false;
648         return true;
649     }
650 
651 }