Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.00% |
72 / 75 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
QRDecomposition | |
96.00% |
72 / 75 |
|
60.00% |
3 / 5 |
35 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
9 | |||
isFullRank | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
getR | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getQ | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
7 | |||
solve | |
91.30% |
21 / 23 |
|
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 | */ |
14 | declare(strict_types=1); |
15 | |
16 | namespace phpOMS\Math\Matrix; |
17 | |
18 | use phpOMS\Math\Geometry\Shape\D2\Triangle; |
19 | use 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 | */ |
31 | final 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 | } |