Matrices

Matrix Operations with PHP

In PHP it can be written as a class Matrix with implementation of a set of matrix operations. This class is a PHP implementation of matrix operations commonly used in linear algebra and, by extension, in various AI and machine learning algorithms. It provides a robust set of methods for performing matrix calculations, making it a valuable tool for developers working on AI projects in PHP.

Example of class Matrix:

 
<?php

namespace Apphp\MLKit\Math\Linear;

use 
Exception;

/**
 * Class Matrix
 *
 * Provides basic matrix operations for linear algebra: addition, subtraction, multiplication, determinant, inverse, etc.
 *
 * @package Apphp\MLKit\Math\Linear
 */
class Matrix {
    
/**
     * @var array<int, array<int, float>> The matrix data.
     */
    
private array $matrix;

    
/**
     * @var int Number of rows.
     */
    
private int $rows;

    
/**
     * @var int Number of columns.
     */
    
private int $cols;

    
/**
     * Matrix constructor.
     *
     * @param array<int, array<int, float>> $matrix
     * @throws Exception
     */
    
public function __construct(array $matrix) {
        if (empty(
$matrix) || !is_array($matrix[0])) {
            throw new 
Exception('Matrix must be a non-empty 2D array.');
        }
        
$this->matrix $matrix;
        
$this->rows count($matrix);
        
$this->cols count($matrix[0]);
    }

    
/**
     * Get the rank of the matrix.
     *
     * @return int
     */
    
public function rank(): int {
        
$epsilon 1e-10// Threshold for considering a value as zero
        
$ref $this->reducedEchelonForm();
        
$rank 0;
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                if (
abs($ref->matrix[$i][$j]) > $epsilon) {
                    
$rank++;
                    break;
                }
            }
        }

        return 
$rank;
    }

    
/**
     * Add another matrix to this matrix.
     *
     * @param Matrix $other
     * @return Matrix
     * @throws Exception
     */
    
public function add(Matrix $other): Matrix {
        if (
$this->rows !== $other->rows || $this->cols !== $other->cols) {
            throw new 
Exception('Matrices must have the same dimensions for addition.');
        }

        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$i][$j] = $this->matrix[$i][$j] + $other->matrix[$i][$j];
            }
        }

        return new 
Matrix($result);
    }

    
/**
     * Subtract another matrix from this matrix.
     *
     * @param Matrix $other
     * @return Matrix
     * @throws Exception
     */
    
public function subtract(Matrix $other): Matrix {
        if (
$this->rows !== $other->rows || $this->cols !== $other->cols) {
            throw new 
Exception('Matrices must have the same dimensions for subtraction.');
        }

        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$i][$j] = $this->matrix[$i][$j] - $other->matrix[$i][$j];
            }
        }

        return new 
Matrix($result);
    }

    
/**
     * Multiply this matrix by a scalar.
     *
     * @param float|int $scalar
     * @return Matrix
     */
    
public function scalarMultiply(float|int $scalar): Matrix {
        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$i][$j] = $this->matrix[$i][$j] * $scalar;
            }
        }

        return new 
Matrix($result);
    }

    
/**
     * Multiply this matrix by another matrix.
     *
     * @param Matrix $other
     * @return Matrix
     * @throws Exception
     */
    
public function multiply(Matrix $other): Matrix {
        if (
$this->cols !== $other->rows) {
            throw new 
Exception('Number of columns in the first matrix must equal the number of rows in the second matrix.');
        }

        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $other->cols$j++) {
                
$result[$i][$j] = 0;
                for (
$k 0$k $this->cols$k++) {
                    
$result[$i][$j] += $this->matrix[$i][$k] * $other->matrix[$k][$j];
                }
            }
        }

        return new 
Matrix($result);
    }

    
/**
     * Transpose the matrix.
     *
     * @return Matrix
     */
    
public function transpose(): Matrix {
        
$result = [];
        for (
$i 0$i $this->cols$i++) {
            for (
$j 0$j $this->rows$j++) {
                
$result[$i][$j] = $this->matrix[$j][$i];
            }
        }
        return new 
Matrix($result);
    }

    
/**
     * Compute the determinant of the matrix.
     *
     * @return float
     * @throws Exception
     */
    
public function determinant(): float {
        if (
$this->rows !== $this->cols) {
            throw new 
Exception('Determinant can only be calculated for square matrices.');
        }

        if (
$this->rows === 1) {
            return 
$this->matrix[0][0];
        }

        if (
$this->rows === 2) {
            return 
$this->matrix[0][0] * $this->matrix[1][1] - $this->matrix[0][1] * $this->matrix[1][0];
        }

        
$det 0;
        for (
$j 0$j $this->cols$j++) {
            
$det += (($j == 0) ? : -1) * $this->matrix[0][$j] * $this->cofactor(0$j)->determinant();
        }

        return 
$det;
    }

    
/**
     * Get the cofactor matrix after removing a row and column.
     *
     * @param int $row
     * @param int $col
     * @return Matrix
     */
    
private function cofactor(int $rowint $col): Matrix {
        
$result = [];
        
$r 0;
        for (
$i 0$i $this->rows$i++) {
            if (
$i == $row) {
                continue;
            }
            
$c 0;
            for (
$j 0$j $this->cols$j++) {
                if (
$j == $col) {
                    continue;
                }
                
$result[$r][$c] = $this->matrix[$i][$j];
                
$c++;
            }
            
$r++;
        }

        return new 
Matrix($result);
    }

    
/**
     * Get the inverse of the matrix.
     *
     * @return Matrix
     * @throws Exception
     */
    
public function inverse(): Matrix {
        
$det $this->determinant();
        if (
$det == 0) {
            throw new 
Exception('Matrix is not invertible.');
        }

        
$adjoint $this->adjoint();
        return 
$adjoint->scalarMultiply($det);
    }

    
/**
     * Get the adjoint of the matrix.
     *
     * @return Matrix
     */
    
private function adjoint(): Matrix {
        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$j][$i] = (($i $j) % == : -1) * $this->cofactor($i$j)->determinant();
            }
        }

        return new 
Matrix($result);
    }

    
/**
     * Get the reduced row echelon form of the matrix.
     *
     * @return Matrix
     */
    
private function reducedEchelonForm(): Matrix {
        
$ref $this->matrix;
        
$lead 0;
        for (
$r 0$r $this->rows$r++) {
            if (
$lead >= $this->cols) {
                return new 
Matrix($ref);
            }
            
$i $r;
            while (
$ref[$i][$lead] == 0) {
                
$i++;
                if (
$i == $this->rows) {
                    
$i $r;
                    
$lead++;
                    if (
$this->cols == $lead) {
                        return new 
Matrix($ref);
                    }
                }
            }
            
$temp $ref[$i];
            
$ref[$i] = $ref[$r];
            
$ref[$r] = $temp;
            
$lv $ref[$r][$lead];
            for (
$j 0$j $this->cols$j++) {
                
$ref[$r][$j] /= $lv;
            }
            for (
$i 0$i $this->rows$i++) {
                if (
$i != $r) {
                    
$lv $ref[$i][$lead];
                    for (
$j 0$j $this->cols$j++) {
                        
$ref[$i][$j] -= $lv $ref[$r][$j];
                    }
                }
            }
            
$lead++;
        }
        return new 
Matrix($ref);
    }

    
/**
     * Get the cofactor matrix of the matrix.
     *
     * @return Matrix
     */
    
public function cofactorMatrix(): Matrix {
        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$i][$j] = (($i $j) % == : -1) * $this->cofactor($i$j)->determinant();
            }
        }
        return new 
Matrix($result);
    }

    
/**
     * Get the adjugate matrix (transpose of cofactor matrix).
     *
     * @return Matrix
     */
    
public function adjugateMatrix(): Matrix {
        return 
$this->cofactorMatrix()->transpose();
    }

    
/**
     * Get a string representation of the matrix.
     *
     * @return string
     */
    
public function toString(): string {
        
$result '';
        for (
$i 0$i $this->rows$i++) {
            
$result .= '[' implode(', '$this->matrix[$i]) . "]\n";
        }
        return 
$result;
    }

    
/**
     * Check if the matrix is square (rows == cols).
     *
     * @return bool
     */
    
public function isSquare(): bool {
        return 
$this->rows === $this->cols;
    }

    
/**
     * Check if the matrix is symmetric (equal to its transpose).
     *
     * @return bool
     */
    
public function isSymmetric(): bool {
        if (!
$this->isSquare()) {
            return 
false;
        }
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                if (
$this->matrix[$i][$j] !== $this->matrix[$j][$i]) {
                    return 
false;
                }
            }
        }
        return 
true;
    }

    
/**
     * Compute the trace (sum of diagonal elements) of a square matrix.
     *
     * @return float
     * @throws Exception
     */
    
public function trace(): float {
        if (!
$this->isSquare()) {
            throw new 
Exception('Trace can only be computed for square matrices.');
        }
        
$trace 0.0;
        for (
$i 0$i $this->rows$i++) {
            
$trace += $this->matrix[$i][$i];
        }
        return 
$trace;
    }

    
/**
     * Get a specific row as an array.
     *
     * @param int $i
     * @return array<int, float>
     * @throws Exception
     */
    
public function getRow(int $i): array {
        if (
$i || $i >= $this->rows) {
            throw new 
Exception('Row index out of bounds.');
        }
        return 
$this->matrix[$i];
    }

    
/**
     * Get a specific column as an array.
     *
     * @param int $j
     * @return array<int, float>
     * @throws Exception
     */
    
public function getColumn(int $j): array {
        if (
$j || $j >= $this->cols) {
            throw new 
Exception('Column index out of bounds.');
        }
        
$col = [];
        for (
$i 0$i $this->rows$i++) {
            
$col[] = $this->matrix[$i][$j];
        }
        return 
$col;
    }

    
/**
     * Check if two matrices are equal (element-wise).
     *
     * @param Matrix $other
     * @return bool
     */
    
public function equals(Matrix $other): bool {
        if (
$this->rows !== $other->rows || $this->cols !== $other->cols) {
            return 
false;
        }
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                if (
$this->matrix[$i][$j] !== $other->matrix[$i][$j]) {
                    return 
false;
                }
            }
        }
        return 
true;
    }

    
/**
     * Fill the matrix with a specific value (in-place).
     *
     * @param float|int $value
     * @return void
     */
    
public function fill(float|int $value): void {
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$this->matrix[$i][$j] = $value;
            }
        }
    }

    
/**
     * Create an identity matrix of given size.
     *
     * @param int $size
     * @return Matrix
     */
    
public static function identity(int $size): Matrix {
        
$result = [];
        for (
$i 0$i $size$i++) {
            
$row array_fill(0$size0.0);
            
$row[$i] = 1.0;
            
// Ensure all elements are int (1, 0) to match test expectation
            
$result[] = array_map(fn($v) => (int)$v$row);
        }
        return new 
Matrix($result);
    }

    
/**
     * Create a zero matrix of given size.
     *
     * @param int $rows
     * @param int $cols
     * @return Matrix
     */
    
public static function zero(int $rowsint $cols): Matrix {
        
$result = [];
        for (
$i 0$i $rows$i++) {
            
$result[] = array_fill(0$cols0.0);
        }
        return new 
Matrix($result);
    }

    
/**
     * Apply a function to each element of the matrix and return a new matrix.
     *
     * @param callable $fn function(float $value, int $i, int $j): float
     * @return Matrix
     */
    
public function map(callable $fn): Matrix {
        
$result = [];
        for (
$i 0$i $this->rows$i++) {
            for (
$j 0$j $this->cols$j++) {
                
$result[$i][$j] = $fn($this->matrix[$i][$j], $i$j);
            }
        }
        return new 
Matrix($result);
    }

    
/**
     * Get the number of rows in the matrix.
     *
     * @return int
     */
    
public function getRows(): int {
        return 
$this->rows;
    }

    
/**
     * Get the number of columns in the matrix.
     *
     * @return int
     */
    
public function getCols(): int {
        return 
$this->cols;
    }

    
/**
     * Get the underlying matrix as a 2D array.
     *
     * @return array<int, array<int, float>>
     */
    
public function toArray(): array {
        return 
$this->matrix;
    }
}