Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
65.09% |
179 / 275 |
|
76.19% |
32 / 42 |
CRAP | |
0.00% |
0 / 1 |
Matrix | |
65.09% |
179 / 275 |
|
76.19% |
32 / 42 |
885.52 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
fromArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
set | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
get | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
transpose | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getMatrix | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSubMatrix | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getSubMatrixByColumnsRows | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
getSubMatrixByColumns | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
getSubMatrixByRows | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
toArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isSymmetric | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
rank | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
11 | |||
setMatrix | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
sub | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
add | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addMatrix | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
getM | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getN | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addScalar | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
mult | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
multMatrix | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
5 | |||
multScalar | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
upperTriangular | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
upperTrianglize | |
85.71% |
18 / 21 |
|
0.00% |
0 / 1 |
9.24 | |||
inverse | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
solve | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
det | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
dot | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
306 | |||
sum | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
90 | |||
isDiagonal | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
42 | |||
pow | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
72 | |||
exp | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
current | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
offsetGet | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
next | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
key | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
valid | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetExists | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
rewind | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetSet | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
offsetUnset | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Math\Matrix |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Math\Matrix; |
16 | |
17 | use phpOMS\Math\Matrix\Exception\InvalidDimensionException; |
18 | |
19 | /** |
20 | * Matrix class |
21 | * |
22 | * @package phpOMS\Math\Matrix |
23 | * @license OMS License 2.0 |
24 | * @link https://jingga.app |
25 | * @since 1.0.0 |
26 | * |
27 | * @phpstan-implements \ArrayAccess<string, mixed> |
28 | * @phpstan-implements \Iterator<string, mixed> |
29 | */ |
30 | class Matrix implements \ArrayAccess, \Iterator |
31 | { |
32 | /** |
33 | * Epsilon for float comparison. |
34 | * |
35 | * @var float |
36 | * @since 1.0.0 |
37 | */ |
38 | public const EPSILON = 4.88e-04; |
39 | |
40 | /** |
41 | * Matrix. |
42 | * |
43 | * @var array<int, array<int, int|float>> |
44 | * @since 1.0.0 |
45 | */ |
46 | public array $matrix = []; |
47 | |
48 | /** |
49 | * Columns. |
50 | * |
51 | * @var int<0, max> |
52 | * @since 1.0.0 |
53 | */ |
54 | protected int $n = 0; |
55 | |
56 | /** |
57 | * Rows. |
58 | * |
59 | * @var int<0, max> |
60 | * @since 1.0.0 |
61 | */ |
62 | protected int $m = 0; |
63 | |
64 | /** |
65 | * Iterator position. |
66 | * |
67 | * @var int |
68 | * @since 1.0.0 |
69 | */ |
70 | protected int $position = 0; |
71 | |
72 | /** |
73 | * Constructor. |
74 | * |
75 | * @param int<0, max> $m Rows |
76 | * @param int<0, max> $n Columns |
77 | * |
78 | * @since 1.0.0 |
79 | */ |
80 | public function __construct(int $m = 1, int $n = 1) |
81 | { |
82 | $this->n = $n; |
83 | $this->m = $m; |
84 | |
85 | for ($i = 0; $i < $m; ++$i) { |
86 | $this->matrix[$i] = \array_fill(0, $n, 0); |
87 | } |
88 | } |
89 | |
90 | /** |
91 | * Create matrix from array |
92 | * |
93 | * @param array $matrix Matrix array |
94 | * |
95 | * @return self |
96 | * |
97 | * @since 1.0.0 |
98 | */ |
99 | public static function fromArray(array $matrix) : self |
100 | { |
101 | $m = new self(); |
102 | $m->setMatrix($matrix); |
103 | |
104 | return $m; |
105 | } |
106 | |
107 | /** |
108 | * Set value. |
109 | * |
110 | * @param int $m Row |
111 | * @param int $n Column |
112 | * @param int|float $value Value |
113 | * |
114 | * @return void |
115 | * |
116 | * @throws InvalidDimensionException |
117 | * |
118 | * @since 1.0.0 |
119 | */ |
120 | public function set(int $m, int $n, int | float $value) : void |
121 | { |
122 | if (!isset($this->matrix[$m], $this->matrix[$m][$n])) { |
123 | throw new InvalidDimensionException($m . 'x' . $n); |
124 | } |
125 | |
126 | $this->matrix[$m][$n] = $value; |
127 | } |
128 | |
129 | /** |
130 | * Get value. |
131 | * |
132 | * @param int $m Row |
133 | * @param int $n Column |
134 | * |
135 | * @return int|float |
136 | * |
137 | * @throws InvalidDimensionException |
138 | * |
139 | * @since 1.0.0 |
140 | */ |
141 | public function get(int $m, int $n = 0) : int | float |
142 | { |
143 | if (!isset($this->matrix[$m], $this->matrix[$m][$n])) { |
144 | throw new InvalidDimensionException($m . 'x' . $n); |
145 | } |
146 | |
147 | return $this->matrix[$m][$n]; |
148 | } |
149 | |
150 | /** |
151 | * Transpose matrix. |
152 | * |
153 | * @return Matrix |
154 | * |
155 | * @since 1.0.0 |
156 | */ |
157 | public function transpose() : self |
158 | { |
159 | $matrix = new self($this->n, $this->m); |
160 | $matrix->setMatrix(\array_map(null, ...$this->matrix)); |
161 | |
162 | return $matrix; |
163 | } |
164 | |
165 | /** |
166 | * Get matrix array. |
167 | * |
168 | * @return array<int, array<int, mixed>> |
169 | * |
170 | * @since 1.0.0 |
171 | */ |
172 | public function getMatrix() : array |
173 | { |
174 | return $this->matrix; |
175 | } |
176 | |
177 | /** |
178 | * Get sub matrix array. |
179 | * |
180 | * @param int $iRow Start row |
181 | * @param int $lRow End row |
182 | * @param int $iCol Start col |
183 | * @param int $lCol End col |
184 | * |
185 | * @return Matrix |
186 | * |
187 | * @since 1.0.0 |
188 | */ |
189 | public function getSubMatrix(int $iRow, int $lRow, int $iCol, int $lCol) : self |
190 | { |
191 | $X = [[]]; |
192 | for ($i = $iRow; $i <= $lRow; ++$i) { |
193 | for ($j = $iCol; $j <= $lCol; ++$j) { |
194 | $X[$i - $iRow][$j - $iCol] = $this->matrix[$i][$j]; |
195 | } |
196 | } |
197 | |
198 | $matrix = new self(); |
199 | $matrix->setMatrix($X); |
200 | |
201 | return $matrix; |
202 | } |
203 | |
204 | /** |
205 | * Get sub matrix array. |
206 | * |
207 | * @param int[] $rows Row indices |
208 | * @param int[] $cols Row indices |
209 | * |
210 | * @return Matrix |
211 | * |
212 | * @since 1.0.0 |
213 | */ |
214 | public function getSubMatrixByColumnsRows(array $rows, array $cols) : self |
215 | { |
216 | $X = [[]]; |
217 | $rlength = \count($rows); |
218 | $clength = \count($cols); |
219 | |
220 | for ($i = 0; $i < $rlength; ++$i) { |
221 | for ($j = 0; $j < $clength; ++$j) { |
222 | $X[$i][$j] = $this->matrix[$rows[$i]][$cols[$j]]; |
223 | } |
224 | } |
225 | |
226 | $matrix = new self(); |
227 | $matrix->setMatrix($X); |
228 | |
229 | return $matrix; |
230 | } |
231 | |
232 | /** |
233 | * Get sub matrix array. |
234 | * |
235 | * @param int $iRow Start row |
236 | * @param int $lRow End row |
237 | * @param int[] $cols Row indices |
238 | * |
239 | * @return Matrix |
240 | * |
241 | * @since 1.0.0 |
242 | */ |
243 | public function getSubMatrixByColumns(int $iRow, int $lRow, array $cols) : self |
244 | { |
245 | $X = [[]]; |
246 | $length = \count($cols); |
247 | |
248 | for ($i = $iRow; $i <= $lRow; ++$i) { |
249 | for ($j = 0; $j < $length; ++$j) { |
250 | $X[$i - $iRow][$j] = $this->matrix[$i][$cols[$j]]; |
251 | } |
252 | } |
253 | |
254 | $matrix = new self(); |
255 | $matrix->setMatrix($X); |
256 | |
257 | return $matrix; |
258 | } |
259 | |
260 | /** |
261 | * Get sub matrix array. |
262 | * |
263 | * @param int[] $rows Row indices |
264 | * @param int $iCol Start col |
265 | * @param int $lCol End col |
266 | * |
267 | * @return Matrix |
268 | * |
269 | * @since 1.0.0 |
270 | */ |
271 | public function getSubMatrixByRows(array $rows, int $iCol, int $lCol) : self |
272 | { |
273 | $X = [[]]; |
274 | $length = \count($rows); |
275 | |
276 | for ($i = 0; $i < $length; ++$i) { |
277 | for ($j = $iCol; $j <= $lCol; ++$j) { |
278 | $X[$i][$j - $iCol] = $this->matrix[$rows[$i]][$j]; |
279 | } |
280 | } |
281 | |
282 | $matrix = new self(); |
283 | $matrix->setMatrix($X); |
284 | |
285 | return $matrix; |
286 | } |
287 | |
288 | /** |
289 | * Get matrix array. |
290 | * |
291 | * @return array<int, array<int, int|float>> |
292 | * |
293 | * @since 1.0.0 |
294 | */ |
295 | public function toArray() : array |
296 | { |
297 | return $this->matrix; |
298 | } |
299 | |
300 | /** |
301 | * Is symmetric. |
302 | * |
303 | * @return bool |
304 | * |
305 | * @since 1.0.0 |
306 | */ |
307 | public function isSymmetric() : bool |
308 | { |
309 | $isSymmetric = true; |
310 | for ($j = 0; ($j < $this->m) & $isSymmetric; ++$j) { |
311 | for ($i = 0; ($i < $this->n) & $isSymmetric; ++$i) { |
312 | $isSymmetric = ($this->matrix[$i][$j] === $this->matrix[$j][$i]); |
313 | } |
314 | } |
315 | |
316 | return $isSymmetric; |
317 | } |
318 | |
319 | /** |
320 | * Get matrix rank. |
321 | * |
322 | * @return int |
323 | * |
324 | * @since 1.0.0 |
325 | */ |
326 | public function rank() : int |
327 | { |
328 | $matrix = $this->matrix; |
329 | $mDim = $this->m; |
330 | $nDim = $this->n; |
331 | |
332 | $rank = 0; |
333 | $selected = \array_fill(0, $mDim, false); |
334 | |
335 | for ($i = 0; $i < $nDim; ++$i) { |
336 | for ($j = 0; $j < $mDim; ++$j) { |
337 | if (!$selected[$j] && \abs($matrix[$j][$i]) > self::EPSILON) { |
338 | break; |
339 | } |
340 | } |
341 | |
342 | if ($j === $mDim) { |
343 | continue; |
344 | } |
345 | |
346 | ++$rank; |
347 | $selected[$j] = true; |
348 | for ($p = $i + 1; $p < $nDim; ++$p) { |
349 | $matrix[$j][$p] /= $matrix[$j][$i]; |
350 | } |
351 | |
352 | for ($k = 0; $k < $mDim; ++$k) { |
353 | if ($k !== $j && \abs($matrix[$k][$i]) > self::EPSILON) { |
354 | for ($p = $i + 1; $p < $nDim; ++$p) { |
355 | $matrix[$k][$p] -= $matrix[$j][$p] * $matrix[$k][$i]; |
356 | } |
357 | } |
358 | } |
359 | } |
360 | |
361 | return $rank; |
362 | } |
363 | |
364 | /** |
365 | * Set matrix array. |
366 | * |
367 | * @param array<int, array<int|float>> $matrix Matrix |
368 | * |
369 | * @return Matrix |
370 | * |
371 | * @since 1.0.0 |
372 | */ |
373 | public function setMatrix(array $matrix) : self |
374 | { |
375 | $this->m = \count($matrix); |
376 | $this->n = \is_array($matrix[0] ?? 1) ? \count($matrix[0]) : 1; |
377 | $this->matrix = $matrix; |
378 | |
379 | return $this; |
380 | } |
381 | |
382 | /** |
383 | * Subtract right. |
384 | * |
385 | * @param int|float|self $value Value |
386 | * |
387 | * @return Matrix |
388 | * |
389 | * @since 1.0.0 |
390 | */ |
391 | public function sub(int | float | self $value) : self |
392 | { |
393 | if (\is_numeric($value)) { |
394 | return $this->addScalar(-$value); |
395 | } |
396 | |
397 | return $this->add($value->mult(-1)); |
398 | } |
399 | |
400 | /** |
401 | * Add right. |
402 | * |
403 | * @param int|float|self $value Value |
404 | * |
405 | * @return Matrix |
406 | * |
407 | * @since 1.0.0 |
408 | */ |
409 | public function add(int | float | self $value) : self |
410 | { |
411 | if (\is_numeric($value)) { |
412 | return $this->addScalar($value); |
413 | } |
414 | |
415 | return $this->addMatrix($value); |
416 | } |
417 | |
418 | /** |
419 | * Add matrix. |
420 | * |
421 | * @param Matrix $matrix Matrix to add |
422 | * |
423 | * @return Matrix |
424 | * |
425 | * @throws InvalidDimensionException |
426 | * |
427 | * @since 1.0.0 |
428 | */ |
429 | private function addMatrix(self $matrix) : self |
430 | { |
431 | if ($this->m !== $matrix->getM() || $this->n !== $matrix->getN()) { |
432 | throw new InvalidDimensionException($matrix->getM() . 'x' . $matrix->getN()); |
433 | } |
434 | |
435 | $matrixArr = $matrix->getMatrix(); |
436 | $newMatrixArr = $this->matrix; |
437 | |
438 | foreach ($newMatrixArr as $i => $vector) { |
439 | foreach ($vector as $j => $_) { |
440 | $newMatrixArr[$i][$j] += $matrixArr[$i][$j]; |
441 | } |
442 | } |
443 | |
444 | $newMatrix = new self($this->m, $this->n); |
445 | $newMatrix->setMatrix($newMatrixArr); |
446 | |
447 | return $newMatrix; |
448 | } |
449 | |
450 | /** |
451 | * Get matrix rows. |
452 | * |
453 | * @return int<0, max> |
454 | * |
455 | * @since 1.0.0 |
456 | */ |
457 | public function getM() : int |
458 | { |
459 | return $this->m; |
460 | } |
461 | |
462 | /** |
463 | * Get matrix columns. |
464 | * |
465 | * @return int<0, max> |
466 | * |
467 | * @since 1.0.0 |
468 | */ |
469 | public function getN() : int |
470 | { |
471 | return $this->n; |
472 | } |
473 | |
474 | /** |
475 | * Add scalar. |
476 | * |
477 | * @param int|float $scalar Scalar |
478 | * |
479 | * @return Matrix |
480 | * |
481 | * @since 1.0.0 |
482 | */ |
483 | private function addScalar(int | float $scalar) : self |
484 | { |
485 | $newMatrixArr = $this->matrix; |
486 | |
487 | foreach ($newMatrixArr as $i => $vector) { |
488 | foreach ($vector as $j => $value) { |
489 | $newMatrixArr[$i][$j] += $scalar; |
490 | } |
491 | } |
492 | |
493 | $newMatrix = new self($this->m, $this->n); |
494 | $newMatrix->setMatrix($newMatrixArr); |
495 | |
496 | return $newMatrix; |
497 | } |
498 | |
499 | /** |
500 | * Multiply right. |
501 | * |
502 | * @param int|float|self $value Factor |
503 | * |
504 | * @return Matrix |
505 | * |
506 | * @since 1.0.0 |
507 | */ |
508 | public function mult(int | float | self $value) : self |
509 | { |
510 | if (\is_numeric($value)) { |
511 | return $this->multScalar($value); |
512 | } |
513 | |
514 | return $this->multMatrix($value); |
515 | } |
516 | |
517 | /** |
518 | * Multiply matrix. |
519 | * |
520 | * @param Matrix $matrix Matrix to multiply with |
521 | * |
522 | * @return Matrix |
523 | * |
524 | * @throws InvalidDimensionException |
525 | * |
526 | * @since 1.0.0 |
527 | */ |
528 | private function multMatrix(self $matrix) : self |
529 | { |
530 | $nDim = $matrix->getN(); |
531 | $mDim = $matrix->getM(); |
532 | |
533 | if ($mDim !== $this->n) { |
534 | throw new InvalidDimensionException($mDim . 'x' . $nDim); |
535 | } |
536 | |
537 | $matrixArr = $matrix->getMatrix(); |
538 | $newMatrix = new self($this->m, $nDim); |
539 | $newMatrixArr = $newMatrix->getMatrix(); |
540 | |
541 | for ($i = 0; $i < $this->m; ++$i) { // Row of $this |
542 | for ($c = 0; $c < $nDim; ++$c) { // Column of $matrix |
543 | $temp = 0; |
544 | |
545 | for ($j = 0; $j < $mDim; ++$j) { // Row of $matrix |
546 | $temp += ($this->matrix[$i][$j] ?? 0) * ($matrixArr[$j][$c] ?? 0); |
547 | } |
548 | |
549 | $newMatrixArr[$i][$c] = $temp; |
550 | } |
551 | } |
552 | |
553 | $newMatrix->setMatrix($newMatrixArr); /* @phpstan-ignore-line */ |
554 | |
555 | return $newMatrix; |
556 | } |
557 | |
558 | /** |
559 | * Multiply matrix. |
560 | * |
561 | * @param int|float $scalar Scalar value |
562 | * |
563 | * @return Matrix |
564 | * |
565 | * @since 1.0.0 |
566 | */ |
567 | private function multScalar(int | float $scalar) : self |
568 | { |
569 | $newMatrixArr = $this->matrix; |
570 | |
571 | foreach ($newMatrixArr as $i => $vector) { |
572 | foreach ($vector as $j => $value) { |
573 | $newMatrixArr[$i][$j] *= $scalar; |
574 | } |
575 | } |
576 | |
577 | $newMatrix = new self($this->m, $this->n); |
578 | $newMatrix->setMatrix($newMatrixArr); |
579 | |
580 | return $newMatrix; |
581 | } |
582 | |
583 | /** |
584 | * Upper triangulize matrix. |
585 | * |
586 | * @return Matrix |
587 | * |
588 | * @since 1.0.0 |
589 | */ |
590 | public function upperTriangular() : self |
591 | { |
592 | $matrix = new self($this->n, $this->n); |
593 | |
594 | $matrixArr = $this->matrix; |
595 | $this->upperTrianglize($matrixArr); |
596 | $matrix->setMatrix($matrixArr); |
597 | |
598 | return $matrix; |
599 | } |
600 | |
601 | /** |
602 | * Trianglize matrix. |
603 | * |
604 | * @param array<int, array<int|float>> $arr Matrix to trianglize |
605 | * |
606 | * @return int Det sign |
607 | * |
608 | * @since 1.0.0 |
609 | */ |
610 | private function upperTrianglize(array &$arr) : int |
611 | { |
612 | $n = $this->n; |
613 | $sign = 1; |
614 | |
615 | for ($i = 0; $i < $n; ++$i) { |
616 | $max = 0; |
617 | |
618 | for ($j = $i; $j < $n; ++$j) { |
619 | if (\abs($arr[$j][$i]) > \abs($arr[$max][$i])) { |
620 | $max = $j; |
621 | } |
622 | } |
623 | |
624 | if ($max !== 0) { |
625 | $sign = -$sign; |
626 | $temp = $arr[$i]; |
627 | $arr[$i] = $arr[$max]; |
628 | $arr[$max] = $temp; |
629 | } |
630 | |
631 | if (!$arr[$i][$i]) { |
632 | return 0; |
633 | } |
634 | |
635 | for ($j = $i + 1; $j < $n; ++$j) { |
636 | $r = $arr[$j][$i] / $arr[$i][$i]; |
637 | |
638 | if (!$r) { |
639 | continue; |
640 | } |
641 | |
642 | for ($c = $i; $c < $n; ++$c) { |
643 | $arr[$j][$c] -= $arr[$i][$c] * $r; |
644 | } |
645 | } |
646 | } |
647 | |
648 | return $sign; |
649 | } |
650 | |
651 | /** |
652 | * Inverse matrix. |
653 | * |
654 | * @return Matrix |
655 | * |
656 | * @throws InvalidDimensionException |
657 | * |
658 | * @since 1.0.0 |
659 | */ |
660 | public function inverse() : self |
661 | { |
662 | return $this->solve(new IdentityMatrix($this->m)); |
663 | } |
664 | |
665 | /** |
666 | * Solve matrix |
667 | * |
668 | * @param Matrix $B Matrix/Vector b |
669 | * |
670 | * @return Matrix|Vector |
671 | * |
672 | * @since 1.0.0 |
673 | */ |
674 | public function solve(self $B) : self |
675 | { |
676 | $M = $this->m === $this->n ? new LUDecomposition($this) : new QRDecomposition($this); |
677 | |
678 | return $M->solve($B); |
679 | } |
680 | |
681 | /** |
682 | * Calculate det. |
683 | * |
684 | * @return float |
685 | * |
686 | * @since 1.0.0 |
687 | */ |
688 | public function det() : float |
689 | { |
690 | $L = new LUDecomposition($this); |
691 | |
692 | return $L->det(); |
693 | } |
694 | |
695 | /** |
696 | * Dot product |
697 | * |
698 | * @param self $B Matrix |
699 | * |
700 | * @return self |
701 | * |
702 | * @since 1.0.0 |
703 | */ |
704 | public function dot(self $B) : self |
705 | { |
706 | $value1 = $this->matrix; |
707 | $value2 = $B->getMatrix(); |
708 | |
709 | $m1 = \count($value1); |
710 | $n1 = ($isMatrix1 = \is_array($value1[0])) ? \count($value1[0]) : 1; |
711 | |
712 | $m2 = \count($value2); |
713 | $n2 = ($isMatrix2 = \is_array($value2[0])) ? \count($value2[0]) : 1; |
714 | |
715 | $result = null; |
716 | |
717 | if ($isMatrix1 && $isMatrix2) { |
718 | if ($m2 !== $n1) { |
719 | throw new InvalidDimensionException($m2 . 'x' . $n2 . ' not compatible with ' . $m1 . 'x' . $n1); |
720 | } |
721 | |
722 | $result = [[]]; |
723 | for ($i = 0; $i < $m1; ++$i) { // Row of 1 |
724 | for ($c = 0; $c < $n2; ++$c) { // Column of 2 |
725 | $temp = 0; |
726 | |
727 | for ($j = 0; $j < $m2; ++$j) { // Row of 2 |
728 | $temp += $value1[$i][$j] * $value2[$j][$c]; |
729 | } |
730 | |
731 | $result[$i][$c] = $temp; |
732 | } |
733 | } |
734 | |
735 | return self::fromArray($result); |
736 | } elseif (!$isMatrix1 && !$isMatrix2) { |
737 | if ($m1 !== $m2) { |
738 | throw new InvalidDimensionException($m1 . 'x' . $m2); |
739 | } |
740 | |
741 | $result = 0; |
742 | for ($i = 0; $i < $m1; ++$i) { |
743 | /** @var array $value1 */ |
744 | /** @var array $value2 */ |
745 | $result += $value1[$i] * $value2[$i]; |
746 | } |
747 | |
748 | return self::fromArray([[$result]]); |
749 | } elseif ($isMatrix1 && !$isMatrix2) { |
750 | $result = []; |
751 | for ($i = 0; $i < $m1; ++$i) { // Row of 1 |
752 | $temp = 0; |
753 | |
754 | for ($c = 0; $c < $m2; ++$c) { // Row of 2 |
755 | /** @var array $value2 */ |
756 | $temp += $value1[$i][$c] * $value2[$c]; |
757 | } |
758 | |
759 | $result[$i] = $temp; |
760 | } |
761 | |
762 | return self::fromArray($result); |
763 | } |
764 | |
765 | throw new \InvalidArgumentException(); |
766 | } |
767 | |
768 | /** |
769 | * Sum the elements in the matrix |
770 | * |
771 | * @param int $axis Axis (-1 -> all dimensions, 0 -> columns, 1 -> rows) |
772 | * |
773 | * @return int|float|self Returns int or float for axis -1 |
774 | * |
775 | * @since 1.0.0 |
776 | */ |
777 | public function sum(int $axis = -1) : int|float|self |
778 | { |
779 | if ($axis === -1) { |
780 | $sum = 0; |
781 | |
782 | foreach ($this->matrix as $row) { |
783 | $sum += \array_sum($row); |
784 | } |
785 | |
786 | return $sum; |
787 | } elseif ($axis === 0) { |
788 | $sum = []; |
789 | foreach ($this->matrix as $row) { |
790 | foreach ($row as $idx2 => $value) { |
791 | if (!isset($sum[$idx2])) { |
792 | $sum[$idx2] = 0; |
793 | } |
794 | |
795 | $sum[$idx2] += $value; |
796 | } |
797 | } |
798 | |
799 | return self::fromArray($sum); |
800 | } elseif ($axis === 1) { |
801 | $sum = []; |
802 | foreach ($this->matrix as $idx => $row) { |
803 | $sum[$idx] = \array_sum($row); |
804 | } |
805 | |
806 | return self::fromArray($sum); |
807 | } |
808 | |
809 | return new self(); |
810 | } |
811 | |
812 | /** |
813 | * Is matrix a diagonal matrix |
814 | * |
815 | * @return bool |
816 | * |
817 | * @since 1.0.0 |
818 | */ |
819 | public function isDiagonal() : bool |
820 | { |
821 | if ($this->m !== $this->n) { |
822 | return false; |
823 | } |
824 | |
825 | for ($i = 0; $i < $this->m; ++$i) { |
826 | for ($j = 0; $j < $this->n; ++$j) { |
827 | if ($i !== $j && \abs($this->matrix[$i][$j]) > self::EPSILON) { |
828 | return false; |
829 | } |
830 | } |
831 | } |
832 | |
833 | return true; |
834 | } |
835 | |
836 | /** |
837 | * Calculate the power of a matrix |
838 | * |
839 | * @param int|float $exponent Exponent |
840 | * |
841 | * @return self |
842 | * |
843 | * @throws InvalidDimensionException |
844 | * |
845 | * @since 1.0.0 |
846 | */ |
847 | public function pow(int | float $exponent) : self |
848 | { |
849 | if ($this->isDiagonal()) { |
850 | $matrix = []; |
851 | |
852 | for ($i = 0; $i < $this->m; ++$i) { |
853 | $row = []; |
854 | for ($j = 0; $j < $this->m; ++$j) { |
855 | $row[] = $i === $j ? \pow($this->matrix[$i][$j], $exponent) : 0; |
856 | } |
857 | |
858 | $matrix[] = $row; |
859 | } |
860 | |
861 | return self::fromArray($matrix); |
862 | } elseif (\is_int($exponent)) { |
863 | if ($this->m !== $this->n) { |
864 | throw new InvalidDimensionException($this->m . 'x' . $this->n); |
865 | } |
866 | |
867 | $matrix = new IdentityMatrix($this->m); |
868 | for ($i = 0; $i < $exponent; ++$i) { |
869 | $matrix = $matrix->mult($this); |
870 | } |
871 | |
872 | return $matrix; |
873 | } else { |
874 | // @todo: implement |
875 | throw new \Exception('Not yet implemented'); |
876 | } |
877 | } |
878 | |
879 | /** |
880 | * Calculate e^M |
881 | * |
882 | * @param int $iterations Iterations for approximation |
883 | * |
884 | * @return self |
885 | * |
886 | * @since 1.0.0 |
887 | */ |
888 | public function exp(int $iterations = 10) : self |
889 | { |
890 | if ($this->m !== $this->n) { |
891 | throw new InvalidDimensionException($this->m . 'x' . $this->n); |
892 | } |
893 | |
894 | $identity = new IdentityMatrix($this->m); |
895 | $matrix = $identity; |
896 | |
897 | $factorial = 1; |
898 | $pow = $matrix; |
899 | |
900 | for ($i = 1; $i <= $iterations; ++$i) { |
901 | $factorial *= $i; |
902 | $coeff = 1 / $factorial; |
903 | |
904 | $term = $pow->mult($coeff); |
905 | $matrix = $matrix->add($term); |
906 | $pow = $pow->mult($matrix); // @todo: maybe wrong order? |
907 | } |
908 | |
909 | return $matrix; |
910 | } |
911 | |
912 | /** |
913 | * {@inheritdoc} |
914 | */ |
915 | public function current() : int |
916 | { |
917 | $row = (int) ($this->position / $this->m); |
918 | |
919 | return $this->matrix[$row][$this->position - $row * $this->n]; |
920 | } |
921 | |
922 | /** |
923 | * {@inheritdoc} |
924 | */ |
925 | public function offsetGet(mixed $offset) : mixed |
926 | { |
927 | if (!\is_int($offset)) { |
928 | return 0; |
929 | } |
930 | |
931 | $offset = (int) $offset; |
932 | $row = (int) ($offset / $this->m); |
933 | |
934 | return $this->matrix[$row][$offset - $row * $this->n]; |
935 | } |
936 | |
937 | /** |
938 | * {@inheritdoc} |
939 | */ |
940 | public function next() : void |
941 | { |
942 | ++$this->position; |
943 | } |
944 | |
945 | /** |
946 | * {@inheritdoc} |
947 | */ |
948 | public function key() : mixed |
949 | { |
950 | return $this->position; |
951 | } |
952 | |
953 | /** |
954 | * {@inheritdoc} |
955 | */ |
956 | public function valid() : bool |
957 | { |
958 | return $this->offsetExists($this->position); |
959 | } |
960 | |
961 | /** |
962 | * {@inheritdoc} |
963 | */ |
964 | public function offsetExists(mixed $offset) : bool |
965 | { |
966 | if (!\is_int($offset)) { |
967 | return false; |
968 | } |
969 | |
970 | $offset = (int) $offset; |
971 | $row = (int) ($offset / $this->m); |
972 | |
973 | return isset($this->matrix[$row][$offset - $row * $this->n]); |
974 | } |
975 | |
976 | /** |
977 | * {@inheritdoc} |
978 | */ |
979 | public function rewind() : void |
980 | { |
981 | $this->position = 0; |
982 | } |
983 | |
984 | /** |
985 | * {@inheritdoc} |
986 | */ |
987 | public function offsetSet(mixed $offset, mixed $value) : void |
988 | { |
989 | if (!\is_int($offset) || !\is_numeric($value)) { |
990 | return; |
991 | } |
992 | |
993 | $offset = (int) $offset; |
994 | $row = (int) ($offset / $this->m); |
995 | $this->matrix[$row][$offset - $row * $this->n] = $value; /* @phpstan-ignore-line */ |
996 | } |
997 | |
998 | /** |
999 | * {@inheritdoc} |
1000 | */ |
1001 | public function offsetUnset(mixed $offset) : void |
1002 | { |
1003 | if (!\is_int($offset)) { |
1004 | return; |
1005 | } |
1006 | |
1007 | $offset = (int) $offset; |
1008 | $row = (int) ($offset / $this->m); |
1009 | unset($this->matrix[$row][$offset - $row * $this->n]); |
1010 | } |
1011 | } |