diff --git a/src/Numerics.Tests/LinearAlgebraTests/Double/Factorization/SvdTests.cs b/src/Numerics.Tests/LinearAlgebraTests/Double/Factorization/SvdTests.cs
index bdb4cf671..30f515a2a 100644
--- a/src/Numerics.Tests/LinearAlgebraTests/Double/Factorization/SvdTests.cs
+++ b/src/Numerics.Tests/LinearAlgebraTests/Double/Factorization/SvdTests.cs
@@ -115,6 +115,103 @@ public void CanFactorizeRandomMatrix(int row, int column)
}
}
+ ///
+ /// Can handle a matrix at all scales (small, medium and large elements).
+ ///
+ /// Scale to test.
+ [TestCase(1)]
+ [TestCase(1E40)]
+ [TestCase(1E-40)]
+ public void CanFactorizeMatrixAtDifferentScales(double scale)
+ {
+ /* Values computed with Mathematica in this way:
+ * matrix = {{4, 7, 4}, {10, 8, 8}, {5, 3, 4}, {5, 4, 5}}
+ * {u, w, v} = SingularValueDecomposition[matrix];
+ * N[u, 20]
+ * N[w, 20]
+ * N[Transpose[v], 20]
+ */
+
+ var matrixX = CreateMatrix.DenseOfRowArrays(
+ new double[] { 4, 7, 4 },
+ new double[] { 10, 8, 8 },
+ new double[] { 5, 3, 4 },
+ new double[] { 5, 4, 5 }
+ );
+
+ var expectedU = CreateMatrix.DenseOfRowArrays(
+ new double[] { 0.42215668141346500811, -0.87675136383986560620, 0.047995632277482876247, -0.22536015980024164867 },
+ new double[] { 0.73993840997833451205, 0.22051356023428738286, -0.46959889408365937068, 0.42818430362045913247 },
+ new double[] { 0.34231752064357875227, 0.38153678983509828442, -0.062299290485614282346, -0.85636860724091826495 },
+ new double[] { 0.39635035112528586728, 0.19264084136045230895, 0.87937028398887675954, 0.18028812784019331894 }
+ );
+
+ var expectedW = CreateMatrix.DenseOfRowArrays(
+ new double[] { 20.381761793431507357, 0, 0 },
+ new double[] { 0, 3.0100938773437216581, 0 },
+ new double[] { 0, 0, 0.72327107324536682962 },
+ new double[] { 0, 0, 0 }
+ );
+
+
+ var expectedVT = CreateMatrix.DenseOfRowArrays(
+ new double[] { 0.62709741747647217739, 0.56359004351894387921, 0.53769423638407554691 },
+ new double[] { 0.52125228212010301763, -0.81657829629776043208, 0.24798375833919346295 },
+ new double[] { -0.57883062063001098569, -0.12476437336740771587, 0.80584673714008074090 }
+ );
+
+ var matrixXScaled = matrixX * scale;
+ var result = matrixXScaled.Svd(computeVectors: true);
+
+ var reconstructedX = result.U * result.W * result.VT;
+ for (var i = 0; i < reconstructedX.RowCount; ++i)
+ {
+ for (var j = 0; j < reconstructedX.ColumnCount; ++j)
+ {
+ Assert.AreEqual(matrixXScaled[i, j], reconstructedX[i, j], 1E-14 * scale);
+ }
+ }
+
+ var signU = new DiagonalMatrix(4); // The sign of the columns of U after SVD is not unique, thus we need to adopt it
+ for (var i = 0; i < expectedU.ColumnCount; ++i)
+ {
+ signU[i, i] = expectedU[0, i] * result.U[0, i] < 0 ? -1 : 1;
+ }
+
+ expectedU *= signU;
+ for (var i = 0; i < expectedU.RowCount; ++i)
+ {
+ for (var j = 0; j < expectedU.ColumnCount; ++j)
+ {
+ Assert.AreEqual(expectedU[i, j], result.U[i, j], 1E-14); // U should be independent of scaling
+ }
+ }
+
+ expectedW *= scale; // W is dependent on scaling
+ for (var i = 0; i < expectedW.RowCount; ++i)
+ {
+ for (var j = 0; j < expectedW.ColumnCount; ++j)
+ {
+ Assert.AreEqual(expectedW[i, j], result.W[i, j], 1E-14 * scale); // W should be scaled by scaling
+ }
+ }
+
+ var signVT = new DiagonalMatrix(3); // The sign of the columns of VT after SVD is not unique, thus we need to adopt it
+ for (int i = 0; i < expectedVT.ColumnCount; ++i)
+ {
+ signVT[i, i] = expectedVT[0, i] * result.VT[0, i] < 0 ? -1 : 1;
+ }
+
+ expectedVT *= signVT;
+ for (var i = 0; i < expectedVT.RowCount; ++i)
+ {
+ for (var j = 0; j < expectedVT.ColumnCount; ++j)
+ {
+ Assert.AreEqual(expectedVT[i, j], result.VT[i, j], 1E-14); // VT should be independent of scaling
+ }
+ }
+ }
+
///
/// Can check rank of a non-square matrix.
///
diff --git a/src/Numerics/Providers/LinearAlgebra/ManagedLinearAlgebraProvider.Double.cs b/src/Numerics/Providers/LinearAlgebra/ManagedLinearAlgebraProvider.Double.cs
index 26f9d2f68..c70ecd9cb 100644
--- a/src/Numerics/Providers/LinearAlgebra/ManagedLinearAlgebraProvider.Double.cs
+++ b/src/Numerics/Providers/LinearAlgebra/ManagedLinearAlgebraProvider.Double.cs
@@ -2008,7 +2008,7 @@ public void SingularValueDecomposition(bool computeVectors, double[] a, int rows
{
test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]);
ztest = test + Math.Abs(e[l]);
- if (ztest.AlmostEqualRelative(test, 15))
+ if (AlmostEqualRelative(ztest, test, 4E-15))
{
e[l] = 0.0;
break;
@@ -2037,7 +2037,7 @@ public void SingularValueDecomposition(bool computeVectors, double[] a, int rows
}
ztest = test + Math.Abs(stemp[ls]);
- if (ztest.AlmostEqualRelative(test, 15))
+ if (AlmostEqualRelative(ztest, test, 4E-15))
{
stemp[ls] = 0.0;
break;
@@ -3575,5 +3575,17 @@ static Complex Cdiv(double xreal, double ximag, double yreal, double yimag)
return new Complex((ximag + (xreal*(yreal/yimag)))/(yimag + (yreal*(yreal/yimag))), (-xreal + (ximag*(yreal/yimag)))/(yimag + (yreal*(yreal/yimag))));
}
+
+ ///
+ /// Determines whether and are equal within a given relative tolerance.
+ ///
+ /// The value x1.
+ /// The value x2.
+ /// The relative tolerance value.
+ /// if the values are equal within tolerance; otherwise, .
+ static bool AlmostEqualRelative(double x1, double x2, double relativeTolerance)
+ {
+ return (x1 == x2) || Math.Abs(x1 - x2) < relativeTolerance * Math.Max(Math.Abs(x1), Math.Abs(x2));
+ }
}
}