Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
BoundaryCheck.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file BoundaryCheck.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2016-2020 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #pragma once
13 
14 #include <cfloat>
15 #include <cmath>
16 #include <iterator>
17 #include <vector>
18 
19 namespace Acts {
20 
37  public:
39  BoundaryCheck(bool check);
40 
47  BoundaryCheck(bool checkLocal0, bool checkLocal1, double tolerance0 = 0,
48  double tolerance1 = 0);
49 
54  BoundaryCheck(const SquareMatrix2& localCovariance, double sigmaMax = 1);
55 
56  operator bool() const { return (m_type != Type::eNone); }
57  bool operator!() const { return !bool(*this); }
58 
69  template <typename Vector2Container>
70  bool isInside(const Vector2& point, const Vector2Container& vertices) const;
71 
80  bool isInside(const Vector2& point, const Vector2& lowerLeft,
81  const Vector2& upperRight) const;
82 
94  template <typename Vector2Container>
95  double distance(const Vector2& point, const Vector2Container& vertices) const;
96 
106  double distance(const Vector2& point, const Vector2& lowerLeft,
107  const Vector2& upperRight) const;
108 
109  enum class Type {
110  eNone,
111  eAbsolute,
112  eChi2
113  };
114 
116  Type type() const;
117 
118  // Broadcast the tolerance
119  const Vector2& tolerance() const;
120 
121  // Return the covariance matrix
122  SquareMatrix2 covariance() const;
123 
124  private:
129  BoundaryCheck transformed(const ActsMatrix<2, 2>& jacobian) const;
130 
132  bool isTolerated(const Vector2& delta) const;
133 
135  double squaredNorm(const Vector2& x) const;
136 
138  template <typename Vector2Container>
140  const Vector2Container& vertices) const;
141 
144  const Vector2& point, const Vector2& lowerLeft,
145  const Vector2& upperRight) const;
146 
149 
153 
154  // To access the m_type
155  friend class CylinderBounds;
156  friend class RectangleBounds;
157  // To be able to use `transformed`
158  friend class CylinderBounds;
159  friend class DiscTrapezoidBounds;
160  // EllipseBounds needs a custom implementation
161  friend class EllipseBounds;
162 };
163 
164 } // namespace Acts
165 
167  return m_type;
168 }
169 
171  return m_tolerance;
172 }
173 
175  return m_weight.inverse();
176 }
177 
179  : m_weight(SquareMatrix2::Identity()),
180  m_tolerance(0, 0),
181  m_type(check ? Type::eAbsolute : Type::eNone) {}
182 
183 inline Acts::BoundaryCheck::BoundaryCheck(bool checkLocal0, bool checkLocal1,
184  double tolerance0, double tolerance1)
185  : m_weight(SquareMatrix2::Identity()),
186  m_tolerance(checkLocal0 ? tolerance0 : DBL_MAX,
187  checkLocal1 ? tolerance1 : DBL_MAX),
188  m_type(Type::eAbsolute) {}
189 
190 inline Acts::BoundaryCheck::BoundaryCheck(const SquareMatrix2& localCovariance,
191  double sigmaMax)
192  : m_weight(localCovariance.inverse()),
193  m_tolerance(sigmaMax, 0),
194  m_type(Type::eChi2) {}
195 
197  const ActsMatrix<2, 2>& jacobian) const {
198  BoundaryCheck bc = *this;
199  if (m_type == Type::eAbsolute) {
200  // project tolerances to the new system. depending on the jacobian we need
201  // to check both tolerances, even when the initial check does not.
202  bc.m_tolerance = (jacobian * m_tolerance).cwiseAbs();
203  } else /* Type::eChi2 */ {
204  bc.m_weight =
205  (jacobian * m_weight.inverse() * jacobian.transpose()).inverse();
206  }
207  return bc;
208 }
209 
210 template <typename Vector2Container>
212  const Vector2& point, const Vector2Container& vertices) const {
213  if (m_type == Type::eNone) {
214  // The null boundary check always succeeds
215  return true;
216  } else if (detail::VerticesHelper::isInsidePolygon(point, vertices)) {
217  // If the point falls inside the polygon, the check always succeeds
218  return true;
219  } else if (m_tolerance == Vector2(0., 0.)) {
220  // Outside of the polygon, since we've eliminated the case of an absence of
221  // check above, we know we'll always fail if the tolerance is zero.
222  //
223  // This allows us to avoid the expensive computeClosestPointOnPolygon
224  // computation in this simple case.
225  //
226  // TODO: When tolerance is not 0, we could also avoid this computation in
227  // some cases by testing against a bounding box of the polygon, padded
228  // on each side with our tolerance. Check if this optimization is
229  // worthwhile in some production workflows, and if so implement it.
230  return false;
231  } else {
232  // We are outside of the polygon, but there is a tolerance. Must find what
233  // the closest point on the polygon is and check if it's within tolerance.
234  auto closestPoint = computeClosestPointOnPolygon(point, vertices);
235  return isTolerated(closestPoint - point);
236  }
237 }
238 
240  const Vector2& lowerLeft,
241  const Vector2& upperRight) const {
242  if (detail::VerticesHelper::isInsideRectangle(point, lowerLeft, upperRight)) {
243  return true;
244  } else {
245  Vector2 closestPoint;
246 
247  if (m_type == Type::eNone || m_type == Type::eAbsolute) {
248  // absolute, can calculate directly
249  closestPoint =
250  computeEuclideanClosestPointOnRectangle(point, lowerLeft, upperRight);
251 
252  } else /* Type::eChi2 */ {
253  // need to calculate by projection and squarednorm
254  Vector2 vertices[] = {{lowerLeft[0], lowerLeft[1]},
255  {upperRight[0], lowerLeft[1]},
256  {upperRight[0], upperRight[1]},
257  {lowerLeft[0], upperRight[1]}};
258  closestPoint = computeClosestPointOnPolygon(point, vertices);
259  }
260 
261  return isTolerated(closestPoint - point);
262  }
263 }
264 
265 template <typename Vector2Container>
267  const Acts::Vector2& point, const Vector2Container& vertices) const {
268  // TODO 2017-04-06 msmk: this should be calculable directly
269  double d = squaredNorm(point - computeClosestPointOnPolygon(point, vertices));
270  d = std::sqrt(d);
271  return detail::VerticesHelper::isInsidePolygon(point, vertices) ? -d : d;
272 }
273 
275  const Vector2& lowerLeft,
276  const Vector2& upperRight) const {
277  if (m_type == Type::eNone || m_type == Type::eAbsolute) {
278  // compute closest point on box
279  double d = (point - computeEuclideanClosestPointOnRectangle(
280  point, lowerLeft, upperRight))
281  .norm();
282  return detail::VerticesHelper::isInsideRectangle(point, lowerLeft,
283  upperRight)
284  ? -d
285  : d;
286 
287  } else /* Type::eChi2 */ {
288  Vector2 vertices[] = {{lowerLeft[0], lowerLeft[1]},
289  {upperRight[0], lowerLeft[1]},
290  {upperRight[0], upperRight[1]},
291  {lowerLeft[0], upperRight[1]}};
292  return distance(point, vertices);
293  }
294 }
295 
296 inline bool Acts::BoundaryCheck::isTolerated(const Vector2& delta) const {
297  if (m_type == Type::eNone) {
298  return true;
299  } else if (m_type == Type::eAbsolute) {
300  return (std::abs(delta[0]) <= m_tolerance[0]) &&
301  (std::abs(delta[1]) <= m_tolerance[1]);
302  } else /* Type::eChi2 */ {
303  // Mahalanobis distances mean is 2 in 2-dim. cut is 1-d sigma.
304  return (squaredNorm(delta) < (2 * m_tolerance[0]));
305  }
306 }
307 
308 inline double Acts::BoundaryCheck::squaredNorm(const Vector2& x) const {
309  return (x.transpose() * m_weight * x).value();
310 }
311 
312 template <typename Vector2Container>
314  const Acts::Vector2& point, const Vector2Container& vertices) const {
315  // calculate the closest position on the segment between `ll0` and `ll1` to
316  // the point as measured by the metric induced by the weight matrix
317  auto closestOnSegment = [&](auto&& ll0, auto&& ll1) {
318  // normal vector and position of the closest point along the normal
319  auto n = ll1 - ll0;
320  auto weighted_n = m_weight * n;
321  auto f = n.dot(weighted_n);
322  auto u = std::isnormal(f)
323  ? (point - ll0).dot(weighted_n) / f
324  : 0.5; // ll0 and ll1 are so close it doesn't matter
325  // u must be in [0, 1] to still be on the polygon segment
326  return ll0 + std::clamp(u, 0.0, 1.0) * n;
327  };
328 
329  auto iv = std::begin(vertices);
330  Vector2 l0 = *iv;
331  Vector2 l1 = *(++iv);
332  Vector2 closest = closestOnSegment(l0, l1);
333  auto closestDist = squaredNorm(closest - point);
334  // Calculate the closest point on other connecting lines and compare distances
335  for (++iv; iv != std::end(vertices); ++iv) {
336  l0 = l1;
337  l1 = *iv;
338  Vector2 current = closestOnSegment(l0, l1);
339  auto currentDist = squaredNorm(current - point);
340  if (currentDist < closestDist) {
341  closest = current;
342  closestDist = currentDist;
343  }
344  }
345  // final edge from last vertex back to the first vertex
346  Vector2 last = closestOnSegment(l1, *std::begin(vertices));
347  if (squaredNorm(last - point) < closestDist) {
348  closest = last;
349  }
350  return closest;
351 }
352 
353 inline Acts::Vector2
355  const Vector2& point, const Vector2& lowerLeft,
356  const Vector2& upperRight) const {
357  /*
358  *
359  * | |
360  * IV | V | I
361  * | |
362  * ------------------------------
363  * | |
364  * | |
365  * VIII | INSIDE | VI
366  * | |
367  * | |
368  * ------------------------------
369  * | |
370  * III | VII | II
371  * | |
372  *
373  */
374 
375  double l0 = point[0], l1 = point[1];
376  double loc0Min = lowerLeft[0], loc0Max = upperRight[0];
377  double loc1Min = lowerLeft[1], loc1Max = upperRight[1];
378 
379  // check if inside
380  if (loc0Min <= l0 && l0 < loc0Max && loc1Min <= l1 && l1 < loc1Max) {
381  // INSIDE
382  double dist = std::abs(loc0Max - l0);
383  Vector2 cls(loc0Max, l1);
384 
385  double test = std::abs(loc0Min - l0);
386  if (test <= dist) {
387  dist = test;
388  cls = {loc0Min, l1};
389  }
390 
391  test = std::abs(loc1Max - l1);
392  if (test <= dist) {
393  dist = test;
394  cls = {l0, loc1Max};
395  }
396 
397  test = std::abs(loc1Min - l1);
398  if (test <= dist) {
399  return {l0, loc1Min};
400  }
401  return cls;
402  } else {
403  // OUTSIDE, check sectors
404  if (l0 > loc0Max) {
405  if (l1 > loc1Max) { // I
406  return {loc0Max, loc1Max};
407  } else if (l1 <= loc1Min) { // II
408  return {loc0Max, loc1Min};
409  } else { // VI
410  return {loc0Max, l1};
411  }
412  } else if (l0 < loc0Min) {
413  if (l1 > loc1Max) { // IV
414  return {loc0Min, loc1Max};
415  } else if (l1 <= loc1Min) { // III
416  return {loc0Min, loc1Min};
417  } else { // VIII
418  return {loc0Min, l1};
419  }
420  } else {
421  if (l1 > loc1Max) { // V
422  return {l0, loc1Max};
423  } else { // l1 <= loc1Min # VII
424  return {l0, loc1Min};
425  }
426  // third case not necessary, see INSIDE above
427  }
428  }
429 }