Line data Source code
1 : /**
2 : * @file test_Dictionary.cpp
3 : *
4 : * @brief Tests for Dictionary class
5 : *
6 : * @author Jonas Buerger - main code
7 : */
8 :
9 : #include "doctest/doctest.h"
10 : #include "Dictionary.h"
11 : #include "IdenticalBlocksDescriptor.h"
12 : #include "VolumeDescriptor.h"
13 : #include "testHelpers.h"
14 :
15 : using namespace elsa;
16 : using namespace doctest;
17 :
18 : TEST_SUITE_BEGIN("core");
19 :
20 : TEST_CASE_TEMPLATE("Constructing a Dictionary operator ", data_t, float, double)
21 12 : {
22 12 : GIVEN("a descriptor for a signal and the number of atoms")
23 12 : {
24 6 : VolumeDescriptor dd({5});
25 6 : index_t nAtoms(10);
26 :
27 6 : WHEN("instantiating an Dictionary operator")
28 6 : {
29 4 : Dictionary dictOp(dd, nAtoms);
30 :
31 4 : THEN("the DataDescriptors are as expected")
32 4 : {
33 2 : VolumeDescriptor representationDescriptor({nAtoms});
34 2 : REQUIRE_EQ(dictOp.getDomainDescriptor(), representationDescriptor);
35 2 : REQUIRE_EQ(dictOp.getRangeDescriptor(), dd);
36 2 : }
37 :
38 4 : AND_THEN("the atoms are normalized")
39 4 : {
40 22 : for (int i = 0; i < dictOp.getNumberOfAtoms(); ++i) {
41 20 : REQUIRE_EQ(dictOp.getAtom(i).l2Norm(), Approx(1));
42 20 : }
43 2 : }
44 4 : }
45 :
46 6 : WHEN("cloning a Dictionary operator")
47 6 : {
48 2 : Dictionary dictOp(dd, nAtoms);
49 2 : auto dictOpClone = dictOp.clone();
50 :
51 2 : THEN("everything matches")
52 2 : {
53 2 : REQUIRE_NE(dictOpClone.get(), &dictOp);
54 2 : REQUIRE_EQ(*dictOpClone, dictOp);
55 2 : }
56 2 : }
57 6 : }
58 :
59 12 : GIVEN("some initial data")
60 12 : {
61 4 : VolumeDescriptor dd({5});
62 4 : index_t nAtoms(10);
63 4 : IdenticalBlocksDescriptor ibd(nAtoms, dd);
64 4 : auto randomDictionary = generateRandomMatrix<data_t>(ibd.getNumberOfCoefficients());
65 4 : DataContainer<data_t> dict(ibd, randomDictionary);
66 :
67 4 : WHEN("instantiating an Dictionary operator")
68 4 : {
69 4 : Dictionary dictOp(dict);
70 :
71 4 : THEN("the DataDescriptors are as expected")
72 4 : {
73 2 : VolumeDescriptor representationDescriptor({nAtoms});
74 2 : REQUIRE_EQ(dictOp.getDomainDescriptor(), representationDescriptor);
75 2 : REQUIRE_EQ(dictOp.getRangeDescriptor(), dd);
76 2 : }
77 :
78 4 : AND_THEN("the atoms are normalized")
79 4 : {
80 22 : for (int i = 0; i < dictOp.getNumberOfAtoms(); ++i) {
81 20 : REQUIRE_EQ(dictOp.getAtom(i).l2Norm(), Approx(1));
82 20 : }
83 2 : }
84 4 : }
85 4 : }
86 :
87 12 : GIVEN("some invalid initial data")
88 12 : {
89 2 : VolumeDescriptor dd({5});
90 2 : DataContainer<data_t> invalidDict(dd);
91 :
92 2 : WHEN("instantiating an Dictionary operator")
93 2 : {
94 2 : THEN("an exception is thrown")
95 2 : {
96 2 : REQUIRE_THROWS_AS(Dictionary{invalidDict}, InvalidArgumentError);
97 2 : }
98 2 : }
99 2 : }
100 12 : }
101 :
102 : TEST_CASE_TEMPLATE("Accessing Dictionary atoms ", data_t, float, double)
103 14 : {
104 14 : GIVEN("some dictionary operator")
105 14 : {
106 14 : VolumeDescriptor dd({5});
107 14 : index_t nAtoms(10);
108 14 : IdenticalBlocksDescriptor ibd(nAtoms, dd);
109 14 : auto randomDictionary = generateRandomMatrix<data_t>(ibd.getNumberOfCoefficients());
110 14 : DataContainer<data_t> dict(ibd, randomDictionary);
111 :
112 : // normalize the atoms beforehand so we can compare
113 154 : for (int i = 0; i < nAtoms; ++i) {
114 140 : auto block = dict.getBlock(i);
115 140 : block /= block.l2Norm();
116 140 : }
117 14 : Dictionary dictOp(dict);
118 :
119 14 : WHEN("accessing an atom")
120 14 : {
121 2 : index_t i = 4;
122 2 : DataContainer<data_t> atom = dictOp.getAtom(i);
123 2 : THEN("the data is correct") { REQUIRE_EQ(atom, dict.getBlock(i)); }
124 2 : }
125 :
126 14 : WHEN("accessing an atom from a const dictionary reference")
127 14 : {
128 2 : index_t i = 4;
129 2 : const Dictionary<data_t>& constDictOp(dictOp);
130 2 : auto atom = constDictOp.getAtom(i);
131 2 : THEN("the data is correct") { REQUIRE_EQ(atom, dict.getBlock(i)); }
132 2 : }
133 :
134 14 : WHEN("accessing an atom with an invalid index")
135 14 : {
136 2 : index_t i = 42;
137 2 : THEN("an exception is thrown")
138 2 : {
139 2 : REQUIRE_THROWS_AS(dictOp.getAtom(i), InvalidArgumentError);
140 2 : }
141 2 : }
142 :
143 14 : WHEN("updating an atom")
144 14 : {
145 2 : index_t i = 4;
146 2 : auto randomData = generateRandomMatrix<data_t>(dd.getNumberOfCoefficients());
147 2 : DataContainer<data_t> newAtom(dd, randomData);
148 :
149 2 : dictOp.updateAtom(i, newAtom);
150 2 : THEN("the data is correct (and normalized)")
151 2 : {
152 2 : REQUIRE_EQ(dictOp.getAtom(i), (newAtom / newAtom.l2Norm()));
153 2 : }
154 2 : }
155 :
156 14 : WHEN("updating an atom with itself")
157 14 : {
158 : // this test makes sure that a normalized atom doesn't get normalized again
159 :
160 2 : index_t i = 4;
161 2 : DataContainer<data_t> newAtom = dictOp.getAtom(i);
162 :
163 2 : dictOp.updateAtom(i, newAtom);
164 2 : THEN("the data is identical") { REQUIRE_EQ(dictOp.getAtom(i), newAtom); }
165 2 : }
166 :
167 14 : WHEN("updating an atom with an invalid index")
168 14 : {
169 2 : index_t i = 42;
170 2 : DataContainer<data_t> dummyAtom(dd);
171 2 : THEN("an exception is thrown")
172 2 : {
173 2 : REQUIRE_THROWS_AS(dictOp.updateAtom(i, dummyAtom), InvalidArgumentError);
174 2 : }
175 2 : }
176 :
177 14 : WHEN("updating an atom with invalid data")
178 14 : {
179 2 : index_t i = 4;
180 2 : VolumeDescriptor invalidDesc({dd.getNumberOfCoefficients() + 1});
181 2 : DataContainer<data_t> invalid(invalidDesc);
182 2 : THEN("an exception is thrown")
183 2 : {
184 2 : REQUIRE_THROWS_AS(dictOp.updateAtom(i, invalid), InvalidArgumentError);
185 2 : }
186 2 : }
187 14 : }
188 14 : }
189 :
190 : TEST_CASE_TEMPLATE("Getting the support of a dictionary ", data_t, float, double)
191 2 : {
192 :
193 2 : GIVEN("some dictionary and a support vector")
194 2 : {
195 2 : VolumeDescriptor dd({5});
196 2 : const index_t nAtoms(10);
197 2 : IdenticalBlocksDescriptor ibd(nAtoms, dd);
198 2 : auto randomDictionary = generateRandomMatrix<data_t>(ibd.getNumberOfCoefficients());
199 2 : DataContainer<data_t> dict(ibd, randomDictionary);
200 2 : Dictionary dictOp(dict);
201 :
202 2 : IndexVector_t support(3);
203 2 : support << 1, 5, 7;
204 :
205 2 : WHEN("getting the support of the dictionary")
206 2 : {
207 2 : auto purgedDict = dictOp.getSupportedDictionary(support);
208 :
209 2 : THEN("the data is correct")
210 2 : {
211 8 : for (index_t i = 0; i < purgedDict.getNumberOfAtoms(); ++i) {
212 6 : REQUIRE_EQ(purgedDict.getAtom(i), dictOp.getAtom(support[i]));
213 6 : }
214 2 : }
215 2 : }
216 2 : }
217 2 : }
218 :
219 : TEST_CASE_TEMPLATE("Using the Dictionary ", data_t, float, double)
220 8 : {
221 :
222 8 : GIVEN("some dictionary")
223 8 : {
224 8 : VolumeDescriptor dd({2});
225 8 : const index_t nAtoms(4);
226 8 : IdenticalBlocksDescriptor ibd(nAtoms, dd);
227 8 : Eigen::Matrix<data_t, Eigen::Dynamic, 1> dictData(ibd.getNumberOfCoefficients());
228 8 : dictData << 1, 2, 3, 4, 5, 6, 7, 8;
229 8 : DataContainer<data_t> dict(ibd, dictData);
230 : /* 1,3,5,7
231 : 2,4,6,8 */
232 8 : Dictionary dictOp(dict);
233 :
234 : // construct a eigen matrix corresponding to the dictionary so we can check the results
235 8 : Eigen::Map<Eigen::Matrix<data_t, Eigen::Dynamic, nAtoms>> matDictData(
236 8 : dictData.data(), dd.getNumberOfCoefficients(), nAtoms);
237 40 : for (index_t i = 0; i < matDictData.cols(); ++i) {
238 32 : matDictData.col(i).normalize();
239 32 : }
240 :
241 8 : WHEN("applying the dictionary to a representation vector")
242 8 : {
243 2 : VolumeDescriptor representationDescriptor({nAtoms});
244 2 : Eigen::Matrix<data_t, Eigen::Dynamic, 1> inputData(
245 2 : representationDescriptor.getNumberOfCoefficients());
246 2 : inputData << 2, 3, 4, 5;
247 2 : DataContainer input(representationDescriptor, inputData);
248 2 : auto output = dictOp.apply(input);
249 :
250 2 : THEN("the result is as expected")
251 2 : {
252 2 : Eigen::Matrix<data_t, Eigen::Dynamic, 1> expected(dd.getNumberOfCoefficients());
253 2 : expected = matDictData * inputData;
254 2 : DataContainer expectedOutput(dd, expected);
255 2 : REQUIRE_UNARY(isApprox(expectedOutput, output));
256 2 : }
257 2 : }
258 :
259 8 : WHEN("applying the adjoint dictionary to a matching vector")
260 8 : {
261 2 : Eigen::Matrix<data_t, Eigen::Dynamic, 1> inputData(dd.getNumberOfCoefficients());
262 2 : inputData << 2, 3;
263 2 : DataContainer input(dd, inputData);
264 2 : auto output = dictOp.applyAdjoint(input);
265 :
266 2 : THEN("the result is as expected")
267 2 : {
268 2 : VolumeDescriptor expectedDescriptor({nAtoms});
269 2 : Eigen::Matrix<data_t, Eigen::Dynamic, 1> expected(
270 2 : expectedDescriptor.getNumberOfCoefficients());
271 2 : expected = matDictData.transpose() * inputData;
272 2 : DataContainer expectedOutput(expectedDescriptor, expected);
273 2 : REQUIRE_UNARY(isApprox(expectedOutput, output));
274 2 : }
275 2 : }
276 :
277 8 : WHEN("applying the dictionary to a non-matching vector")
278 8 : {
279 2 : VolumeDescriptor invalidDesc({nAtoms + 1});
280 2 : DataContainer<data_t> invalid(invalidDesc);
281 2 : THEN("an exception is thrown")
282 2 : {
283 2 : REQUIRE_THROWS_AS(dictOp.apply(invalid), InvalidArgumentError);
284 2 : }
285 2 : }
286 :
287 8 : WHEN("applying the adjoint dictionary to a non-matching vector")
288 8 : {
289 2 : VolumeDescriptor invalidDesc({dd.getNumberOfCoefficients() + 1});
290 2 : DataContainer<data_t> invalid(invalidDesc);
291 2 : THEN("an exception is thrown")
292 2 : {
293 2 : REQUIRE_THROWS_AS(dictOp.applyAdjoint(invalid), InvalidArgumentError);
294 2 : }
295 2 : }
296 8 : }
297 8 : }
298 : TEST_SUITE_END();
|