ATLAS Offline Software
Loading...
Searching...
No Matches
MultiComponentStateModeCalculator.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
12
15//
16#include "CxxUtils/phihelper.h"
17#include <cmath>
18#include <stdexcept>
19
20#include<boost/container/static_vector.hpp>
21
22
23namespace {
24constexpr double invsqrt2PI =
25 M_2_SQRTPI / (2. * M_SQRT2); // 1. / sqrt(2. * M_PI);
26
27// Simple representation of 1D component
28struct Component
29{
30 Component() = default;
31 ~Component() = default;
32 Component(const Component&) = default;
33 Component& operator=(const Component&) = default;
34 Component(Component&&) = default;
35 Component& operator=(Component&&) = default;
36 // Constructor with arguments
37 Component(double aWeight, double aMean, double aSigma)
38 : weight(aWeight)
39 , mean(aMean)
40 , sigma(aSigma)
41 {}
42 double weight = 0;
43 double mean = 0;
44 double sigma = 0;
45};
46
47using VecOfComponents =
48 boost::container::static_vector<Component,
50struct pdfAndDeriv
51{
52 double value = 0.;
53 double deriv1 = 0.;
54 double deriv2 = .0;
55};
56
59double
60gaus(double x, double mean, double sigma)
61{
62 const double invertsigma = 1. / sigma;
63 const double z = (x - mean) * invertsigma;
64 return (invsqrt2PI * invertsigma) * exp(-0.5 * z * z);
65}
66
68double
69pdf(double x, int i, const std::array<VecOfComponents, 5>& mixture)
70{
71 double pdf(0.);
72 auto component = mixture[i].begin();
73 for (; component != mixture[i].end(); ++component) {
74 pdf += component->weight * gaus(x, component->mean, component->sigma);
75 }
76 return pdf;
77}
78
81pdfAndDeriv
82fullPdf(double x, int i, const std::array<VecOfComponents, 5>& mixture)
83{
84 pdfAndDeriv pdf{};
85 auto component = mixture[i].begin();
86 for (; component != mixture[i].end(); ++component) {
87 const double componentgaus = gaus(x, component->mean, component->sigma);
88 pdf.value += component->weight * componentgaus;
89 const double invertSigma = 1. / component->sigma;
90 const double z = (x - component->mean) * invertSigma;
91 pdf.deriv1 += -1. * component->weight * z * componentgaus * invertSigma;
92 pdf.deriv2 += component->weight * invertSigma * invertSigma * (z * z - 1.) *
93 componentgaus;
94 }
95 return pdf;
96}
97
100double
101width(int i, const std::array<VecOfComponents, 5>& mixture)
102{
103 double pdf(0.);
104 auto component = mixture[i].begin();
105 for (; component != mixture[i].end(); ++component) {
106 pdf += component->weight * component->sigma;
107 }
108 return pdf;
109}
110
111void
112fillMixture(const Trk::MultiComponentState& multiComponentState,
113 std::array<VecOfComponents, 5>& mixture)
114{
115 constexpr Trk::ParamDefs parameter[5] = {
117 };
118
119 // Loop over all the components in the multi-component state
120 for (const Trk::ComponentParameters& component : multiComponentState) {
121
122 // And then for each component over each 5 parameters
123 for (size_t i = 0; i < 5; ++i) {
124 const Trk::TrackParameters* componentParameters = component.params.get();
125 const AmgSymMatrix(5)* measuredCov = componentParameters->covariance();
126 if (!measuredCov) {
127 return;
128 }
129 // Enums for Perigee
130 // d0=0, z0=1, phi0=2, theta=3, qOverP=4,
131 double weight = component.weight;
132 double mean = componentParameters->parameters()[parameter[i]];
133 // FIXME ATLASRECTS-598 this std::abs() should not be necessary... for
134 // some reason cov(qOverP,qOverP) can be negative
135 double sigma = sqrt(std::abs((*measuredCov)(parameter[i], parameter[i])));
136 // Ensure that we don't have any problems with the cyclical nature of
137 // phi Use first state as reference point
138 if (i == 2) { // phi
139 const double deltaPhi =
140 multiComponentState.begin()->params->parameters()[2] - mean;
141 if (deltaPhi > M_PI) {
142 mean += 2 * M_PI;
143 } else if (deltaPhi < -M_PI) {
144 mean -= 2 * M_PI;
145 }
146 }
147 mixture[i].emplace_back(weight, mean, sigma);
148 }
149 }
150}
151
152double
153findMode(double xStart,
154 int i,
155 const std::array<VecOfComponents, 5>& mixture)
156{
157
158 bool converged = false;
159 double tolerance(1.);
160 // start position for mode
161 double currentMode(xStart);
162 double nextMode(currentMode);
163 // pdf at current mode and next mode
164 pdfAndDeriv currentPdf = fullPdf(currentMode, i, mixture);
165
166 // Allow up to 20 iterations for convergence
167 for (int iteration = 0; iteration < 20; ++iteration) {
168 // calculate next mode point
169 if (currentPdf.deriv2 != 0.0) {
170 nextMode = currentMode - currentPdf.deriv1 / currentPdf.deriv2;
171 } else {
172 return xStart;
173 }
174
175 // Calculate the mixture pdf at next mode point
176 pdfAndDeriv const nextPdf = fullPdf(nextMode, i, mixture);
177 // check if we have converged
178 if ((nextPdf.value + currentPdf.value) != 0.0) {
179 tolerance = std::abs(nextPdf.value - currentPdf.value) /
180 (nextPdf.value + currentPdf.value);
181 } else {
182 return xStart;
183 }
184 if (tolerance < 1.e-8) {
185 converged = true;
186 break;
187 }
188 // if we have not yet converged
189 // next becomes current and we retry
190 currentPdf = nextPdf;
191 currentMode = nextMode;
192 }
193
194 if (!converged) {
195 return xStart;
196 }
197
198 return currentMode;
199}
200
201double
202findRoot(double& result,
203 double xlo,
204 double xhi,
205 double value,
206 double i,
207 const std::array<VecOfComponents, 5>& mixture)
208{
209 // Do the root finding using the Brent-Decker method. Returns a boolean
210 // status and loads 'result' with our best guess at the root if true.
211 double a(xlo);
212 double b(xhi);
213 double fa = pdf(a, i, mixture) - value;
214 double fb = pdf(b, i, mixture) - value;
215
216 if (fb * fa > 0) {
217 return false;
218 }
219 bool ac_equal(false);
220 double fc = fb;
221 double c(0);
222 double d(0);
223 double e(0);
224 constexpr int MaxIterations = 20;
225 constexpr double tolerance = 1.e-6;
226
227 for (int iter = 0; iter <= MaxIterations; iter++) {
228
229 if ((fb < 0 && fc < 0) || (fb > 0 && fc > 0)) {
230 // Rename a,b,c and adjust bounding interval d
231 ac_equal = true;
232 c = a;
233 fc = fa;
234 d = b - a;
235 e = b - a;
236 }
237
238 if (std::abs(fc) < std::abs(fb)) {
239 ac_equal = true;
240 a = b;
241 b = c;
242 c = a;
243 fa = fb;
244 fb = fc;
245 fc = fa;
246 }
247
248 const double tol = 0.5 * tolerance * std::abs(b);
249 const double m = 0.5 * (c - b);
250
251 if (fb == 0 || std::abs(m) <= tol) {
252 result = b;
253 return true;
254 }
255
256 if (std::abs(e) < tol || std::abs(fa) <= std::abs(fb)) {
257 // Bounds decreasing too slowly: use bisection
258 d = m;
259 e = m;
260 } else {
261 // Attempt inverse cubic interpolation
262 double p = 0;
263 double q = 0;
264 double r = 0;
265 if (fa == 0.)[[unlikely]]{
266 throw std::runtime_error{"findRoot: divisor fa is zero."};
267 }
268 const double s = fb / fa;
269
270 if (ac_equal) {
271 p = 2 * m * s;
272 q = 1 - s;
273 } else {
274 q = fa / fc;
275 r = fb / fc;
276 p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
277 q = (q - 1) * (r - 1) * (s - 1);
278 }
279 // Check whether we are in bounds
280 if (p > 0) {
281 q = -q;
282 } else {
283 p = -p;
284 }
285
286 const double min1 = 3 * m * q - std::abs(tol * q);
287 const double min2 = std::abs(e * q);
288 if (2 * p < (min1 < min2 ? min1 : min2)) {
289 // Accept the interpolation
290 e = d;
291 d = p / q;
292 } else {
293 // Interpolation failed: use bisection.
294 d = m;
295 e = m;
296 }
297 }
298 // Move last best guess to a
299 a = b;
300 fa = fb;
301 // Evaluate new trial root
302 if (std::abs(d) > tol) {
303 b += d;
304 } else {
305 b += (m > 0 ? +tol : -tol);
306 }
307 fb = pdf(b, i, mixture) - value;
308 }
309 // Return our best guess if we run out of iterations
310 result = b;
311
312 return false;
313}
314
318std::array<double, 10>
319evaluateMode(const std::array<VecOfComponents, 5>& mixture)
320{
321 std::array<double, 10> modes{};
322 /* loop over the 5 direction , d0,z0,phi,theta,qOverP*/
323
324 for (int i = 0; i < 5; i++) {
325
326 double largerPdfComponent = 0.0;
327 double largerMeanComponent = 0.0;
328 /*
329 * Loop over the mixture in the ith direction and find the component
330 * whose mean give the larger value for the Gaussian Mixture pdf.
331 * This should be a good enough starting point for the mode
332 * finding in this direction
333 */
334 for (const Component& component : mixture[i]) {
335 const double pdfValue = pdf(component.mean, i, mixture);
336 if (pdfValue > largerPdfComponent) {
337 largerPdfComponent = pdfValue;
338 largerMeanComponent = component.mean;
339 }
340 }
341 modes[i] = findMode(largerMeanComponent, i, mixture);
342 // Calculate the FWHM and return this back so that it can be used to correct
343 // the covariance matrix
344 if (largerMeanComponent != modes[i]) {
345 // mode calculation was successful now calulate FWHM
346 const double currentWidth = width(i, mixture);
347 modes[i + 5] = -1; // Failure is flagged with a value less than 0;
348
349 const double pdfVal = pdf(modes[i], i, mixture);
350 double highX(0);
351 double lowX(0);
352
353 double upperbound = modes[i] + 1.5 * currentWidth;
354 while (true) {
355 if (pdf(upperbound, i, mixture) > pdfVal * 0.5) {
356 upperbound += currentWidth;
357 } else {
358 break;
359 }
360 }
361
362 const bool highXFound =
363 findRoot(highX, modes[i], upperbound, pdfVal * 0.5, i, mixture);
364
365 double lowerbound = modes[i] - 1.5 * currentWidth;
366 while (true) {
367 if (pdf(lowerbound, i, mixture) > pdfVal * 0.5) {
368 lowerbound -= currentWidth;
369 } else {
370 break;
371 }
372 }
373 const bool lowXFound =
374 findRoot(lowX, lowerbound, modes[i], pdfVal * 0.5, i, mixture);
375 if (highXFound && lowXFound) {
376 const double FWHM = highX - lowX;
377 modes[i + 5] = FWHM / 2.35482; // 2 * sqrt( 2* log(2))
378 }
379 // Ensure that phi is between -pi and pi
380 if (i == 2) {
381 modes[i] = CxxUtils::wrapToPi(modes[i]);
382 }
383 }
384 }
385 return modes;
386}
387} // end of anonymous namespace
388
389std::array<double, 10>
391 const Trk::MultiComponentState& multiComponentState)
392{
393 // Check to see if all components have covariance
394 if (!MultiComponentStateHelpers::allHaveCovariance(multiComponentState)) {
395 return {};
396 }
397 std::array<VecOfComponents, 5> mixture;
398
399 fillMixture(multiComponentState, mixture);
400 return evaluateMode(mixture);
401}
402
#define M_PI
Scalar deltaPhi(const MatrixBase< Derived > &vec) const
#define AmgSymMatrix(dim)
static Double_t a
const double width
#define x
#define z
void mean(std::vector< double > &bins, std::vector< double > &values, const std::vector< std::string > &files, const std::string &histname, const std::string &tplotname, const std::string &label="")
int r
Definition globals.cxx:22
float modes(const std::vector< float > &mus, const std::vector< float > &log_sigma2s, const std::vector< float > &alphas)
constexpr T wrapToPi(T phi)
Wrap angle in radians to [-pi, pi].
Definition phihelper.h:31
constexpr int8_t maxNumberofStateComponents
The state is described by N Gaussian components The Beth Heitler Material effect are also described b...
bool allHaveCovariance(const MultiComponentState &in)
Check to see if all components have covariance Matrix.
std::array< double, 10 > calculateMode(const MultiComponentState &)
Method to calculate mode with MultiComponentState state as input.
std::vector< ComponentParameters > MultiComponentState
ParamDefs
This file defines the parameter enums in the Trk namespace.
Definition ParamDefs.h:32
@ theta
Definition ParamDefs.h:66
@ qOverP
perigee
Definition ParamDefs.h:67
@ phi
Definition ParamDefs.h:75
@ d0
Definition ParamDefs.h:63
@ z0
Definition ParamDefs.h:64
ParametersBase< TrackParametersDim, Charged > TrackParameters
Helper for azimuthal angle calculations.
#define unlikely(x)
constexpr double tolerance