Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

   1  <?php
   2  
   3  namespace Matrix;
   4  
   5  class Functions
   6  {
   7      /**
   8       * Validates an array of matrix, converting an array to a matrix if required.
   9       *
  10       * @param Matrix|array $matrix Matrix or an array to treat as a matrix.
  11       * @return Matrix The new matrix
  12       * @throws Exception If argument isn't a valid matrix or array.
  13       */
  14      private static function validateMatrix($matrix)
  15      {
  16          if (is_array($matrix)) {
  17              $matrix = new Matrix($matrix);
  18          }
  19          if (!$matrix instanceof Matrix) {
  20              throw new Exception('Must be Matrix or array');
  21          }
  22  
  23          return $matrix;
  24      }
  25  
  26      /**
  27       * Calculate the adjoint of the matrix
  28       *
  29       * @param Matrix $matrix The matrix whose adjoint we wish to calculate
  30       * @return Matrix
  31       *
  32       * @throws Exception
  33       */
  34      private static function getAdjoint(Matrix $matrix)
  35      {
  36          return self::transpose(
  37              self::getCofactors($matrix)
  38          );
  39      }
  40  
  41      /**
  42       * Return the adjoint of this matrix
  43       * The adjugate, classical adjoint, or adjunct of a square matrix is the transpose of its cofactor matrix.
  44       * The adjugate has sometimes been called the "adjoint", but today the "adjoint" of a matrix normally refers
  45       *     to its corresponding adjoint operator, which is its conjugate transpose.
  46       *
  47       * @param Matrix|array $matrix The matrix whose adjoint we wish to calculate
  48       * @return Matrix
  49       * @throws Exception
  50       **/
  51      public static function adjoint($matrix)
  52      {
  53          $matrix = self::validateMatrix($matrix);
  54  
  55          if (!$matrix->isSquare()) {
  56              throw new Exception('Adjoint can only be calculated for a square matrix');
  57          }
  58  
  59          return self::getAdjoint($matrix);
  60      }
  61  
  62      /**
  63       * Calculate the cofactors of the matrix
  64       *
  65       * @param Matrix $matrix The matrix whose cofactors we wish to calculate
  66       * @return Matrix
  67       *
  68       * @throws Exception
  69       */
  70      private static function getCofactors(Matrix $matrix)
  71      {
  72          $cofactors = self::getMinors($matrix);
  73          $dimensions = $matrix->rows;
  74  
  75          $cof = 1;
  76          for ($i = 0; $i < $dimensions; ++$i) {
  77              $cofs = $cof;
  78              for ($j = 0; $j < $dimensions; ++$j) {
  79                  $cofactors[$i][$j] *= $cofs;
  80                  $cofs = -$cofs;
  81              }
  82              $cof = -$cof;
  83          }
  84  
  85          return new Matrix($cofactors);
  86      }
  87  
  88      /**
  89       * Return the cofactors of this matrix
  90       *
  91       * @param Matrix|array $matrix The matrix whose cofactors we wish to calculate
  92       * @return Matrix
  93       *
  94       * @throws Exception
  95       */
  96      public static function cofactors($matrix)
  97      {
  98          $matrix = self::validateMatrix($matrix);
  99  
 100          if (!$matrix->isSquare()) {
 101              throw new Exception('Cofactors can only be calculated for a square matrix');
 102          }
 103  
 104          return self::getCofactors($matrix);
 105      }
 106  
 107      /**
 108       * @param Matrix $matrix
 109       * @param int $row
 110       * @param int $column
 111       * @return float
 112       * @throws Exception
 113       */
 114      private static function getDeterminantSegment(Matrix $matrix, $row, $column)
 115      {
 116          $tmpMatrix = $matrix->toArray();
 117          unset($tmpMatrix[$row]);
 118          array_walk(
 119              $tmpMatrix,
 120              function (&$row) use ($column) {
 121                  unset($row[$column]);
 122              }
 123          );
 124  
 125          return self::getDeterminant(new Matrix($tmpMatrix));
 126      }
 127  
 128      /**
 129       * Calculate the determinant of the matrix
 130       *
 131       * @param Matrix $matrix The matrix whose determinant we wish to calculate
 132       * @return float
 133       *
 134       * @throws Exception
 135       */
 136      private static function getDeterminant(Matrix $matrix)
 137      {
 138          $dimensions = $matrix->rows;
 139          $determinant = 0;
 140  
 141          switch ($dimensions) {
 142              case 1:
 143                  $determinant = $matrix->getValue(1, 1);
 144                  break;
 145              case 2:
 146                  $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) -
 147                      $matrix->getValue(1, 2) * $matrix->getValue(2, 1);
 148                  break;
 149              default:
 150                  for ($i = 1; $i <= $dimensions; ++$i) {
 151                      $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1);
 152                      if (($i % 2) == 0) {
 153                          $determinant -= $det;
 154                      } else {
 155                          $determinant += $det;
 156                      }
 157                  }
 158                  break;
 159          }
 160  
 161          return $determinant;
 162      }
 163  
 164      /**
 165       * Return the determinant of this matrix
 166       *
 167       * @param Matrix|array $matrix The matrix whose determinant we wish to calculate
 168       * @return float
 169       * @throws Exception
 170       **/
 171      public static function determinant($matrix)
 172      {
 173          $matrix = self::validateMatrix($matrix);
 174  
 175          if (!$matrix->isSquare()) {
 176              throw new Exception('Determinant can only be calculated for a square matrix');
 177          }
 178  
 179          return self::getDeterminant($matrix);
 180      }
 181  
 182      /**
 183       * Return the diagonal of this matrix
 184       *
 185       * @param Matrix|array $matrix The matrix whose diagonal we wish to calculate
 186       * @return Matrix
 187       * @throws Exception
 188       **/
 189      public static function diagonal($matrix)
 190      {
 191          $matrix = self::validateMatrix($matrix);
 192  
 193          if (!$matrix->isSquare()) {
 194              throw new Exception('Diagonal can only be extracted from a square matrix');
 195          }
 196  
 197          $dimensions = $matrix->rows;
 198          $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
 199              ->toArray();
 200  
 201          for ($i = 0; $i < $dimensions; ++$i) {
 202              $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1);
 203          }
 204  
 205          return new Matrix($grid);
 206      }
 207  
 208      /**
 209       * Return the antidiagonal of this matrix
 210       *
 211       * @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate
 212       * @return Matrix
 213       * @throws Exception
 214       **/
 215      public static function antidiagonal($matrix)
 216      {
 217          $matrix = self::validateMatrix($matrix);
 218  
 219          if (!$matrix->isSquare()) {
 220              throw new Exception('Anti-Diagonal can only be extracted from a square matrix');
 221          }
 222  
 223          $dimensions = $matrix->rows;
 224          $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
 225              ->toArray();
 226  
 227          for ($i = 0; $i < $dimensions; ++$i) {
 228              $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i);
 229          }
 230  
 231          return new Matrix($grid);
 232      }
 233  
 234      /**
 235       * Return the identity matrix
 236       * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix
 237       *   with ones on the main diagonal and zeros elsewhere
 238       *
 239       * @param Matrix|array $matrix The matrix whose identity we wish to calculate
 240       * @return Matrix
 241       * @throws Exception
 242       **/
 243      public static function identity($matrix)
 244      {
 245          $matrix = self::validateMatrix($matrix);
 246  
 247          if (!$matrix->isSquare()) {
 248              throw new Exception('Identity can only be created for a square matrix');
 249          }
 250  
 251          $dimensions = $matrix->rows;
 252  
 253          return Builder::createIdentityMatrix($dimensions);
 254      }
 255  
 256      /**
 257       * Return the inverse of this matrix
 258       *
 259       * @param Matrix|array $matrix The matrix whose inverse we wish to calculate
 260       * @return Matrix
 261       * @throws Exception
 262       **/
 263      public static function inverse($matrix, string $type = 'inverse')
 264      {
 265          $matrix = self::validateMatrix($matrix);
 266  
 267          if (!$matrix->isSquare()) {
 268              throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix');
 269          }
 270  
 271          $determinant = self::getDeterminant($matrix);
 272          if ($determinant == 0.0) {
 273              throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant');
 274          }
 275  
 276          if ($matrix->rows == 1) {
 277              return new Matrix([[1 / $matrix->getValue(1, 1)]]);
 278          }
 279  
 280          return self::getAdjoint($matrix)
 281              ->multiply(1 / $determinant);
 282      }
 283  
 284      /**
 285       * Calculate the minors of the matrix
 286       *
 287       * @param Matrix $matrix The matrix whose minors we wish to calculate
 288       * @return array[]
 289       *
 290       * @throws Exception
 291       */
 292      protected static function getMinors(Matrix $matrix)
 293      {
 294          $minors = $matrix->toArray();
 295          $dimensions = $matrix->rows;
 296          if ($dimensions == 1) {
 297              return $minors;
 298          }
 299  
 300          for ($i = 0; $i < $dimensions; ++$i) {
 301              for ($j = 0; $j < $dimensions; ++$j) {
 302                  $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j);
 303              }
 304          }
 305  
 306          return $minors;
 307      }
 308  
 309      /**
 310       * Return the minors of the matrix
 311       * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or
 312       *     more of its rows or columns.
 313       * Minors obtained by removing just one row and one column from square matrices (first minors) are required for
 314       *     calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of
 315       *     square matrices.
 316       *
 317       * @param Matrix|array $matrix The matrix whose minors we wish to calculate
 318       * @return Matrix
 319       * @throws Exception
 320       **/
 321      public static function minors($matrix)
 322      {
 323          $matrix = self::validateMatrix($matrix);
 324  
 325          if (!$matrix->isSquare()) {
 326              throw new Exception('Minors can only be calculated for a square matrix');
 327          }
 328  
 329          return new Matrix(self::getMinors($matrix));
 330      }
 331  
 332      /**
 333       * Return the trace of this matrix
 334       * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right)
 335       *     of the matrix
 336       *
 337       * @param Matrix|array $matrix The matrix whose trace we wish to calculate
 338       * @return float
 339       * @throws Exception
 340       **/
 341      public static function trace($matrix)
 342      {
 343          $matrix = self::validateMatrix($matrix);
 344  
 345          if (!$matrix->isSquare()) {
 346              throw new Exception('Trace can only be extracted from a square matrix');
 347          }
 348  
 349          $dimensions = $matrix->rows;
 350          $result = 0;
 351          for ($i = 1; $i <= $dimensions; ++$i) {
 352              $result += $matrix->getValue($i, $i);
 353          }
 354  
 355          return $result;
 356      }
 357  
 358      /**
 359       * Return the transpose of this matrix
 360       *
 361       * @param Matrix|\a $matrix The matrix whose transpose we wish to calculate
 362       * @return Matrix
 363       **/
 364      public static function transpose($matrix)
 365      {
 366          $matrix = self::validateMatrix($matrix);
 367  
 368          $array = array_values(array_merge([null], $matrix->toArray()));
 369          $grid = call_user_func_array(
 370              'array_map',
 371              $array
 372          );
 373  
 374          return new Matrix($grid);
 375      }
 376  }