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 : MockOperator(const DataDescriptor& domain, const DataDescriptor& range)
26 : : LinearOperator<data_t>(domain, range)
27 596 : {
28 596 : }
29 :
30 : protected:
31 : void applyImpl([[maybe_unused]] const DataContainer<data_t>& x,
32 : DataContainer<data_t>& Ax) const override
33 56 : {
34 56 : Ax = 1;
35 56 : }
36 :
37 : void applyAdjointImpl([[maybe_unused]] const DataContainer<data_t>& y,
38 : DataContainer<data_t>& Aty) const override
39 40 : {
40 40 : Aty = 3;
41 40 : }
42 :
43 : protected:
44 : MockOperator<data_t>* cloneImpl() const override
45 412 : {
46 412 : return new MockOperator<data_t>(this->getDomainDescriptor(), this->getRangeDescriptor());
47 412 : }
48 : };
49 :
50 : TYPE_TO_STRING(complex<float>);
51 : TYPE_TO_STRING(complex<double>);
52 :
53 : TEST_SUITE_BEGIN("functionals");
54 :
55 : TEST_CASE_TEMPLATE("LinearResidual: Testing trivial linear residual", TestType, float, double,
56 : complex<float>, complex<double>)
57 12 : {
58 12 : GIVEN("a descriptor")
59 12 : {
60 12 : IndexVector_t numCoeff(3);
61 12 : numCoeff << 11, 33, 55;
62 12 : VolumeDescriptor dd(numCoeff);
63 :
64 12 : WHEN("instantiating")
65 12 : {
66 12 : LinearResidual<TestType> linRes(dd);
67 :
68 12 : THEN("the residual is as expected")
69 12 : {
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 4 : REQUIRE_THROWS_AS(linRes.getOperator(), Error);
77 4 : REQUIRE_THROWS_AS(linRes.getDataVector(), Error);
78 4 : }
79 :
80 12 : THEN("a clone behaves as expected")
81 12 : {
82 4 : auto linResClone = linRes.clone();
83 :
84 4 : REQUIRE_NE(linResClone.get(), &linRes);
85 4 : REQUIRE_EQ(*linResClone, linRes);
86 4 : }
87 :
88 12 : THEN("the Jacobian and evaluate work as expected")
89 12 : {
90 4 : 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 4 : }
97 12 : }
98 12 : }
99 12 : }
100 :
101 : TEST_CASE_TEMPLATE("LinearResidual: Testing with just an data vector", TestType, float, double,
102 : complex<float>, complex<double>)
103 12 : {
104 12 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
105 :
106 12 : GIVEN("a descriptor and data")
107 12 : {
108 12 : IndexVector_t numCoeff(2);
109 12 : numCoeff << 18, 36;
110 12 : VolumeDescriptor dd(numCoeff);
111 :
112 12 : Vector randomData(dd.getNumberOfCoefficients());
113 12 : randomData.setRandom();
114 12 : DataContainer<TestType> dc(dd, randomData);
115 :
116 12 : WHEN("instantiating")
117 12 : {
118 12 : LinearResidual<TestType> linRes(dc);
119 :
120 12 : THEN("the residual is as expected")
121 12 : {
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 4 : REQUIRE_THROWS_AS(linRes.getOperator(), Error);
130 4 : }
131 :
132 12 : THEN("a clone behaves as expected")
133 12 : {
134 4 : auto linResClone = linRes.clone();
135 :
136 4 : REQUIRE_NE(linResClone.get(), &linRes);
137 4 : REQUIRE_EQ(*linResClone, linRes);
138 4 : }
139 :
140 12 : THEN("the Jacobian and evaluate work as expected")
141 12 : {
142 4 : Identity<TestType> idOp(dd);
143 4 : 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 4 : }
151 12 : }
152 12 : }
153 12 : }
154 :
155 : TEST_CASE_TEMPLATE("LinearResidual: Testing with just an operator", TestType, float, double,
156 : complex<float>, complex<double>)
157 12 : {
158 12 : GIVEN("descriptors and an operator")
159 12 : {
160 12 : IndexVector_t numCoeff(3);
161 12 : numCoeff << 11, 33, 55;
162 12 : VolumeDescriptor ddDomain(numCoeff);
163 :
164 12 : IndexVector_t numCoeff2(2);
165 12 : numCoeff2 << 18, 36;
166 12 : VolumeDescriptor ddRange(numCoeff2);
167 :
168 12 : MockOperator<TestType> mockOp(ddDomain, ddRange);
169 :
170 12 : WHEN("instantiating")
171 12 : {
172 12 : LinearResidual<TestType> linRes(mockOp);
173 :
174 12 : THEN("the residual is as expected")
175 12 : {
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 4 : REQUIRE_THROWS_AS(linRes.getDataVector(), Error);
184 4 : }
185 :
186 12 : THEN("a clone behaves as expected")
187 12 : {
188 4 : auto linResClone = linRes.clone();
189 :
190 4 : REQUIRE_NE(linResClone.get(), &linRes);
191 4 : REQUIRE_EQ(*linResClone, linRes);
192 4 : }
193 :
194 12 : THEN("the Jacobian and evaluate work as expected")
195 12 : {
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 4 : }
202 12 : }
203 12 : }
204 12 : }
205 :
206 : TEST_CASE_TEMPLATE("LinearResidual: Testing with operator and data", TestType, float, double,
207 : complex<float>, complex<double>)
208 12 : {
209 12 : using Vector = Eigen::Matrix<TestType, Eigen::Dynamic, 1>;
210 :
211 12 : GIVEN("an operator and data")
212 12 : {
213 12 : IndexVector_t numCoeff(3);
214 12 : numCoeff << 11, 33, 55;
215 12 : VolumeDescriptor ddDomain(numCoeff);
216 12 : IndexVector_t numCoeff2(2);
217 12 : numCoeff2 << 18, 36;
218 12 : VolumeDescriptor ddRange(numCoeff2);
219 :
220 12 : MockOperator<TestType> mockOp(ddDomain, ddRange);
221 :
222 12 : Vector randomData(ddRange.getNumberOfCoefficients());
223 12 : randomData.setRandom();
224 12 : DataContainer<TestType> dc(ddRange, randomData);
225 :
226 12 : WHEN("instantiating")
227 12 : {
228 12 : LinearResidual<TestType> linRes(mockOp, dc);
229 :
230 12 : THEN("the residual is as expected")
231 12 : {
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 4 : }
241 :
242 12 : THEN("a clone behaves as expected")
243 12 : {
244 4 : auto linResClone = linRes.clone();
245 :
246 4 : REQUIRE_NE(linResClone.get(), &linRes);
247 4 : REQUIRE_EQ(*linResClone, linRes);
248 4 : }
249 :
250 12 : THEN("the Jacobian and evaluate work as expected")
251 12 : {
252 4 : 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 4 : }
260 12 : }
261 12 : }
262 12 : }
263 :
264 : TEST_SUITE_END();
|