2020#include " itkQRDecomposition.h"
2121
2222// Exercise the deprecated VNL engine for the old-vs-new equivalence checks.
23- #define ITK_LEGACY_TEST
24- #include " vnl/algo/vnl_qr.h"
23+ // The VNL symbol is unavailable under ITK_FUTURE_LEGACY_REMOVE.
24+ #ifndef ITK_FUTURE_LEGACY_REMOVE
25+ # define ITK_LEGACY_TEST
26+ # include " vnl/algo/vnl_qr.h"
27+ #endif
2528
2629#include < gtest/gtest.h>
2730#include < cmath>
@@ -77,6 +80,51 @@ TEST(QRDecomposition, SolveResidual)
7780}
7881
7982
83+ // Multi-column RHS: each column solves against the one shared factorization.
84+ TEST (QRDecomposition, SolveMatrixRHS)
85+ {
86+ const unsigned int n = 5 ;
87+ const unsigned int k = 3 ;
88+ const vnl_matrix<double > A = MakeMatrix<double >(n, n);
89+ vnl_matrix<double > B (n, k);
90+ for (unsigned int i = 0 ; i < n; ++i)
91+ for (unsigned int j = 0 ; j < k; ++j)
92+ B (i, j) = std::cos (0.3 * (i + 1 ) * (j + 2 ));
93+
94+ const itk::QRDecomposition<double > qr (A);
95+ const vnl_matrix<double > X = qr.Solve (B);
96+ ASSERT_EQ (X.rows (), n);
97+ ASSERT_EQ (X.cols (), k);
98+ EXPECT_LT ((A * X - B).fro_norm () / B.fro_norm (), 1e-10 );
99+ for (unsigned int j = 0 ; j < k; ++j)
100+ EXPECT_LT ((X.get_column (j) - qr.Solve (B.get_column (j))).two_norm (), 1e-12 );
101+ }
102+
103+
104+ // Overdetermined (rows > cols) multi-column solve yields the least-squares
105+ // solution, characterized by the normal equations A^T (A X - B) == 0.
106+ TEST (QRDecomposition, SolveMatrixRHSOverdetermined)
107+ {
108+ const unsigned int m = 6 ;
109+ const unsigned int n = 3 ;
110+ const unsigned int k = 2 ;
111+ const vnl_matrix<double > A = MakeMatrix<double >(m, n);
112+ vnl_matrix<double > B (m, k);
113+ for (unsigned int i = 0 ; i < m; ++i)
114+ for (unsigned int j = 0 ; j < k; ++j)
115+ B (i, j) = std::cos (0.35 * (i + 1 ) * (j + 2 )) - 0.2 * (j + 1 );
116+
117+ const itk::QRDecomposition<double > qr (A);
118+ const vnl_matrix<double > X = qr.Solve (B);
119+ ASSERT_EQ (X.rows (), n);
120+ ASSERT_EQ (X.cols (), k);
121+ EXPECT_LT ((A.transpose () * (A * X - B)).fro_norm () / (A.transpose () * B).fro_norm (), 1e-10 );
122+ for (unsigned int j = 0 ; j < k; ++j)
123+ EXPECT_LT ((X.get_column (j) - qr.Solve (B.get_column (j))).two_norm (), 1e-12 );
124+ }
125+
126+
127+ #ifndef ITK_FUTURE_LEGACY_REMOVE
80128// itk:: solve and determinant agree with the (sign-stable) legacy vnl_qr.
81129TEST (QRDecomposition, EquivalentToVnlQR)
82130{
@@ -94,6 +142,45 @@ TEST(QRDecomposition, EquivalentToVnlQR)
94142}
95143
96144
145+ // Multi-column solve matches legacy vnl_qr column-for-column. This is the exact
146+ // operation itkLandmarkBasedTransformInitializer relies on (square Q, matrix C).
147+ TEST (QRDecomposition, MatrixRHSEquivalentToVnlQR)
148+ {
149+ for (const unsigned int n : { 3u , 4u , 6u })
150+ {
151+ const unsigned int k = n - 1 ;
152+ const vnl_matrix<double > A = MakeMatrix<double >(n, n);
153+ vnl_matrix<double > B (n, k);
154+ for (unsigned int i = 0 ; i < n; ++i)
155+ for (unsigned int j = 0 ; j < k; ++j)
156+ B (i, j) = std::cos (0.4 * (i + 1 ) * (j + 2 )) - 0.3 * (j + 1 );
157+
158+ const vnl_matrix<double > xItk = itk::QRDecomposition<double >(A).Solve (B);
159+ const vnl_matrix<double > xVnl = vnl_qr<double >(A).solve (B);
160+ EXPECT_LT ((xItk - xVnl).fro_norm () / xVnl.fro_norm (), 1e-10 );
161+ }
162+ }
163+
164+
165+ // Inverse matches legacy vnl_qr.inverse(). This is the operation itk::fem::Element
166+ // (JacobianInverse / JacobianDeterminant) relies on.
167+ TEST (QRDecomposition, InverseEquivalentToVnlQR)
168+ {
169+ for (const unsigned int n : { 2u , 3u , 5u })
170+ {
171+ const vnl_matrix<double > A = MakeMatrix<double >(n, n);
172+ const vnl_matrix<double > invItk = itk::QRDecomposition<double >(A).Inverse ();
173+ const vnl_matrix<double > invVnl = vnl_qr<double >(A).inverse ();
174+ EXPECT_LT ((invItk - invVnl).fro_norm () / invVnl.fro_norm (), 1e-10 );
175+
176+ vnl_matrix<double > ident (n, n);
177+ ident.set_identity ();
178+ EXPECT_LT ((A * invItk - ident).fro_norm (), 1e-10 );
179+ }
180+ }
181+ #endif
182+
183+
97184// Single-precision path reconstructs.
98185TEST (QRDecomposition, FloatReconstructs)
99186{
0 commit comments