416 lines
14 KiB
PHP
416 lines
14 KiB
PHP
|
<?php
|
||
|
|
||
|
require_once "../Matrix.php";
|
||
|
|
||
|
class TestMatrix {
|
||
|
|
||
|
function TestMatrix() {
|
||
|
|
||
|
// define test variables
|
||
|
|
||
|
$errorCount = 0;
|
||
|
$warningCount = 0;
|
||
|
$columnwise = array(1.,2.,3.,4.,5.,6.,7.,8.,9.,10.,11.,12.);
|
||
|
$rowwise = array(1.,4.,7.,10.,2.,5.,8.,11.,3.,6.,9.,12.);
|
||
|
$avals = array(array(1.,4.,7.,10.),array(2.,5.,8.,11.),array(3.,6.,9.,12.));
|
||
|
$rankdef = $avals;
|
||
|
$tvals = array(array(1.,2.,3.),array(4.,5.,6.),array(7.,8.,9.),array(10.,11.,12.));
|
||
|
$subavals = array(array(5.,8.,11.),array(6.,9.,12.));
|
||
|
$rvals = array(array(1.,4.,7.),array(2.,5.,8.,11.),array(3.,6.,9.,12.));
|
||
|
$pvals = array(array(1.,1.,1.),array(1.,2.,3.),array(1.,3.,6.));
|
||
|
$ivals = array(array(1.,0.,0.,0.),array(0.,1.,0.,0.),array(0.,0.,1.,0.));
|
||
|
$evals = array(array(0.,1.,0.,0.),array(1.,0.,2.e-7,0.),array(0.,-2.e-7,0.,1.),array(0.,0.,1.,0.));
|
||
|
$square = array(array(166.,188.,210.),array(188.,214.,240.),array(210.,240.,270.));
|
||
|
$sqSolution = array(array(13.),array(15.));
|
||
|
$condmat = array(array(1.,3.),array(7.,9.));
|
||
|
$rows = 3;
|
||
|
$cols = 4;
|
||
|
$invalidID = 5; /* should trigger bad shape for construction with val */
|
||
|
$raggedr = 0; /* (raggedr,raggedc) should be out of bounds in ragged array */
|
||
|
$raggedc = 4;
|
||
|
$validID = 3; /* leading dimension of intended test Matrices */
|
||
|
$nonconformld = 4; /* leading dimension which is valid, but nonconforming */
|
||
|
$ib = 1; /* index ranges for sub Matrix */
|
||
|
$ie = 2;
|
||
|
$jb = 1;
|
||
|
$je = 3;
|
||
|
$rowindexset = array(1,2);
|
||
|
$badrowindexset = array(1,3);
|
||
|
$columnindexset = array(1,2,3);
|
||
|
$badcolumnindexset = array(1,2,4);
|
||
|
$columnsummax = 33.;
|
||
|
$rowsummax = 30.;
|
||
|
$sumofdiagonals = 15;
|
||
|
$sumofsquares = 650;
|
||
|
|
||
|
/**
|
||
|
* Test matrix methods
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Constructors and constructor-like methods:
|
||
|
*
|
||
|
* Matrix(double[], int)
|
||
|
* Matrix(double[][])
|
||
|
* Matrix(int, int)
|
||
|
* Matrix(int, int, double)
|
||
|
* Matrix(int, int, double[][])
|
||
|
* constructWithCopy(double[][])
|
||
|
* random(int,int)
|
||
|
* identity(int)
|
||
|
*/
|
||
|
echo "<p>Testing constructors and constructor-like methods...</p>";
|
||
|
|
||
|
$A = new Matrix($columnwise, 3);
|
||
|
if($A instanceof Matrix) {
|
||
|
$this->try_success("Column-packed constructor...");
|
||
|
} else
|
||
|
$errorCount = $this->try_failure($errorCount, "Column-packed constructor...", "Unable to construct Matrix");
|
||
|
|
||
|
$T = new Matrix($tvals);
|
||
|
if($T instanceof Matrix)
|
||
|
$this->try_success("2D array constructor...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "2D array constructor...", "Unable to construct Matrix");
|
||
|
|
||
|
$A = new Matrix($columnwise, $validID);
|
||
|
$B = new Matrix($avals);
|
||
|
$tmp = $B->get(0,0);
|
||
|
$avals[0][0] = 0.0;
|
||
|
$C = $B->minus($A);
|
||
|
$avals[0][0] = $tmp;
|
||
|
$B = Matrix::constructWithCopy($avals);
|
||
|
$tmp = $B->get(0,0);
|
||
|
$avals[0][0] = 0.0;
|
||
|
/** check that constructWithCopy behaves properly **/
|
||
|
if ( ( $tmp - $B->get(0,0) ) != 0.0 )
|
||
|
$errorCount = $this->try_failure($errorCount,"constructWithCopy... ","copy not effected... data visible outside");
|
||
|
else
|
||
|
$this->try_success("constructWithCopy... ","");
|
||
|
|
||
|
$I = new Matrix($ivals);
|
||
|
if ( $this->checkMatrices($I,Matrix::identity(3,4)) )
|
||
|
$this->try_success("identity... ","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"identity... ","identity Matrix not successfully created");
|
||
|
|
||
|
/**
|
||
|
* Access Methods:
|
||
|
*
|
||
|
* getColumnDimension()
|
||
|
* getRowDimension()
|
||
|
* getArray()
|
||
|
* getArrayCopy()
|
||
|
* getColumnPackedCopy()
|
||
|
* getRowPackedCopy()
|
||
|
* get(int,int)
|
||
|
* getMatrix(int,int,int,int)
|
||
|
* getMatrix(int,int,int[])
|
||
|
* getMatrix(int[],int,int)
|
||
|
* getMatrix(int[],int[])
|
||
|
* set(int,int,double)
|
||
|
* setMatrix(int,int,int,int,Matrix)
|
||
|
* setMatrix(int,int,int[],Matrix)
|
||
|
* setMatrix(int[],int,int,Matrix)
|
||
|
* setMatrix(int[],int[],Matrix)
|
||
|
*/
|
||
|
print "<p>Testing access methods...</p>";
|
||
|
|
||
|
$B = new Matrix($avals);
|
||
|
if($B->getRowDimension() == $rows)
|
||
|
$this->try_success("getRowDimension...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "getRowDimension...");
|
||
|
|
||
|
if($B->getColumnDimension() == $cols)
|
||
|
$this->try_success("getColumnDimension...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "getColumnDimension...");
|
||
|
|
||
|
$barray = $B->getArray();
|
||
|
if($this->checkArrays($barray, $avals))
|
||
|
$this->try_success("getArray...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "getArray...");
|
||
|
|
||
|
$bpacked = $B->getColumnPackedCopy();
|
||
|
if($this->checkArrays($bpacked, $columnwise))
|
||
|
$this->try_success("getColumnPackedCopy...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "getColumnPackedCopy...");
|
||
|
|
||
|
$bpacked = $B->getRowPackedCopy();
|
||
|
if($this->checkArrays($bpacked, $rowwise))
|
||
|
$this->try_success("getRowPackedCopy...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "getRowPackedCopy...");
|
||
|
|
||
|
/**
|
||
|
* Array-like methods:
|
||
|
* minus
|
||
|
* minusEquals
|
||
|
* plus
|
||
|
* plusEquals
|
||
|
* arrayLeftDivide
|
||
|
* arrayLeftDivideEquals
|
||
|
* arrayRightDivide
|
||
|
* arrayRightDivideEquals
|
||
|
* arrayTimes
|
||
|
* arrayTimesEquals
|
||
|
* uminus
|
||
|
*/
|
||
|
print "<p>Testing array-like methods...</p>";
|
||
|
|
||
|
/**
|
||
|
* I/O methods:
|
||
|
* read
|
||
|
* print
|
||
|
* serializable:
|
||
|
* writeObject
|
||
|
* readObject
|
||
|
*/
|
||
|
print "<p>Testing I/O methods...</p>";
|
||
|
|
||
|
/**
|
||
|
* Test linear algebra methods
|
||
|
*/
|
||
|
echo "<p>Testing linear algebra methods...<p>";
|
||
|
|
||
|
$A = new Matrix($columnwise, 3);
|
||
|
if( $this->checkMatrices($A->transpose(), $T) )
|
||
|
$this->try_success("Transpose check...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Transpose check...", "Matrices are not equal");
|
||
|
|
||
|
if($this->checkScalars($A->norm1(), $columnsummax))
|
||
|
$this->try_success("Maximum column sum...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Maximum column sum...", "Incorrect: " . $A->norm1() . " != " . $columnsummax);
|
||
|
|
||
|
if($this->checkScalars($A->normInf(), $rowsummax))
|
||
|
$this->try_success("Maximum row sum...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Maximum row sum...", "Incorrect: " . $A->normInf() . " != " . $rowsummax );
|
||
|
|
||
|
if($this->checkScalars($A->normF(), sqrt($sumofsquares)))
|
||
|
$this->try_success("Frobenius norm...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Frobenius norm...", "Incorrect:" . $A->normF() . " != " . sqrt($sumofsquares));
|
||
|
|
||
|
if($this->checkScalars($A->trace(), $sumofdiagonals))
|
||
|
$this->try_success("Matrix trace...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Matrix trace...", "Incorrect: " . $A->trace() . " != " . $sumofdiagonals);
|
||
|
|
||
|
$B = $A->getMatrix(0, $A->getRowDimension(), 0, $A->getRowDimension());
|
||
|
if( $B->det() == 0 )
|
||
|
$this->try_success("Matrix determinant...");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "Matrix determinant...", "Incorrect: " . $B->det() . " != " . 0);
|
||
|
|
||
|
$A = new Matrix($columnwise,3);
|
||
|
$SQ = new Matrix($square);
|
||
|
if ($this->checkMatrices($SQ, $A->times($A->transpose())))
|
||
|
$this->try_success("times(Matrix)...");
|
||
|
else {
|
||
|
$errorCount = $this->try_failure($errorCount, "times(Matrix)...", "Unable to multiply matrices");
|
||
|
$SQ->toHTML();
|
||
|
$AT->toHTML();
|
||
|
}
|
||
|
|
||
|
$A = new Matrix($columnwise, 4);
|
||
|
|
||
|
$QR = $A->qr();
|
||
|
$R = $QR->getR();
|
||
|
$Q = $QR->getQ();
|
||
|
if($this->checkMatrices($A, $Q->times($R)))
|
||
|
$this->try_success("QRDecomposition...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"QRDecomposition...","incorrect qr decomposition calculation");
|
||
|
|
||
|
$A = new Matrix($columnwise, 4);
|
||
|
$SVD = $A->svd();
|
||
|
$U = $SVD->getU();
|
||
|
$S = $SVD->getS();
|
||
|
$V = $SVD->getV();
|
||
|
if ($this->checkMatrices($A, $U->times($S->times($V->transpose()))))
|
||
|
$this->try_success("SingularValueDecomposition...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"SingularValueDecomposition...","incorrect singular value decomposition calculation");
|
||
|
|
||
|
$n = $A->getColumnDimension();
|
||
|
$A = $A->getMatrix(0,$n-1,0,$n-1);
|
||
|
$A->set(0,0,0.);
|
||
|
|
||
|
$LU = $A->lu();
|
||
|
$L = $LU->getL();
|
||
|
if ( $this->checkMatrices($A->getMatrix($LU->getPivot(),0,$n-1), $L->times($LU->getU())) )
|
||
|
$this->try_success("LUDecomposition...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"LUDecomposition...","incorrect LU decomposition calculation");
|
||
|
|
||
|
$X = $A->inverse();
|
||
|
if ( $this->checkMatrices($A->times($X),Matrix::identity(3,3)) )
|
||
|
$this->try_success("inverse()...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount, "inverse()...","incorrect inverse calculation");
|
||
|
|
||
|
$DEF = new Matrix($rankdef);
|
||
|
if($this->checkScalars($DEF->rank(), min($DEF->getRowDimension(), $DEF->getColumnDimension())-1))
|
||
|
$this->try_success("Rank...");
|
||
|
else
|
||
|
$this->try_failure("Rank...", "incorrect rank calculation");
|
||
|
|
||
|
$B = new Matrix($condmat);
|
||
|
$SVD = $B->svd();
|
||
|
$singularvalues = $SVD->getSingularValues();
|
||
|
if($this->checkScalars($B->cond(), $singularvalues[0]/$singularvalues[min($B->getRowDimension(), $B->getColumnDimension())-1]))
|
||
|
$this->try_success("Condition number...");
|
||
|
else
|
||
|
$this->try_failure("Condition number...", "incorrect condition number calculation");
|
||
|
|
||
|
$SUB = new Matrix($subavals);
|
||
|
$O = new Matrix($SUB->getRowDimension(),1,1.0);
|
||
|
$SOL = new Matrix($sqSolution);
|
||
|
$SQ = $SUB->getMatrix(0,$SUB->getRowDimension()-1,0,$SUB->getRowDimension()-1);
|
||
|
if ( $this->checkMatrices($SQ->solve($SOL),$O) )
|
||
|
$this->try_success("solve()...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"solve()...","incorrect lu solve calculation");
|
||
|
|
||
|
$A = new Matrix($pvals);
|
||
|
$Chol = $A->chol();
|
||
|
$L = $Chol->getL();
|
||
|
if ( $this->checkMatrices($A, $L->times($L->transpose())) )
|
||
|
$this->try_success("CholeskyDecomposition...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"CholeskyDecomposition...","incorrect Cholesky decomposition calculation");
|
||
|
|
||
|
$X = $Chol->solve(Matrix::identity(3,3));
|
||
|
if ( $this->checkMatrices($A->times($X), Matrix::identity(3,3)) )
|
||
|
$this->try_success("CholeskyDecomposition solve()...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"CholeskyDecomposition solve()...","incorrect Choleskydecomposition solve calculation");
|
||
|
|
||
|
$Eig = $A->eig();
|
||
|
$D = $Eig->getD();
|
||
|
$V = $Eig->getV();
|
||
|
if( $this->checkMatrices($A->times($V),$V->times($D)) )
|
||
|
$this->try_success("EigenvalueDecomposition (symmetric)...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"EigenvalueDecomposition (symmetric)...","incorrect symmetric Eigenvalue decomposition calculation");
|
||
|
|
||
|
$A = new Matrix($evals);
|
||
|
$Eig = $A->eig();
|
||
|
$D = $Eig->getD();
|
||
|
$V = $Eig->getV();
|
||
|
if ( $this->checkMatrices($A->times($V),$V->times($D)) )
|
||
|
$this->try_success("EigenvalueDecomposition (nonsymmetric)...","");
|
||
|
else
|
||
|
$errorCount = $this->try_failure($errorCount,"EigenvalueDecomposition (nonsymmetric)...","incorrect nonsymmetric Eigenvalue decomposition calculation");
|
||
|
|
||
|
print("<b>{$errorCount} total errors</b>.");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print appropriate messages for successful outcome try
|
||
|
* @param string $s
|
||
|
* @param string $e
|
||
|
*/
|
||
|
function try_success($s, $e = "") {
|
||
|
print "> ". $s ."success<br />";
|
||
|
if ($e != "")
|
||
|
print "> Message: ". $e ."<br />";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print appropriate messages for unsuccessful outcome try
|
||
|
* @param int $count
|
||
|
* @param string $s
|
||
|
* @param string $e
|
||
|
* @return int incremented counter
|
||
|
*/
|
||
|
function try_failure($count, $s, $e="") {
|
||
|
print "> ". $s ."*** failure ***<br />> Message: ". $e ."<br />";
|
||
|
return ++$count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print appropriate messages for unsuccessful outcome try
|
||
|
* @param int $count
|
||
|
* @param string $s
|
||
|
* @param string $e
|
||
|
* @return int incremented counter
|
||
|
*/
|
||
|
function try_warning($count, $s, $e="") {
|
||
|
print "> ". $s ."*** warning ***<br />> Message: ". $e ."<br />";
|
||
|
return ++$count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check magnitude of difference of "scalars".
|
||
|
* @param float $x
|
||
|
* @param float $y
|
||
|
*/
|
||
|
function checkScalars($x, $y) {
|
||
|
$eps = pow(2.0,-52.0);
|
||
|
if ($x == 0 & abs($y) < 10*$eps) return;
|
||
|
if ($y == 0 & abs($x) < 10*$eps) return;
|
||
|
if (abs($x-$y) > 10 * $eps * max(abs($x),abs($y)))
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check norm of difference of "vectors".
|
||
|
* @param float $x[]
|
||
|
* @param float $y[]
|
||
|
*/
|
||
|
function checkVectors($x, $y) {
|
||
|
$nx = count($x);
|
||
|
$ny = count($y);
|
||
|
if ($nx == $ny)
|
||
|
for($i=0; $i < $nx; ++$i)
|
||
|
$this->checkScalars($x[$i],$y[$i]);
|
||
|
else
|
||
|
die("Attempt to compare vectors of different lengths");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check norm of difference of "arrays".
|
||
|
* @param float $x[][]
|
||
|
* @param float $y[][]
|
||
|
*/
|
||
|
function checkArrays($x, $y) {
|
||
|
$A = new Matrix($x);
|
||
|
$B = new Matrix($y);
|
||
|
return $this->checkMatrices($A,$B);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check norm of difference of "matrices".
|
||
|
* @param matrix $X
|
||
|
* @param matrix $Y
|
||
|
*/
|
||
|
function checkMatrices($X = null, $Y = null) {
|
||
|
if( $X == null || $Y == null )
|
||
|
return false;
|
||
|
|
||
|
$eps = pow(2.0,-52.0);
|
||
|
if ($X->norm1() == 0. & $Y->norm1() < 10*$eps) return true;
|
||
|
if ($Y->norm1() == 0. & $X->norm1() < 10*$eps) return true;
|
||
|
|
||
|
$A = $X->minus($Y);
|
||
|
|
||
|
if ($A->norm1() > 1000 * $eps * max($X->norm1(),$Y->norm1()))
|
||
|
die("The norm of (X-Y) is too large: ".$A->norm1());
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
$test = new TestMatrix;
|
||
|
?>
|