12#define BOOST_TEST_MODULE ITSMFTMaterialPhysics
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15#include <boost/test/unit_test.hpp>
34constexpr float AbsTol = 1.e-5f;
35constexpr float RelTol = 5.e-4f;
37bool closeTo(
float a,
float b,
float absTol = AbsTol,
float relTol = RelTol)
39 const float diff = std::fabs(
a -
b);
40 return diff <= absTol || diff <= relTol * std::fabs(
b);
46constexpr double kHighlandConst2 = 0.0136 * 0.0136;
47constexpr double kStragglingConst = 0.0007;
48constexpr float kMinMomentumGeV = 0.01f;
56 double momentumAfterGeV{};
57 double signedEnergyChangeGeV{};
58 double highlandTheta2Rad2{};
59 double relativeInverseMomentumVariance{};
61 bool requestedAboveCap{
false};
63 bool nonFinite{
false};
66Oracle referenceCharged(
double p0,
double mass,
double absCharge,
double xOverX0,
double arealDensity,
70 const double q2 = absCharge * absCharge;
71 const double e0 = std::sqrt(p0 * p0 + mass * mass);
72 const double beta2 = (p0 * p0) / (e0 * e0);
77 if (arealDensity > 0.) {
78 const double ekin = e0 - mass;
79 const double bg0 = p0 / mass;
80 const double dedx0 = o2::track::BetheBlochSolidOpt<double>(bg0) * q2;
81 const double fullStepLoss = dedx0 * arealDensity;
82 const double ratio = std::fabs(fullStepLoss) / ekin * o2::track::ELoss2EKinThreshInv;
83 if (!std::isfinite(ratio) || ratio >=
static_cast<double>(o2::track::MaxELossIter)) {
84 oracle.substeps =
static_cast<uint8_t
>(o2::track::MaxELossIter);
85 oracle.requestedAboveCap =
true;
87 oracle.substeps =
static_cast<uint8_t
>(1 +
static_cast<int>(ratio));
89 const double arealDensityStep = arealDensity /
static_cast<double>(oracle.substeps);
90 for (uint8_t
i = 0;
i < oracle.substeps; ++
i) {
91 const double bg = p / mass;
92 const double dedx = o2::track::BetheBlochSolidOpt<double>(bg) * q2;
93 const double dE = dedx * arealDensityStep;
94 e = alongMomentum ? (e - dE) : (e + dE);
95 if (!std::isfinite(e)) {
96 oracle.nonFinite =
true;
100 oracle.stopped =
true;
103 p = std::sqrt(e * e - mass * mass);
104 if (!std::isfinite(p)) {
105 oracle.nonFinite =
true;
111 oracle.momentumAfterGeV = p;
112 oracle.signedEnergyChangeGeV = e - e0;
113 oracle.highlandTheta2Rad2 = (xOverX0 > 0.) ? (kHighlandConst2 / (beta2 * p0 * p0) * xOverX0 * q2) : 0.;
114 oracle.relativeInverseMomentumVariance = (oracle.signedEnergyChangeGeV != 0.)
115 ? (kStragglingConst * kStragglingConst * std::fabs(oracle.signedEnergyChangeGeV) * e0 * e0 / (p0 * p0 * p0 * p0))
120float energyChange(
float before,
float after,
PID pid)
122 const double mass =
pid.getMass();
123 return std::sqrt(
static_cast<double>(after) * after + mass * mass) -
124 std::sqrt(
static_cast<double>(before) * before + mass * mass);
132 for (uint8_t
id = 0;
id < PID::NIDsTot; ++
id) {
134 if (
pid.getMass() == 0.f) {
138 float resultMomentum = 0.f;
139 float resultTheta2 = 0.f;
140 float resultVariance = 0.f;
142 BOOST_CHECK_MESSAGE(
result,
"PID id " <<
static_cast<int>(
id) <<
" failed");
149 for (uint8_t
id : {
static_cast<uint8_t
>(PID::NIDsTot),
static_cast<uint8_t
>(255)}) {
152 float chargedMomentum = 0.f;
153 float chargedTheta2 = 0.f;
154 float chargedVariance = 0.f;
155 const bool charged =
calculateMaterialPhysics(1.f,
pid, 1, MaterialTraversalDirection::AlongMomentum, material, chargedMomentum, chargedTheta2, chargedVariance);
169 float q1Momentum = 0.f;
170 float q1Theta2 = 0.f;
171 float q1Variance = 0.f;
172 const bool q1 =
calculateMaterialPhysics(2.f, PID::Electron, 1, MaterialTraversalDirection::AlongMomentum, material, q1Momentum, q1Theta2, q1Variance);
174 float q2resultMomentum = 0.f;
175 float q2resultTheta2 = 0.f;
176 float q2resultVariance = 0.f;
177 const bool q2result =
calculateMaterialPhysics(2.f, PID::Electron, 2, MaterialTraversalDirection::AlongMomentum, material, q2resultMomentum, q2resultTheta2, q2resultVariance);
179 BOOST_REQUIRE(q2result);
181 BOOST_CHECK(closeTo(q2resultTheta2, 4.f * q1Theta2));
187 for (uint8_t absCharge : {1, 2, 3, 255}) {
189 float resultMomentum = 0.f;
190 float resultTheta2 = 0.f;
191 float resultVariance = 0.f;
192 const bool result =
calculateMaterialPhysics(1.f, PID::Photon, absCharge, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
204 float baseMomentum = 0.f;
205 float baseTheta2 = 0.f;
206 float baseVariance = 0.f;
207 const bool base =
calculateMaterialPhysics(1.5f, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, baseMomentum, baseTheta2, baseVariance);
209 for (uint8_t absCharge : {2, 3, 200}) {
211 float resultMomentum = 0.f;
212 float resultTheta2 = 0.f;
213 float resultVariance = 0.f;
214 const bool result =
calculateMaterialPhysics(1.5f, PID::Pion, absCharge, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
216 const float expectedRatio =
static_cast<float>(absCharge) *
static_cast<float>(absCharge);
217 BOOST_CHECK(closeTo(resultTheta2, expectedRatio * baseTheta2));
224 for (uint8_t
raw : {2, 255}) {
227 float resultMomentum = 0.f;
228 float resultTheta2 = 0.f;
229 float resultVariance = 0.f;
240 const std::vector<IntegratedMaterialBudget> invalidMaterials = {
241 {-1.f, 0.1f}, {0.1f, -1.f}, {-1.f, -1.f}};
242 for (
auto material : invalidMaterials) {
244 float resultMomentum = 0.f;
245 float resultTheta2 = 0.f;
246 float resultVariance = 0.f;
247 const bool result =
calculateMaterialPhysics(1.f, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
258 for (
float momentum : {0.f, -1.f}) {
260 float resultMomentum = 0.f;
261 float resultTheta2 = 0.f;
262 float resultVariance = 0.f;
263 const bool result =
calculateMaterialPhysics(momentum, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
275 float resultMomentum = 0.f;
276 float resultTheta2 = 0.f;
277 float resultVariance = 0.f;
278 const bool result =
calculateMaterialPhysics(1.f, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
288 const float p0 = 2.f;
289 const float mass =
PID(PID::Pion).getMass();
292 float resultMomentum = 0.f;
293 float resultTheta2 = 0.f;
294 float resultVariance = 0.f;
295 const bool result =
calculateMaterialPhysics(p0, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
302 const double e0 = std::sqrt(
static_cast<double>(p0) * p0 +
static_cast<double>(mass) * mass);
303 const double beta2 = (
static_cast<double>(p0) * p0) / (e0 * e0);
304 const double expectedTheta2 = kHighlandConst2 / (beta2 * p0 * p0) * material.xOverX0;
305 BOOST_CHECK(closeTo(resultTheta2,
static_cast<float>(expectedTheta2)));
312 float resultMomentum = 0.f;
313 float resultTheta2 = 0.f;
314 float resultVariance = 0.f;
315 const bool result =
calculateMaterialPhysics(2.f, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
318 BOOST_CHECK_LT(resultMomentum, 2.f);
319 BOOST_CHECK_LT(energyChange(2.f, resultMomentum, PID::Pion), 0.f);
321 BOOST_CHECK_GT(resultVariance, 0.f);
326 const float p0 = 1.2f;
327 const PID pid = PID::Kaon;
328 const uint8_t absCharge = 1;
331 float resultMomentum = 0.f;
332 float resultTheta2 = 0.f;
333 float resultVariance = 0.f;
334 const bool result =
calculateMaterialPhysics(p0,
pid, absCharge, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
337 auto oracle = referenceCharged(p0,
pid.getMass(), absCharge, material.xOverX0, material.arealDensityGPerCm2,
true);
340 BOOST_CHECK(closeTo(resultMomentum,
static_cast<float>(oracle.momentumAfterGeV)));
341 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid),
static_cast<float>(oracle.signedEnergyChangeGeV)));
342 BOOST_CHECK(closeTo(resultTheta2,
static_cast<float>(oracle.highlandTheta2Rad2)));
343 BOOST_CHECK(closeTo(resultVariance,
static_cast<float>(oracle.relativeInverseMomentumVariance)));
348 const float p0 = 1.5f;
351 float lossMomentum = 0.f;
352 float lossTheta2 = 0.f;
353 float lossVariance = 0.f;
354 const bool loss =
calculateMaterialPhysics(p0, PID::Proton, 1, MaterialTraversalDirection::AlongMomentum, material, lossMomentum, lossTheta2, lossVariance);
356 float gainMomentum = 0.f;
357 float gainTheta2 = 0.f;
358 float gainVariance = 0.f;
359 const bool gain =
calculateMaterialPhysics(p0, PID::Proton, 1, MaterialTraversalDirection::OppositeMomentum, material, gainMomentum, gainTheta2, gainVariance);
363 BOOST_CHECK_LT(energyChange(p0, lossMomentum, PID::Proton), 0.f);
364 BOOST_CHECK_GT(energyChange(p0, gainMomentum, PID::Proton), 0.f);
365 BOOST_CHECK(closeTo(energyChange(p0, lossMomentum, PID::Proton), -energyChange(p0, gainMomentum, PID::Proton), AbsTol, 1.e-2f));
366 BOOST_CHECK_LT(lossMomentum, p0);
367 BOOST_CHECK_GT(gainMomentum, p0);
372 const float p0 = 1.f;
373 const PID pid = PID::Proton;
374 const double mass =
pid.getMass();
375 const double e0 = std::sqrt(
static_cast<double>(p0) * p0 + mass * mass);
376 const double ekin = e0 - mass;
377 const double bg0 = p0 / mass;
378 const double dedx0 = o2::track::BetheBlochSolidOpt<double>(bg0);
380 auto arealDensityForRatio = [&](
double ratio) {
381 return ratio * ekin / (o2::track::ELoss2EKinThreshInv * dedx0);
389 for (
const double ratio : {0.3, 5.5, 48.9, 49.5, 60.0, 1.e6}) {
392 float resultMomentum = 0.f;
393 float resultTheta2 = 0.f;
394 float resultVariance = 0.f;
396 BOOST_REQUIRE_MESSAGE(
result,
"unexpected failure for ratio " << ratio);
398 auto oracle = referenceCharged(p0, mass, 1., 0., material.arealDensityGPerCm2,
false);
399 BOOST_REQUIRE(!oracle.stopped && !oracle.nonFinite);
400 BOOST_CHECK(closeTo(resultMomentum,
static_cast<float>(oracle.momentumAfterGeV)));
409 const float p0 = 1.f;
410 const PID pid = PID::Proton;
413 float resultMomentum = 0.f;
414 float resultTheta2 = 0.f;
415 float resultVariance = 0.f;
419 auto oracle = referenceCharged(p0,
pid.getMass(), 1., 0., material.arealDensityGPerCm2,
false);
420 BOOST_REQUIRE(!oracle.stopped && !oracle.nonFinite);
422 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid),
static_cast<float>(oracle.signedEnergyChangeGeV), AbsTol, 2.e-3f));
430 const float p0 = 0.3f;
431 const PID pid = PID::Proton;
432 const double mass =
pid.getMass();
435 float resultMomentum = 0.f;
436 float resultTheta2 = 0.f;
437 float resultVariance = 0.f;
441 const double e0 = std::sqrt(
static_cast<double>(p0) * p0 + mass * mass);
442 const double bg0 = p0 / mass;
443 const double dedx0 = o2::track::BetheBlochSolidOpt<double>(bg0);
444 const double naiveEnergyAfter = e0 - dedx0 * material.arealDensityGPerCm2;
446 auto oracle = referenceCharged(p0, mass, 1., 0., material.arealDensityGPerCm2,
true);
447 BOOST_REQUIRE(!oracle.stopped && !oracle.nonFinite);
448 const double recomputedEnergyAfter = e0 + oracle.signedEnergyChangeGeV;
450 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid),
static_cast<float>(oracle.signedEnergyChangeGeV)));
451 BOOST_CHECK_GT(std::fabs(recomputedEnergyAfter - naiveEnergyAfter), 1.e-4);
458 float resultMomentum = 0.f;
459 float resultTheta2 = 0.f;
460 float resultVariance = 0.f;
461 const bool result =
calculateMaterialPhysics(0.5f, PID::Proton, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
472 float atThresholdMomentum = 0.f;
473 float atThresholdTheta2 = 0.f;
474 float atThresholdVariance = 0.f;
475 const bool atThreshold =
calculateMaterialPhysics(kMinMomentumGeV, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, atThresholdMomentum, atThresholdTheta2, atThresholdVariance);
476 BOOST_REQUIRE(atThreshold);
479 float belowThresholdMomentum = 0.f;
480 float belowThresholdTheta2 = 0.f;
481 float belowThresholdVariance = 0.f;
483 MaterialTraversalDirection::AlongMomentum, material, belowThresholdMomentum, belowThresholdTheta2, belowThresholdVariance);
494 float resultMomentum = 0.f;
495 float resultTheta2 = 0.f;
496 float resultVariance = 0.f;
497 const bool result =
calculateMaterialPhysics(0.1f, PID::Pion, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
512 const float p0 = 1.f;
515 float resultMomentum = 0.f;
516 float resultTheta2 = 0.f;
517 float resultVariance = 0.f;
518 const bool result =
calculateMaterialPhysics(p0, PID::Proton, 1, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
524 float repeatMomentum = 0.f;
525 float repeatTheta2 = 0.f;
526 float repeatVariance = 0.f;
527 const bool repeat =
calculateMaterialPhysics(p0, PID::Proton, 1, MaterialTraversalDirection::AlongMomentum, material, repeatMomentum, repeatTheta2, repeatVariance);
529 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(resultMomentum), std::bit_cast<uint32_t>(repeatMomentum));
530 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(resultTheta2), std::bit_cast<uint32_t>(repeatTheta2));
531 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(resultVariance), std::bit_cast<uint32_t>(repeatVariance));
536 const float p0 = 1.f;
537 const PID pid = PID::Proton;
538 const double mass =
pid.getMass();
541 float resultMomentum = 0.f;
542 float resultTheta2 = 0.f;
543 float resultVariance = 0.f;
547 const double e0 = std::sqrt(
static_cast<double>(p0) * p0 + mass * mass);
548 const double bg0 = p0 / mass;
549 const double dedx = o2::track::BetheBlochSolidOpt<double>(bg0);
550 const double expectedEnergyAfter = e0 - dedx * material.arealDensityGPerCm2;
551 const double expectedSignedChange = expectedEnergyAfter - e0;
552 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid),
static_cast<float>(expectedSignedChange)));
562 const float p0 = 1.f;
563 const PID pid = PID::Electron;
564 const double mass =
pid.getMass();
567 const double e0 = std::sqrt(
static_cast<double>(p0) * p0 + mass * mass);
568 const double bg0 = p0 / mass;
569 const double dedxUnit = o2::track::BetheBlochSolidOpt<double>(bg0);
571 float baseSignedChange = 0.f;
572 float baseVariance = 0.f;
573 for (uint8_t absCharge : {1, 2, 3}) {
575 float resultMomentum = 0.f;
576 float resultTheta2 = 0.f;
577 float resultVariance = 0.f;
578 const bool result =
calculateMaterialPhysics(p0,
pid, absCharge, MaterialTraversalDirection::AlongMomentum, material, resultMomentum, resultTheta2, resultVariance);
581 const double q2 =
static_cast<double>(absCharge) * absCharge;
582 const double expectedDE = dedxUnit * q2 * material.arealDensityGPerCm2;
583 const double expectedEnergyAfter = e0 - expectedDE;
584 const double expectedSignedChange = expectedEnergyAfter - e0;
585 const double expectedMomentumAfter = std::sqrt(expectedEnergyAfter * expectedEnergyAfter - mass * mass);
586 const double expectedVariance = kStragglingConst * kStragglingConst * std::fabs(expectedSignedChange) * e0 * e0 /
587 (
static_cast<double>(p0) * p0 * p0 * p0);
589 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid),
static_cast<float>(expectedSignedChange)));
590 BOOST_CHECK(closeTo(resultMomentum,
static_cast<float>(expectedMomentumAfter)));
591 BOOST_CHECK(closeTo(resultVariance,
static_cast<float>(expectedVariance)));
593 if (absCharge == 1) {
594 baseSignedChange = energyChange(p0, resultMomentum,
pid);
595 baseVariance = resultVariance;
597 const float q2f =
static_cast<float>(absCharge) *
static_cast<float>(absCharge);
598 BOOST_CHECK(closeTo(energyChange(p0, resultMomentum,
pid), q2f * baseSignedChange));
599 BOOST_CHECK(closeTo(resultVariance, q2f * baseVariance));
608 float aMomentum = 0.f;
610 float aVariance = 0.f;
611 const bool a =
calculateMaterialPhysics(1.3f, PID::Kaon, 1, MaterialTraversalDirection::AlongMomentum, material, aMomentum, aTheta2, aVariance);
613 float bMomentum = 0.f;
615 float bVariance = 0.f;
616 const bool b =
calculateMaterialPhysics(1.3f, PID::Kaon, 1, MaterialTraversalDirection::AlongMomentum, material, bMomentum, bTheta2, bVariance);
618 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(aMomentum), std::bit_cast<uint32_t>(bMomentum));
619 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(aTheta2), std::bit_cast<uint32_t>(bTheta2));
620 BOOST_CHECK_EQUAL(std::bit_cast<uint32_t>(aVariance), std::bit_cast<uint32_t>(bVariance));
o2::raw::RawFileWriter * raw
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
bool calculateMaterialPhysics(float momentumGeV, o2::track::PID pid, uint8_t absCharge, MaterialTraversalDirection direction, IntegratedMaterialBudget material, float &momentumAfterGeV, float &highlandTheta2Rad2, float &relativeInverseMomentumVariance) noexcept
MaterialTraversalDirection
BOOST_AUTO_TEST_CASE(EveryValidMassivePidIdChargedSucceeds)
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())