phpOMS/Math/Matrix/QRDecomposition.php
2018-09-21 23:17:37 +02:00

284 lines
6.2 KiB
PHP

<?php
/**
* Orange Management
*
* PHP Version 7.2
*
* @package phpOMS\Math\Matrix
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link http://website.orange-management.de
*/
declare(strict_types=1);
namespace phpOMS\Math\Matrix;
use phpOMS\Math\Matrix\Exception\InvalidDimensionException;
use phpOMS\Math\Geometry\Shape\D2\Triangle;
/**
* QR decomposition
*
* For every matrix A = Q*R
*
* @package phpOMS\Math\Matrix
* @license OMS License 1.0
* @link http://website.orange-management.de
* @since 1.0.0
*/
final class QRDecomposition
{
/**
* QR matrix.
*
* @var array[]
* @since 1.0.0
*/
private $QR = [];
/**
* Dimension m
*
* @var int
* @since 1.0.0
*/
private $m = 0;
/**
* Dimension n
*
* @var int
* @since 1.0.0
*/
private $n = 0;
/**
* R diagonal
*
* @var array
* @since 1.0.0
*/
private $Rdiag = [];
/**
* Constructor.
*
* @param Matrix $M Matrix
*
* @since 1.0.0
*/
public function __construct(Matrix $M)
{
// Initialize.
$this->QR = $M->toArray();
$this->m = $M->getM();
$this->n = $M->getN();
// Main loop.
for ($k = 0; $k < $this->n; ++$k) {
// Compute 2-norm of k-th column without under/overflow.
$nrm = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$nrm = Triangle::getHypot($nrm, $this->QR[$i][$k]);
}
if ($nrm != 0.0) {
// Form k-th Householder vector.
if ($this->QR[$k][$k] < 0) {
$nrm = -$nrm;
}
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$k] /= $nrm;
}
$this->QR[$k][$k] += 1.0;
// Apply transformation to remaining columns.
for ($j = $k + 1; $j < $this->n; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $this->QR[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
$this->Rdiag[$k] = -$nrm;
}
}
/**
* Matrix has full rank
*
* @return bool
*
* @since 1.0.0
*/
public function isFullRank() : bool
{
for ($j = 0; $j < $this->n; ++$j) {
if ($this->Rdiag[$j] == 0) {
return false;
}
}
return true;
}
/**
* Get H matrix
*
* @return Matrix
*
* @since 1.0.0
*/
public function getH() : Matrix
{
$H = [[]];
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i >= $j) {
$H[$i][$j] = $this->QR[$i][$j];
} else {
$H[$i][$j] = 0.0;
}
}
}
$matrix = new Matrix();
$matrix->setMatrix($H);
return $matrix;
}
/**
* Get R matrix
*
* @return Matrix
*
* @since 1.0.0
*/
public function getR() : Matrix
{
$R = [[]];
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i < $j) {
$R[$i][$j] = $this->QR[$i][$j];
} elseif ($i == $j) {
$R[$i][$j] = $this->Rdiag[$i];
} else {
$R[$i][$j] = 0.0;
}
}
}
$matrix = new Matrix();
$matrix->setMatrix($R);
return $matrix;
}
/**
* Get Q matrix
*
* @return Matrix
*
* @since 1.0.0
*/
public function getQ() : Matrix
{
$Q = [[]];
for ($k = $this->n - 1; $k >= 0; --$k) {
for ($i = 0; $i < $this->m; ++$i) {
$Q[$i][$k] = 0.0;
}
$Q[$k][$k] = 1.0;
for ($j = $k; $j < $this->n; ++$j) {
if ($this->QR[$k][$k] != 0) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $Q[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$Q[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
}
$matrix = new Matrix();
$matrix->setMatrix($Q);
return $matrix;
}
/**
* Solve Ax = b
*
* @param Matrix $B Matrix
*
* @return Matrix
*
* @since 1.0.0
*/
public function solve(Matrix $B) : Matrix
{
if ($B->getM() !== $this->m) {
throw new InvalidDimensionException($B->getM());
}
if (!$this->isFullRank()) {
throw new \Exception('Rank');
}
$nx = $B->getN();
$X = $B->toArray();
// Compute Y = transpose(Q)*B
for ($k = 0; $k < $this->n; ++$k) {
for ($j = 0; $j < $nx; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $X[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$X[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
// Solve R*X = Y;
for ($k = $this->n - 1; $k >= 0; --$k) {
for ($j = 0; $j < $nx; ++$j) {
$X[$k][$j] /= $this->Rdiag[$k];
}
for ($i = 0; $i < $k; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
}
}
}
$matrix = new Matrix();
$matrix->setMatrix($X);
return $matrix->getSubMatrix(0, $this->n - 1, 0, $nx - 1);
}
}