001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.math; 006 007import edu.wpi.first.math.numbers.N1; 008import java.util.Objects; 009import org.ejml.MatrixDimensionException; 010import org.ejml.data.DMatrixRMaj; 011import org.ejml.dense.row.CommonOps_DDRM; 012import org.ejml.dense.row.MatrixFeatures_DDRM; 013import org.ejml.dense.row.NormOps_DDRM; 014import org.ejml.dense.row.factory.DecompositionFactory_DDRM; 015import org.ejml.interfaces.decomposition.CholeskyDecomposition_F64; 016import org.ejml.simple.SimpleMatrix; 017 018/** 019 * A shape-safe wrapper over Efficient Java Matrix Library (EJML) matrices. 020 * 021 * <p>This class is intended to be used alongside the state space library. 022 * 023 * @param <R> The number of rows in this matrix. 024 * @param <C> The number of columns in this matrix. 025 */ 026public class Matrix<R extends Num, C extends Num> { 027 protected final SimpleMatrix m_storage; 028 029 /** 030 * Constructs an empty zero matrix of the given dimensions. 031 * 032 * @param rows The number of rows of the matrix. 033 * @param columns The number of columns of the matrix. 034 */ 035 public Matrix(Nat<R> rows, Nat<C> columns) { 036 this.m_storage = 037 new SimpleMatrix( 038 Objects.requireNonNull(rows).getNum(), Objects.requireNonNull(columns).getNum()); 039 } 040 041 /** 042 * Constructs a new {@link Matrix} with the given storage. Caller should make sure that the 043 * provided generic bounds match the shape of the provided {@link Matrix}. 044 * 045 * <p>NOTE:It is not recommend to use this constructor unless the {@link SimpleMatrix} API is 046 * absolutely necessary due to the desired function not being accessible through the {@link 047 * Matrix} wrapper. 048 * 049 * @param storage The {@link SimpleMatrix} to back this value. 050 */ 051 public Matrix(SimpleMatrix storage) { 052 this.m_storage = Objects.requireNonNull(storage); 053 } 054 055 /** 056 * Constructs a new matrix with the storage of the supplied matrix. 057 * 058 * @param other The {@link Matrix} to copy the storage of. 059 */ 060 public Matrix(Matrix<R, C> other) { 061 this.m_storage = Objects.requireNonNull(other).getStorage().copy(); 062 } 063 064 /** 065 * Gets the underlying {@link SimpleMatrix} that this {@link Matrix} wraps. 066 * 067 * <p>NOTE:The use of this method is heavily discouraged as this removes any guarantee of type 068 * safety. This should only be called if the {@link SimpleMatrix} API is absolutely necessary due 069 * to the desired function not being accessible through the {@link Matrix} wrapper. 070 * 071 * @return The underlying {@link SimpleMatrix} storage. 072 */ 073 public SimpleMatrix getStorage() { 074 return m_storage; 075 } 076 077 /** 078 * Gets the number of columns in this matrix. 079 * 080 * @return The number of columns, according to the internal storage. 081 */ 082 public final int getNumCols() { 083 return this.m_storage.numCols(); 084 } 085 086 /** 087 * Gets the number of rows in this matrix. 088 * 089 * @return The number of rows, according to the internal storage. 090 */ 091 public final int getNumRows() { 092 return this.m_storage.numRows(); 093 } 094 095 /** 096 * Get an element of this matrix. 097 * 098 * @param row The row of the element. 099 * @param col The column of the element. 100 * @return The element in this matrix at row,col. 101 */ 102 public final double get(int row, int col) { 103 return this.m_storage.get(row, col); 104 } 105 106 /** 107 * Sets the value at the given indices. 108 * 109 * @param row The row of the element. 110 * @param col The column of the element. 111 * @param value The value to insert at the given location. 112 */ 113 public final void set(int row, int col, double value) { 114 this.m_storage.set(row, col, value); 115 } 116 117 /** 118 * Sets a row to a given row vector. 119 * 120 * @param row The row to set. 121 * @param val The row vector to set the given row to. 122 */ 123 public final void setRow(int row, Matrix<N1, C> val) { 124 this.m_storage.setRow(row, 0, Objects.requireNonNull(val).m_storage.getDDRM().getData()); 125 } 126 127 /** 128 * Sets a column to a given column vector. 129 * 130 * @param column The column to set. 131 * @param val The column vector to set the given row to. 132 */ 133 public final void setColumn(int column, Matrix<R, N1> val) { 134 this.m_storage.setColumn(column, 0, Objects.requireNonNull(val).m_storage.getDDRM().getData()); 135 } 136 137 /** 138 * Sets all the elements in "this" matrix equal to the specified value. 139 * 140 * @param value The value each element is set to. 141 */ 142 public void fill(double value) { 143 this.m_storage.fill(value); 144 } 145 146 /** 147 * Returns the diagonal elements inside a vector or square matrix. 148 * 149 * <p>If "this" {@link Matrix} is a vector then a square matrix is returned. If a "this" {@link 150 * Matrix} is a matrix then a vector of diagonal elements is returned. 151 * 152 * @return The diagonal elements inside a vector or a square matrix. 153 */ 154 public final Matrix<R, C> diag() { 155 return new Matrix<>(this.m_storage.diag()); 156 } 157 158 /** 159 * Returns the largest element of this matrix. 160 * 161 * @return The largest element of this matrix. 162 */ 163 public final double max() { 164 return CommonOps_DDRM.elementMax(this.m_storage.getDDRM()); 165 } 166 167 /** 168 * Returns the absolute value of the element in this matrix with the largest absolute value. 169 * 170 * @return The absolute value of the element with the largest absolute value. 171 */ 172 public final double maxAbs() { 173 return CommonOps_DDRM.elementMaxAbs(this.m_storage.getDDRM()); 174 } 175 176 /** 177 * Returns the smallest element of this matrix. 178 * 179 * @return The smallest element of this matrix. 180 */ 181 public final double minInternal() { 182 return CommonOps_DDRM.elementMin(this.m_storage.getDDRM()); 183 } 184 185 /** 186 * Calculates the mean of the elements in this matrix. 187 * 188 * @return The mean value of this matrix. 189 */ 190 public final double mean() { 191 return this.elementSum() / (double) this.m_storage.getNumElements(); 192 } 193 194 /** 195 * Multiplies this matrix with another that has C rows. 196 * 197 * <p>As matrix multiplication is only defined if the number of columns in the first matrix 198 * matches the number of rows in the second, this operation will fail to compile under any other 199 * circumstances. 200 * 201 * @param other The other matrix to multiply by. 202 * @param <C2> The number of columns in the second matrix. 203 * @return The result of the matrix multiplication between "this" and the given matrix. 204 */ 205 public final <C2 extends Num> Matrix<R, C2> times(Matrix<C, C2> other) { 206 return new Matrix<>(this.m_storage.mult(Objects.requireNonNull(other).m_storage)); 207 } 208 209 /** 210 * Multiplies all the elements of this matrix by the given scalar. 211 * 212 * @param value The scalar value to multiply by. 213 * @return A new matrix with all the elements multiplied by the given value. 214 */ 215 public Matrix<R, C> times(double value) { 216 return new Matrix<>(this.m_storage.scale(value)); 217 } 218 219 /** 220 * Returns a matrix which is the result of an element by element multiplication of "this" and 221 * other. 222 * 223 * <p>c<sub>i,j</sub> = a<sub>i,j</sub>*other<sub>i,j</sub> 224 * 225 * @param other The other {@link Matrix} to preform element multiplication on. 226 * @return The element by element multiplication of "this" and other. 227 */ 228 public final Matrix<R, C> elementTimes(Matrix<R, C> other) { 229 return new Matrix<>(this.m_storage.elementMult(Objects.requireNonNull(other).m_storage)); 230 } 231 232 /** 233 * Subtracts the given value from all the elements of this matrix. 234 * 235 * @param value The value to subtract. 236 * @return The resultant matrix. 237 */ 238 public final Matrix<R, C> minus(double value) { 239 return new Matrix<>(this.m_storage.minus(value)); 240 } 241 242 /** 243 * Subtracts the given matrix from this matrix. 244 * 245 * @param value The matrix to subtract. 246 * @return The resultant matrix. 247 */ 248 public final Matrix<R, C> minus(Matrix<R, C> value) { 249 return new Matrix<>(this.m_storage.minus(Objects.requireNonNull(value).m_storage)); 250 } 251 252 /** 253 * Adds the given value to all the elements of this matrix. 254 * 255 * @param value The value to add. 256 * @return The resultant matrix. 257 */ 258 public final Matrix<R, C> plus(double value) { 259 return new Matrix<>(this.m_storage.plus(value)); 260 } 261 262 /** 263 * Adds the given matrix to this matrix. 264 * 265 * @param value The matrix to add. 266 * @return The resultant matrix. 267 */ 268 public final Matrix<R, C> plus(Matrix<R, C> value) { 269 return new Matrix<>(this.m_storage.plus(Objects.requireNonNull(value).m_storage)); 270 } 271 272 /** 273 * Divides all elements of this matrix by the given value. 274 * 275 * @param value The value to divide by. 276 * @return The resultant matrix. 277 */ 278 public Matrix<R, C> div(int value) { 279 return new Matrix<>(this.m_storage.divide((double) value)); 280 } 281 282 /** 283 * Divides all elements of this matrix by the given value. 284 * 285 * @param value The value to divide by. 286 * @return The resultant matrix. 287 */ 288 public Matrix<R, C> div(double value) { 289 return new Matrix<>(this.m_storage.divide(value)); 290 } 291 292 /** 293 * Calculates the transpose, Máµ€ of this matrix. 294 * 295 * @return The transpose matrix. 296 */ 297 public final Matrix<C, R> transpose() { 298 return new Matrix<>(this.m_storage.transpose()); 299 } 300 301 /** 302 * Returns a copy of this matrix. 303 * 304 * @return A copy of this matrix. 305 */ 306 public final Matrix<R, C> copy() { 307 return new Matrix<>(this.m_storage.copy()); 308 } 309 310 /** 311 * Returns the inverse matrix of "this" matrix. 312 * 313 * @return The inverse of "this" matrix. 314 * @throws org.ejml.data.SingularMatrixException If "this" matrix is non-invertable. 315 */ 316 public final Matrix<R, C> inv() { 317 return new Matrix<>(this.m_storage.invert()); 318 } 319 320 /** 321 * Returns the solution x to the equation Ax = b, where A is "this" matrix. 322 * 323 * <p>The matrix equation could also be written as x = A<sup>-1</sup>b. Where the pseudo inverse 324 * is used if A is not square. 325 * 326 * @param <C2> Columns in b. 327 * @param b The right-hand side of the equation to solve. 328 * @return The solution to the linear system. 329 */ 330 @SuppressWarnings("ParameterName") 331 public final <C2 extends Num> Matrix<C, C2> solve(Matrix<R, C2> b) { 332 return new Matrix<>(this.m_storage.solve(Objects.requireNonNull(b).m_storage)); 333 } 334 335 /** 336 * Computes the matrix exponential using Eigen's solver. This method only works for square 337 * matrices, and will otherwise throw an {@link MatrixDimensionException}. 338 * 339 * @return The exponential of A. 340 */ 341 public final Matrix<R, C> exp() { 342 if (this.getNumRows() != this.getNumCols()) { 343 throw new MatrixDimensionException( 344 "Non-square matrices cannot be exponentiated! " 345 + "This matrix is " 346 + this.getNumRows() 347 + " x " 348 + this.getNumCols()); 349 } 350 Matrix<R, C> toReturn = new Matrix<>(new SimpleMatrix(this.getNumRows(), this.getNumCols())); 351 WPIMathJNI.exp( 352 this.m_storage.getDDRM().getData(), 353 this.getNumRows(), 354 toReturn.m_storage.getDDRM().getData()); 355 return toReturn; 356 } 357 358 /** 359 * Computes the matrix power using Eigen's solver. This method only works for square matrices, and 360 * will otherwise throw an {@link MatrixDimensionException}. 361 * 362 * @param exponent The exponent. 363 * @return The exponential of A. 364 */ 365 public final Matrix<R, C> pow(double exponent) { 366 if (this.getNumRows() != this.getNumCols()) { 367 throw new MatrixDimensionException( 368 "Non-square matrices cannot be raised to a power! " 369 + "This matrix is " 370 + this.getNumRows() 371 + " x " 372 + this.getNumCols()); 373 } 374 Matrix<R, C> toReturn = new Matrix<>(new SimpleMatrix(this.getNumRows(), this.getNumCols())); 375 WPIMathJNI.pow( 376 this.m_storage.getDDRM().getData(), 377 this.getNumRows(), 378 exponent, 379 toReturn.m_storage.getDDRM().getData()); 380 return toReturn; 381 } 382 383 /** 384 * Returns the determinant of this matrix. 385 * 386 * @return The determinant of this matrix. 387 */ 388 public final double det() { 389 return this.m_storage.determinant(); 390 } 391 392 /** 393 * Computes the Frobenius normal of the matrix. 394 * 395 * <p>normF = Sqrt{ ∑<sub>i=1:m</sub> ∑<sub>j=1:n</sub> { a<sub>ij</sub><sup>2</sup>} } 396 * 397 * @return The matrix's Frobenius normal. 398 */ 399 public final double normF() { 400 return this.m_storage.normF(); 401 } 402 403 /** 404 * Computes the induced p = 1 matrix norm. 405 * 406 * <p>||A||<sub>1</sub>= max(j=1 to n; sum(i=1 to m; |a<sub>ij</sub>|)) 407 * 408 * @return The norm. 409 */ 410 public final double normIndP1() { 411 return NormOps_DDRM.inducedP1(this.m_storage.getDDRM()); 412 } 413 414 /** 415 * Computes the sum of all the elements in the matrix. 416 * 417 * @return Sum of all the elements. 418 */ 419 public final double elementSum() { 420 return this.m_storage.elementSum(); 421 } 422 423 /** 424 * Computes the trace of the matrix. 425 * 426 * @return The trace of the matrix. 427 */ 428 public final double trace() { 429 return this.m_storage.trace(); 430 } 431 432 /** 433 * Returns a matrix which is the result of an element by element power of "this" and b. 434 * 435 * <p>c<sub>i,j</sub> = a<sub>i,j</sub> ^ b 436 * 437 * @param b Scalar. 438 * @return The element by element power of "this" and b. 439 */ 440 @SuppressWarnings("ParameterName") 441 public final Matrix<R, C> elementPower(double b) { 442 return new Matrix<>(this.m_storage.elementPower(b)); 443 } 444 445 /** 446 * Returns a matrix which is the result of an element by element power of "this" and b. 447 * 448 * <p>c<sub>i,j</sub> = a<sub>i,j</sub> ^ b 449 * 450 * @param b Scalar. 451 * @return The element by element power of "this" and b. 452 */ 453 @SuppressWarnings("ParameterName") 454 public final Matrix<R, C> elementPower(int b) { 455 return new Matrix<>(this.m_storage.elementPower((double) b)); 456 } 457 458 /** 459 * Extracts a given row into a row vector with new underlying storage. 460 * 461 * @param row The row to extract a vector from. 462 * @return A row vector from the given row. 463 */ 464 public final Matrix<N1, C> extractRowVector(int row) { 465 return new Matrix<>(this.m_storage.extractVector(true, row)); 466 } 467 468 /** 469 * Extracts a given column into a column vector with new underlying storage. 470 * 471 * @param column The column to extract a vector from. 472 * @return A column vector from the given column. 473 */ 474 public final Matrix<R, N1> extractColumnVector(int column) { 475 return new Matrix<>(this.m_storage.extractVector(false, column)); 476 } 477 478 /** 479 * Extracts a matrix of a given size and start position with new underlying storage. 480 * 481 * @param <R2> Number of rows to extract. 482 * @param <C2> Number of columns to extract. 483 * @param height The number of rows of the extracted matrix. 484 * @param width The number of columns of the extracted matrix. 485 * @param startingRow The starting row of the extracted matrix. 486 * @param startingCol The starting column of the extracted matrix. 487 * @return The extracted matrix. 488 */ 489 public final <R2 extends Num, C2 extends Num> Matrix<R2, C2> block( 490 Nat<R2> height, Nat<C2> width, int startingRow, int startingCol) { 491 return new Matrix<>( 492 this.m_storage.extractMatrix( 493 startingRow, 494 startingRow + Objects.requireNonNull(height).getNum(), 495 startingCol, 496 startingCol + Objects.requireNonNull(width).getNum())); 497 } 498 499 /** 500 * Extracts a matrix of a given size and start position with new underlying storage. 501 * 502 * @param <R2> Number of rows to extract. 503 * @param <C2> Number of columns to extract. 504 * @param height The number of rows of the extracted matrix. 505 * @param width The number of columns of the extracted matrix. 506 * @param startingRow The starting row of the extracted matrix. 507 * @param startingCol The starting column of the extracted matrix. 508 * @return The extracted matrix. 509 */ 510 public final <R2 extends Num, C2 extends Num> Matrix<R2, C2> block( 511 int height, int width, int startingRow, int startingCol) { 512 return new Matrix<R2, C2>( 513 this.m_storage.extractMatrix( 514 startingRow, startingRow + height, startingCol, startingCol + width)); 515 } 516 517 /** 518 * Assign a matrix of a given size and start position. 519 * 520 * @param <R2> Rows in block assignment. 521 * @param <C2> Columns in block assignment. 522 * @param startingRow The row to start at. 523 * @param startingCol The column to start at. 524 * @param other The matrix to assign the block to. 525 */ 526 public <R2 extends Num, C2 extends Num> void assignBlock( 527 int startingRow, int startingCol, Matrix<R2, C2> other) { 528 this.m_storage.insertIntoThis( 529 startingRow, startingCol, Objects.requireNonNull(other).m_storage); 530 } 531 532 /** 533 * Extracts a submatrix from the supplied matrix and inserts it in a submatrix in "this". The 534 * shape of "this" is used to determine the size of the matrix extracted. 535 * 536 * @param <R2> Number of rows to extract. 537 * @param <C2> Number of columns to extract. 538 * @param startingRow The starting row in the supplied matrix to extract the submatrix. 539 * @param startingCol The starting column in the supplied matrix to extract the submatrix. 540 * @param other The matrix to extract the submatrix from. 541 */ 542 public <R2 extends Num, C2 extends Num> void extractFrom( 543 int startingRow, int startingCol, Matrix<R2, C2> other) { 544 CommonOps_DDRM.extract( 545 other.m_storage.getDDRM(), startingRow, startingCol, this.m_storage.getDDRM()); 546 } 547 548 /** 549 * Decompose "this" matrix using Cholesky Decomposition. If the "this" matrix is zeros, it will 550 * return the zero matrix. 551 * 552 * @param lowerTriangular Whether or not we want to decompose to the lower triangular Cholesky 553 * matrix. 554 * @return The decomposed matrix. 555 * @throws RuntimeException if the matrix could not be decomposed(ie. is not positive 556 * semidefinite). 557 */ 558 public Matrix<R, C> lltDecompose(boolean lowerTriangular) { 559 SimpleMatrix temp = m_storage.copy(); 560 561 CholeskyDecomposition_F64<DMatrixRMaj> chol = 562 DecompositionFactory_DDRM.chol(temp.numRows(), lowerTriangular); 563 if (!chol.decompose(temp.getMatrix())) { 564 // check that the input is not all zeros -- if they are, we special case and return all 565 // zeros. 566 var matData = temp.getDDRM().data; 567 var isZeros = true; 568 for (double matDatum : matData) { 569 isZeros &= Math.abs(matDatum) < 1e-6; 570 } 571 if (isZeros) { 572 return new Matrix<>(new SimpleMatrix(temp.numRows(), temp.numCols())); 573 } 574 575 throw new RuntimeException( 576 "Cholesky decomposition failed! Input matrix:\n" + m_storage.toString()); 577 } 578 579 return new Matrix<>(SimpleMatrix.wrap(chol.getT(null))); 580 } 581 582 /** 583 * Returns the row major data of this matrix as a double array. 584 * 585 * @return The row major data of this matrix as a double array. 586 */ 587 public double[] getData() { 588 return m_storage.getDDRM().getData(); 589 } 590 591 /** 592 * Creates the identity matrix of the given dimension. 593 * 594 * @param dim The dimension of the desired matrix as a {@link Nat}. 595 * @param <D> The dimension of the desired matrix as a generic. 596 * @return The DxD identity matrix. 597 */ 598 public static <D extends Num> Matrix<D, D> eye(Nat<D> dim) { 599 return new Matrix<>(SimpleMatrix.identity(Objects.requireNonNull(dim).getNum())); 600 } 601 602 /** 603 * Creates the identity matrix of the given dimension. 604 * 605 * @param dim The dimension of the desired matrix as a {@link Num}. 606 * @param <D> The dimension of the desired matrix as a generic. 607 * @return The DxD identity matrix. 608 */ 609 public static <D extends Num> Matrix<D, D> eye(D dim) { 610 return new Matrix<>(SimpleMatrix.identity(Objects.requireNonNull(dim).getNum())); 611 } 612 613 /** 614 * Entrypoint to the {@link MatBuilder} class for creation of custom matrices with the given 615 * dimensions and contents. 616 * 617 * @param rows The number of rows of the desired matrix. 618 * @param cols The number of columns of the desired matrix. 619 * @param <R> The number of rows of the desired matrix as a generic. 620 * @param <C> The number of columns of the desired matrix as a generic. 621 * @return A builder to construct the matrix. 622 */ 623 public static <R extends Num, C extends Num> MatBuilder<R, C> mat(Nat<R> rows, Nat<C> cols) { 624 return new MatBuilder<>(Objects.requireNonNull(rows), Objects.requireNonNull(cols)); 625 } 626 627 /** 628 * Reassigns dimensions of a {@link Matrix} to allow for operations with other matrices that have 629 * wildcard dimensions. 630 * 631 * @param <R1> Row dimension to assign. 632 * @param <C1> Column dimension to assign. 633 * @param mat The {@link Matrix} to remove the dimensions from. 634 * @return The matrix with reassigned dimensions. 635 */ 636 public static <R1 extends Num, C1 extends Num> Matrix<R1, C1> changeBoundsUnchecked( 637 Matrix<?, ?> mat) { 638 return new Matrix<>(mat.m_storage); 639 } 640 641 /** 642 * Checks if another {@link Matrix} is identical to "this" one within a specified tolerance. 643 * 644 * <p>This will check if each element is in tolerance of the corresponding element from the other 645 * {@link Matrix} or if the elements have the same symbolic meaning. For two elements to have the 646 * same symbolic meaning they both must be either Double.NaN, Double.POSITIVE_INFINITY, or 647 * Double.NEGATIVE_INFINITY. 648 * 649 * <p>NOTE:It is recommend to use {@link Matrix#isEqual(Matrix, double)} over this method when 650 * checking if two matrices are equal as {@link Matrix#isEqual(Matrix, double)} will return false 651 * if an element is uncountable. This method should only be used when uncountable elements need to 652 * compared. 653 * 654 * @param other The {@link Matrix} to check against this one. 655 * @param tolerance The tolerance to check equality with. 656 * @return true if this matrix is identical to the one supplied. 657 */ 658 public boolean isIdentical(Matrix<?, ?> other, double tolerance) { 659 return MatrixFeatures_DDRM.isIdentical( 660 this.m_storage.getDDRM(), other.m_storage.getDDRM(), tolerance); 661 } 662 663 /** 664 * Checks if another {@link Matrix} is equal to "this" within a specified tolerance. 665 * 666 * <p>This will check if each element is in tolerance of the corresponding element from the other 667 * {@link Matrix}. 668 * 669 * <p>tol ≥ |a<sub>ij</sub> - b<sub>ij</sub>| 670 * 671 * @param other The {@link Matrix} to check against this one. 672 * @param tolerance The tolerance to check equality with. 673 * @return true if this matrix is equal to the one supplied. 674 */ 675 public boolean isEqual(Matrix<?, ?> other, double tolerance) { 676 return MatrixFeatures_DDRM.isEquals( 677 this.m_storage.getDDRM(), other.m_storage.getDDRM(), tolerance); 678 } 679 680 @Override 681 public String toString() { 682 return m_storage.toString(); 683 } 684 685 /** 686 * Checks if an object is equal to this {@link Matrix}. 687 * 688 * <p>a<sub>ij</sub> == b<sub>ij</sub> 689 * 690 * @param other The Object to check against this {@link Matrix}. 691 * @return true if the object supplied is a {@link Matrix} and is equal to this matrix. 692 */ 693 @Override 694 public boolean equals(Object other) { 695 if (this == other) { 696 return true; 697 } 698 if (!(other instanceof Matrix)) { 699 return false; 700 } 701 702 Matrix<?, ?> matrix = (Matrix<?, ?>) other; 703 if (MatrixFeatures_DDRM.hasUncountable(matrix.m_storage.getDDRM())) { 704 return false; 705 } 706 return MatrixFeatures_DDRM.isEquals(this.m_storage.getDDRM(), matrix.m_storage.getDDRM()); 707 } 708 709 @Override 710 public int hashCode() { 711 return Objects.hash(m_storage); 712 } 713}