|
| 1 | +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. |
| 2 | +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 3 | +// All rights not expressly granted are reserved. |
| 4 | +// |
| 5 | +// This software is distributed under the terms of the GNU General Public |
| 6 | +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 7 | +// |
| 8 | +// In applying this license CERN does not waive the privileges and immunities |
| 9 | +// granted to it by virtue of its status as an Intergovernmental Organization |
| 10 | +// or submit itself to any jurisdiction. |
| 11 | + |
| 12 | +/// \file HelixHelper.h |
| 13 | +/// \brief Helper classes for helical tracks manipulations |
| 14 | +/// \author ruben.shahoyan@cern.ch |
| 15 | + |
| 16 | +#ifndef _ALICEO2_HELIX_HELPER_ |
| 17 | +#define _ALICEO2_HELIX_HELPER_ |
| 18 | + |
| 19 | +#include "CommonConstants/MathConstants.h" |
| 20 | +#include "MathUtils/Utils.h" |
| 21 | +#include "MathUtils/Primitive2D.h" |
| 22 | + |
| 23 | +namespace o2 |
| 24 | +{ |
| 25 | +namespace track |
| 26 | +{ |
| 27 | + |
| 28 | +///__________________________________________________________________________ |
| 29 | +//< precalculated track radius, center, alpha sin,cos and their combinations |
| 30 | +struct TrackAuxPar : public o2::math_utils::CircleXYf_t { |
| 31 | + float c, s, cc, ss, cs; // cos ans sin of track alpha and their products |
| 32 | + |
| 33 | + GPUdDefault() TrackAuxPar() = default; |
| 34 | + |
| 35 | + template <typename T> |
| 36 | + GPUd() TrackAuxPar(const T& trc, float bz) |
| 37 | + { |
| 38 | + set(trc, bz); |
| 39 | + } |
| 40 | + GPUd() float cosDif(const TrackAuxPar& t) const { return c * t.c + s * t.s; } // cos(alpha_this - alha_t) |
| 41 | + GPUd() float sinDif(const TrackAuxPar& t) const { return s * t.c - c * t.s; } // sin(alpha_this - alha_t) |
| 42 | + |
| 43 | + template <typename T> |
| 44 | + GPUd() void set(const T& trc, float bz) |
| 45 | + { |
| 46 | + trc.getCircleParams(bz, *this, s, c); |
| 47 | + cc = c * c; |
| 48 | + ss = s * s; |
| 49 | + cs = c * s; |
| 50 | + } |
| 51 | + ClassDefNV(TrackAuxPar, 1); |
| 52 | +}; |
| 53 | + |
| 54 | +//__________________________________________________________ |
| 55 | +//< crossing coordinates of 2 circles |
| 56 | +struct CrossInfo { |
| 57 | + static constexpr float MaxDistXYDef = 10.; |
| 58 | + float xDCA[2] = {}; |
| 59 | + float yDCA[2] = {}; |
| 60 | + int nDCA = 0; |
| 61 | + |
| 62 | + GPUd() int circlesCrossInfo(const TrackAuxPar& trax0, const TrackAuxPar& trax1, float maxDistXY = MaxDistXYDef, bool isCollinear = false) |
| 63 | + { |
| 64 | + const auto& trcA = trax0.rC > trax1.rC ? trax0 : trax1; // designate the largest circle as A |
| 65 | + const auto& trcB = trax0.rC > trax1.rC ? trax1 : trax0; |
| 66 | + nDCA = 0; |
| 67 | + float xDist = trcB.xC - trcA.xC, yDist = trcB.yC - trcA.yC; |
| 68 | + float dist2 = xDist * xDist + yDist * yDist, dist = o2::gpu::GPUCommonMath::Sqrt(dist2), rsum = trcA.rC + trcB.rC; |
| 69 | + if (dist < 1e-12) { |
| 70 | + return nDCA; // circles are concentric? |
| 71 | + } |
| 72 | + if (dist > rsum) { // circles don't touch, chose a point in between |
| 73 | + // the parametric equation of lines connecting the centers is |
| 74 | + // x = x0 + t/dist * (x1-x0), y = y0 + t/dist * (y1-y0) |
| 75 | + if (dist - rsum > maxDistXY) { // too large distance |
| 76 | + return nDCA; |
| 77 | + } |
| 78 | + notTouchingXY(dist, xDist, yDist, trcA, trcB.rC, isCollinear); |
| 79 | + } else if (auto dfr = dist + trcB.rC - trcA.rC; dfr < 0.) { // the small circle is nestled into large one w/o touching |
| 80 | + if (dfr > -maxDistXY) { |
| 81 | + // select the point of closest approach of 2 circles |
| 82 | + notTouchingXY(dist, xDist, yDist, trcA, -trcB.rC, isCollinear); |
| 83 | + } else { |
| 84 | + return nDCA; |
| 85 | + } |
| 86 | + } else { // 2 intersection points |
| 87 | + if (isCollinear) { |
| 88 | + /// collinear tracks, e.g. electrons from photon conversion |
| 89 | + /// if there are 2 crossings of the circle it is better to take |
| 90 | + /// a weighted average of the crossing points as a radius |
| 91 | + float r2r = trcA.rC + trcB.rC; |
| 92 | + float r1_r = trcA.rC / r2r; |
| 93 | + float r2_r = trcB.rC / r2r; |
| 94 | + xDCA[0] = r2_r * trcA.xC + r1_r * trcB.xC; |
| 95 | + yDCA[0] = r2_r * trcA.yC + r1_r * trcB.yC; |
| 96 | + nDCA = 1; |
| 97 | + } else if (o2::gpu::GPUCommonMath::Abs(xDist) < o2::gpu::GPUCommonMath::Abs(yDist)) { |
| 98 | + // to simplify calculations, we move to new frame x->x+Xc0, y->y+Yc0, so that |
| 99 | + // the 1st one is centered in origin |
| 100 | + float a = (trcA.rC * trcA.rC - trcB.rC * trcB.rC + dist2) / (2. * yDist), b = -xDist / yDist, ab = a * b, bb = b * b; |
| 101 | + float det = ab * ab - (1. + bb) * (a * a - trcA.rC * trcA.rC); |
| 102 | + if (det > 0.) { |
| 103 | + det = o2::gpu::GPUCommonMath::Sqrt(det); |
| 104 | + xDCA[0] = (-ab + det) / (1. + b * b); |
| 105 | + yDCA[0] = a + b * xDCA[0] + trcA.yC; |
| 106 | + xDCA[0] += trcA.xC; |
| 107 | + xDCA[1] = (-ab - det) / (1. + b * b); |
| 108 | + yDCA[1] = a + b * xDCA[1] + trcA.yC; |
| 109 | + xDCA[1] += trcA.xC; |
| 110 | + nDCA = 2; |
| 111 | + } else { // due to the finite precision the det<=0, i.e. the circles are barely touching, fall back to this special case |
| 112 | + notTouchingXY(dist, xDist, yDist, trcA, trcB.rC); |
| 113 | + } |
| 114 | + } else { |
| 115 | + float a = (trcA.rC * trcA.rC - trcB.rC * trcB.rC + dist2) / (2. * xDist), b = -yDist / xDist, ab = a * b, bb = b * b; |
| 116 | + float det = ab * ab - (1. + bb) * (a * a - trcA.rC * trcA.rC); |
| 117 | + if (det > 0.) { |
| 118 | + det = o2::gpu::GPUCommonMath::Sqrt(det); |
| 119 | + yDCA[0] = (-ab + det) / (1. + bb); |
| 120 | + xDCA[0] = a + b * yDCA[0] + trcA.xC; |
| 121 | + yDCA[0] += trcA.yC; |
| 122 | + yDCA[1] = (-ab - det) / (1. + bb); |
| 123 | + xDCA[1] = a + b * yDCA[1] + trcA.xC; |
| 124 | + yDCA[1] += trcA.yC; |
| 125 | + nDCA = 2; |
| 126 | + } else { // due to the finite precision the det<=0, i.e. the circles are barely touching, fall back to this special case |
| 127 | + notTouchingXY(dist, xDist, yDist, trcA, trcB.rC); |
| 128 | + } |
| 129 | + } |
| 130 | + } |
| 131 | + return nDCA; |
| 132 | + } |
| 133 | + |
| 134 | + GPUd() void notTouchingXY(float dist, float xDist, float yDist, const TrackAuxPar& trcA, float rBSign, bool isCollinear = false) |
| 135 | + { |
| 136 | + if (isCollinear) { |
| 137 | + /// for collinear tracks it is better to take |
| 138 | + /// a weighted average of the crossing points as a radius |
| 139 | + float r2r = trcA.rC + rBSign; |
| 140 | + float r1_r = trcA.rC / r2r; |
| 141 | + float r2_r = rBSign / r2r; |
| 142 | + xDCA[0] = r2_r * trcA.xC + r1_r * (xDist + trcA.xC); |
| 143 | + yDCA[0] = r2_r * trcA.yC + r1_r * (yDist + trcA.yC); |
| 144 | + } else { |
| 145 | + // fast method to calculate DCA between 2 circles, assuming that they don't touch each outer: |
| 146 | + // the parametric equation of lines connecting the centers is x = xA + t/dist * xDist, y = yA + t/dist * yDist |
| 147 | + // with xA,yY being the center of the circle A ( = trcA.xC, trcA.yC ), xDist = trcB.xC = trcA.xC ... |
| 148 | + // There are 2 special cases: |
| 149 | + // (a) small circle is inside the large one: provide rBSign as -trcB.rC |
| 150 | + // (b) circle are side by side: provide rBSign as trcB.rC |
| 151 | + auto t2d = (dist + trcA.rC - rBSign) / dist; |
| 152 | + xDCA[0] = trcA.xC + 0.5 * (xDist * t2d); |
| 153 | + yDCA[0] = trcA.yC + 0.5 * (yDist * t2d); |
| 154 | + } |
| 155 | + nDCA = 1; |
| 156 | + } |
| 157 | + |
| 158 | + template <typename T> |
| 159 | + GPUd() int linesCrossInfo(const TrackAuxPar& trax0, const T& tr0, |
| 160 | + const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) |
| 161 | + { |
| 162 | + /// closest approach of 2 straight lines |
| 163 | + /// TrackParam propagation can be parameterized in lab in a form |
| 164 | + /// xLab(t) = (x*cosAlp - y*sinAlp) + t*(cosAlp - sinAlp* snp/csp) = xLab0 + t*(cosAlp - sinAlp* snp/csp) |
| 165 | + /// yLab(t) = (x*sinAlp + y*cosAlp) + t*(sinAlp + cosAlp* snp/csp) = yLab0 + t*(sinAlp + cosAlp* snp/csp) |
| 166 | + /// zLab(t) = z + t * tgl / csp = zLab0 + t * tgl / csp |
| 167 | + /// where t is the x-step in the track alpha-frame, xLab,yLab,zLab are reference track coordinates in lab |
| 168 | + /// frame (filled by TrackAuxPar for straight line tracks). |
| 169 | + /// |
| 170 | + /// Therefore, for the parametric track equation in lab 3D we have (wrt tracking-X increment t) |
| 171 | + /// xL(t) = xL + t Kx; Kx = (cosAlp - sinAlp* snp/csp) |
| 172 | + /// yL(t) = yL + t Ky; Ky = (sinAlp + cosAlp* snp/csp) |
| 173 | + /// zL(t) = zL + t Kz; Kz = tgl / csp |
| 174 | + /// Note that Kx^2 + Ky^2 + Kz^2 = (1+tgl^2) / csp^2 |
| 175 | + nDCA = 0; |
| 176 | + float dx = trax1.xC - trax0.xC; // for straight line TrackAuxPar stores lab coordinates at referene point!!! |
| 177 | + float dy = trax1.yC - trax0.yC; // |
| 178 | + float dz = tr1.getZ() - tr0.getZ(); |
| 179 | + auto csp0i2 = 1. / tr0.getCsp2(); // 1 / csp^2 |
| 180 | + auto csp0i = o2::gpu::GPUCommonMath::Sqrt(csp0i2); |
| 181 | + auto tgp0 = tr0.getSnp() * csp0i; |
| 182 | + float kx0 = trax0.c - trax0.s * tgp0; |
| 183 | + float ky0 = trax0.s + trax0.c * tgp0; |
| 184 | + float kz0 = tr0.getTgl() * csp0i; |
| 185 | + auto csp1i2 = 1. / tr1.getCsp2(); // 1 / csp^2 |
| 186 | + auto csp1i = o2::gpu::GPUCommonMath::Sqrt(csp1i2); |
| 187 | + auto tgp1 = tr1.getSnp() * o2::gpu::GPUCommonMath::Sqrt(csp1i2); |
| 188 | + float kx1 = trax1.c - trax1.s * tgp1; |
| 189 | + float ky1 = trax1.s + trax1.c * tgp1; |
| 190 | + float kz1 = tr1.getTgl() * csp1i; |
| 191 | + /// Minimize |vecL1 - vecL0|^2 wrt t0 and t1: point of closest approach |
| 192 | + /// Leads to system |
| 193 | + /// A Dx = B with Dx = {dx0, dx1} |
| 194 | + /// with A = |
| 195 | + /// | kx0^2+ky0^2+kz0^2 -(kx0*kx1+ky0*ky1+kz0*kz1) | = (1+tgl0^2) / csp0^2 .... |
| 196 | + /// | -(kx0*kx1+ky0*ky1+kz0*kz1) kx0^2+ky0^2+kz0^2 | ..... (1+tgl1^2) / csp1^2 |
| 197 | + /// and B = {(dx Kx0 + dy Ky0 + dz Kz0), -(dx Kx1 + dy Ky1 + dz Kz1) } |
| 198 | + /// |
| 199 | + float a00 = (1.f + tr0.getTgl() * tr0.getTgl()) * csp0i2, a11 = (1.f + tr1.getTgl() * tr1.getTgl()) * csp1i2, a01 = -(kx0 * kx1 + ky0 * ky1 + kz0 * kz1); |
| 200 | + float b0 = dx * kx0 + dy * ky0 + dz * kz0, b1 = -(dx * kx1 + dy * ky1 + dz * kz1); |
| 201 | + float det = a00 * a11 - a01 * a01, det0 = b0 * a11 - b1 * a01, det1 = a00 * b1 - a01 * b0; |
| 202 | + if (o2::gpu::GPUCommonMath::Sqrt(det) > o2::constants::math::Almost0) { |
| 203 | + auto detI = 1. / det; |
| 204 | + auto t0 = det0 * detI; |
| 205 | + auto t1 = det1 * detI; |
| 206 | + float addx0 = kx0 * t0, addy0 = ky0 * t0, addx1 = kx1 * t1, addy1 = ky1 * t1; |
| 207 | + dx += addx1 - addx0; // recalculate XY distance at DCA |
| 208 | + dy += addy1 - addy0; |
| 209 | + if (dx * dx + dy * dy > maxDistXY * maxDistXY) { |
| 210 | + return nDCA; |
| 211 | + } |
| 212 | + xDCA[0] = (trax0.xC + addx0 + trax1.xC + addx1) * 0.5; |
| 213 | + yDCA[0] = (trax0.yC + addy0 + trax1.yC + addy1) * 0.5; |
| 214 | + nDCA = 1; |
| 215 | + } |
| 216 | + return nDCA; |
| 217 | + } |
| 218 | + |
| 219 | + template <typename T> |
| 220 | + GPUd() int circleLineCrossInfo(const TrackAuxPar& trax0, const T& tr0, |
| 221 | + const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) |
| 222 | + { |
| 223 | + /// closest approach of line and circle |
| 224 | + /// TrackParam propagation can be parameterized in lab in a form |
| 225 | + /// xLab(t) = (x*cosAlp - y*sinAlp) + t*(cosAlp - sinAlp* snp/csp) = xLab0 + t*(cosAlp - sinAlp* snp/csp) |
| 226 | + /// yLab(t) = (x*sinAlp + y*cosAlp) + t*(sinAlp + cosAlp* snp/csp) = yLab0 + t*(sinAlp + cosAlp* snp/csp) |
| 227 | + /// zLab(t) = z + t * tgl / csp = zLab0 + t * tgl / csp |
| 228 | + /// where t is the x-step in the track alpha-frame, xLab,yLab,zLab are reference track coordinates in lab |
| 229 | + /// frame (filled by TrackAuxPar for straight line tracks). |
| 230 | + /// |
| 231 | + /// Therefore, for the parametric track equation in lab 3D we have (wrt tracking-X increment t) |
| 232 | + /// xL(t) = xL + t Kx; Kx = (cosAlp - sinAlp* snp/csp) |
| 233 | + /// yL(t) = yL + t Ky; Ky = (sinAlp + cosAlp* snp/csp) |
| 234 | + /// zL(t) = zL + t Kz; Kz = tgl / csp |
| 235 | + /// Note that Kx^2 + Ky^2 = 1 / csp^2 |
| 236 | + |
| 237 | + const auto& traxH = trax0.rC > trax1.rC ? trax0 : trax1; // circle (for the line rC is set to 0) |
| 238 | + const auto& traxL = trax0.rC > trax1.rC ? trax1 : trax0; // line |
| 239 | + const auto& trcL = trax0.rC > trax1.rC ? tr1 : tr0; // track of the line |
| 240 | + |
| 241 | + // solve quadratic equation of line crossing the circle |
| 242 | + float dx = traxL.xC - traxH.xC; // X distance between the line lab reference and circle center |
| 243 | + float dy = traxL.yC - traxH.yC; // Y... |
| 244 | + // t^2(kx^2+ky^2) + 2t(dx*kx+dy*ky) + dx^2 + dy^2 - r^2 = 0 |
| 245 | + auto cspi2 = 1. / trcL.getCsp2(); // 1 / csp^2 == kx^2 + ky^2 |
| 246 | + auto cspi = o2::gpu::GPUCommonMath::Sqrt(cspi2); |
| 247 | + auto tgp = trcL.getSnp() * cspi; |
| 248 | + float kx = traxL.c - traxL.s * tgp; |
| 249 | + float ky = traxL.s + traxL.c * tgp; |
| 250 | + double dk = dx * kx + dy * ky; |
| 251 | + double det = dk * dk - cspi2 * (dx * dx + dy * dy - traxH.rC * traxH.rC); |
| 252 | + if (det > 0) { // 2 crossings |
| 253 | + det = o2::gpu::GPUCommonMath::Sqrt(det); |
| 254 | + float t0 = (-dk + det) * cspi2; |
| 255 | + float t1 = (-dk - det) * cspi2; |
| 256 | + xDCA[0] = traxL.xC + kx * t0; |
| 257 | + yDCA[0] = traxL.yC + ky * t0; |
| 258 | + xDCA[1] = traxL.xC + kx * t1; |
| 259 | + yDCA[1] = traxL.yC + ky * t1; |
| 260 | + nDCA = 2; |
| 261 | + } else { |
| 262 | + // there is no crossing, find the point of the closest approach on the line which is closest to the circle center |
| 263 | + float t = -dk * cspi2; |
| 264 | + float xL = traxL.xC + kx * t, yL = traxL.yC + ky * t; // point on the line, need to average with point on the circle |
| 265 | + float dxc = xL - traxH.xC, dyc = yL - traxH.yC, dist = o2::gpu::GPUCommonMath::Sqrt(dxc * dxc + dyc * dyc); |
| 266 | + if (dist - traxH.rC > maxDistXY) { // too large distance |
| 267 | + return nDCA; |
| 268 | + } |
| 269 | + float drcf = traxH.rC / dist; // radius / distance to circle center |
| 270 | + float xH = traxH.xC + dxc * drcf, yH = traxH.yC + dyc * drcf; |
| 271 | + xDCA[0] = (xL + xH) * 0.5; |
| 272 | + yDCA[0] = (yL + yH) * 0.5; |
| 273 | + nDCA = 1; |
| 274 | + } |
| 275 | + return nDCA; |
| 276 | + } |
| 277 | + |
| 278 | + template <typename T> |
| 279 | + GPUd() int set(const TrackAuxPar& trax0, const T& tr0, const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef, bool isCollinear = false) |
| 280 | + { |
| 281 | + // calculate up to 2 crossings between 2 circles |
| 282 | + nDCA = 0; |
| 283 | + if (trax0.rC > o2::constants::math::Almost0 && trax1.rC > o2::constants::math::Almost0) { // both are not straight lines |
| 284 | + nDCA = circlesCrossInfo(trax0, trax1, maxDistXY, isCollinear); |
| 285 | + } else if (trax0.rC < o2::constants::math::Almost0 && trax1.rC < o2::constants::math::Almost0) { // both are straigt lines |
| 286 | + nDCA = linesCrossInfo(trax0, tr0, trax1, tr1, maxDistXY); |
| 287 | + } else { |
| 288 | + nDCA = circleLineCrossInfo(trax0, tr0, trax1, tr1, maxDistXY); |
| 289 | + } |
| 290 | + // |
| 291 | + return nDCA; |
| 292 | + } |
| 293 | + |
| 294 | + GPUdDefault() CrossInfo() = default; |
| 295 | + |
| 296 | + template <typename T> |
| 297 | + GPUd() CrossInfo(const TrackAuxPar& trax0, const T& tr0, const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef, bool isCollinear = false) |
| 298 | + { |
| 299 | + set(trax0, tr0, trax1, tr1, maxDistXY, isCollinear); |
| 300 | + } |
| 301 | + ClassDefNV(CrossInfo, 1); |
| 302 | +}; |
| 303 | + |
| 304 | +} // namespace track |
| 305 | +} // namespace o2 |
| 306 | + |
| 307 | +#endif |
0 commit comments