Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
   4  
   5  /**
   6   *    For an m-by-n matrix A with m >= n, the singular value decomposition is
   7   *    an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
   8   *    an n-by-n orthogonal matrix V so that A = U*S*V'.
   9   *
  10   *    The singular values, sigma[$k] = S[$k][$k], are ordered so that
  11   *    sigma[0] >= sigma[1] >= ... >= sigma[n-1].
  12   *
  13   *    The singular value decompostion always exists, so the constructor will
  14   *    never fail.  The matrix condition number and the effective numerical
  15   *    rank can be computed from this decomposition.
  16   *
  17   *    @author  Paul Meagher
  18   *
  19   *    @version 1.1
  20   */
  21  class SingularValueDecomposition
  22  {
  23      /**
  24       * Internal storage of U.
  25       *
  26       * @var array
  27       */
  28      private $U = [];
  29  
  30      /**
  31       * Internal storage of V.
  32       *
  33       * @var array
  34       */
  35      private $V = [];
  36  
  37      /**
  38       * Internal storage of singular values.
  39       *
  40       * @var array
  41       */
  42      private $s = [];
  43  
  44      /**
  45       * Row dimension.
  46       *
  47       * @var int
  48       */
  49      private $m;
  50  
  51      /**
  52       * Column dimension.
  53       *
  54       * @var int
  55       */
  56      private $n;
  57  
  58      /**
  59       * Construct the singular value decomposition.
  60       *
  61       * Derived from LINPACK code.
  62       *
  63       * @param mixed $Arg Rectangular matrix
  64       */
  65      public function __construct($Arg)
  66      {
  67          // Initialize.
  68          $A = $Arg->getArray();
  69          $this->m = $Arg->getRowDimension();
  70          $this->n = $Arg->getColumnDimension();
  71          $nu = min($this->m, $this->n);
  72          $e = [];
  73          $work = [];
  74          $wantu = true;
  75          $wantv = true;
  76          $nct = min($this->m - 1, $this->n);
  77          $nrt = max(0, min($this->n - 2, $this->m));
  78  
  79          // Reduce A to bidiagonal form, storing the diagonal elements
  80          // in s and the super-diagonal elements in e.
  81          $kMax = max($nct, $nrt);
  82          for ($k = 0; $k < $kMax; ++$k) {
  83              if ($k < $nct) {
  84                  // Compute the transformation for the k-th column and
  85                  // place the k-th diagonal in s[$k].
  86                  // Compute 2-norm of k-th column without under/overflow.
  87                  $this->s[$k] = 0;
  88                  for ($i = $k; $i < $this->m; ++$i) {
  89                      $this->s[$k] = hypo($this->s[$k], $A[$i][$k]);
  90                  }
  91                  if ($this->s[$k] != 0.0) {
  92                      if ($A[$k][$k] < 0.0) {
  93                          $this->s[$k] = -$this->s[$k];
  94                      }
  95                      for ($i = $k; $i < $this->m; ++$i) {
  96                          $A[$i][$k] /= $this->s[$k];
  97                      }
  98                      $A[$k][$k] += 1.0;
  99                  }
 100                  $this->s[$k] = -$this->s[$k];
 101              }
 102  
 103              for ($j = $k + 1; $j < $this->n; ++$j) {
 104                  if (($k < $nct) & ($this->s[$k] != 0.0)) {
 105                      // Apply the transformation.
 106                      $t = 0;
 107                      for ($i = $k; $i < $this->m; ++$i) {
 108                          $t += $A[$i][$k] * $A[$i][$j];
 109                      }
 110                      $t = -$t / $A[$k][$k];
 111                      for ($i = $k; $i < $this->m; ++$i) {
 112                          $A[$i][$j] += $t * $A[$i][$k];
 113                      }
 114                      // Place the k-th row of A into e for the
 115                      // subsequent calculation of the row transformation.
 116                      $e[$j] = $A[$k][$j];
 117                  }
 118              }
 119  
 120              if ($wantu && ($k < $nct)) {
 121                  // Place the transformation in U for subsequent back
 122                  // multiplication.
 123                  for ($i = $k; $i < $this->m; ++$i) {
 124                      $this->U[$i][$k] = $A[$i][$k];
 125                  }
 126              }
 127  
 128              if ($k < $nrt) {
 129                  // Compute the k-th row transformation and place the
 130                  // k-th super-diagonal in e[$k].
 131                  // Compute 2-norm without under/overflow.
 132                  $e[$k] = 0;
 133                  for ($i = $k + 1; $i < $this->n; ++$i) {
 134                      $e[$k] = hypo($e[$k], $e[$i]);
 135                  }
 136                  if ($e[$k] != 0.0) {
 137                      if ($e[$k + 1] < 0.0) {
 138                          $e[$k] = -$e[$k];
 139                      }
 140                      for ($i = $k + 1; $i < $this->n; ++$i) {
 141                          $e[$i] /= $e[$k];
 142                      }
 143                      $e[$k + 1] += 1.0;
 144                  }
 145                  $e[$k] = -$e[$k];
 146                  if (($k + 1 < $this->m) && ($e[$k] != 0.0)) {
 147                      // Apply the transformation.
 148                      for ($i = $k + 1; $i < $this->m; ++$i) {
 149                          $work[$i] = 0.0;
 150                      }
 151                      for ($j = $k + 1; $j < $this->n; ++$j) {
 152                          for ($i = $k + 1; $i < $this->m; ++$i) {
 153                              $work[$i] += $e[$j] * $A[$i][$j];
 154                          }
 155                      }
 156                      for ($j = $k + 1; $j < $this->n; ++$j) {
 157                          $t = -$e[$j] / $e[$k + 1];
 158                          for ($i = $k + 1; $i < $this->m; ++$i) {
 159                              $A[$i][$j] += $t * $work[$i];
 160                          }
 161                      }
 162                  }
 163                  if ($wantv) {
 164                      // Place the transformation in V for subsequent
 165                      // back multiplication.
 166                      for ($i = $k + 1; $i < $this->n; ++$i) {
 167                          $this->V[$i][$k] = $e[$i];
 168                      }
 169                  }
 170              }
 171          }
 172  
 173          // Set up the final bidiagonal matrix or order p.
 174          $p = min($this->n, $this->m + 1);
 175          if ($nct < $this->n) {
 176              $this->s[$nct] = $A[$nct][$nct];
 177          }
 178          if ($this->m < $p) {
 179              $this->s[$p - 1] = 0.0;
 180          }
 181          if ($nrt + 1 < $p) {
 182              $e[$nrt] = $A[$nrt][$p - 1];
 183          }
 184          $e[$p - 1] = 0.0;
 185          // If required, generate U.
 186          if ($wantu) {
 187              for ($j = $nct; $j < $nu; ++$j) {
 188                  for ($i = 0; $i < $this->m; ++$i) {
 189                      $this->U[$i][$j] = 0.0;
 190                  }
 191                  $this->U[$j][$j] = 1.0;
 192              }
 193              for ($k = $nct - 1; $k >= 0; --$k) {
 194                  if ($this->s[$k] != 0.0) {
 195                      for ($j = $k + 1; $j < $nu; ++$j) {
 196                          $t = 0;
 197                          for ($i = $k; $i < $this->m; ++$i) {
 198                              $t += $this->U[$i][$k] * $this->U[$i][$j];
 199                          }
 200                          $t = -$t / $this->U[$k][$k];
 201                          for ($i = $k; $i < $this->m; ++$i) {
 202                              $this->U[$i][$j] += $t * $this->U[$i][$k];
 203                          }
 204                      }
 205                      for ($i = $k; $i < $this->m; ++$i) {
 206                          $this->U[$i][$k] = -$this->U[$i][$k];
 207                      }
 208                      $this->U[$k][$k] = 1.0 + $this->U[$k][$k];
 209                      for ($i = 0; $i < $k - 1; ++$i) {
 210                          $this->U[$i][$k] = 0.0;
 211                      }
 212                  } else {
 213                      for ($i = 0; $i < $this->m; ++$i) {
 214                          $this->U[$i][$k] = 0.0;
 215                      }
 216                      $this->U[$k][$k] = 1.0;
 217                  }
 218              }
 219          }
 220  
 221          // If required, generate V.
 222          if ($wantv) {
 223              for ($k = $this->n - 1; $k >= 0; --$k) {
 224                  if (($k < $nrt) && ($e[$k] != 0.0)) {
 225                      for ($j = $k + 1; $j < $nu; ++$j) {
 226                          $t = 0;
 227                          for ($i = $k + 1; $i < $this->n; ++$i) {
 228                              $t += $this->V[$i][$k] * $this->V[$i][$j];
 229                          }
 230                          $t = -$t / $this->V[$k + 1][$k];
 231                          for ($i = $k + 1; $i < $this->n; ++$i) {
 232                              $this->V[$i][$j] += $t * $this->V[$i][$k];
 233                          }
 234                      }
 235                  }
 236                  for ($i = 0; $i < $this->n; ++$i) {
 237                      $this->V[$i][$k] = 0.0;
 238                  }
 239                  $this->V[$k][$k] = 1.0;
 240              }
 241          }
 242  
 243          // Main iteration loop for the singular values.
 244          $pp = $p - 1;
 245          $iter = 0;
 246          $eps = 2.0 ** (-52.0);
 247  
 248          while ($p > 0) {
 249              // Here is where a test for too many iterations would go.
 250              // This section of the program inspects for negligible
 251              // elements in the s and e arrays.  On completion the
 252              // variables kase and k are set as follows:
 253              // kase = 1  if s(p) and e[k-1] are negligible and k<p
 254              // kase = 2  if s(k) is negligible and k<p
 255              // kase = 3  if e[k-1] is negligible, k<p, and
 256              //           s(k), ..., s(p) are not negligible (qr step).
 257              // kase = 4  if e(p-1) is negligible (convergence).
 258              for ($k = $p - 2; $k >= -1; --$k) {
 259                  if ($k == -1) {
 260                      break;
 261                  }
 262                  if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) {
 263                      $e[$k] = 0.0;
 264  
 265                      break;
 266                  }
 267              }
 268              if ($k == $p - 2) {
 269                  $kase = 4;
 270              } else {
 271                  for ($ks = $p - 1; $ks >= $k; --$ks) {
 272                      if ($ks == $k) {
 273                          break;
 274                      }
 275                      $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.);
 276                      if (abs($this->s[$ks]) <= $eps * $t) {
 277                          $this->s[$ks] = 0.0;
 278  
 279                          break;
 280                      }
 281                  }
 282                  if ($ks == $k) {
 283                      $kase = 3;
 284                  } elseif ($ks == $p - 1) {
 285                      $kase = 1;
 286                  } else {
 287                      $kase = 2;
 288                      $k = $ks;
 289                  }
 290              }
 291              ++$k;
 292  
 293              // Perform the task indicated by kase.
 294              switch ($kase) {
 295                  // Deflate negligible s(p).
 296                  case 1:
 297                      $f = $e[$p - 2];
 298                      $e[$p - 2] = 0.0;
 299                      for ($j = $p - 2; $j >= $k; --$j) {
 300                          $t = hypo($this->s[$j], $f);
 301                          $cs = $this->s[$j] / $t;
 302                          $sn = $f / $t;
 303                          $this->s[$j] = $t;
 304                          if ($j != $k) {
 305                              $f = -$sn * $e[$j - 1];
 306                              $e[$j - 1] = $cs * $e[$j - 1];
 307                          }
 308                          if ($wantv) {
 309                              for ($i = 0; $i < $this->n; ++$i) {
 310                                  $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1];
 311                                  $this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1];
 312                                  $this->V[$i][$j] = $t;
 313                              }
 314                          }
 315                      }
 316  
 317                      break;
 318                  // Split at negligible s(k).
 319                  case 2:
 320                      $f = $e[$k - 1];
 321                      $e[$k - 1] = 0.0;
 322                      for ($j = $k; $j < $p; ++$j) {
 323                          $t = hypo($this->s[$j], $f);
 324                          $cs = $this->s[$j] / $t;
 325                          $sn = $f / $t;
 326                          $this->s[$j] = $t;
 327                          $f = -$sn * $e[$j];
 328                          $e[$j] = $cs * $e[$j];
 329                          if ($wantu) {
 330                              for ($i = 0; $i < $this->m; ++$i) {
 331                                  $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1];
 332                                  $this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1];
 333                                  $this->U[$i][$j] = $t;
 334                              }
 335                          }
 336                      }
 337  
 338                      break;
 339                  // Perform one qr step.
 340                  case 3:
 341                      // Calculate the shift.
 342                      $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k]));
 343                      $sp = $this->s[$p - 1] / $scale;
 344                      $spm1 = $this->s[$p - 2] / $scale;
 345                      $epm1 = $e[$p - 2] / $scale;
 346                      $sk = $this->s[$k] / $scale;
 347                      $ek = $e[$k] / $scale;
 348                      $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;
 349                      $c = ($sp * $epm1) * ($sp * $epm1);
 350                      $shift = 0.0;
 351                      if (($b != 0.0) || ($c != 0.0)) {
 352                          $shift = sqrt($b * $b + $c);
 353                          if ($b < 0.0) {
 354                              $shift = -$shift;
 355                          }
 356                          $shift = $c / ($b + $shift);
 357                      }
 358                      $f = ($sk + $sp) * ($sk - $sp) + $shift;
 359                      $g = $sk * $ek;
 360                      // Chase zeros.
 361                      for ($j = $k; $j < $p - 1; ++$j) {
 362                          $t = hypo($f, $g);
 363                          $cs = $f / $t;
 364                          $sn = $g / $t;
 365                          if ($j != $k) {
 366                              $e[$j - 1] = $t;
 367                          }
 368                          $f = $cs * $this->s[$j] + $sn * $e[$j];
 369                          $e[$j] = $cs * $e[$j] - $sn * $this->s[$j];
 370                          $g = $sn * $this->s[$j + 1];
 371                          $this->s[$j + 1] = $cs * $this->s[$j + 1];
 372                          if ($wantv) {
 373                              for ($i = 0; $i < $this->n; ++$i) {
 374                                  $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1];
 375                                  $this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1];
 376                                  $this->V[$i][$j] = $t;
 377                              }
 378                          }
 379                          $t = hypo($f, $g);
 380                          $cs = $f / $t;
 381                          $sn = $g / $t;
 382                          $this->s[$j] = $t;
 383                          $f = $cs * $e[$j] + $sn * $this->s[$j + 1];
 384                          $this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1];
 385                          $g = $sn * $e[$j + 1];
 386                          $e[$j + 1] = $cs * $e[$j + 1];
 387                          if ($wantu && ($j < $this->m - 1)) {
 388                              for ($i = 0; $i < $this->m; ++$i) {
 389                                  $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1];
 390                                  $this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1];
 391                                  $this->U[$i][$j] = $t;
 392                              }
 393                          }
 394                      }
 395                      $e[$p - 2] = $f;
 396                      $iter = $iter + 1;
 397  
 398                      break;
 399                  // Convergence.
 400                  case 4:
 401                      // Make the singular values positive.
 402                      if ($this->s[$k] <= 0.0) {
 403                          $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);
 404                          if ($wantv) {
 405                              for ($i = 0; $i <= $pp; ++$i) {
 406                                  $this->V[$i][$k] = -$this->V[$i][$k];
 407                              }
 408                          }
 409                      }
 410                      // Order the singular values.
 411                      while ($k < $pp) {
 412                          if ($this->s[$k] >= $this->s[$k + 1]) {
 413                              break;
 414                          }
 415                          $t = $this->s[$k];
 416                          $this->s[$k] = $this->s[$k + 1];
 417                          $this->s[$k + 1] = $t;
 418                          if ($wantv && ($k < $this->n - 1)) {
 419                              for ($i = 0; $i < $this->n; ++$i) {
 420                                  $t = $this->V[$i][$k + 1];
 421                                  $this->V[$i][$k + 1] = $this->V[$i][$k];
 422                                  $this->V[$i][$k] = $t;
 423                              }
 424                          }
 425                          if ($wantu && ($k < $this->m - 1)) {
 426                              for ($i = 0; $i < $this->m; ++$i) {
 427                                  $t = $this->U[$i][$k + 1];
 428                                  $this->U[$i][$k + 1] = $this->U[$i][$k];
 429                                  $this->U[$i][$k] = $t;
 430                              }
 431                          }
 432                          ++$k;
 433                      }
 434                      $iter = 0;
 435                      --$p;
 436  
 437                      break;
 438              } // end switch
 439          } // end while
 440      }
 441  
 442      /**
 443       * Return the left singular vectors.
 444       *
 445       * @return Matrix U
 446       */
 447      public function getU()
 448      {
 449          return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));
 450      }
 451  
 452      /**
 453       * Return the right singular vectors.
 454       *
 455       * @return Matrix V
 456       */
 457      public function getV()
 458      {
 459          return new Matrix($this->V);
 460      }
 461  
 462      /**
 463       * Return the one-dimensional array of singular values.
 464       *
 465       * @return array diagonal of S
 466       */
 467      public function getSingularValues()
 468      {
 469          return $this->s;
 470      }
 471  
 472      /**
 473       * Return the diagonal matrix of singular values.
 474       *
 475       * @return Matrix S
 476       */
 477      public function getS()
 478      {
 479          $S = [];
 480          for ($i = 0; $i < $this->n; ++$i) {
 481              for ($j = 0; $j < $this->n; ++$j) {
 482                  $S[$i][$j] = 0.0;
 483              }
 484              $S[$i][$i] = $this->s[$i];
 485          }
 486  
 487          return new Matrix($S);
 488      }
 489  
 490      /**
 491       * Two norm.
 492       *
 493       * @return float max(S)
 494       */
 495      public function norm2()
 496      {
 497          return $this->s[0];
 498      }
 499  
 500      /**
 501       * Two norm condition number.
 502       *
 503       * @return float max(S)/min(S)
 504       */
 505      public function cond()
 506      {
 507          return $this->s[0] / $this->s[min($this->m, $this->n) - 1];
 508      }
 509  
 510      /**
 511       * Effective numerical matrix rank.
 512       *
 513       * @return int Number of nonnegligible singular values
 514       */
 515      public function rank()
 516      {
 517          $eps = 2.0 ** (-52.0);
 518          $tol = max($this->m, $this->n) * $this->s[0] * $eps;
 519          $r = 0;
 520          $iMax = count($this->s);
 521          for ($i = 0; $i < $iMax; ++$i) {
 522              if ($this->s[$i] > $tol) {
 523                  ++$r;
 524              }
 525          }
 526  
 527          return $r;
 528      }
 529  }