Line data Source code
1 : /**
2 : * @file test_WeightedL2NormPow2.cpp
3 : *
4 : * @brief Tests for the WeightedL2NormPow2 class
5 : *
6 : * @author Matthias Wieczorek - initial code
7 : * @author David Frank - rewrite
8 : * @author Tobias Lasser - modernization
9 : */
10 :
11 : #include <doctest/doctest.h>
12 :
13 : #include "testHelpers.h"
14 : #include "WeightedL2NormPow2.h"
15 : #include "LinearResidual.h"
16 : #include "Identity.h"
17 : #include "VolumeDescriptor.h"
18 : #include "TypeCasts.hpp"
19 :
20 : using namespace elsa;
21 : using namespace doctest;
22 :
23 16 : TYPE_TO_STRING(complex<float>);
24 16 : TYPE_TO_STRING(complex<double>);
25 :
26 : TEST_SUITE_BEGIN("functionals");
27 :
28 60 : TEST_CASE_TEMPLATE("WeightedL2NormPow2: Testing without residual", TestType, float, double,
29 : complex<float>, complex<double>)
30 : {
31 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
32 : using Scalar = GetFloatingPointType_t<TestType>;
33 :
34 40 : GIVEN("just data (no residual)")
35 : {
36 40 : IndexVector_t numCoeff(2);
37 20 : numCoeff << 7, 17;
38 40 : VolumeDescriptor dd(numCoeff);
39 :
40 40 : Vector scalingData(dd.getNumberOfCoefficients());
41 20 : scalingData.setRandom();
42 40 : DataContainer<TestType> scaleFactors(dd, scalingData);
43 :
44 40 : Scaling<TestType> scalingOp(dd, scaleFactors);
45 :
46 40 : WHEN("instantiating")
47 : {
48 40 : WeightedL2NormPow2<TestType> func(scalingOp);
49 :
50 24 : THEN("the functional is as expected")
51 : {
52 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
53 4 : REQUIRE_EQ(func.getWeightingOperator(), scalingOp);
54 :
55 4 : auto* linRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
56 4 : REQUIRE_UNARY(linRes);
57 4 : REQUIRE_UNARY_FALSE(linRes->hasOperator());
58 4 : REQUIRE_UNARY_FALSE(linRes->hasDataVector());
59 : }
60 :
61 24 : THEN("a clone behaves as expected")
62 : {
63 8 : auto wl2Clone = func.clone();
64 :
65 4 : REQUIRE_NE(wl2Clone.get(), &func);
66 4 : REQUIRE_EQ(*wl2Clone, func);
67 : }
68 :
69 40 : Vector dataVec(dd.getNumberOfCoefficients());
70 20 : dataVec.setRandom();
71 40 : DataContainer<TestType> x(dd, dataVec);
72 :
73 40 : Vector Wx = scalingData.array() * dataVec.array();
74 :
75 22 : THEN("the evaluate works as expected")
76 : {
77 : // TODO: with complex numbers this for some reason doesn't work, the result is
78 : // always the negation of the expected
79 : if constexpr (std::is_floating_point_v<TestType>)
80 2 : REQUIRE_UNARY(checkApproxEq(func.evaluate(x),
81 : static_cast<Scalar>(0.5) * Wx.dot(dataVec)));
82 : }
83 :
84 24 : THEN("the gradient works as expected")
85 : {
86 4 : DataContainer<TestType> dcWx(dd, Wx);
87 4 : REQUIRE_UNARY(isApprox(func.getGradient(x), dcWx));
88 : }
89 :
90 24 : THEN("the Hessian works as expected")
91 : {
92 4 : REQUIRE_EQ(func.getHessian(x), leaf(scalingOp));
93 : }
94 : }
95 : }
96 20 : }
97 :
98 60 : TEST_CASE_TEMPLATE("WeightedL2NormPow2: Testing with residual", TestType, float, double,
99 : complex<float>, complex<double>)
100 : {
101 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
102 : using Scalar = GetFloatingPointType_t<TestType>;
103 :
104 40 : GIVEN("a residual with data")
105 : {
106 : // linear residual
107 40 : IndexVector_t numCoeff(2);
108 20 : numCoeff << 47, 11;
109 40 : VolumeDescriptor dd(numCoeff);
110 :
111 40 : Vector randomData(dd.getNumberOfCoefficients());
112 20 : randomData.setRandom();
113 40 : DataContainer<TestType> b(dd, randomData);
114 :
115 40 : Identity<TestType> A(dd);
116 :
117 40 : LinearResidual linRes(A, b);
118 :
119 : // scaling operator
120 40 : Vector scalingData(dd.getNumberOfCoefficients());
121 20 : scalingData.setRandom();
122 40 : DataContainer<TestType> scaleFactors(dd, scalingData);
123 :
124 40 : Scaling scalingOp(dd, scaleFactors);
125 :
126 40 : WHEN("instantiating")
127 : {
128 40 : WeightedL2NormPow2 func(linRes, scalingOp);
129 :
130 24 : THEN("the functional is as expected")
131 : {
132 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
133 4 : REQUIRE_EQ(func.getWeightingOperator(), scalingOp);
134 :
135 4 : auto* lRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
136 4 : REQUIRE_UNARY(lRes);
137 4 : REQUIRE_EQ(*lRes, linRes);
138 : }
139 :
140 24 : THEN("a clone behaves as expected")
141 : {
142 8 : auto wl2Clone = func.clone();
143 :
144 4 : REQUIRE_NE(wl2Clone.get(), &func);
145 4 : REQUIRE_EQ(*wl2Clone, func);
146 : }
147 :
148 40 : Vector dataVec(dd.getNumberOfCoefficients());
149 20 : dataVec.setRandom();
150 40 : DataContainer<TestType> x(dd, dataVec);
151 :
152 40 : Vector WRx = scalingData.array() * (dataVec - randomData).array();
153 :
154 22 : THEN("the evaluate works was expected")
155 : {
156 : // TODO: with complex numbers this for some reason doesn't work, the result is
157 : // always the negation of the expected
158 : if constexpr (std::is_floating_point_v<TestType>)
159 2 : REQUIRE_UNARY(
160 : checkApproxEq(func.evaluate(x),
161 : static_cast<Scalar>(0.5) * WRx.dot(dataVec - randomData)));
162 : }
163 :
164 24 : THEN("the gradient works was expected")
165 : {
166 4 : DataContainer<TestType> dcWRx(dd, WRx);
167 4 : REQUIRE_UNARY(isApprox(func.getGradient(x), dcWRx));
168 : }
169 :
170 24 : THEN("the Hessian works was expected")
171 : {
172 8 : auto hessian = func.getHessian(x);
173 8 : Vector Wx = scalingData.array() * dataVec.array();
174 4 : DataContainer<TestType> dcWx(dd, Wx);
175 4 : REQUIRE_UNARY(isApprox(hessian.apply(x), dcWx));
176 : }
177 : }
178 : }
179 20 : }
180 :
181 : TEST_SUITE_END();
|