Line data Source code
1 : /**
2 : * @file test_LinearResidual.cpp
3 : *
4 : * @brief Tests for LinearResidual class
5 : *
6 : * @author Matthias Wieczorek - main code
7 : * @author Tobias Lasser - rewrite
8 : */
9 :
10 : #include <doctest/doctest.h>
11 :
12 : #include "testHelpers.h"
13 : #include "LinearResidual.h"
14 : #include "Identity.h"
15 : #include "VolumeDescriptor.h"
16 :
17 : using namespace elsa;
18 : using namespace doctest;
19 :
20 : // mock operator, which outputs 1 for apply and 3 for applyAdjoint
21 : template <typename data_t = real_t>
22 : class MockOperator : public LinearOperator<data_t>
23 : {
24 : public:
25 72 : MockOperator(const DataDescriptor& domain, const DataDescriptor& range)
26 72 : : LinearOperator<data_t>(domain, range)
27 : {
28 72 : }
29 :
30 : protected:
31 16 : void applyImpl([[maybe_unused]] const DataContainer<data_t>& x,
32 : DataContainer<data_t>& Ax) const override
33 : {
34 16 : Ax = 1;
35 16 : }
36 :
37 0 : void applyAdjointImpl([[maybe_unused]] const DataContainer<data_t>& y,
38 : DataContainer<data_t>& Aty) const override
39 : {
40 0 : Aty = 3;
41 0 : }
42 :
43 : protected:
44 48 : MockOperator<data_t>* cloneImpl() const override
45 : {
46 48 : return new MockOperator<data_t>(this->getDomainDescriptor(), this->getRangeDescriptor());
47 : }
48 : };
49 :
50 64 : TYPE_TO_STRING(complex<float>);
51 64 : TYPE_TO_STRING(complex<double>);
52 :
53 : TEST_SUITE_BEGIN("functionals");
54 :
55 92 : TEST_CASE_TEMPLATE("LinearResidual: Testing trivial linear residual", TestType, float, double,
56 : complex<float>, complex<double>)
57 : {
58 24 : GIVEN("a descriptor")
59 : {
60 24 : IndexVector_t numCoeff(3);
61 12 : numCoeff << 11, 33, 55;
62 24 : VolumeDescriptor dd(numCoeff);
63 :
64 24 : WHEN("instantiating")
65 : {
66 24 : LinearResidual<TestType> linRes(dd);
67 :
68 16 : THEN("the residual is as expected")
69 : {
70 4 : REQUIRE_EQ(linRes.getDomainDescriptor(), dd);
71 4 : REQUIRE_EQ(linRes.getRangeDescriptor(), dd);
72 :
73 4 : REQUIRE_UNARY_FALSE(linRes.hasOperator());
74 4 : REQUIRE_UNARY_FALSE(linRes.hasDataVector());
75 :
76 8 : REQUIRE_THROWS_AS(linRes.getOperator(), Error);
77 8 : REQUIRE_THROWS_AS(linRes.getDataVector(), Error);
78 : }
79 :
80 16 : THEN("a clone behaves as expected")
81 : {
82 8 : auto linResClone = linRes.clone();
83 :
84 4 : REQUIRE_NE(linResClone.get(), &linRes);
85 4 : REQUIRE_EQ(*linResClone, linRes);
86 : }
87 :
88 16 : THEN("the Jacobian and evaluate work as expected")
89 : {
90 8 : Identity<TestType> idOp(dd);
91 4 : DataContainer<TestType> dcX(dd);
92 4 : dcX = 1;
93 :
94 4 : REQUIRE_EQ(linRes.getJacobian(dcX), leaf(idOp));
95 4 : REQUIRE_UNARY(isApprox(linRes.evaluate(dcX), dcX));
96 : }
97 : }
98 : }
99 12 : }
100 :
101 92 : TEST_CASE_TEMPLATE("LinearResidual: Testing with just an data vector", TestType, float, double,
102 : complex<float>, complex<double>)
103 : {
104 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
105 :
106 24 : GIVEN("a descriptor and data")
107 : {
108 24 : IndexVector_t numCoeff(2);
109 12 : numCoeff << 18, 36;
110 24 : VolumeDescriptor dd(numCoeff);
111 :
112 24 : Vector randomData(dd.getNumberOfCoefficients());
113 12 : randomData.setRandom();
114 24 : DataContainer<TestType> dc(dd, randomData);
115 :
116 24 : WHEN("instantiating")
117 : {
118 24 : LinearResidual<TestType> linRes(dc);
119 :
120 16 : THEN("the residual is as expected")
121 : {
122 4 : REQUIRE_EQ(linRes.getDomainDescriptor(), dd);
123 4 : REQUIRE_EQ(linRes.getRangeDescriptor(), dd);
124 :
125 4 : REQUIRE_UNARY_FALSE(linRes.hasOperator());
126 4 : REQUIRE_UNARY(linRes.hasDataVector());
127 :
128 4 : REQUIRE_UNARY(isApprox(linRes.getDataVector(), dc));
129 8 : REQUIRE_THROWS_AS(linRes.getOperator(), Error);
130 : }
131 :
132 16 : THEN("a clone behaves as expected")
133 : {
134 8 : auto linResClone = linRes.clone();
135 :
136 4 : REQUIRE_NE(linResClone.get(), &linRes);
137 4 : REQUIRE_EQ(*linResClone, linRes);
138 : }
139 :
140 16 : THEN("the Jacobian and evaluate work as expected")
141 : {
142 8 : Identity<TestType> idOp(dd);
143 8 : DataContainer<TestType> dcX(dd);
144 4 : dcX = 1;
145 :
146 4 : REQUIRE_EQ(linRes.getJacobian(dcX), leaf(idOp));
147 :
148 4 : DataContainer<TestType> tmp = dcX - dc;
149 4 : REQUIRE_UNARY(isApprox(linRes.evaluate(dcX), tmp));
150 : }
151 : }
152 : }
153 12 : }
154 :
155 92 : TEST_CASE_TEMPLATE("LinearResidual: Testing with just an operator", TestType, float, double,
156 : complex<float>, complex<double>)
157 : {
158 24 : GIVEN("descriptors and an operator")
159 : {
160 24 : IndexVector_t numCoeff(3);
161 12 : numCoeff << 11, 33, 55;
162 24 : VolumeDescriptor ddDomain(numCoeff);
163 :
164 24 : IndexVector_t numCoeff2(2);
165 12 : numCoeff2 << 18, 36;
166 24 : VolumeDescriptor ddRange(numCoeff2);
167 :
168 24 : MockOperator<TestType> mockOp(ddDomain, ddRange);
169 :
170 24 : WHEN("instantiating")
171 : {
172 24 : LinearResidual<TestType> linRes(mockOp);
173 :
174 16 : THEN("the residual is as expected")
175 : {
176 4 : REQUIRE_EQ(linRes.getDomainDescriptor(), ddDomain);
177 4 : REQUIRE_EQ(linRes.getRangeDescriptor(), ddRange);
178 :
179 4 : REQUIRE_UNARY(linRes.hasOperator());
180 4 : REQUIRE_UNARY_FALSE(linRes.hasDataVector());
181 :
182 4 : REQUIRE_EQ(linRes.getOperator(), mockOp);
183 8 : REQUIRE_THROWS_AS(linRes.getDataVector(), Error);
184 : }
185 :
186 16 : THEN("a clone behaves as expected")
187 : {
188 8 : auto linResClone = linRes.clone();
189 :
190 4 : REQUIRE_NE(linResClone.get(), &linRes);
191 4 : REQUIRE_EQ(*linResClone, linRes);
192 : }
193 :
194 16 : THEN("the Jacobian and evaluate work as expected")
195 : {
196 4 : DataContainer<TestType> dcX(ddDomain);
197 4 : dcX = 1;
198 :
199 4 : REQUIRE_EQ(linRes.getJacobian(dcX), leaf(mockOp));
200 4 : REQUIRE_UNARY(isApprox(linRes.evaluate(dcX), mockOp.apply(dcX)));
201 : }
202 : }
203 : }
204 12 : }
205 :
206 92 : TEST_CASE_TEMPLATE("LinearResidual: Testing with operator and data", TestType, float, double,
207 : complex<float>, complex<double>)
208 : {
209 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
210 :
211 24 : GIVEN("an operator and data")
212 : {
213 24 : IndexVector_t numCoeff(3);
214 12 : numCoeff << 11, 33, 55;
215 24 : VolumeDescriptor ddDomain(numCoeff);
216 24 : IndexVector_t numCoeff2(2);
217 12 : numCoeff2 << 18, 36;
218 24 : VolumeDescriptor ddRange(numCoeff2);
219 :
220 24 : MockOperator<TestType> mockOp(ddDomain, ddRange);
221 :
222 24 : Vector randomData(ddRange.getNumberOfCoefficients());
223 12 : randomData.setRandom();
224 24 : DataContainer<TestType> dc(ddRange, randomData);
225 :
226 24 : WHEN("instantiating")
227 : {
228 24 : LinearResidual<TestType> linRes(mockOp, dc);
229 :
230 16 : THEN("the residual is as expected")
231 : {
232 4 : REQUIRE_EQ(linRes.getDomainDescriptor(), ddDomain);
233 4 : REQUIRE_EQ(linRes.getRangeDescriptor(), ddRange);
234 :
235 4 : REQUIRE_UNARY(linRes.hasOperator());
236 4 : REQUIRE_UNARY(linRes.hasDataVector());
237 :
238 4 : REQUIRE_EQ(linRes.getOperator(), mockOp);
239 4 : REQUIRE_UNARY(isApprox(linRes.getDataVector(), dc));
240 : }
241 :
242 16 : THEN("a clone behaves as expected")
243 : {
244 8 : auto linResClone = linRes.clone();
245 :
246 4 : REQUIRE_NE(linResClone.get(), &linRes);
247 4 : REQUIRE_EQ(*linResClone, linRes);
248 : }
249 :
250 16 : THEN("the Jacobian and evaluate work as expected")
251 : {
252 8 : DataContainer<TestType> dcX(ddDomain);
253 4 : dcX = 1;
254 :
255 4 : REQUIRE_EQ(linRes.getJacobian(dcX), leaf(mockOp));
256 :
257 4 : DataContainer<TestType> tmp = mockOp.apply(dcX) - dc;
258 4 : REQUIRE_UNARY(isApprox(linRes.evaluate(dcX), tmp));
259 : }
260 : }
261 : }
262 12 : }
263 :
264 : TEST_SUITE_END();
|