Line data Source code
1 : /**
2 : * @file test_Quadric.cpp
3 : *
4 : * @brief Tests for the Quadric class
5 : *
6 : * @author Matthias Wieczorek - initial code
7 : * @author Maximilian Hornung - modularization
8 : * @author David Frank - rewrite
9 : * @author Tobias Lasser - modernization
10 : */
11 :
12 : #include <doctest/doctest.h>
13 :
14 : #include "testHelpers.h"
15 : #include "Quadric.h"
16 : #include "Identity.h"
17 : #include "Scaling.h"
18 : #include "VolumeDescriptor.h"
19 : #include "TypeCasts.hpp"
20 :
21 : using namespace elsa;
22 : using namespace doctest;
23 :
24 4 : TYPE_TO_STRING(complex<float>);
25 4 : TYPE_TO_STRING(complex<double>);
26 :
27 : TEST_SUITE_BEGIN("functionals");
28 :
29 68 : TEST_CASE_TEMPLATE("Quadric: Testing without residual", TestType, float, double, complex<float>,
30 : complex<double>)
31 : {
32 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
33 :
34 60 : GIVEN("no operator and no data")
35 : {
36 24 : IndexVector_t numCoeff(3);
37 12 : numCoeff << 13, 11, 7;
38 24 : VolumeDescriptor dd(numCoeff);
39 :
40 24 : WHEN("instantiating")
41 : {
42 24 : Quadric<TestType> func(dd);
43 :
44 16 : THEN("the functional is as expected")
45 : {
46 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
47 :
48 4 : auto* linRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
49 4 : REQUIRE_UNARY(linRes);
50 4 : REQUIRE_UNARY_FALSE(linRes->hasDataVector());
51 4 : REQUIRE_UNARY_FALSE(linRes->hasOperator());
52 :
53 4 : const auto& gradExpr = func.getGradientExpression();
54 4 : REQUIRE_UNARY_FALSE(gradExpr.hasDataVector());
55 4 : REQUIRE_UNARY_FALSE(gradExpr.hasOperator());
56 : }
57 :
58 16 : THEN("a clone behaves as expected")
59 : {
60 8 : auto qClone = func.clone();
61 :
62 4 : REQUIRE_NE(qClone.get(), &func);
63 4 : REQUIRE_EQ(*qClone, func);
64 : }
65 :
66 16 : THEN("the evaluate, gradient and Hessian work as expected")
67 : {
68 8 : Vector dataVec(dd.getNumberOfCoefficients());
69 4 : dataVec.setRandom();
70 4 : DataContainer<TestType> x(dd, dataVec);
71 :
72 4 : TestType trueValue = static_cast<TestType>(0.5) * x.squaredL2Norm();
73 4 : REQUIRE_UNARY(checkApproxEq(func.evaluate(x), trueValue));
74 4 : REQUIRE_UNARY(isApprox(func.getGradient(x), x));
75 4 : REQUIRE_EQ(func.getHessian(x), leaf(Identity<TestType>(dd)));
76 : }
77 : }
78 : }
79 :
80 60 : GIVEN("an operator but no data")
81 : {
82 24 : IndexVector_t numCoeff(3);
83 12 : numCoeff << 13, 11, 7;
84 24 : VolumeDescriptor dd(numCoeff);
85 :
86 24 : Scaling scalingOp(dd, static_cast<TestType>(3.0));
87 :
88 24 : WHEN("instantiating")
89 : {
90 24 : Quadric<TestType> func(scalingOp);
91 :
92 16 : THEN("the functional is as expected")
93 : {
94 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
95 :
96 4 : auto* linRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
97 4 : REQUIRE_UNARY(linRes);
98 4 : REQUIRE_UNARY_FALSE(linRes->hasDataVector());
99 4 : REQUIRE_UNARY_FALSE(linRes->hasOperator());
100 :
101 4 : const auto& gradExpr = func.getGradientExpression();
102 4 : REQUIRE_UNARY_FALSE(gradExpr.hasDataVector());
103 4 : REQUIRE_EQ(gradExpr.getOperator(), scalingOp);
104 : }
105 :
106 16 : THEN("a clone behaves as expected")
107 : {
108 8 : auto qClone = func.clone();
109 :
110 4 : REQUIRE_NE(qClone.get(), &func);
111 4 : REQUIRE_EQ(*qClone, func);
112 : }
113 :
114 16 : THEN("the evaluate, gradient and Hessian work as expected")
115 : {
116 8 : Vector dataVec(dd.getNumberOfCoefficients());
117 4 : dataVec.setRandom();
118 4 : DataContainer<TestType> x(dd, dataVec);
119 :
120 2 : TestType trueValue =
121 4 : static_cast<TestType>(0.5) * scalingOp.getScaleFactor() * x.squaredL2Norm();
122 4 : REQUIRE_UNARY(checkApproxEq(func.evaluate(x), trueValue));
123 4 : REQUIRE_EQ(func.getGradient(x), scalingOp.getScaleFactor() * x);
124 4 : REQUIRE_EQ(func.getHessian(x), leaf(scalingOp));
125 : }
126 : }
127 : }
128 :
129 60 : GIVEN("data but no operator")
130 : {
131 24 : IndexVector_t numCoeff(3);
132 12 : numCoeff << 13, 11, 7;
133 24 : VolumeDescriptor dd(numCoeff);
134 :
135 24 : Vector randomData(dd.getNumberOfCoefficients());
136 12 : randomData.setRandom();
137 24 : DataContainer<TestType> dc(dd, randomData);
138 :
139 24 : WHEN("instantiating")
140 : {
141 24 : Quadric<TestType> func(dc);
142 :
143 16 : THEN("the functional is as expected")
144 : {
145 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
146 :
147 4 : auto* linRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
148 4 : REQUIRE_UNARY(linRes);
149 4 : REQUIRE_UNARY_FALSE(linRes->hasDataVector());
150 4 : REQUIRE_UNARY_FALSE(linRes->hasOperator());
151 :
152 4 : const auto& gradExpr = func.getGradientExpression();
153 4 : REQUIRE_EQ(gradExpr.getDataVector(), dc);
154 4 : REQUIRE_UNARY_FALSE(gradExpr.hasOperator());
155 : }
156 :
157 16 : THEN("a clone behaves as expected")
158 : {
159 8 : auto qClone = func.clone();
160 :
161 4 : REQUIRE_NE(qClone.get(), &func);
162 4 : REQUIRE_EQ(*qClone, func);
163 : }
164 :
165 16 : THEN("the evaluate, gradient and Hessian work as expected")
166 : {
167 8 : Vector dataVec(dd.getNumberOfCoefficients());
168 4 : dataVec.setRandom();
169 4 : DataContainer<TestType> x(dd, dataVec);
170 :
171 4 : TestType trueValue = static_cast<TestType>(0.5) * x.squaredL2Norm() - x.dot(dc);
172 4 : REQUIRE_UNARY(checkApproxEq(func.evaluate(x), trueValue));
173 4 : REQUIRE_EQ(func.getGradient(x), x - dc);
174 4 : REQUIRE_EQ(func.getHessian(x), leaf(Identity<TestType>(dd)));
175 : }
176 : }
177 : }
178 :
179 60 : GIVEN("an operator and data")
180 : {
181 24 : IndexVector_t numCoeff(3);
182 12 : numCoeff << 13, 11, 7;
183 24 : VolumeDescriptor dd(numCoeff);
184 :
185 24 : Identity<TestType> idOp(dd);
186 :
187 24 : Vector randomData(dd.getNumberOfCoefficients());
188 12 : randomData.setRandom();
189 24 : DataContainer<TestType> dc(dd, randomData);
190 :
191 24 : WHEN("instantiating")
192 : {
193 24 : Quadric<TestType> func(idOp, dc);
194 :
195 16 : THEN("the functional is as expected")
196 : {
197 4 : REQUIRE_EQ(func.getDomainDescriptor(), dd);
198 :
199 4 : auto* linRes = downcast_safe<LinearResidual<TestType>>(&func.getResidual());
200 4 : REQUIRE_UNARY(linRes);
201 4 : REQUIRE_UNARY_FALSE(linRes->hasDataVector());
202 4 : REQUIRE_UNARY_FALSE(linRes->hasOperator());
203 :
204 4 : const auto& gradExpr = func.getGradientExpression();
205 4 : REQUIRE(isApprox(gradExpr.getDataVector(), dc));
206 4 : REQUIRE_EQ(gradExpr.getOperator(), idOp);
207 : }
208 :
209 16 : THEN("a clone behaves as expected")
210 : {
211 8 : auto qClone = func.clone();
212 :
213 4 : REQUIRE_NE(qClone.get(), &func);
214 4 : REQUIRE_EQ(*qClone, func);
215 : }
216 :
217 16 : THEN("the evaluate, gradient and Hessian work as expected")
218 : {
219 8 : Vector dataVec(dd.getNumberOfCoefficients());
220 4 : dataVec.setRandom();
221 8 : DataContainer<TestType> x(dd, dataVec);
222 :
223 4 : TestType trueValue = static_cast<TestType>(0.5) * x.dot(x) - x.dot(dc);
224 4 : REQUIRE_UNARY(checkApproxEq(func.evaluate(x), trueValue));
225 4 : DataContainer<TestType> grad(dd, (dataVec - randomData).eval());
226 4 : REQUIRE_EQ(func.getGradient(x), grad);
227 4 : REQUIRE_EQ(func.getHessian(x), leaf(idOp));
228 : }
229 : }
230 : }
231 48 : }
232 :
233 : TEST_SUITE_END();
|