See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
1 <?php 2 3 /** 4 * 5 * Class for the management of Matrices 6 * 7 * @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix) 8 * @license https://opensource.org/licenses/MIT MIT 9 */ 10 11 namespace Matrix; 12 13 use Generator; 14 use Matrix\Decomposition\LU; 15 use Matrix\Decomposition\QR; 16 17 /** 18 * Matrix object. 19 * 20 * @package Matrix 21 * 22 * @property-read int $rows The number of rows in the matrix 23 * @property-read int $columns The number of columns in the matrix 24 * @method Matrix antidiagonal() 25 * @method Matrix adjoint() 26 * @method Matrix cofactors() 27 * @method float determinant() 28 * @method Matrix diagonal() 29 * @method Matrix identity() 30 * @method Matrix inverse() 31 * @method Matrix minors() 32 * @method float trace() 33 * @method Matrix transpose() 34 * @method Matrix add(...$matrices) 35 * @method Matrix subtract(...$matrices) 36 * @method Matrix multiply(...$matrices) 37 * @method Matrix divideby(...$matrices) 38 * @method Matrix divideinto(...$matrices) 39 * @method Matrix directsum(...$matrices) 40 */ 41 class Matrix 42 { 43 protected $rows; 44 protected $columns; 45 protected $grid = []; 46 47 /* 48 * Create a new Matrix object from an array of values 49 * 50 * @param array $grid 51 */ 52 final public function __construct(array $grid) 53 { 54 $this->buildFromArray(array_values($grid)); 55 } 56 57 /* 58 * Create a new Matrix object from an array of values 59 * 60 * @param array $grid 61 */ 62 protected function buildFromArray(array $grid): void 63 { 64 $this->rows = count($grid); 65 $columns = array_reduce( 66 $grid, 67 function ($carry, $value) { 68 return max($carry, is_array($value) ? count($value) : 1); 69 } 70 ); 71 $this->columns = $columns; 72 73 array_walk( 74 $grid, 75 function (&$value) use ($columns) { 76 if (!is_array($value)) { 77 $value = [$value]; 78 } 79 $value = array_pad(array_values($value), $columns, null); 80 } 81 ); 82 83 $this->grid = $grid; 84 } 85 86 /** 87 * Validate that a row number is a positive integer 88 * 89 * @param int $row 90 * @return int 91 * @throws Exception 92 */ 93 public static function validateRow(int $row): int 94 { 95 if ((!is_numeric($row)) || (intval($row) < 1)) { 96 throw new Exception('Invalid Row'); 97 } 98 99 return (int)$row; 100 } 101 102 /** 103 * Validate that a column number is a positive integer 104 * 105 * @param int $column 106 * @return int 107 * @throws Exception 108 */ 109 public static function validateColumn(int $column): int 110 { 111 if ((!is_numeric($column)) || (intval($column) < 1)) { 112 throw new Exception('Invalid Column'); 113 } 114 115 return (int)$column; 116 } 117 118 /** 119 * Validate that a row number falls within the set of rows for this matrix 120 * 121 * @param int $row 122 * @return int 123 * @throws Exception 124 */ 125 protected function validateRowInRange(int $row): int 126 { 127 $row = static::validateRow($row); 128 if ($row > $this->rows) { 129 throw new Exception('Requested Row exceeds matrix size'); 130 } 131 132 return $row; 133 } 134 135 /** 136 * Validate that a column number falls within the set of columns for this matrix 137 * 138 * @param int $column 139 * @return int 140 * @throws Exception 141 */ 142 protected function validateColumnInRange(int $column): int 143 { 144 $column = static::validateColumn($column); 145 if ($column > $this->columns) { 146 throw new Exception('Requested Column exceeds matrix size'); 147 } 148 149 return $column; 150 } 151 152 /** 153 * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows 154 * A $rowCount value of 0 will return all rows of the matrix from $row 155 * A negative $rowCount value will return rows until that many rows from the end of the matrix 156 * 157 * Note that row numbers start from 1, not from 0 158 * 159 * @param int $row 160 * @param int $rowCount 161 * @return static 162 * @throws Exception 163 */ 164 public function getRows(int $row, int $rowCount = 1): Matrix 165 { 166 $row = $this->validateRowInRange($row); 167 if ($rowCount === 0) { 168 $rowCount = $this->rows - $row + 1; 169 } 170 171 return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); 172 } 173 174 /** 175 * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns 176 * A $columnCount value of 0 will return all columns of the matrix from $column 177 * A negative $columnCount value will return columns until that many columns from the end of the matrix 178 * 179 * Note that column numbers start from 1, not from 0 180 * 181 * @param int $column 182 * @param int $columnCount 183 * @return Matrix 184 * @throws Exception 185 */ 186 public function getColumns(int $column, int $columnCount = 1): Matrix 187 { 188 $column = $this->validateColumnInRange($column); 189 if ($columnCount < 1) { 190 $columnCount = $this->columns + $columnCount - $column + 1; 191 } 192 193 $grid = []; 194 for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { 195 $grid[] = array_column($this->grid, $i); 196 } 197 198 return (new static($grid))->transpose(); 199 } 200 201 /** 202 * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, 203 * and $rowCount rows 204 * A negative $rowCount value will drop rows until that many rows from the end of the matrix 205 * A $rowCount value of 0 will remove all rows of the matrix from $row 206 * 207 * Note that row numbers start from 1, not from 0 208 * 209 * @param int $row 210 * @param int $rowCount 211 * @return static 212 * @throws Exception 213 */ 214 public function dropRows(int $row, int $rowCount = 1): Matrix 215 { 216 $this->validateRowInRange($row); 217 if ($rowCount === 0) { 218 $rowCount = $this->rows - $row + 1; 219 } 220 221 $grid = $this->grid; 222 array_splice($grid, $row - 1, (int)$rowCount); 223 224 return new static($grid); 225 } 226 227 /** 228 * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, 229 * and $columnCount columns 230 * A negative $columnCount value will drop columns until that many columns from the end of the matrix 231 * A $columnCount value of 0 will remove all columns of the matrix from $column 232 * 233 * Note that column numbers start from 1, not from 0 234 * 235 * @param int $column 236 * @param int $columnCount 237 * @return static 238 * @throws Exception 239 */ 240 public function dropColumns(int $column, int $columnCount = 1): Matrix 241 { 242 $this->validateColumnInRange($column); 243 if ($columnCount < 1) { 244 $columnCount = $this->columns + $columnCount - $column + 1; 245 } 246 247 $grid = $this->grid; 248 array_walk( 249 $grid, 250 function (&$row) use ($column, $columnCount) { 251 array_splice($row, $column - 1, (int)$columnCount); 252 } 253 ); 254 255 return new static($grid); 256 } 257 258 /** 259 * Return a value from this matrix, from the "cell" identified by the row and column numbers 260 * Note that row and column numbers start from 1, not from 0 261 * 262 * @param int $row 263 * @param int $column 264 * @return mixed 265 * @throws Exception 266 */ 267 public function getValue(int $row, int $column) 268 { 269 $row = $this->validateRowInRange($row); 270 $column = $this->validateColumnInRange($column); 271 272 return $this->grid[$row - 1][$column - 1]; 273 } 274 275 /** 276 * Returns a Generator that will yield each row of the matrix in turn as a vector matrix 277 * or the value of each cell if the matrix is a column vector 278 * 279 * @return Generator|Matrix[]|mixed[] 280 */ 281 public function rows(): Generator 282 { 283 foreach ($this->grid as $i => $row) { 284 yield $i + 1 => ($this->columns == 1) 285 ? $row[0] 286 : new static([$row]); 287 } 288 } 289 290 /** 291 * Returns a Generator that will yield each column of the matrix in turn as a vector matrix 292 * or the value of each cell if the matrix is a row vector 293 * 294 * @return Generator|Matrix[]|mixed[] 295 */ 296 public function columns(): Generator 297 { 298 for ($i = 0; $i < $this->columns; ++$i) { 299 yield $i + 1 => ($this->rows == 1) 300 ? $this->grid[0][$i] 301 : new static(array_column($this->grid, $i)); 302 } 303 } 304 305 /** 306 * Identify if the row and column dimensions of this matrix are equal, 307 * i.e. if it is a "square" matrix 308 * 309 * @return bool 310 */ 311 public function isSquare(): bool 312 { 313 return $this->rows === $this->columns; 314 } 315 316 /** 317 * Identify if this matrix is a vector 318 * i.e. if it comprises only a single row or a single column 319 * 320 * @return bool 321 */ 322 public function isVector(): bool 323 { 324 return $this->rows === 1 || $this->columns === 1; 325 } 326 327 /** 328 * Return the matrix as a 2-dimensional array 329 * 330 * @return array 331 */ 332 public function toArray(): array 333 { 334 return $this->grid; 335 } 336 337 /** 338 * Solve A*X = B. 339 * 340 * @param Matrix $B Right hand side 341 * 342 * @throws Exception 343 * 344 * @return Matrix ... Solution if A is square, least squares solution otherwise 345 */ 346 public function solve(Matrix $B): Matrix 347 { 348 if ($this->columns === $this->rows) { 349 return (new LU($this))->solve($B); 350 } 351 352 return (new QR($this))->solve($B); 353 } 354 355 protected static $getters = [ 356 'rows', 357 'columns', 358 ]; 359 360 /** 361 * Access specific properties as read-only (no setters) 362 * 363 * @param string $propertyName 364 * @return mixed 365 * @throws Exception 366 */ 367 public function __get(string $propertyName) 368 { 369 $propertyName = strtolower($propertyName); 370 371 // Test for function calls 372 if (in_array($propertyName, self::$getters)) { 373 return $this->$propertyName; 374 } 375 376 throw new Exception('Property does not exist'); 377 } 378 379 protected static $functions = [ 380 'adjoint', 381 'antidiagonal', 382 'cofactors', 383 'determinant', 384 'diagonal', 385 'identity', 386 'inverse', 387 'minors', 388 'trace', 389 'transpose', 390 ]; 391 392 protected static $operations = [ 393 'add', 394 'subtract', 395 'multiply', 396 'divideby', 397 'divideinto', 398 'directsum', 399 ]; 400 401 /** 402 * Returns the result of the function call or operation 403 * 404 * @param string $functionName 405 * @param mixed[] $arguments 406 * @return Matrix|float 407 * @throws Exception 408 */ 409 public function __call(string $functionName, $arguments) 410 { 411 $functionName = strtolower(str_replace('_', '', $functionName)); 412 413 // Test for function calls 414 if (in_array($functionName, self::$functions, true)) { 415 return Functions::$functionName($this, ...$arguments); 416 } 417 // Test for operation calls 418 if (in_array($functionName, self::$operations, true)) { 419 return Operations::$functionName($this, ...$arguments); 420 } 421 throw new Exception('Function or Operation does not exist'); 422 } 423 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body