All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PointIsolationAlg_test.cc
Go to the documentation of this file.
1 /**
2  * @file PointIsolationAlg_test.cc
3  * @brief Unit tests for PointIsolationAlg
4  * @author Gianluca Petrillo (petrillo@fnal.gov)
5  * @date May 27, 2016
6  * @see PointIsolationAlg.h
7  * @ingroup RemoveIsolatedSpacePoints
8  *
9  * This test sets up point distributions with known isolation features,
10  * runs the algorithm with various isolation radius settings and verifies that
11  * the results are as expected.
12  *
13  * The test is run with no arguments.
14  *
15  * Two tests are run:
16  *
17  * * `PointIsolationTest1`: low multiplicity unit tests
18  * * `PointIsolationTest2`: larger scale test
19  *
20  * See the documentation of the two functions for more information.
21  *
22  */
23 
24 // LArSoft libraries
26 #include "cetlib/pow.h"
27 
28 // Boost libraries
29 #define BOOST_TEST_MODULE ( PointIsolationAlg_test )
30 #include "boost/test/unit_test.hpp"
31 
32 // C/C++ standard libraries
33 #include <array>
34 #include <algorithm> // std::sort()
35 #include <numeric> // std::iota()
36 
37 
38 // BEGIN RemoveIsolatedSpacePoints group ---------------------------------------
39 /// @ingroup RemoveIsolatedSpacePoints
40 /// @{
41 //------------------------------------------------------------------------------
42 //--- Test code
43 //---
44 /**
45  * @brief Low-multiplicity unit test
46  *
47  * This tests exercises simple point dispositions: a single point, two points,
48  * three points at different distance, and two "pairs" of points.
49  * The isolation radius of the test is fixed.
50  *
51  * This test uses coordinate type `float`.
52  *
53  */
55 
56  using Coord_t = float;
57  using PointIsolationAlg_t = lar::example::PointIsolationAlg<Coord_t>;
58 
59  using Point_t = std::array<Coord_t, 3U>;
60 
61  PointIsolationAlg_t::Configuration_t config;
62  config.radius2 = cet::square(1.);
63  config.rangeX = { -2., +2. };
64  config.rangeY = { -2., +2. };
65  config.rangeZ = { -2., +2. };
66 
67  std::vector<Point_t> points;
68  std::vector<size_t> result, expected;
69 
70  PointIsolationAlg_t algo(config);
71 
72  // first test: a single point
73  points.push_back({{ +1., +1., +1. }});
74  expected.clear();
75 
76  result = algo.removeIsolatedPoints(points);
77  std::sort(result.begin(), result.end());
78  BOOST_TEST(result == expected, boost::test_tools::per_element());
79 
80 
81  // second test: two far points
82  points.push_back({{ -1., -1., -1. }});
83  expected.clear();
84 
85  result = algo.removeIsolatedPoints(points);
86  std::sort(result.begin(), result.end());
87  BOOST_TEST(result == expected, boost::test_tools::per_element());
88 
89 
90  // third test: two close points, another isolated
91  points.push_back({{ +0.5, +1.0, +1.0 }});
92  expected.insert(expected.end(), { 0U, 2U });
93  std::sort(expected.begin(), expected.end());
94 
95  result = algo.removeIsolatedPoints(points);
96  std::sort(result.begin(), result.end());
97  BOOST_TEST(result == expected, boost::test_tools::per_element());
98 
99 
100  // fourth test: two close points, another two also close
101  points.push_back({{ -0.5, -1.0, -1.0 }});
102  expected.insert(expected.end(), { 1U, 3U });
103  std::sort(expected.begin(), expected.end());
104 
105  result = algo.removeIsolatedPoints(points);
106  std::sort(result.begin(), result.end());
107  BOOST_TEST(result == expected, boost::test_tools::per_element());
108 
109 } // PointIsolationTest1()
110 
111 
112 //------------------------------------------------------------------------------
113 /**
114  * @brief Creates a "star" disposition of points
115  * @tparam T type of coordinate being used
116  * @param nShells number of points on each ray of the star (origin excluded)
117  * @param distance distance from the origin of the farthest point
118  * @return a collection of points (each a `std::array<T, 3>`
119  *
120  * Points are aligned on a semi-axis, sparser and sparser as they go away from
121  * origin. nShell is the number of points beside the origin on each semi-axis.
122  * Origin is always included.
123  * A sequence is generated for each of the semi-axes (x, y, and z, two
124  * directions each).
125  * Example for `nShells = 5`, showing only one axis (that is, two semi-axes):
126  * ~~~~
127  * o o o o o O o o o o o
128  * ~~~~
129  * (points are marked with `o`, with `O` being the origin)
130  *
131  * The order of the points in the set is:
132  *
133  */
134 template <typename T>
135 auto CreateStarOfPoints(unsigned int nShells, T distance = T(1))
136  -> decltype(auto)
137 {
138 
139  using Coord_t = T;
140  using Point_t = std::array<Coord_t, 3U>;
141 
142  std::vector<Point_t> points;
143  points.reserve(1 + 1 * nShells);
144 
145  // fill shell by shell
146  while (nShells-- > 0) {
147  points.push_back({{ distance, 0., 0. }});
148  points.push_back({{ -distance, 0., 0. }});
149  points.push_back({{ 0., distance, 0. }});
150  points.push_back({{ 0., -distance, 0. }});
151  points.push_back({{ 0., 0., distance }});
152  points.push_back({{ 0., 0., -distance }});
153  distance /= 2;
154  } // while
155 
156  // add the origin
157  points.push_back({{ 0., 0., 0. }});
158 
159  return points;
160 } // CreateStarOfPoints()
161 
162 
163 /**
164  * @brief Tests various isolation radii on a star-distributed set of points
165  * @param levels number of shells in the star (0 is only its centre)
166  *
167  * The test uses a star-distributed set of points, as produced by
168  * `CreateStarOfPoints()`.
169  * This distribution has the characteristic that all the points farther than
170  * the isolation radius _from the origin_ are indeed isolated. This makes the
171  * prediction of the number of isolated points easier.
172  *
173  *
174  * This test uses coordinate type `double`.
175  *
176  */
177 void PointIsolationTest2(unsigned int levels) {
178 
179  using Coord_t = double;
180  using PointIsolationAlg_t = lar::example::PointIsolationAlg<Coord_t>;
181 
182  using Point_t = std::array<Coord_t, 3U>;
183 
184  //
185  // prepare the input
186  //
187  constexpr Coord_t starRadius = 1.;
188  std::vector<Point_t> points = CreateStarOfPoints<Coord_t>(levels, starRadius);
189 
190  //
191  // prepare the algorithm
192  //
193  PointIsolationAlg_t::Configuration_t config;
194  config.radius2 = cet::square(1.);
195  config.rangeX = { -2., +2. };
196  config.rangeY = { -2., +2. };
197  config.rangeZ = { -2., +2. };
198  PointIsolationAlg_t algo(config);
199 
200  //
201  // check every level
202  //
203  constexpr unsigned int nSemiDirections = 6;
204  // small step (smaller than smallest distance between shells):
205  const Coord_t epsilonStep = starRadius / (2 << levels);
206  double baseRadius = starRadius; // starting radius
207 
208  // with the wider isolation radius, we expect all the points to be
209  // non-isolated; the most isolated points are at the beginning of the list
210  size_t const maxExpectedPoints = 1 + levels * nSemiDirections;
211  assert(maxExpectedPoints == points.size());
212 
213  std::vector<size_t> expectedBase(maxExpectedPoints);
214  std::iota(expectedBase.begin(), expectedBase.end(), 0U);
215 
216  // check radii that fall in between all the shells;
217  // the first radius is a bit more than half the star radius, and it is
218  // expected to include all points; the next, a bit more than a fourth of the
219  // star radius, will define the 6 points on the outer shell as isolated;
220  // and so forth.
221  // Level N has a radius that includes N shells plus the origin.
222  // Level 0 has a radius so small that it includes only the origin.
223  unsigned int level = levels;
224  do {
225 
226  // compute and set up a proper isolation radius for this level
227  baseRadius /= 2.;
228  config.radius2 = cet::square(baseRadius + epsilonStep);
229  algo.reconfigure(config);
230 
231  BOOST_TEST_MESSAGE
232  ("[" << level <<"] testing with radius " << (baseRadius + epsilonStep));
233 
234  // we expect to progressively have less and less non-isolated points...
235  unsigned int const nExpected = (level > 1)? (1 + level * nSemiDirections): 0;
236  // ... and we expect those points to be the first ones in the collection
237  std::vector<size_t> expected
238  (expectedBase.end() - nExpected, expectedBase.end());
239 
240  std::vector<size_t> result = algo.removeIsolatedPoints(points);
241  BOOST_TEST(result.size() == expected.size());
242 
243  std::sort(result.begin(), result.end());
244  std::sort(expected.begin(), expected.end());
245  BOOST_TEST(result == expected, boost::test_tools::per_element());
246 
247  } while (--level > 0);
248 
249 } // PointIsolationTest2()
250 
251 
252 //------------------------------------------------------------------------------
253 //--- tests
254 //
255 BOOST_AUTO_TEST_CASE(PointIsolationAlgTest) {
257 } // PointIsolationAlgTest()
258 
259 
260 BOOST_AUTO_TEST_CASE(PointIsolationAlgVerificationTest) {
262 } // PointIsolationAlgVerificationTest()
263 
264 
265 /// @}
266 // END RemoveIsolatedSpacePoints group -----------------------------------------
Algorithm to detect isolated space points.
BOOST_AUTO_TEST_CASE(AllTests)
enum geo::coordinates Coord_t
auto CreateStarOfPoints(unsigned int nShells, T distance=T(1)) -> decltype(auto)
Creates a &quot;star&quot; disposition of points.
recob::tracking::Point_t Point_t
void PointIsolationTest2(unsigned int levels)
Tests various isolation radii on a star-distributed set of points.
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
Algorithm(s) dealing with point isolation in space.
void PointIsolationTest1()
Low-multiplicity unit test.