Line data Source code
1 : /**
2 : * @file test_WLSProblem.cpp
3 : *
4 : * @brief Tests for the WLSProblem class
5 : *
6 : * @author Matthias Wieczorek - initial code
7 : * @author Maximilian Hornung - modularization
8 : * @author David Frank - rewrite
9 : * @author Tobias Lasser - rewrite, modernization
10 : * @author Nikola Dinev - added tests for conversion constructor
11 : */
12 :
13 : #include "doctest/doctest.h"
14 :
15 : #include "WLSProblem.h"
16 : #include "Identity.h"
17 : #include "Scaling.h"
18 : #include "L2NormPow2.h"
19 : #include "WeightedL2NormPow2.h"
20 : #include "Quadric.h"
21 : #include "TikhonovProblem.h"
22 : #include "BlockLinearOperator.h"
23 : #include "RandomBlocksDescriptor.h"
24 : #include "VolumeDescriptor.h"
25 : #include "testHelpers.h"
26 :
27 : using namespace elsa;
28 : using namespace doctest;
29 :
30 : TEST_SUITE_BEGIN("problems");
31 :
32 : TEST_CASE_TEMPLATE("WLSProblems: Testing with operator and data", TestType, float, double)
33 8 : {
34 8 : GIVEN("the operator and data")
35 8 : {
36 8 : IndexVector_t numCoeff(2);
37 8 : numCoeff << 7, 13;
38 8 : VolumeDescriptor dd(numCoeff);
39 :
40 8 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> bVec(dd.getNumberOfCoefficients());
41 8 : bVec.setRandom();
42 8 : DataContainer<TestType> dcB(dd, bVec);
43 :
44 8 : Identity<TestType> idOp(dd);
45 :
46 8 : WHEN("setting up a ls problem without x0")
47 8 : {
48 4 : WLSProblem<TestType> prob(idOp, dcB);
49 :
50 4 : THEN("the clone works correctly")
51 4 : {
52 2 : auto probClone = prob.clone();
53 :
54 2 : REQUIRE_NE(probClone.get(), &prob);
55 2 : REQUIRE_EQ(*probClone, prob);
56 2 : }
57 :
58 4 : THEN("the problem behaves as expected")
59 4 : {
60 2 : DataContainer<TestType> dcZero(dd);
61 2 : dcZero = 0;
62 2 : REQUIRE_UNARY(isApprox(prob.getCurrentSolution(), dcZero));
63 :
64 2 : REQUIRE_UNARY(
65 2 : checkApproxEq(prob.evaluate(), as<TestType>(0.5) * bVec.squaredNorm()));
66 2 : REQUIRE_UNARY(
67 2 : checkApproxEq(prob.getGradient(), static_cast<TestType>(-1.0f) * dcB));
68 :
69 2 : auto hessian = prob.getHessian();
70 2 : REQUIRE_UNARY(isApprox(hessian.apply(dcB), dcB));
71 2 : }
72 4 : }
73 :
74 8 : WHEN("setting up a ls problem with x0")
75 8 : {
76 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> x0Vec(dd.getNumberOfCoefficients());
77 4 : x0Vec.setRandom();
78 4 : DataContainer<TestType> dcX0(dd, x0Vec);
79 :
80 4 : WLSProblem<TestType> prob(idOp, dcB, dcX0);
81 :
82 4 : THEN("the clone works correctly")
83 4 : {
84 2 : auto probClone = prob.clone();
85 :
86 2 : REQUIRE_NE(probClone.get(), &prob);
87 2 : REQUIRE_EQ(*probClone, prob);
88 2 : }
89 :
90 4 : THEN("the problem behaves as expected")
91 4 : {
92 :
93 2 : REQUIRE_UNARY(isApprox(prob.getCurrentSolution(), dcX0));
94 :
95 2 : REQUIRE_UNARY(checkApproxEq(prob.evaluate(),
96 2 : as<TestType>(0.5) * (x0Vec - bVec).squaredNorm()));
97 2 : REQUIRE_UNARY(isApprox(prob.getGradient(), dcX0 - dcB));
98 :
99 2 : auto hessian = prob.getHessian();
100 2 : REQUIRE_UNARY(isApprox(hessian.apply(dcB), dcB));
101 2 : }
102 4 : }
103 8 : }
104 8 : }
105 :
106 : TEST_CASE_TEMPLATE("WLSProblems: Testing with weights, operator and data", TestType, float, double)
107 8 : {
108 8 : GIVEN("weights, operator and data")
109 8 : {
110 8 : IndexVector_t numCoeff(3);
111 8 : numCoeff << 7, 13, 17;
112 8 : VolumeDescriptor dd(numCoeff);
113 :
114 8 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> bVec(dd.getNumberOfCoefficients());
115 8 : bVec.setRandom();
116 8 : DataContainer<TestType> dcB(dd, bVec);
117 :
118 8 : Identity<TestType> idOp(dd);
119 :
120 8 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> weightsVec(dd.getNumberOfCoefficients());
121 8 : weightsVec.setRandom();
122 8 : DataContainer dcWeights(dd, weightsVec);
123 8 : Scaling scaleOp(dd, dcWeights);
124 :
125 8 : WHEN("setting up a wls problem without x0")
126 8 : {
127 4 : WLSProblem<TestType> prob(scaleOp, idOp, dcB);
128 :
129 4 : THEN("the clone works correctly")
130 4 : {
131 2 : auto probClone = prob.clone();
132 :
133 2 : REQUIRE_NE(probClone.get(), &prob);
134 2 : REQUIRE_EQ(*probClone, prob);
135 2 : }
136 :
137 4 : THEN("the problem behaves as expected")
138 4 : {
139 2 : DataContainer<TestType> dcZero(dd);
140 2 : dcZero = 0;
141 2 : REQUIRE_UNARY(isApprox(prob.getCurrentSolution(), dcZero));
142 :
143 2 : REQUIRE_UNARY(checkApproxEq(
144 2 : prob.evaluate(),
145 2 : as<TestType>(0.5) * bVec.dot((weightsVec.array() * bVec.array()).matrix())));
146 2 : DataContainer<TestType> tmpDc = as<TestType>(-1) * dcWeights * dcB;
147 2 : REQUIRE_UNARY(isApprox(prob.getGradient(), tmpDc));
148 :
149 2 : auto hessian = prob.getHessian();
150 2 : REQUIRE_UNARY(isApprox(hessian.apply(dcB), dcWeights * dcB));
151 2 : }
152 4 : }
153 :
154 8 : WHEN("setting up a wls problem with x0")
155 8 : {
156 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> x0Vec(dd.getNumberOfCoefficients());
157 4 : x0Vec.setRandom();
158 4 : DataContainer<TestType> dcX0(dd, x0Vec);
159 :
160 4 : WLSProblem<TestType> prob(scaleOp, idOp, dcB, dcX0);
161 :
162 4 : THEN("the clone works correctly")
163 4 : {
164 2 : auto probClone = prob.clone();
165 :
166 2 : REQUIRE_NE(probClone.get(), &prob);
167 2 : REQUIRE_EQ(*probClone, prob);
168 2 : }
169 :
170 4 : THEN("the problem behaves as expected")
171 4 : {
172 2 : DataContainer<TestType> dcZero(dd);
173 2 : dcZero = 0;
174 2 : REQUIRE_UNARY(isApprox(prob.getCurrentSolution(), dcX0));
175 :
176 2 : REQUIRE_UNARY(checkApproxEq(
177 2 : prob.evaluate(),
178 2 : as<TestType>(0.5)
179 2 : * (x0Vec - bVec)
180 2 : .dot((weightsVec.array() * (x0Vec - bVec).array()).matrix())));
181 2 : REQUIRE_UNARY(isApprox(prob.getGradient(), dcWeights * (dcX0 - dcB)));
182 :
183 2 : auto hessian = prob.getHessian();
184 2 : REQUIRE_UNARY(isApprox(hessian.apply(dcB), dcWeights * dcB));
185 2 : }
186 4 : }
187 8 : }
188 8 : }
189 :
190 : TEST_CASE_TEMPLATE("WLSProblems: Testing different optimization problems", TestType, float, double)
191 18 : {
192 18 : GIVEN("an optimization problem with only a (w)ls data term")
193 18 : {
194 2 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
195 :
196 2 : Scaling<TestType> W{desc, static_cast<TestType>(3.0)};
197 :
198 2 : LinearResidual<TestType> residual{desc};
199 2 : L2NormPow2<TestType> func{residual};
200 2 : WeightedL2NormPow2<TestType> weightedFunc{residual, W};
201 2 : Problem<TestType> prob{func};
202 2 : Problem<TestType> weightedProb{weightedFunc};
203 :
204 2 : WHEN("converting to a wls problem")
205 2 : {
206 2 : WLSProblem<TestType> lsProb{prob};
207 2 : WLSProblem<TestType> wlsProb{weightedProb};
208 :
209 2 : THEN("only the type of the problem changes")
210 2 : {
211 2 : REQUIRE_EQ(lsProb.getDataTerm(), prob.getDataTerm());
212 2 : REQUIRE_EQ(lsProb.getRegularizationTerms(), prob.getRegularizationTerms());
213 2 : REQUIRE_EQ(wlsProb.getDataTerm(), weightedProb.getDataTerm());
214 2 : REQUIRE_EQ(wlsProb.getRegularizationTerms(), weightedProb.getRegularizationTerms());
215 2 : }
216 2 : }
217 2 : }
218 :
219 18 : GIVEN("an optimization problem with a non-(w)ls data term")
220 18 : {
221 2 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
222 :
223 2 : Quadric<TestType> quadric{desc};
224 2 : Problem prob{quadric};
225 :
226 2 : WHEN("converting to a WLSProblem")
227 2 : {
228 2 : THEN("an exception is thrown") { REQUIRE_THROWS(WLSProblem<TestType>{prob}); }
229 2 : }
230 2 : }
231 :
232 18 : GIVEN("an optimization problem with a non-(w)ls regularization term")
233 18 : {
234 2 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
235 :
236 2 : Quadric<TestType> quadric{desc};
237 2 : RegularizationTerm regTerm{static_cast<TestType>(5), quadric};
238 2 : Problem prob{L2NormPow2<TestType>{desc}, regTerm};
239 :
240 2 : WHEN("converting to a WLSProblem")
241 2 : {
242 2 : THEN("an exception is thrown") { REQUIRE_THROWS(WLSProblem<TestType>{prob}); }
243 2 : }
244 2 : }
245 :
246 18 : GIVEN("an optimization problem with a wls data term that has negative weighting factors")
247 18 : {
248 4 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
249 :
250 4 : Scaling<TestType> W1{desc, static_cast<TestType>(-3.0)};
251 :
252 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> anisotropicW =
253 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Constant(343, 1);
254 4 : anisotropicW[256] = -3.0;
255 :
256 4 : Scaling<TestType> W2{desc, DataContainer(desc, anisotropicW)};
257 :
258 4 : LinearResidual<TestType> residual{desc};
259 4 : WeightedL2NormPow2<TestType> weightedFunc1{residual, W1};
260 4 : WeightedL2NormPow2<TestType> weightedFunc2{residual, W2};
261 :
262 4 : WHEN("convering to a WLSProblem and no regularization terms are present")
263 4 : {
264 2 : Problem prob1{weightedFunc1};
265 2 : Problem prob2{weightedFunc2};
266 :
267 2 : WLSProblem converted1{prob1};
268 2 : WLSProblem converted2{prob2};
269 :
270 2 : THEN("only the type of the problem changes")
271 2 : {
272 2 : REQUIRE_EQ(prob1.getDataTerm(), converted1.getDataTerm());
273 2 : REQUIRE_EQ(prob1.getRegularizationTerms(), converted1.getRegularizationTerms());
274 2 : REQUIRE_EQ(prob2.getDataTerm(), converted2.getDataTerm());
275 2 : REQUIRE_EQ(prob2.getRegularizationTerms(), converted2.getRegularizationTerms());
276 2 : }
277 2 : }
278 :
279 4 : WHEN("convering to a WLSProblem and regularization terms are present")
280 4 : {
281 2 : RegularizationTerm regTerm{static_cast<TestType>(1.0), L2NormPow2<TestType>{desc}};
282 2 : Problem prob1{weightedFunc1, regTerm};
283 2 : Problem prob2{weightedFunc2, regTerm};
284 :
285 2 : THEN("an exception is thrown")
286 2 : {
287 2 : REQUIRE_THROWS(WLSProblem{prob1});
288 2 : REQUIRE_THROWS(WLSProblem{prob2});
289 2 : }
290 2 : }
291 4 : }
292 :
293 18 : GIVEN("an optimization problem with a (w)ls regularization term that has negative weighting "
294 18 : "factors")
295 18 : {
296 2 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
297 :
298 2 : Scaling<TestType> W1{desc, static_cast<TestType>(-3.0)};
299 :
300 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> anisotropicW =
301 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Constant(343, 1);
302 2 : anisotropicW[256] = -3.0;
303 :
304 2 : Scaling<TestType> W2{desc, DataContainer(desc, anisotropicW)};
305 :
306 2 : LinearResidual<TestType> residual{desc};
307 2 : WeightedL2NormPow2<TestType> weightedFunc1{residual, W1};
308 2 : WeightedL2NormPow2<TestType> weightedFunc2{residual, W2};
309 2 : L2NormPow2<TestType> nonWeightedFunc{desc};
310 :
311 2 : RegularizationTerm negWeights{static_cast<TestType>(1.0), weightedFunc1};
312 2 : RegularizationTerm mixedWeights{static_cast<TestType>(1.0), weightedFunc2};
313 2 : RegularizationTerm noWeightsNegLambda{static_cast<TestType>(-1.0), nonWeightedFunc};
314 2 : WHEN("convering to a WLSProblem")
315 2 : {
316 2 : Problem prob1{L2NormPow2<TestType>{desc}, negWeights};
317 2 : Problem prob2{L2NormPow2<TestType>{desc}, mixedWeights};
318 2 : Problem prob3{L2NormPow2<TestType>{desc}, noWeightsNegLambda};
319 :
320 2 : THEN("an exception is thrown")
321 2 : {
322 2 : REQUIRE_THROWS(WLSProblem{prob1});
323 2 : REQUIRE_THROWS(WLSProblem{prob2});
324 2 : REQUIRE_THROWS(WLSProblem{prob3});
325 2 : }
326 2 : }
327 2 : }
328 :
329 18 : GIVEN("an OptimizationProblem with only (w)ls terms")
330 18 : {
331 4 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
332 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> vec =
333 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343);
334 4 : DataContainer<TestType> b{desc, vec};
335 :
336 4 : Scaling<TestType> A{desc, static_cast<TestType>(2.0)};
337 :
338 4 : Scaling<TestType> isoW{desc, static_cast<TestType>(3.0)};
339 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> vecW =
340 4 : Eigen::abs(Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343).array());
341 4 : DataContainer<TestType> dcW{desc, vecW};
342 4 : Scaling<TestType> nonIsoW{desc, dcW};
343 :
344 4 : std::vector<std::unique_ptr<Functional<TestType>>> dataTerms;
345 :
346 4 : dataTerms.push_back(std::make_unique<L2NormPow2<TestType>>(desc));
347 4 : dataTerms.push_back(std::make_unique<L2NormPow2<TestType>>(LinearResidual{b}));
348 4 : dataTerms.push_back(std::make_unique<L2NormPow2<TestType>>(LinearResidual{A}));
349 4 : dataTerms.push_back(std::make_unique<L2NormPow2<TestType>>(LinearResidual{A, b}));
350 4 : dataTerms.push_back(
351 4 : std::make_unique<WeightedL2NormPow2<TestType>>(LinearResidual{A, b}, isoW));
352 4 : dataTerms.push_back(
353 4 : std::make_unique<WeightedL2NormPow2<TestType>>(LinearResidual{A, b}, nonIsoW));
354 :
355 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> regVec =
356 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343);
357 4 : DataContainer<TestType> bReg{desc, regVec};
358 :
359 4 : Scaling<TestType> AReg{desc, static_cast<TestType>(0.25)};
360 :
361 4 : Scaling<TestType> isoWReg{desc, static_cast<TestType>(1.5)};
362 4 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> vecWReg =
363 4 : Eigen::abs(Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343).array());
364 4 : DataContainer<TestType> dcWReg{desc, vecWReg};
365 4 : Scaling<TestType> nonIsoWReg{desc, dcWReg};
366 :
367 4 : std::vector<std::unique_ptr<RegularizationTerm<TestType>>> regTerms;
368 4 : auto weight = static_cast<TestType>(0.5);
369 4 : regTerms.push_back(
370 4 : std::make_unique<RegularizationTerm<TestType>>(weight, L2NormPow2<TestType>{desc}));
371 4 : regTerms.push_back(std::make_unique<RegularizationTerm<TestType>>(
372 4 : weight, L2NormPow2<TestType>{LinearResidual{bReg}}));
373 4 : regTerms.push_back(std::make_unique<RegularizationTerm<TestType>>(
374 4 : weight, L2NormPow2<TestType>{LinearResidual{AReg}}));
375 4 : regTerms.push_back(std::make_unique<RegularizationTerm<TestType>>(
376 4 : weight, L2NormPow2<TestType>{LinearResidual{AReg, bReg}}));
377 4 : regTerms.push_back(std::make_unique<RegularizationTerm<TestType>>(
378 4 : weight, WeightedL2NormPow2{LinearResidual{AReg, bReg}, isoWReg}));
379 4 : regTerms.push_back(std::make_unique<RegularizationTerm<TestType>>(
380 4 : weight, WeightedL2NormPow2{LinearResidual{AReg, bReg}, nonIsoWReg}));
381 :
382 4 : std::array descriptions = {"has no operator and no vector",
383 4 : "has no operator, but has a vector",
384 4 : "has an operator, but no vector",
385 4 : "has an operator and a vector",
386 4 : "has an operator and a vector, and is weighted (isotropic)",
387 4 : "has an operator and a vector, and is weighted (nonisotropic)"};
388 :
389 28 : for (std::size_t i = 0; i < descriptions.size(); i++) {
390 168 : for (std::size_t j = 0; j < descriptions.size(); j++) {
391 144 : WHEN("Trying different settings")
392 144 : {
393 2 : INFO("The data term ", descriptions[i]);
394 2 : INFO("The regularization term ", descriptions[j]);
395 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> xVec =
396 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343);
397 2 : DataContainer<TestType> x{desc, xVec};
398 2 : Problem prob{*dataTerms[i], *regTerms[j], x};
399 :
400 2 : THEN("the problem can be converted and all operations yield the same result as "
401 2 : "for the initial problem")
402 2 : {
403 2 : WLSProblem<TestType> converted{prob};
404 2 : REQUIRE_UNARY(checkApproxEq(prob.evaluate(), converted.evaluate()));
405 :
406 2 : auto gradDiff = prob.getGradient();
407 2 : gradDiff -= converted.getGradient();
408 2 : REQUIRE_UNARY(checkApproxEq(gradDiff.squaredL2Norm(), 0));
409 2 : }
410 2 : }
411 144 : }
412 24 : }
413 4 : }
414 :
415 18 : GIVEN("a TikhonovProblem with L2 regularization")
416 18 : {
417 2 : VolumeDescriptor desc{IndexVector_t::Constant(1, 343)};
418 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1> vec =
419 2 : Eigen::Matrix<TestType, Eigen::Dynamic, 1>::Random(343);
420 2 : DataContainer<TestType> b{desc, vec};
421 :
422 2 : Scaling<TestType> A{desc, static_cast<TestType>(2.0)};
423 2 : WLSProblem<TestType> prob{A, b};
424 :
425 2 : TestType regWeight = 4.0;
426 2 : TikhonovProblem<TestType> l2Reg{prob,
427 2 : RegularizationTerm{regWeight, L2NormPow2<TestType>{desc}}};
428 :
429 2 : THEN("the problem can be converted into a block form WLSProblem")
430 2 : {
431 2 : WLSProblem<TestType> conv{l2Reg};
432 :
433 2 : Scaling<TestType> lambdaScaling(desc, std::sqrt(regWeight));
434 2 : std::vector<std::unique_ptr<LinearOperator<TestType>>> opList(0);
435 2 : opList.push_back(A.clone());
436 2 : opList.push_back(lambdaScaling.clone());
437 2 : BlockLinearOperator<TestType> blockOp{opList,
438 2 : BlockLinearOperator<TestType>::BlockType::ROW};
439 :
440 2 : std::vector<std::unique_ptr<DataDescriptor>> descList(0);
441 2 : descList.push_back(desc.clone());
442 2 : descList.push_back(desc.clone());
443 2 : RandomBlocksDescriptor vecDesc{descList};
444 2 : DataContainer<TestType> blockVec{vecDesc};
445 2 : blockVec.getBlock(0) = b;
446 2 : blockVec.getBlock(1) = 0;
447 :
448 2 : L2NormPow2<TestType> blockWls{LinearResidual<TestType>{blockOp, blockVec}};
449 2 : REQUIRE_EQ(conv.getDataTerm(), blockWls);
450 2 : REQUIRE_UNARY(conv.getRegularizationTerms().empty());
451 2 : }
452 2 : }
453 18 : }
454 :
455 : TEST_SUITE_END();
|