192 lines
5.5 KiB
PHP
192 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace Matrix\Decomposition;
|
|
|
|
use Matrix\Exception;
|
|
use Matrix\Matrix;
|
|
|
|
class QR
|
|
{
|
|
private $qrMatrix;
|
|
private $rows;
|
|
private $columns;
|
|
|
|
private $rDiagonal = [];
|
|
|
|
public function __construct(Matrix $matrix)
|
|
{
|
|
$this->qrMatrix = $matrix->toArray();
|
|
$this->rows = $matrix->rows;
|
|
$this->columns = $matrix->columns;
|
|
|
|
$this->decompose();
|
|
}
|
|
|
|
public function getHouseholdVectors(): Matrix
|
|
{
|
|
$householdVectors = [];
|
|
for ($row = 0; $row < $this->rows; ++$row) {
|
|
for ($column = 0; $column < $this->columns; ++$column) {
|
|
if ($row >= $column) {
|
|
$householdVectors[$row][$column] = $this->qrMatrix[$row][$column];
|
|
} else {
|
|
$householdVectors[$row][$column] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new Matrix($householdVectors);
|
|
}
|
|
|
|
public function getQ(): Matrix
|
|
{
|
|
$qGrid = [];
|
|
|
|
$rowCount = $this->rows;
|
|
for ($k = $this->columns - 1; $k >= 0; --$k) {
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
|
$qGrid[$i][$k] = 0.0;
|
|
}
|
|
$qGrid[$k][$k] = 1.0;
|
|
if ($this->columns > $this->rows) {
|
|
$qGrid = array_slice($qGrid, 0, $this->rows);
|
|
}
|
|
|
|
for ($j = $k; $j < $this->columns; ++$j) {
|
|
if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) {
|
|
$s = 0.0;
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j];
|
|
}
|
|
$s = -$s / $this->qrMatrix[$k][$k];
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
array_walk(
|
|
$qGrid,
|
|
function (&$row) use ($rowCount) {
|
|
$row = array_reverse($row);
|
|
$row = array_slice($row, 0, $rowCount);
|
|
}
|
|
);
|
|
|
|
return new Matrix($qGrid);
|
|
}
|
|
|
|
public function getR(): Matrix
|
|
{
|
|
$rGrid = [];
|
|
|
|
for ($row = 0; $row < $this->columns; ++$row) {
|
|
for ($column = 0; $column < $this->columns; ++$column) {
|
|
if ($row < $column) {
|
|
$rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0;
|
|
} elseif ($row === $column) {
|
|
$rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0;
|
|
} else {
|
|
$rGrid[$row][$column] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->columns > $this->rows) {
|
|
$rGrid = array_slice($rGrid, 0, $this->rows);
|
|
}
|
|
|
|
return new Matrix($rGrid);
|
|
}
|
|
|
|
private function hypo($a, $b): float
|
|
{
|
|
if (abs($a) > abs($b)) {
|
|
$r = $b / $a;
|
|
$r = abs($a) * sqrt(1 + $r * $r);
|
|
} elseif ($b != 0.0) {
|
|
$r = $a / $b;
|
|
$r = abs($b) * sqrt(1 + $r * $r);
|
|
} else {
|
|
$r = 0.0;
|
|
}
|
|
|
|
return $r;
|
|
}
|
|
|
|
/**
|
|
* QR Decomposition computed by Householder reflections.
|
|
*/
|
|
private function decompose(): void
|
|
{
|
|
for ($k = 0; $k < $this->columns; ++$k) {
|
|
// Compute 2-norm of k-th column without under/overflow.
|
|
$norm = 0.0;
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$norm = $this->hypo($norm, $this->qrMatrix[$i][$k]);
|
|
}
|
|
if ($norm != 0.0) {
|
|
// Form k-th Householder vector.
|
|
if ($this->qrMatrix[$k][$k] < 0.0) {
|
|
$norm = -$norm;
|
|
}
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$this->qrMatrix[$i][$k] /= $norm;
|
|
}
|
|
$this->qrMatrix[$k][$k] += 1.0;
|
|
// Apply transformation to remaining columns.
|
|
for ($j = $k + 1; $j < $this->columns; ++$j) {
|
|
$s = 0.0;
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j];
|
|
}
|
|
$s = -$s / $this->qrMatrix[$k][$k];
|
|
for ($i = $k; $i < $this->rows; ++$i) {
|
|
$this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k];
|
|
}
|
|
}
|
|
}
|
|
$this->rDiagonal[$k] = -$norm;
|
|
}
|
|
}
|
|
|
|
public function isFullRank(): bool
|
|
{
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
|
if ($this->rDiagonal[$j] == 0.0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Least squares solution of A*X = B.
|
|
*
|
|
* @param Matrix $B a Matrix with as many rows as A and any number of columns
|
|
*
|
|
* @throws Exception
|
|
*
|
|
* @return Matrix matrix that minimizes the two norm of Q*R*X-B
|
|
*/
|
|
public function solve(Matrix $B): Matrix
|
|
{
|
|
if ($B->rows !== $this->rows) {
|
|
throw new Exception('Matrix row dimensions are not equal');
|
|
}
|
|
|
|
if (!$this->isFullRank()) {
|
|
throw new Exception('Can only perform this operation on a full-rank matrix');
|
|
}
|
|
|
|
// Compute Y = transpose(Q)*B
|
|
$Y = $this->getQ()->transpose()
|
|
->multiply($B);
|
|
// Solve R*X = Y;
|
|
return $this->getR()->inverse()
|
|
->multiply($Y);
|
|
}
|
|
}
|