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