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.
<?php

declare(strict_types=1);

namespace Phpml\Math;

use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\MatrixException;
use Phpml\Math\LinearAlgebra\LUDecomposition;

class Matrix
{
    /**
     * @var array
     */
    private $matrix = [];

    /**
     * @var int
     */
    private $rows;

    /**
     * @var int
     */
    private $columns;

    /**
     * @var float
     */
    private $determinant;

    /**
     * @throws InvalidArgumentException
     */
    public function __construct(array $matrix, bool $validate = true)
    {
        // When a row vector is given
        if (!is_array($matrix[0])) {
            $this->rows = 1;
            $this->columns = count($matrix);
            $matrix = [$matrix];
        } else {
            $this->rows = count($matrix);
            $this->columns = count($matrix[0]);
        }

        if ($validate) {
            for ($i = 0; $i < $this->rows; ++$i) {
                if (count($matrix[$i]) !== $this->columns) {
                    throw new InvalidArgumentException('Matrix dimensions did not match');
                }
            }
        }

        $this->matrix = $matrix;
    }

    public static function fromFlatArray(array $array): self
    {
        $matrix = [];
        foreach ($array as $value) {
            $matrix[] = [$value];
        }

        return new self($matrix);
    }

    public function toArray(): array
    {
        return $this->matrix;
    }

    public function toScalar(): float
    {
        return $this->matrix[0][0];
    }

    public function getRows(): int
    {
        return $this->rows;
    }

    public function getColumns(): int
    {
        return $this->columns;
    }

    /**
     * @throws MatrixException
     */
    public function getColumnValues(int $column): array
    {
        if ($column >= $this->columns) {
            throw new MatrixException('Column out of range');
        }

        return array_column($this->matrix, $column);
    }

    /**
     * @return float|int
     *
     * @throws MatrixException
     */
    public function getDeterminant()
    {
        if ($this->determinant !== null) {
            return $this->determinant;
        }

        if (!$this->isSquare()) {
            throw new MatrixException('Matrix is not square matrix');
        }

        $lu = new LUDecomposition($this);

        return $this->determinant = $lu->det();
    }

    public function isSquare(): bool
    {
        return $this->columns === $this->rows;
    }

    public function transpose(): self
    {
        if ($this->rows === 1) {
< $matrix = array_map(function ($el) {
> $matrix = array_map(static function ($el): array {
return [$el]; }, $this->matrix[0]); } else { $matrix = array_map(null, ...$this->matrix); } return new self($matrix, false); } public function multiply(self $matrix): self { if ($this->columns !== $matrix->getRows()) { throw new InvalidArgumentException('Inconsistent matrix supplied'); } $array1 = $this->toArray(); $array2 = $matrix->toArray(); $colCount = $matrix->columns; /* - To speed-up multiplication, we need to avoid use of array index operator [ ] as much as possible( See #255 for details) - A combination of "foreach" and "array_column" works much faster then accessing the array via index operator */ $product = []; foreach ($array1 as $row => $rowData) { for ($col = 0; $col < $colCount; ++$col) { $columnData = array_column($array2, $col); $sum = 0; foreach ($rowData as $key => $valueData) { $sum += $valueData * $columnData[$key]; } $product[$row][$col] = $sum; } } return new self($product, false); } /** * @param float|int $value */ public function divideByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value; } } return new self($newMatrix, false); } /** * @param float|int $value */ public function multiplyByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value; } } return new self($newMatrix, false); } /** * Element-wise addition of the matrix with another one */ public function add(self $other): self {
< return $this->_add($other);
> return $this->sum($other);
} /** * Element-wise subtracting of another matrix from this one */ public function subtract(self $other): self {
< return $this->_add($other, -1);
> return $this->sum($other, -1);
} public function inverse(): self { if (!$this->isSquare()) { throw new MatrixException('Matrix is not square matrix'); } $LU = new LUDecomposition($this); $identity = $this->getIdentity(); $inverse = $LU->solve($identity); return new self($inverse, false); } public function crossOut(int $row, int $column): self { $newMatrix = []; $r = 0; for ($i = 0; $i < $this->rows; ++$i) { $c = 0; if ($row != $i) { for ($j = 0; $j < $this->columns; ++$j) { if ($column != $j) { $newMatrix[$r][$c] = $this->matrix[$i][$j]; ++$c; } } ++$r; } } return new self($newMatrix, false); } public function isSingular(): bool { return $this->getDeterminant() == 0; } /** * Frobenius norm (Hilbert–Schmidt norm, Euclidean norm) (‖A‖F) * Square root of the sum of the square of all elements. * * https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm * * _____________ * /ᵐ ⁿ * ‖A‖F = √ Σ Σ |aᵢⱼ|² * ᵢ₌₁ ᵢ₌₁ */ public function frobeniusNorm(): float { $squareSum = 0; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { $squareSum += $this->matrix[$i][$j] ** 2; } } return $squareSum ** .5; } /** * Returns the transpose of given array */ public static function transposeArray(array $array): array { return (new self($array, false))->transpose()->toArray(); } /** * Returns the dot product of two arrays<br> * Matrix::dot(x, y) ==> x.y' */ public static function dot(array $array1, array $array2): array { $m1 = new self($array1, false); $m2 = new self($array2, false); return $m1->multiply($m2->transpose())->toArray()[0]; } /** * Element-wise addition or substraction depending on the given sign parameter */
< private function _add(self $other, int $sign = 1): self
> private function sum(self $other, int $sign = 1): self
{ $a1 = $this->toArray(); $a2 = $other->toArray(); $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($k = 0; $k < $this->columns; ++$k) { $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; } } return new self($newMatrix, false); } /** * Returns diagonal identity matrix of the same size of this matrix */ private function getIdentity(): self { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { $array[$i][$i] = 1; } return new self($array, false); } }