Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Functions;
   7  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   8  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   9  
  10  /**
  11   * Matrix class.
  12   *
  13   * @author Paul Meagher
  14   * @author Michael Bommarito
  15   * @author Lukasz Karapuda
  16   * @author Bartek Matosiuk
  17   *
  18   * @version 1.8
  19   *
  20   * @see https://math.nist.gov/javanumerics/jama/
  21   */
  22  class Matrix
  23  {
  24      const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.';
  25      const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.';
  26      const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.';
  27      const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.';
  28      const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.';
  29      const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.';
  30  
  31      /**
  32       * Matrix storage.
  33       *
  34       * @var array
  35       */
  36      public $A = [];
  37  
  38      /**
  39       * Matrix row dimension.
  40       *
  41       * @var int
  42       */
  43      private $m;
  44  
  45      /**
  46       * Matrix column dimension.
  47       *
  48       * @var int
  49       */
  50      private $n;
  51  
  52      /**
  53       * Polymorphic constructor.
  54       *
  55       * As PHP has no support for polymorphic constructors, we use tricks to make our own sort of polymorphism using func_num_args, func_get_arg, and gettype. In essence, we're just implementing a simple RTTI filter and calling the appropriate constructor.
  56       */
  57      public function __construct(...$args)
  58      {
  59          if (count($args) > 0) {
  60              $match = implode(',', array_map('gettype', $args));
  61  
  62              switch ($match) {
  63                  //Rectangular matrix - m x n initialized from 2D array
  64                  case 'array':
  65                      $this->m = count($args[0]);
  66                      $this->n = count($args[0][0]);
  67                      $this->A = $args[0];
  68  
  69                      break;
  70                      //Square matrix - n x n
  71                  case 'integer':
  72                      $this->m = $args[0];
  73                      $this->n = $args[0];
  74                      $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
  75  
  76                      break;
  77                      //Rectangular matrix - m x n
  78                  case 'integer,integer':
  79                      $this->m = $args[0];
  80                      $this->n = $args[1];
  81                      $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
  82  
  83                      break;
  84                      //Rectangular matrix - m x n initialized from packed array
  85                  case 'array,integer':
  86                      $this->m = $args[1];
  87                      if ($this->m != 0) {
  88                          $this->n = count($args[0]) / $this->m;
  89                      } else {
  90                          $this->n = 0;
  91                      }
  92                      if (($this->m * $this->n) == count($args[0])) {
  93                          for ($i = 0; $i < $this->m; ++$i) {
  94                              for ($j = 0; $j < $this->n; ++$j) {
  95                                  $this->A[$i][$j] = $args[0][$i + $j * $this->m];
  96                              }
  97                          }
  98                      } else {
  99                          throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
 100                      }
 101  
 102                      break;
 103                  default:
 104                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 105  
 106                      break;
 107              }
 108          } else {
 109              throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 110          }
 111      }
 112  
 113      /**
 114       * getArray.
 115       *
 116       * @return array Matrix array
 117       */
 118      public function getArray()
 119      {
 120          return $this->A;
 121      }
 122  
 123      /**
 124       * getRowDimension.
 125       *
 126       * @return int Row dimension
 127       */
 128      public function getRowDimension()
 129      {
 130          return $this->m;
 131      }
 132  
 133      /**
 134       * getColumnDimension.
 135       *
 136       * @return int Column dimension
 137       */
 138      public function getColumnDimension()
 139      {
 140          return $this->n;
 141      }
 142  
 143      /**
 144       * get.
 145       *
 146       * Get the i,j-th element of the matrix.
 147       *
 148       * @param int $i Row position
 149       * @param int $j Column position
 150       *
 151       * @return float|int
 152       */
 153      public function get($i = null, $j = null)
 154      {
 155          return $this->A[$i][$j];
 156      }
 157  
 158      /**
 159       * getMatrix.
 160       *
 161       *    Get a submatrix
 162       *
 163       * @return Matrix Submatrix
 164       */
 165      public function getMatrix(...$args)
 166      {
 167          if (count($args) > 0) {
 168              $match = implode(',', array_map('gettype', $args));
 169  
 170              switch ($match) {
 171                  //A($i0...; $j0...)
 172                  case 'integer,integer':
 173                      [$i0, $j0] = $args;
 174                      if ($i0 >= 0) {
 175                          $m = $this->m - $i0;
 176                      } else {
 177                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 178                      }
 179                      if ($j0 >= 0) {
 180                          $n = $this->n - $j0;
 181                      } else {
 182                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 183                      }
 184                      $R = new self($m, $n);
 185                      for ($i = $i0; $i < $this->m; ++$i) {
 186                          for ($j = $j0; $j < $this->n; ++$j) {
 187                              $R->set($i, $j, $this->A[$i][$j]);
 188                          }
 189                      }
 190  
 191                      return $R;
 192  
 193                      break;
 194                      //A($i0...$iF; $j0...$jF)
 195                  case 'integer,integer,integer,integer':
 196                      [$i0, $iF, $j0, $jF] = $args;
 197                      if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
 198                          $m = $iF - $i0;
 199                      } else {
 200                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 201                      }
 202                      if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
 203                          $n = $jF - $j0;
 204                      } else {
 205                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 206                      }
 207                      $R = new self($m + 1, $n + 1);
 208                      for ($i = $i0; $i <= $iF; ++$i) {
 209                          for ($j = $j0; $j <= $jF; ++$j) {
 210                              $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
 211                          }
 212                      }
 213  
 214                      return $R;
 215  
 216                      break;
 217                      //$R = array of row indices; $C = array of column indices
 218                  case 'array,array':
 219                      [$RL, $CL] = $args;
 220                      if (count($RL) > 0) {
 221                          $m = count($RL);
 222                      } else {
 223                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 224                      }
 225                      if (count($CL) > 0) {
 226                          $n = count($CL);
 227                      } else {
 228                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 229                      }
 230                      $R = new self($m, $n);
 231                      for ($i = 0; $i < $m; ++$i) {
 232                          for ($j = 0; $j < $n; ++$j) {
 233                              $R->set($i, $j, $this->A[$RL[$i]][$CL[$j]]);
 234                          }
 235                      }
 236  
 237                      return $R;
 238  
 239                      break;
 240                      //A($i0...$iF); $CL = array of column indices
 241                  case 'integer,integer,array':
 242                      [$i0, $iF, $CL] = $args;
 243                      if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
 244                          $m = $iF - $i0;
 245                      } else {
 246                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 247                      }
 248                      if (count($CL) > 0) {
 249                          $n = count($CL);
 250                      } else {
 251                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 252                      }
 253                      $R = new self($m, $n);
 254                      for ($i = $i0; $i < $iF; ++$i) {
 255                          for ($j = 0; $j < $n; ++$j) {
 256                              $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]);
 257                          }
 258                      }
 259  
 260                      return $R;
 261  
 262                      break;
 263                      //$RL = array of row indices
 264                  case 'array,integer,integer':
 265                      [$RL, $j0, $jF] = $args;
 266                      if (count($RL) > 0) {
 267                          $m = count($RL);
 268                      } else {
 269                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 270                      }
 271                      if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
 272                          $n = $jF - $j0;
 273                      } else {
 274                          throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
 275                      }
 276                      $R = new self($m, $n + 1);
 277                      for ($i = 0; $i < $m; ++$i) {
 278                          for ($j = $j0; $j <= $jF; ++$j) {
 279                              $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
 280                          }
 281                      }
 282  
 283                      return $R;
 284  
 285                      break;
 286                  default:
 287                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 288  
 289                      break;
 290              }
 291          } else {
 292              throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 293          }
 294      }
 295  
 296      /**
 297       * checkMatrixDimensions.
 298       *
 299       *    Is matrix B the same size?
 300       *
 301       * @param Matrix $B Matrix B
 302       *
 303       * @return bool
 304       */
 305      public function checkMatrixDimensions($B = null)
 306      {
 307          if ($B instanceof self) {
 308              if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
 309                  return true;
 310              }
 311  
 312              throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
 313          }
 314  
 315          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 316      }
 317  
 318      //    function checkMatrixDimensions()
 319  
 320      /**
 321       * set.
 322       *
 323       * Set the i,j-th element of the matrix.
 324       *
 325       * @param int $i Row position
 326       * @param int $j Column position
 327       * @param float|int $c value
 328       */
 329      public function set($i = null, $j = null, $c = null): void
 330      {
 331          // Optimized set version just has this
 332          $this->A[$i][$j] = $c;
 333      }
 334  
 335      //    function set()
 336  
 337      /**
 338       * identity.
 339       *
 340       * Generate an identity matrix.
 341       *
 342       * @param int $m Row dimension
 343       * @param int $n Column dimension
 344       *
 345       * @return Matrix Identity matrix
 346       */
 347      public function identity($m = null, $n = null)
 348      {
 349          return $this->diagonal($m, $n, 1);
 350      }
 351  
 352      /**
 353       * diagonal.
 354       *
 355       *    Generate a diagonal matrix
 356       *
 357       * @param int $m Row dimension
 358       * @param int $n Column dimension
 359       * @param mixed $c Diagonal value
 360       *
 361       * @return Matrix Diagonal matrix
 362       */
 363      public function diagonal($m = null, $n = null, $c = 1)
 364      {
 365          $R = new self($m, $n);
 366          for ($i = 0; $i < $m; ++$i) {
 367              $R->set($i, $i, $c);
 368          }
 369  
 370          return $R;
 371      }
 372  
 373      /**
 374       * getMatrixByRow.
 375       *
 376       *    Get a submatrix by row index/range
 377       *
 378       * @param int $i0 Initial row index
 379       * @param int $iF Final row index
 380       *
 381       * @return Matrix Submatrix
 382       */
 383      public function getMatrixByRow($i0 = null, $iF = null)
 384      {
 385          if (is_int($i0)) {
 386              if (is_int($iF)) {
 387                  return $this->getMatrix($i0, 0, $iF + 1, $this->n);
 388              }
 389  
 390              return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
 391          }
 392  
 393          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 394      }
 395  
 396      /**
 397       * getMatrixByCol.
 398       *
 399       *    Get a submatrix by column index/range
 400       *
 401       * @param int $j0 Initial column index
 402       * @param int $jF Final column index
 403       *
 404       * @return Matrix Submatrix
 405       */
 406      public function getMatrixByCol($j0 = null, $jF = null)
 407      {
 408          if (is_int($j0)) {
 409              if (is_int($jF)) {
 410                  return $this->getMatrix(0, $j0, $this->m, $jF + 1);
 411              }
 412  
 413              return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
 414          }
 415  
 416          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 417      }
 418  
 419      /**
 420       * transpose.
 421       *
 422       *    Tranpose matrix
 423       *
 424       * @return Matrix Transposed matrix
 425       */
 426      public function transpose()
 427      {
 428          $R = new self($this->n, $this->m);
 429          for ($i = 0; $i < $this->m; ++$i) {
 430              for ($j = 0; $j < $this->n; ++$j) {
 431                  $R->set($j, $i, $this->A[$i][$j]);
 432              }
 433          }
 434  
 435          return $R;
 436      }
 437  
 438      //    function transpose()
 439  
 440      /**
 441       * trace.
 442       *
 443       *    Sum of diagonal elements
 444       *
 445       * @return float Sum of diagonal elements
 446       */
 447      public function trace()
 448      {
 449          $s = 0;
 450          $n = min($this->m, $this->n);
 451          for ($i = 0; $i < $n; ++$i) {
 452              $s += $this->A[$i][$i];
 453          }
 454  
 455          return $s;
 456      }
 457  
 458      /**
 459       * plus.
 460       *
 461       *    A + B
 462       *
 463       * @return Matrix Sum
 464       */
 465      public function plus(...$args)
 466      {
 467          if (count($args) > 0) {
 468              $match = implode(',', array_map('gettype', $args));
 469  
 470              switch ($match) {
 471                  case 'object':
 472                      if ($args[0] instanceof self) {
 473                          $M = $args[0];
 474                      } else {
 475                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 476                      }
 477  
 478                      break;
 479                  case 'array':
 480                      $M = new self($args[0]);
 481  
 482                      break;
 483                  default:
 484                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 485  
 486                      break;
 487              }
 488              $this->checkMatrixDimensions($M);
 489              for ($i = 0; $i < $this->m; ++$i) {
 490                  for ($j = 0; $j < $this->n; ++$j) {
 491                      $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
 492                  }
 493              }
 494  
 495              return $M;
 496          }
 497  
 498          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 499      }
 500  
 501      /**
 502       * plusEquals.
 503       *
 504       *    A = A + B
 505       *
 506       * @return $this
 507       */
 508      public function plusEquals(...$args)
 509      {
 510          if (count($args) > 0) {
 511              $match = implode(',', array_map('gettype', $args));
 512  
 513              switch ($match) {
 514                  case 'object':
 515                      if ($args[0] instanceof self) {
 516                          $M = $args[0];
 517                      } else {
 518                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 519                      }
 520  
 521                      break;
 522                  case 'array':
 523                      $M = new self($args[0]);
 524  
 525                      break;
 526                  default:
 527                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 528  
 529                      break;
 530              }
 531              $this->checkMatrixDimensions($M);
 532              for ($i = 0; $i < $this->m; ++$i) {
 533                  for ($j = 0; $j < $this->n; ++$j) {
 534                      $validValues = true;
 535                      $value = $M->get($i, $j);
 536                      [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
 537                      [$value, $validValues] = $this->validateExtractedValue($value, $validValues);
 538                      if ($validValues) {
 539                          $this->A[$i][$j] += $value;
 540                      } else {
 541                          $this->A[$i][$j] = ExcelError::NAN();
 542                      }
 543                  }
 544              }
 545  
 546              return $this;
 547          }
 548  
 549          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 550      }
 551  
 552      /**
 553       * minus.
 554       *
 555       *    A - B
 556       *
 557       * @return Matrix Sum
 558       */
 559      public function minus(...$args)
 560      {
 561          if (count($args) > 0) {
 562              $match = implode(',', array_map('gettype', $args));
 563  
 564              switch ($match) {
 565                  case 'object':
 566                      if ($args[0] instanceof self) {
 567                          $M = $args[0];
 568                      } else {
 569                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 570                      }
 571  
 572                      break;
 573                  case 'array':
 574                      $M = new self($args[0]);
 575  
 576                      break;
 577                  default:
 578                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 579  
 580                      break;
 581              }
 582              $this->checkMatrixDimensions($M);
 583              for ($i = 0; $i < $this->m; ++$i) {
 584                  for ($j = 0; $j < $this->n; ++$j) {
 585                      $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
 586                  }
 587              }
 588  
 589              return $M;
 590          }
 591  
 592          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 593      }
 594  
 595      /**
 596       * minusEquals.
 597       *
 598       *    A = A - B
 599       *
 600       * @return $this
 601       */
 602      public function minusEquals(...$args)
 603      {
 604          if (count($args) > 0) {
 605              $match = implode(',', array_map('gettype', $args));
 606  
 607              switch ($match) {
 608                  case 'object':
 609                      if ($args[0] instanceof self) {
 610                          $M = $args[0];
 611                      } else {
 612                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 613                      }
 614  
 615                      break;
 616                  case 'array':
 617                      $M = new self($args[0]);
 618  
 619                      break;
 620                  default:
 621                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 622  
 623                      break;
 624              }
 625              $this->checkMatrixDimensions($M);
 626              for ($i = 0; $i < $this->m; ++$i) {
 627                  for ($j = 0; $j < $this->n; ++$j) {
 628                      $validValues = true;
 629                      $value = $M->get($i, $j);
 630                      [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
 631                      [$value, $validValues] = $this->validateExtractedValue($value, $validValues);
 632                      if ($validValues) {
 633                          $this->A[$i][$j] -= $value;
 634                      } else {
 635                          $this->A[$i][$j] = ExcelError::NAN();
 636                      }
 637                  }
 638              }
 639  
 640              return $this;
 641          }
 642  
 643          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 644      }
 645  
 646      /**
 647       * arrayTimes.
 648       *
 649       *    Element-by-element multiplication
 650       *    Cij = Aij * Bij
 651       *
 652       * @return Matrix Matrix Cij
 653       */
 654      public function arrayTimes(...$args)
 655      {
 656          if (count($args) > 0) {
 657              $match = implode(',', array_map('gettype', $args));
 658  
 659              switch ($match) {
 660                  case 'object':
 661                      if ($args[0] instanceof self) {
 662                          $M = $args[0];
 663                      } else {
 664                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 665                      }
 666  
 667                      break;
 668                  case 'array':
 669                      $M = new self($args[0]);
 670  
 671                      break;
 672                  default:
 673                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 674  
 675                      break;
 676              }
 677              $this->checkMatrixDimensions($M);
 678              for ($i = 0; $i < $this->m; ++$i) {
 679                  for ($j = 0; $j < $this->n; ++$j) {
 680                      $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
 681                  }
 682              }
 683  
 684              return $M;
 685          }
 686  
 687          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 688      }
 689  
 690      /**
 691       * arrayTimesEquals.
 692       *
 693       *    Element-by-element multiplication
 694       *    Aij = Aij * Bij
 695       *
 696       * @return $this
 697       */
 698      public function arrayTimesEquals(...$args)
 699      {
 700          if (count($args) > 0) {
 701              $match = implode(',', array_map('gettype', $args));
 702  
 703              switch ($match) {
 704                  case 'object':
 705                      if ($args[0] instanceof self) {
 706                          $M = $args[0];
 707                      } else {
 708                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 709                      }
 710  
 711                      break;
 712                  case 'array':
 713                      $M = new self($args[0]);
 714  
 715                      break;
 716                  default:
 717                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 718  
 719                      break;
 720              }
 721              $this->checkMatrixDimensions($M);
 722              for ($i = 0; $i < $this->m; ++$i) {
 723                  for ($j = 0; $j < $this->n; ++$j) {
 724                      $validValues = true;
 725                      $value = $M->get($i, $j);
 726                      [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
 727                      [$value, $validValues] = $this->validateExtractedValue($value, $validValues);
 728                      if ($validValues) {
 729                          $this->A[$i][$j] *= $value;
 730                      } else {
 731                          $this->A[$i][$j] = ExcelError::NAN();
 732                      }
 733                  }
 734              }
 735  
 736              return $this;
 737          }
 738  
 739          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 740      }
 741  
 742      /**
 743       * arrayRightDivide.
 744       *
 745       *    Element-by-element right division
 746       *    A / B
 747       *
 748       * @return Matrix Division result
 749       */
 750      public function arrayRightDivide(...$args)
 751      {
 752          if (count($args) > 0) {
 753              $match = implode(',', array_map('gettype', $args));
 754  
 755              switch ($match) {
 756                  case 'object':
 757                      if ($args[0] instanceof self) {
 758                          $M = $args[0];
 759                      } else {
 760                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 761                      }
 762  
 763                      break;
 764                  case 'array':
 765                      $M = new self($args[0]);
 766  
 767                      break;
 768                  default:
 769                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 770  
 771                      break;
 772              }
 773              $this->checkMatrixDimensions($M);
 774              for ($i = 0; $i < $this->m; ++$i) {
 775                  for ($j = 0; $j < $this->n; ++$j) {
 776                      $validValues = true;
 777                      $value = $M->get($i, $j);
 778                      [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
 779                      [$value, $validValues] = $this->validateExtractedValue($value, $validValues);
 780                      if ($validValues) {
 781                          if ($value == 0) {
 782                              //    Trap for Divide by Zero error
 783                              $M->set($i, $j, '#DIV/0!');
 784                          } else {
 785                              $M->set($i, $j, $this->A[$i][$j] / $value);
 786                          }
 787                      } else {
 788                          $M->set($i, $j, ExcelError::NAN());
 789                      }
 790                  }
 791              }
 792  
 793              return $M;
 794          }
 795  
 796          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 797      }
 798  
 799      /**
 800       * arrayRightDivideEquals.
 801       *
 802       *    Element-by-element right division
 803       *    Aij = Aij / Bij
 804       *
 805       * @return Matrix Matrix Aij
 806       */
 807      public function arrayRightDivideEquals(...$args)
 808      {
 809          if (count($args) > 0) {
 810              $match = implode(',', array_map('gettype', $args));
 811  
 812              switch ($match) {
 813                  case 'object':
 814                      if ($args[0] instanceof self) {
 815                          $M = $args[0];
 816                      } else {
 817                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 818                      }
 819  
 820                      break;
 821                  case 'array':
 822                      $M = new self($args[0]);
 823  
 824                      break;
 825                  default:
 826                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 827  
 828                      break;
 829              }
 830              $this->checkMatrixDimensions($M);
 831              for ($i = 0; $i < $this->m; ++$i) {
 832                  for ($j = 0; $j < $this->n; ++$j) {
 833                      $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
 834                  }
 835              }
 836  
 837              return $M;
 838          }
 839  
 840          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 841      }
 842  
 843      /**
 844       * arrayLeftDivide.
 845       *
 846       *    Element-by-element Left division
 847       *    A / B
 848       *
 849       * @return Matrix Division result
 850       */
 851      public function arrayLeftDivide(...$args)
 852      {
 853          if (count($args) > 0) {
 854              $match = implode(',', array_map('gettype', $args));
 855  
 856              switch ($match) {
 857                  case 'object':
 858                      if ($args[0] instanceof self) {
 859                          $M = $args[0];
 860                      } else {
 861                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 862                      }
 863  
 864                      break;
 865                  case 'array':
 866                      $M = new self($args[0]);
 867  
 868                      break;
 869                  default:
 870                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 871  
 872                      break;
 873              }
 874              $this->checkMatrixDimensions($M);
 875              for ($i = 0; $i < $this->m; ++$i) {
 876                  for ($j = 0; $j < $this->n; ++$j) {
 877                      $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
 878                  }
 879              }
 880  
 881              return $M;
 882          }
 883  
 884          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 885      }
 886  
 887      /**
 888       * arrayLeftDivideEquals.
 889       *
 890       *    Element-by-element Left division
 891       *    Aij = Aij / Bij
 892       *
 893       * @return Matrix Matrix Aij
 894       */
 895      public function arrayLeftDivideEquals(...$args)
 896      {
 897          if (count($args) > 0) {
 898              $match = implode(',', array_map('gettype', $args));
 899  
 900              switch ($match) {
 901                  case 'object':
 902                      if ($args[0] instanceof self) {
 903                          $M = $args[0];
 904                      } else {
 905                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 906                      }
 907  
 908                      break;
 909                  case 'array':
 910                      $M = new self($args[0]);
 911  
 912                      break;
 913                  default:
 914                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 915  
 916                      break;
 917              }
 918              $this->checkMatrixDimensions($M);
 919              for ($i = 0; $i < $this->m; ++$i) {
 920                  for ($j = 0; $j < $this->n; ++$j) {
 921                      $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
 922                  }
 923              }
 924  
 925              return $M;
 926          }
 927  
 928          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
 929      }
 930  
 931      /**
 932       * times.
 933       *
 934       *    Matrix multiplication
 935       *
 936       * @return Matrix Product
 937       */
 938      public function times(...$args)
 939      {
 940          if (count($args) > 0) {
 941              $match = implode(',', array_map('gettype', $args));
 942  
 943              switch ($match) {
 944                  case 'object':
 945                      if ($args[0] instanceof self) {
 946                          $B = $args[0];
 947                      } else {
 948                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
 949                      }
 950                      if ($this->n == $B->m) {
 951                          $C = new self($this->m, $B->n);
 952                          for ($j = 0; $j < $B->n; ++$j) {
 953                              $Bcolj = [];
 954                              for ($k = 0; $k < $this->n; ++$k) {
 955                                  $Bcolj[$k] = $B->A[$k][$j];
 956                              }
 957                              for ($i = 0; $i < $this->m; ++$i) {
 958                                  $Arowi = $this->A[$i];
 959                                  $s = 0;
 960                                  for ($k = 0; $k < $this->n; ++$k) {
 961                                      $s += $Arowi[$k] * $Bcolj[$k];
 962                                  }
 963                                  $C->A[$i][$j] = $s;
 964                              }
 965                          }
 966  
 967                          return $C;
 968                      }
 969  
 970                      throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
 971                  case 'array':
 972                      $B = new self($args[0]);
 973                      if ($this->n == $B->m) {
 974                          $C = new self($this->m, $B->n);
 975                          for ($i = 0; $i < $C->m; ++$i) {
 976                              for ($j = 0; $j < $C->n; ++$j) {
 977                                  $s = '0';
 978                                  for ($k = 0; $k < $C->n; ++$k) {
 979                                      $s += $this->A[$i][$k] * $B->A[$k][$j];
 980                                  }
 981                                  $C->A[$i][$j] = $s;
 982                              }
 983                          }
 984  
 985                          return $C;
 986                      }
 987  
 988                      throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
 989                  case 'integer':
 990                      $C = new self($this->A);
 991                      for ($i = 0; $i < $C->m; ++$i) {
 992                          for ($j = 0; $j < $C->n; ++$j) {
 993                              $C->A[$i][$j] *= $args[0];
 994                          }
 995                      }
 996  
 997                      return $C;
 998                  case 'double':
 999                      $C = new self($this->m, $this->n);
1000                      for ($i = 0; $i < $C->m; ++$i) {
1001                          for ($j = 0; $j < $C->n; ++$j) {
1002                              $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
1003                          }
1004                      }
1005  
1006                      return $C;
1007                  case 'float':
1008                      $C = new self($this->A);
1009                      for ($i = 0; $i < $C->m; ++$i) {
1010                          for ($j = 0; $j < $C->n; ++$j) {
1011                              $C->A[$i][$j] *= $args[0];
1012                          }
1013                      }
1014  
1015                      return $C;
1016                  default:
1017                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1018              }
1019          } else {
1020              throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1021          }
1022      }
1023  
1024      /**
1025       * power.
1026       *
1027       *    A = A ^ B
1028       *
1029       * @return $this
1030       */
1031      public function power(...$args)
1032      {
1033          if (count($args) > 0) {
1034              $match = implode(',', array_map('gettype', $args));
1035  
1036              switch ($match) {
1037                  case 'object':
1038                      if ($args[0] instanceof self) {
1039                          $M = $args[0];
1040                      } else {
1041                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1042                      }
1043  
1044                      break;
1045                  case 'array':
1046                      $M = new self($args[0]);
1047  
1048                      break;
1049                  default:
1050                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1051  
1052                      break;
1053              }
1054              $this->checkMatrixDimensions($M);
1055              for ($i = 0; $i < $this->m; ++$i) {
1056                  for ($j = 0; $j < $this->n; ++$j) {
1057                      $validValues = true;
1058                      $value = $M->get($i, $j);
1059                      [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
1060                      [$value, $validValues] = $this->validateExtractedValue($value, $validValues);
1061                      if ($validValues) {
1062                          $this->A[$i][$j] = $this->A[$i][$j] ** $value;
1063                      } else {
1064                          $this->A[$i][$j] = ExcelError::NAN();
1065                      }
1066                  }
1067              }
1068  
1069              return $this;
1070          }
1071  
1072          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1073      }
1074  
1075      /**
1076       * concat.
1077       *
1078       *    A = A & B
1079       *
1080       * @return $this
1081       */
1082      public function concat(...$args)
1083      {
1084          if (count($args) > 0) {
1085              $match = implode(',', array_map('gettype', $args));
1086  
1087              switch ($match) {
1088                  case 'object':
1089                      if ($args[0] instanceof self) {
1090                          $M = $args[0];
1091                      } else {
1092                          throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1093                      }
1094  
1095                      break;
1096                  case 'array':
1097                      $M = new self($args[0]);
1098  
1099                      break;
1100                  default:
1101                      throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1102  
1103                      break;
1104              }
1105              $this->checkMatrixDimensions($M);
1106              for ($i = 0; $i < $this->m; ++$i) {
1107                  for ($j = 0; $j < $this->n; ++$j) {
1108                      // @phpstan-ignore-next-line
1109                      $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
1110                  }
1111              }
1112  
1113              return $this;
1114          }
1115  
1116          throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1117      }
1118  
1119      /**
1120       * Solve A*X = B.
1121       *
1122       * @param Matrix $B Right hand side
1123       *
1124       * @return Matrix ... Solution if A is square, least squares solution otherwise
1125       */
1126      public function solve(self $B)
1127      {
1128          if ($this->m == $this->n) {
1129              $LU = new LUDecomposition($this);
1130  
1131              return $LU->solve($B);
1132          }
1133          $QR = new QRDecomposition($this);
1134  
1135          return $QR->solve($B);
1136      }
1137  
1138      /**
1139       * Matrix inverse or pseudoinverse.
1140       *
1141       * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
1142       */
1143      public function inverse()
1144      {
1145          return $this->solve($this->identity($this->m, $this->m));
1146      }
1147  
1148      /**
1149       * det.
1150       *
1151       *    Calculate determinant
1152       *
1153       * @return float Determinant
1154       */
1155      public function det()
1156      {
1157          $L = new LUDecomposition($this);
1158  
1159          return $L->det();
1160      }
1161  
1162      /**
1163       * @param mixed $value
1164       */
1165      private function validateExtractedValue($value, bool $validValues): array
1166      {
1167          if (!is_numeric($value) && is_array($value)) {
1168              $value = Functions::flattenArray($value)[0];
1169          }
1170          if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
1171              $value = trim($value, '"');
1172              $validValues &= StringHelper::convertToNumberIfFraction($value);
1173          }
1174  
1175          return [$value, $validValues];
1176      }
1177  }