See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body