Skip to content

Commit da47751

Browse files
authored
Replace array_search with floating-point filter in Eigenvector::eigenvectors (#473)
* Test Eigenvectors can handle numerical imprecision To mimic the error found in the QR algorithm, we have to test with matrices that have duplicate eigenvalues and introduce numerical precision errors. To do this, a list of perturbed eigenvalues is passed to the eigenvectors method. The perturbation is achieved by adding a random +/- offset on an order of magnitude smaller than the default matrix error. This should allow the math to work out fine while causing the floating point comparison to fail. * Replace array_search with floating-point filter array_search seems to fail in most cases when looking for a float in an array of floats. And even if it does find a match, if the key is 0, php evaluates `!0` to true. To find a match, we can instead loop through and compare the numbers with `Arithmetic::almostEqual` and then explicitly check if `$key === false`
1 parent ea4f212 commit da47751

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

src/LinearAlgebra/Eigenvector.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace MathPHP\LinearAlgebra;
44

5+
use MathPHP\Arithmetic;
56
use MathPHP\Exception;
67
use MathPHP\Exception\MatrixException;
78
use MathPHP\Functions\Map\Single;
@@ -66,8 +67,14 @@ public static function eigenvectors(NumericMatrix $A, array $eigenvalues = []):
6667
foreach ($eigenvalues as $eigenvalue) {
6768
// If this is a duplicate eigenvalue, and this is the second instance, the first
6869
// pass already found all the vectors.
69-
$key = \array_search($eigenvalue, \array_column($solution_array, 'eigenvalue'));
70-
if (!$key) {
70+
$key = false;
71+
foreach (\array_column($solution_array, 'eigenvalue') as $i => $v) {
72+
if (Arithmetic::almostEqual($v, $eigenvalue, $A->getError())) {
73+
$key = $i;
74+
break;
75+
}
76+
}
77+
if ($key === false) {
7178
$ = MatrixFactory::identity($number)->scalarMultiply($eigenvalue);
7279
$T = $A->subtract($);
7380

tests/LinearAlgebra/Eigen/EigenvectorTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,63 @@ public function dataProviderForEigenvector(): array
131131
];
132132
}
133133

134+
/**
135+
* @test eigenvector can handle numerical precision errors
136+
* @dataProvider dataProviderForPerturbedEigenvalues
137+
* @param array $A
138+
* @param array $E
139+
* @param array $S
140+
*/
141+
public function testEigenvectorsPerturbedEigenvalues(array $A, array $E, array $S)
142+
{
143+
// Perturb E
144+
foreach ($E as $i => $component) {
145+
$E[$i] = $component + (random_int(-1, 1) * 10**-12);
146+
}
147+
148+
// Given
149+
$A = MatrixFactory::create($A);
150+
$S = MatrixFactory::create($S);
151+
152+
// When
153+
$eigenvectors = Eigenvector::eigenvectors($A, $E);
154+
155+
// Then
156+
$this->assertEqualsWithDelta($S, $eigenvectors, 0.0001);
157+
}
158+
159+
public function dataProviderForPerturbedEigenvalues(): array
160+
{
161+
return [
162+
[ // Matrix has duplicate eigenvalues. One vector is on an axis.
163+
[
164+
[2, 0, 1],
165+
[2, 1, 2],
166+
[3, 0, 4],
167+
],
168+
[5, 1, 1],
169+
[
170+
[1 / \sqrt(14), 0, \M_SQRT1_2],
171+
[2 / \sqrt(14), 1, 0],
172+
[3 / \sqrt(14), 0, -1 * \M_SQRT1_2],
173+
]
174+
],
175+
[ // Matrix has duplicate eigenvalues. no solution on the axis
176+
[
177+
[2, 2, -3],
178+
[2, 5, -6],
179+
[3, 6, -8],
180+
],
181+
[-3, 1, 1],
182+
[
183+
[1 / \sqrt(14), 1 / \M_SQRT3, 5 / \sqrt(42)],
184+
[2 / \sqrt(14), 1 / \M_SQRT3, -4 / \sqrt(42)],
185+
[3 / \sqrt(14), 1 / \M_SQRT3, -1 / \sqrt(42)],
186+
]
187+
],
188+
];
189+
}
190+
134191
/**
135192
* @test eigenvectors throws a BadDataException when the matrix is not square
136193
*/

0 commit comments

Comments
 (0)