Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.00% covered (success)
96.00%
72 / 75
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
QRDecomposition
96.00% covered (success)
96.00%
72 / 75
60.00% covered (warning)
60.00%
3 / 5
35
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
9
 isFullRank
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getR
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 getQ
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
7
 solve
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
11.08
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Math\Matrix
8 * @copyright Dennis Eichhorn
9 * @copyright JAMA - https://math.nist.gov/javanumerics/jama/
10 * @license   OMS License 2.0
11 * @version   1.0.0
12 * @link      https://jingga.app
13 */
14declare(strict_types=1);
15
16namespace phpOMS\Math\Matrix;
17
18use phpOMS\Math\Geometry\Shape\D2\Triangle;
19use phpOMS\Math\Matrix\Exception\InvalidDimensionException;
20
21/**
22 * QR decomposition
23 *
24 * For every matrix A = Q*R
25 *
26 * @package phpOMS\Math\Matrix
27 * @license OMS License 2.0
28 * @link    https://jingga.app
29 * @since   1.0.0
30 */
31final class QRDecomposition
32{
33    /**
34     * QR matrix.
35     *
36     * @var array[]
37     * @since 1.0.0
38     */
39    private array $QR = [];
40
41    /**
42     * Dimension m
43     *
44     * @var int
45     * @since 1.0.0
46     */
47    private int $m = 0;
48
49    /**
50     * Dimension n
51     *
52     * @var int
53     * @since 1.0.0
54     */
55    private int $n = 0;
56
57    /**
58     * R diagonal
59     *
60     * @var array<int, int|float>
61     * @since 1.0.0
62     */
63    private array $Rdiag = [];
64
65    /**
66     * Constructor.
67     *
68     * @param Matrix $M Matrix
69     *
70     * @since 1.0.0
71     */
72    public function __construct(Matrix $M)
73    {
74        // Initialize.
75        $this->QR = $M->toArray();
76        $this->m  = $M->getM();
77        $this->n  = $M->getN();
78
79        // Main loop.
80        for ($k = 0; $k < $this->n; ++$k) {
81            // Compute 2-norm of k-th column without under/overflow.
82            $nrm = 0.0;
83            for ($i = $k; $i < $this->m; ++$i) {
84                $nrm = Triangle::getHypot($nrm, $this->QR[$i][$k]);
85            }
86
87            if ($nrm != 0) {
88                // Form k-th Householder vector.
89                if ($this->QR[$k][$k] < 0) {
90                    $nrm = -$nrm;
91                }
92
93                for ($i = $k; $i < $this->m; ++$i) {
94                    $this->QR[$i][$k] /= $nrm;
95                }
96
97                $this->QR[$k][$k] += 1.0;
98
99                // Apply transformation to remaining columns.
100                for ($j = $k + 1; $j < $this->n; ++$j) {
101                    $s = 0.0;
102                    for ($i = $k; $i < $this->m; ++$i) {
103                        $s += $this->QR[$i][$k] * $this->QR[$i][$j];
104                    }
105
106                    $s = -$s / $this->QR[$k][$k];
107                    for ($i = $k; $i < $this->m; ++$i) {
108                        $this->QR[$i][$j] += $s * $this->QR[$i][$k];
109                    }
110                }
111            }
112
113            $this->Rdiag[$k] = -$nrm;
114        }
115    }
116
117    /**
118     * Matrix has full rank
119     *
120     * @return bool
121     *
122     * @since 1.0.0
123     */
124    public function isFullRank() : bool
125    {
126        for ($j = 0; $j < $this->n; ++$j) {
127            if (\abs($this->Rdiag[$j]) < Matrix::EPSILON) {
128                return false;
129            }
130        }
131
132        return true;
133    }
134
135    /**
136     * Get R matrix
137     *
138     * @return Matrix
139     *
140     * @since 1.0.0
141     */
142    public function getR() : Matrix
143    {
144        $R = [[]];
145
146        for ($i = 0; $i < $this->n; ++$i) {
147            for ($j = 0; $j < $this->n; ++$j) {
148                if ($i < $j) {
149                    $R[$i][$j] = $this->QR[$i][$j];
150                } elseif ($i === $j) {
151                    $R[$i][$j] = $this->Rdiag[$i];
152                } else {
153                    $R[$i][$j] = 0.0;
154                }
155            }
156        }
157
158        $matrix = new Matrix();
159        $matrix->setMatrix($R);
160
161        return $matrix;
162    }
163
164    /**
165     * Get Q matrix
166     *
167     * @return Matrix
168     *
169     * @since 1.0.0
170     */
171    public function getQ() : Matrix
172    {
173        $Q = [[]];
174
175        for ($k = $this->n - 1; $k >= 0; --$k) {
176            for ($i = 0; $i < $this->m; ++$i) {
177                $Q[$i][$k] = 0.0;
178            }
179
180            $Q[$k][$k] = 1.0;
181            for ($j = $k; $j < $this->n; ++$j) {
182                if ($this->QR[$k][$k] != 0) {
183                    $s = 0.0;
184                    for ($i = $k; $i < $this->m; ++$i) {
185                        $s += $this->QR[$i][$k] * $Q[$i][$j];
186                    }
187
188                    $s = -$s / $this->QR[$k][$k];
189                    for ($i = $k; $i < $this->m; ++$i) {
190                        $Q[$i][$j] += $s * $this->QR[$i][$k];
191                    }
192                }
193            }
194        }
195
196        $matrix = new Matrix();
197        $matrix->setMatrix($Q);
198
199        return $matrix;
200    }
201
202    /**
203     * Solve Ax = b
204     *
205     * @param Matrix $B Matrix
206     *
207     * @return Matrix
208     *
209     * @throws InvalidDimensionException
210     * @throws \Exception
211     *
212     * @since 1.0.0
213     */
214    public function solve(Matrix $B) : Matrix
215    {
216        if ($B->getM() !== $this->m) {
217            throw new InvalidDimensionException($B->getM());
218        }
219
220        if (!$this->isFullRank()) {
221            throw new \Exception('Rank');
222        }
223
224        $nx = $B->getN();
225        $X  = $B->toArray();
226
227        // Compute Y = transpose(Q)*B
228        for ($k = 0; $k < $this->n; ++$k) {
229            for ($j = 0; $j < $nx; ++$j) {
230                $s = 0.0;
231                for ($i = $k; $i < $this->m; ++$i) {
232                    $s += $this->QR[$i][$k] * $X[$i][$j];
233                }
234
235                $s = -$s / $this->QR[$k][$k];
236                for ($i = $k; $i < $this->m; ++$i) {
237                    $X[$i][$j] += $s * $this->QR[$i][$k];
238                }
239            }
240        }
241
242        // Solve R*X = Y;
243        for ($k = $this->n - 1; $k >= 0; --$k) {
244            for ($j = 0; $j < $nx; ++$j) {
245                $X[$k][$j] /= $this->Rdiag[$k];
246            }
247
248            for ($i = 0; $i < $k; ++$i) {
249                for ($j = 0; $j < $nx; ++$j) {
250                    $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
251                }
252            }
253        }
254
255        $matrix = new Matrix();
256        $matrix->setMatrix($X);
257
258        return $matrix->getSubMatrix(0, $this->n - 1, 0, $nx - 1);
259    }
260}