Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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