src/Matrix.php
<?php
/**
* matrix.class.php
*
* Will set up the defines for error checking as well as provide
* the Matrix class for include. As this is made to be modular,
* only the class (and possibly helper classes) along with their
* defines will be found in this file.
* @package Math
* @subpackage Matrix
*/
namespace jlawrence\eos;
/**
* Matrix Class
*
* This class will allow you to create and use Matrices
* as well as providing common Matrix operations. It
* uses PHP5 for OOP and Error Throwing and is commented
* for the PHPDoc Parser for documentation creation.
*
* @version $Id: matrix.class.php 10 2012-08-06 23:41:36Z jlawrence11 $
* @author Jon Lawrence <JLawrence11@gmail.com>
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @copyright Copyright �2012, Jon Lawrence
* @package Math
* @subpackage Matrix
*/
class Matrix {
/**
* Invalid String input type
*/
const E_INVALID_INPUT = 5001;
/**
* Matrix needed to be a square matrix for the operation
*/
const E_NOT_SQUARE = 5002;
/**
* Matrix was undefined
*/
const E_NO_MATRIX = 5003;
/**
* Matrix had varying column lengths
*/
const E_INVALID_MATRIX = 5004;
/**
* Matrix operation required rows/cols to be even, they were not
*/
const E_NOT_EQUAL = 5005;
/**
* Determinate was '0' while preforming another operation
*/
const E_NO_INVERSE = 5006;
private $matrix;
/**
* Construct method
*
* For format of input string, see the see tag below
*
* @see Matrix::_assign()
* @param string $mText Matrix text input
*/
public function __construct($mText="")
{
if ($mText) $this->_assign($mText);
}
/**
* Create a matrix based on string input similar to the TI Calculators
* input string "[1,2,3;4,5,6;7,8,9]" is the equivalent of the matrix:
* <pre>
* | 1 2 3 |
* | 4 5 6 |
* | 7 8 9 |
* </pre>
*
* @param String $mText The matrix in string format to assign to the current object
* @return Boolean True if is passes verification after being converted
* @throws \Exception If the input text is not in a valid format
*/
public function _assign($mText)
{
if(trim($mText)=="")
return false;
$mText = preg_replace("/\s/", "", $mText);
if(!preg_match("/^\[(([\-]*[0-9\. ]+[,]{0,1})+[;]{0,1})*\]$/", $mText)) {
throw new \Exception("'{$mText}' is not a valid input", Matrix::E_INVALID_INPUT);
}
$mText = preg_replace("/(\[|\])/", "", $mText);
$rows = explode(";", $mText);
$i=0;$j=0;
foreach($rows as $row)
{
$cols = explode(",", $row);
foreach($cols as $value)
{
$this->matrix[$i][$j] = $value;
$j++;
}
$i++;
$j = 0;
}
return $this->_verify();
}
/**
* Private function that will verify all the columns have the same
* number of items, ensuring it is a valid matrix
*
* @access private
* @param array|bool $mArray
* @return bool True if it passes, false if not a valid matrix
*/
private function _verify($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
$nSet = false;
if(is_array($mArray))
{
foreach($mArray as $row)
{
$cols = count($row);
if($nSet===false) {
$nSet = $cols;
}
if($cols != $nSet) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* Is it a valid matrix?
*
* Public function to tell the class user whether or not the passed
* array is valid, if no array is passed, it will tell the user whether
* the matrix of the current instance is valid. Valid is denoted by all
* rows have the same number of columns.
*
* @param array|bool $mArray Array to be used, if not assigned will default to $this->matrix
* @return bool True/False depending on if array is a valid matrix
*/
public function isValid($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
return $this->_verify($mArray);
}
/**
* Is it a square Matrix?
*
* Will determine whether or not the matrix is valid, and if it
* is, will determine if the matrix is a square matrix (n by n).
*
* @param array|bool $mArray Matrix array, if not assigned will use $this->matrix
* @return bool True/False depending on whether or not the matrix is square
*/
public function isSquare($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
if(!$this->_verify($mArray)) {
return false;
}
$rows = count($mArray);
$cols = count($mArray[0]);
return ($rows == $cols);
}
/**
* Get 'n' from a square (n by n) Matrix
*
* Will check to see if a matrix is square, if so, will return 'n', which
* is the number of rows==columns in the matrix
*
* @param array|bool $mArray Matrix array, uses $this->matrix if not assigned
* @return int The 'n' of a square matrix, or false if not square
* @throws \Exception If not a square matrix, throws an exception
*/
public function _getN($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
if($this->isSquare($mArray)) {
return count($mArray);
} else {
$m = $this->toString($mArray);
throw new \Exception("'{$m}' is not a square matrix", Matrix::E_NOT_SQUARE);
}
}
/**
* Create an Identity Matrix
*
* Creates an Identity Matrix of size 'n'.
*
* @link http://en.wikipedia.org/wiki/Identity_matrix
* @param int $n The rows/cols of identity matrix
* @param bool $useInternal If true will set $this->matrix
* @return Matrix|bool Return an identity matrix if $useInternal is false, otherwise 'true'
*/
public function createIdentity($n, $useInternal = true)
{
$mArray = array();
for($rows=0;$rows<$n;$rows++) {
for($cols=0;$cols<$n;$cols++) {
if($rows==$cols) {
$mArray[$rows][$cols] = 1;
} else {
$mArray[$rows][$cols] = 0;
}
}
}
if($useInternal == true) {
$this->matrix = $mArray;
return true;
} else {
$nMatrix = new Matrix($this->toString($mArray));
return $nMatrix;
}
}
/**
* Convert current Matrix to string format
*
* Convert an array to the string format used by this class.
*
* @see Matrix::_assign()
* @param array|bool $mArray if not assigned will use this instance's matrix.
* @throws \Exception If matrix is not an array
* @return string The array broken down in to string format
*/
public function toString($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
$rows=array();
if(is_array($mArray))
{
foreach($mArray as $cols){
$rows[] = implode(",", $cols);
}
$retString = sprintf("[%s]", implode($rows, ";"));
return $retString;
} else {
throw new \Exception("No matrix to convert", Matrix::E_NO_MATRIX);
}
}
/**
* Overload PHP's class __toString() method
*
* PHP magic method for "echoing" this object without a specific method called
* Will use {@link Matrix::toString()} with no parameters for it's return.
*
* @return string Returns the $matrix value in string format
*/
public function __toString()
{
return $this->toString();
}
/**
* Get Matrix Array
*
* Will return the matrix array of the current instance.
*
* @return array The matrix array of the current instance
*/
public function getArray()
{
return $this->matrix;
}
/**
* Formatted Matrix output for use in console
*
* Will output the matrix in 'pretty' format, if used with 'echo' and
* HTML, surround it by the '<<pre>>' and '<</pre>>' tags to display properly
*
* @param int $width The width of printing space to use
* @param array|bool $mArray Matrix array, defaults to $this->matrix
* @return string "Pretty-Printed" matrix in ASCII format
*/
public function prettyPrint($width=80, $mArray=false)
{
if(!$mArray) $mArray = $this->matrix;
if(!$this->_verify($mArray)) return false;
$out = "";
$aCount = count($mArray[0]);
$space = floor(($width-4)/$aCount);
$space_2 = floor($space/2);
foreach($mArray as $row)
{
$out .= sprintf("| %{$space_2}.2f", $row[0]);
for($i=1;$i<$aCount;$i++)
{
$out .= sprintf("%{$space}.2f", $row[$i]);
}
$out .= sprintf("%{$space_2}s |\n", " ");
}
return $out;
}
/**
* Adds two matrices together
*
* Will add the inputted Matrix to the current instance, and return
* the result as Matrix class.
*
* @link http://en.wikipedia.org/wiki/Matrix_addition
* @param Matrix $nMatrix Matrix class to be added to current instance
* @return Matrix The result of the addition
* @throws \Exception $msg of exception explains problem
*/
public function addMatrix(Matrix $nMatrix)
{
if(!$this->_verify() || !$nMatrix->_verify())
throw new \Exception("Matrices have varying column sizes", Matrix::E_INVALID_MATRIX);
$matrix1 = $this->getArray();
$matrix2 = $nMatrix->getArray();
if((count($matrix1)!=count($matrix2)) || (count($matrix1[0])!=count($matrix2[0])))
{
$m1 = $this->toString($matrix1);
$m2 = $this->toString($matrix2);
throw new \Exception("The rows and/or columns '{$m1}' and '{$m2}' are not the same", Matrix::E_NOT_EQUAL);
}
$rArray = array();
for($row=0;$row<count($matrix1);$row++) {
for($col=0;$col<count($matrix1[0]);$col++) {
$rArray[$row][$col] = $matrix1[$row][$col] + $matrix2[$row][$col];
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Subtract Matrices
*
* Will subtract the inputted Matrix from the current instance, and return
* the result as Matrix class.
*
* @link http://en.wikipedia.org/wiki/Matrix_subtraction
* @param Matrix $nMatrix Matrix class to be subtracted from current instance
* @return Matrix The result of the subtraction
* @throws \Exception $msg of exception explains problem
*/
public function subMatrix(Matrix $nMatrix)
{
if(!$this->_verify() || !$nMatrix->_verify())
throw new \Exception("Matrices have varying column sizes", Matrix::E_INVALID_MATRIX);
$matrix1 = $this->getArray();
$matrix2 = $nMatrix->getArray();
if((count($matrix1)!=count($matrix2)) || (count($matrix1[0])!=count($matrix2[0])))
{
$m1 = $this->toString($matrix1);
$m2 = $this->toString($matrix2);
throw new \Exception("The rows and/or columns '{$m1}' and '{$m2}' are not the same", Matrix::E_NOT_EQUAL);
}
$rArray = array();
for($row=0;$row<count($matrix1);$row++) {
for($col=0;$col<count($matrix1[0]);$col++) {
$rArray[$row][$col] = $matrix1[$row][$col] - $matrix2[$row][$col];
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Multiply current matrix by a scalar value
*
* Multiplies a matrix by a scalar value (int/float/etc) (constant, ie '2')
*
* @link http://en.wikipedia.org/wiki/Scalar_multiplication
* @param float $k The value to multiply the matrix by
* @return Matrix Returns a new Matrix instance with the result
* @throws \Exception if the instance matrix is not valid
*/
public function mpScalar($k)
{
//we'll verify a true matrix to ... help the user
if(!$this->_verify())
throw new \Exception("Matrix '{$this}' has varying column sizes", Matrix::E_INVALID_MATRIX);
$cArray = $this->getArray();
$rArray = array();
$rows = count($cArray);
$cols = count($cArray[0]);
for($i=0;$i<$rows;$i++) {
for($j=0;$j<$cols;$j++) {
$rArray[$i][$j] = $cArray[$i][$j] * $k;
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Get the Matrix Determinant
*
* Finds the determinant of the square matrix, user should not use
* the parameter, as that is meant to allow recursive calling
* of this function from within itself.
*
* @link http://en.wikipedia.org/wiki/Matrix_determinant
* @param array|bool $mArray The array to find a determinate of
* @return float The Determinate of the square matrix
* @throws \Exception If matrix is 1,1 or is not square
*/
public function getDeterminant($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
//print_r($mArray);
if(!$this->isSquare($mArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN($mArray);
if($n < 1){
// @codeCoverageIgnoreStart
// Should never get this far
throw new \Exception("No Matrix", Matrix::E_NO_MATRIX);
// @codeCoverageIgnoreEnd
} elseif ($n == 1) {
$det = $mArray[0][0];
} elseif ($n == 2) {
$det = $mArray[0][0]*$mArray[1][1] - $mArray[1][0]*$mArray[0][1];
} else {
$det = 0;
$nArray = array();
for($j1=0;$j1<$n;$j1++) {
for($i=1;$i<$n;$i++) {
$j2 = 0;
for($j=0;$j<$n;$j++) {
if($j==$j1) {
continue;
}
$nArray[$i-1][$j2] = $mArray[$i][$j];
$j2++;
}
}
$det += pow(-1,2+$j1)*$mArray[0][$j1]*$this->getDeterminant($nArray);
}
}
return $det;
}
/**
* coFactor Matrix
*
* Will return a Matrix of coFactors for the matrix provided, or an array
* of the matrix as is the default.
*
* @link http://en.wikipedia.org/wiki/Matrix_cofactors
* @param array|bool $cArray A matrix in array format (or $this->matrix by default)
* @param bool $asArray When set to true, will return an array, when false a Matrix Object
* @return Matrix|array A matrix of coFactors for the array provided (or current matrix)
* @throws \Exception if the matrix is not square
*/
public function coFactor($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
if(!$this->isSquare($cArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN($cArray);
$minor = array();
$rArray = array();
for($j=0;$j<$n;$j++){
for($i=0;$i<$n;$i++) {
//Form the adjugate
$i1 = 0;
for($ii=0;$ii<$n;$ii++) {
if($ii==$i) {
continue;
}
$j1=0;
for($jj=0;$jj<$n;$jj++) {
if($jj==$j) {
continue;
}
$minor[$i1][$j1] = $cArray[$ii][$jj];
$j1++;
}
$i1++;
}
$det = $this->getDeterminant($minor);
$rArray[$i][$j] = pow(-1,$i+$j+2)*$det;
}
}
if($asArray==false){
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
} else {
return $rArray;
}
}
/**
* Will transpose the current matrix or array provided
*
* Transposes the current matrix, or the array provided
*
* @link http://en.wikipedia.org/wiki/Matrix_transpose
* @param array|bool $cArray the array to transpose (defaults to $this->matrix)
* @param bool $asArray whether to return an array or Matrix object
* @return array|Matrix Defaults to returning an array of the transposed matrix
* @throws \Exception if the matrix is not square
*/
public function transpose($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
if(!$this->isSquare($cArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN();
$nArray = array();
for($i=0;$i<$n;$i++) {
for($j=0;$j<$n;$j++) {
$nArray[$j][$i] = $cArray[$i][$j];
}
}
if($asArray==true) {
return $nArray;
} else {
$nMatrix = new Matrix($this->toString($nArray));
return $nMatrix;
}
}
/**
* Adjugate Matrix
*
* Will return the Adjugate matrix of the array provided
* or the current matrix instance if not provided.
*
* @link http://en.wikipedia.org/wiki/Adjugate_matrix
* @param array|bool $cArray Defaults to $this->matrix if not provided
* @param bool $asArray Whether to return an array or Matrix object
* @return array|Matrix Defaults to return the array of the Adjugate matrix
*/
public function adjugate($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
$rArray = $this->transpose($this->coFactor($cArray));
if($asArray==true)
return $rArray;
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Inverse of current matrix
*
* Will give the inverse of the array provided or the current matrix
* Matrix returned denoted by A^-1
*
* @link http://en.wikipedia.org/wiki/Inverse_matrix
* @param array|bool $cArray Array to invert (defaults to $this->matrix)
* @return Matrix By default returns a new instance of Matrix
* @throws \Exception for any number of reasons that would make the inverse not available
*/
public function inverse($cArray = false)
{
if(!$cArray) $cArray = $this->matrix;
$det = $this->getDeterminant($cArray);
if($det == 0)
throw new \Exception("Determinant of {$this} is 0, No Inverse found", Matrix::E_NO_INVERSE);
$scalar = 1/$det;
$adj = $this->adjugate($cArray, false);
$iMatrix = $adj->mpScalar($scalar);
return $iMatrix;
}
/**
* Multiply Matrices
*
* This function will multiply the current matrix with the matrix provided.
* If current Matrix is denoted by 'A' and the inputted is denoted by 'B',
* When written, this will return AB.
*
* @link http://en.wikipedia.org/wiki/Matrix_multiplication
* @param Matrix $bMatrix The matrix to multiply with the current
* @return Matrix The result of multiplication.
* @throws \Exception $msg explains why operation failed
*/
public function mpMatrix(Matrix $bMatrix)
{
if(!$this->_verify() || !$bMatrix->_verify()) {
// @codeCoverageIgnoreStart
// Should never get this far
$eM1 = $this->toString();
$eM2 = $bMatrix->toString();
throw new \Exception("Either '{$eM1}' and/or '{$eM2}' is not a valid Matrix", Matrix::E_INVALID_MATRIX);
// @codeCoverageIgnoreEnd
}
$aArray = $this->matrix;
$bArray = $bMatrix->getArray();
//The number of columns in A must match the number of rows in B
if(count($aArray[0]) != count($bArray)) {
$mA = $this->toString();
$mB = $bMatrix->toString();
throw new \Exception("Columns in '{$mA}' don't match Rows of '{$mB}'", Matrix::E_NOT_EQUAL);
}
$rArray = array();
//Loop through rows of Matrix A
for($i=0;$i<count($aArray);$i++) {
//Loop through the columns of Matrix B
for($j=0;$j<count($bArray[0]);$j++) {
$value = 0;
//loop through the rows of Matrix B
for($k=0;$k<count($bArray);$k++) {
$value += $aArray[$i][$k] * $bArray[$k][$j];
}
$rArray[$i][$j] = $value;
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
}
?>