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 : TYPE_TO_STRING(complex<float>);
25 : TYPE_TO_STRING(complex<double>);
26 :
27 : TEST_SUITE_BEGIN("functionals");
28 :
29 : TEST_CASE_TEMPLATE("Quadric: Testing without residual", TestType, float, double, complex<float>,
30 : complex<double>)
31 48 : {
32 48 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
33 :
34 48 : GIVEN("no operator and no data")
35 48 : {
36 12 : IndexVector_t numCoeff(3);
37 12 : numCoeff << 13, 11, 7;
38 12 : VolumeDescriptor dd(numCoeff);
39 :
40 12 : WHEN("instantiating")
41 12 : {
42 12 : Quadric<TestType> func(dd);
43 :
44 12 : THEN("the functional is as expected")
45 12 : {
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 4 : }
57 :
58 12 : THEN("a clone behaves as expected")
59 12 : {
60 4 : auto qClone = func.clone();
61 :
62 4 : REQUIRE_NE(qClone.get(), &func);
63 4 : REQUIRE_EQ(*qClone, func);
64 4 : }
65 :
66 12 : THEN("the evaluate, gradient and Hessian work as expected")
67 12 : {
68 4 : 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 4 : }
77 12 : }
78 12 : }
79 :
80 48 : GIVEN("an operator but no data")
81 48 : {
82 12 : IndexVector_t numCoeff(3);
83 12 : numCoeff << 13, 11, 7;
84 12 : VolumeDescriptor dd(numCoeff);
85 :
86 12 : Scaling scalingOp(dd, static_cast<TestType>(3.0));
87 :
88 12 : WHEN("instantiating")
89 12 : {
90 12 : Quadric<TestType> func(scalingOp);
91 :
92 12 : THEN("the functional is as expected")
93 12 : {
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 4 : }
105 :
106 12 : THEN("a clone behaves as expected")
107 12 : {
108 4 : auto qClone = func.clone();
109 :
110 4 : REQUIRE_NE(qClone.get(), &func);
111 4 : REQUIRE_EQ(*qClone, func);
112 4 : }
113 :
114 12 : THEN("the evaluate, gradient and Hessian work as expected")
115 12 : {
116 4 : Vector dataVec(dd.getNumberOfCoefficients());
117 4 : dataVec.setRandom();
118 4 : DataContainer<TestType> x(dd, dataVec);
119 :
120 4 : 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 4 : }
126 12 : }
127 12 : }
128 :
129 48 : GIVEN("data but no operator")
130 48 : {
131 12 : IndexVector_t numCoeff(3);
132 12 : numCoeff << 13, 11, 7;
133 12 : VolumeDescriptor dd(numCoeff);
134 :
135 12 : Vector randomData(dd.getNumberOfCoefficients());
136 12 : randomData.setRandom();
137 12 : DataContainer<TestType> dc(dd, randomData);
138 :
139 12 : WHEN("instantiating")
140 12 : {
141 12 : Quadric<TestType> func(dc);
142 :
143 12 : THEN("the functional is as expected")
144 12 : {
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 4 : }
156 :
157 12 : THEN("a clone behaves as expected")
158 12 : {
159 4 : auto qClone = func.clone();
160 :
161 4 : REQUIRE_NE(qClone.get(), &func);
162 4 : REQUIRE_EQ(*qClone, func);
163 4 : }
164 :
165 12 : THEN("the evaluate, gradient and Hessian work as expected")
166 12 : {
167 4 : 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 4 : }
176 12 : }
177 12 : }
178 :
179 48 : GIVEN("an operator and data")
180 48 : {
181 12 : IndexVector_t numCoeff(3);
182 12 : numCoeff << 13, 11, 7;
183 12 : VolumeDescriptor dd(numCoeff);
184 :
185 12 : Identity<TestType> idOp(dd);
186 :
187 12 : Vector randomData(dd.getNumberOfCoefficients());
188 12 : randomData.setRandom();
189 12 : DataContainer<TestType> dc(dd, randomData);
190 :
191 12 : WHEN("instantiating")
192 12 : {
193 12 : Quadric<TestType> func(idOp, dc);
194 :
195 12 : THEN("the functional is as expected")
196 12 : {
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 4 : }
208 :
209 12 : THEN("a clone behaves as expected")
210 12 : {
211 4 : auto qClone = func.clone();
212 :
213 4 : REQUIRE_NE(qClone.get(), &func);
214 4 : REQUIRE_EQ(*qClone, func);
215 4 : }
216 :
217 12 : THEN("the evaluate, gradient and Hessian work as expected")
218 12 : {
219 4 : Vector dataVec(dd.getNumberOfCoefficients());
220 4 : dataVec.setRandom();
221 4 : 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 4 : }
229 12 : }
230 12 : }
231 48 : }
232 :
233 : TEST_SUITE_END();
|