1#include <gtest/gtest.h>
24 class DecayTest :
public ::testing::Test {
26 static void SetUpTestSuite() {
28 char** argv =
nullptr;
29 ippl::initialize(argc, argv);
32 static void TearDownTestSuite() { ippl::finalize(); }
34 void SetUp()
override {
40 void TearDown()
override {
46 ippl::Vector<int, 3>
nr = 8;
47 ippl::Vector<double, 3> rmin = -4.0;
48 ippl::Vector<double, 3> rmax = 4.0;
49 ippl::Vector<double, 3> origin = rmin;
50 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(
nr);
51 std::array<bool, 3> decomp = {
true,
true,
true};
53 ippl::NDIndex<3> domain;
54 for (
unsigned i = 0; i < 3; ++i) {
55 domain[i] = ippl::Index(
nr[i]);
60 auto pc = std::make_shared<PC_t>(mesh, fl);
61 pc->Sp =
static_cast<short>(species);
62 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
66 void createParticles(std::shared_ptr<PC_t>& pc,
size_t nPart,
double pz) {
71 pc->createParticles(nPart);
72 auto R_host = pc->R.getHostMirror();
73 auto P_host = pc->P.getHostMirror();
75 for (
size_t i = 0; i < nPart; ++i) {
84 Kokkos::deep_copy(pc->R.getView(), R_host);
85 Kokkos::deep_copy(pc->P.getView(), P_host);
89 void createParticlesWithPositions(std::shared_ptr<PC_t>& pc,
size_t nPart,
double pz) {
94 pc->createParticles(nPart);
95 auto R_host = pc->R.getHostMirror();
96 auto P_host = pc->P.getHostMirror();
98 for (
size_t i = 0; i < nPart; ++i) {
99 R_host(i)[0] = 0.1 *
static_cast<double>(i);
100 R_host(i)[1] = 0.2 *
static_cast<double>(i);
101 R_host(i)[2] = 0.3 *
static_cast<double>(i);
107 Kokkos::deep_copy(pc->R.getView(), R_host);
108 Kokkos::deep_copy(pc->P.getView(), P_host);
112 std::pair<size_t, size_t> runDecay(
113 size_t nPart,
double pz,
int seed,
size_t containerIndex,
double tau0,
double dt) {
114 auto pc = makeContainer();
115 createParticles(pc, nPart, pz);
116 const size_t before = pc->getTotalNum();
120 const size_t marked = decay.apply(*pc, dt, 0, containerIndex);
121 const size_t destroyed = pc->deleteInvalidParticles();
122 const size_t after = pc->getTotalNum();
124 EXPECT_EQ(marked, destroyed);
125 EXPECT_EQ(before, after + destroyed);
126 return {destroyed, after};
131 bool oldUseQMAttributes_m =
false;
138 TEST_F(DecayTest, ApplyReturnsZeroForNonPositiveDt) {
139 auto pc = makeContainer();
140 createParticles(pc, 64, 0.0);
141 const size_t before = pc->getTotalNum();
144 EXPECT_EQ(decay.apply(*pc, 0.0, 7, 2), 0u);
145 EXPECT_EQ(decay.apply(*pc, -1.0, 7, 2), 0u);
146 EXPECT_EQ(pc->getTotalNum(), before);
149 TEST_F(DecayTest, ApplyReturnsZeroForInvalidLifetime) {
150 const std::array<double, 4> invalidTau = {
151 0.0, -1.0, std::numeric_limits<double>::quiet_NaN(),
152 std::numeric_limits<double>::infinity()};
154 for (
double tau0 : invalidTau) {
155 auto pc = makeContainer();
156 createParticles(pc, 32, 0.0);
157 const size_t before = pc->getTotalNum();
160 EXPECT_EQ(decay.apply(*pc, 1.0, 0, 1), 0u);
161 EXPECT_EQ(pc->getTotalNum(), before);
165 TEST_F(DecayTest, ApplyReturnsZeroForEmptyContainer) {
166 auto pc = makeContainer();
167 ASSERT_EQ(pc->getLocalNum(), 0u);
170 EXPECT_EQ(decay.apply(*pc, 1.0, 0, 0), 0u);
171 EXPECT_EQ(pc->getTotalNum(), 0u);
174 TEST_F(DecayTest, LargeDtDecaysAllRestParticles) {
175 auto [destroyed, remaining] = runDecay(256, 0.0, 1234, 0, 1.0e-12, 1.0);
177 EXPECT_EQ(destroyed, 256u);
178 EXPECT_EQ(remaining, 0u);
181 TEST_F(DecayTest, HigherMomentumDecaysFewerParticles) {
182 constexpr size_t nPart = 2000;
183 auto [lowMomentumDestroyed, lowMomentumRemaining] = runDecay(nPart, 0.0, 77, 3, 1.0, 1.0);
184 auto [highMomentumDestroyed, highMomentumRemaining] =
185 runDecay(nPart, 10.0, 77, 3, 1.0, 1.0);
187 EXPECT_GT(lowMomentumDestroyed, highMomentumDestroyed);
188 EXPECT_GT(lowMomentumDestroyed, nPart / 3);
189 EXPECT_LT(highMomentumDestroyed, nPart / 3);
190 EXPECT_LT(lowMomentumRemaining, highMomentumRemaining);
193 TEST_F(DecayTest, SameSeedAndInputsAreReproducible) {
194 auto [firstDestroyed, firstRemaining] = runDecay(512, 0.5, 98765, 4, 2.0, 0.5);
195 auto [secondDestroyed, secondRemaining] = runDecay(512, 0.5, 98765, 4, 2.0, 0.5);
197 EXPECT_EQ(firstDestroyed, secondDestroyed);
198 EXPECT_EQ(firstRemaining, secondRemaining);
205 TEST_F(DecayTest, NoDaughterContainerStillDestroysOnly) {
206 auto muons = makeContainer();
208 createParticles(muons, 128, 0.0);
213 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
214 const size_t destroyed = muons->deleteInvalidParticles();
215 EXPECT_EQ(destroyed, marked);
216 EXPECT_EQ(destroyed, 128u);
217 EXPECT_EQ(muons->getTotalNum(), 0u);
224 TEST_F(DecayTest, DaughterContainerReceivesDecayedParticles) {
225 auto muons = makeContainer();
229 createParticles(muons, 256, 0.0);
235 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
236 const size_t destroyed = muons->deleteInvalidParticles();
237 EXPECT_EQ(destroyed, marked);
238 EXPECT_EQ(destroyed, 256u);
239 EXPECT_EQ(muons->getTotalNum(), 0u);
240 EXPECT_EQ(electrons->getTotalNum(), 256u);
243 TEST_F(DecayTest, DaughterPositionMatchesParent) {
244 constexpr size_t nPart = 64;
245 auto muons = makeContainer();
249 createParticlesWithPositions(muons, nPart, 0.0);
252 auto muR_host = muons->R.getHostMirror();
253 Kokkos::deep_copy(muR_host, muons->R.getView());
259 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
260 const size_t destroyed = muons->deleteInvalidParticles();
261 EXPECT_EQ(destroyed, marked);
262 ASSERT_EQ(destroyed, nPart);
263 ASSERT_EQ(electrons->getLocalNum(), nPart);
265 auto eR_host = electrons->R.getHostMirror();
266 Kokkos::deep_copy(eR_host, electrons->R.getView());
268 for (
size_t i = 0; i < nPart; ++i) {
269 EXPECT_DOUBLE_EQ(eR_host(i)[0], muR_host(i)[0]);
270 EXPECT_DOUBLE_EQ(eR_host(i)[1], muR_host(i)[1]);
271 EXPECT_DOUBLE_EQ(eR_host(i)[2], muR_host(i)[2]);
275 TEST_F(DecayTest, DaughterMomentumIsPhysicalForRestMuons) {
276 constexpr size_t nPart = 1024;
277 auto muons = makeContainer();
281 createParticles(muons, nPart, 0.0);
287 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
288 const size_t destroyed = muons->deleteInvalidParticles();
289 EXPECT_EQ(destroyed, marked);
290 ASSERT_EQ(destroyed, nPart);
291 ASSERT_EQ(electrons->getLocalNum(), nPart);
293 auto eP_host = electrons->P.getHostMirror();
294 Kokkos::deep_copy(eP_host, electrons->P.getView());
298 for (
size_t i = 0; i < nPart; ++i) {
300 const double bg2 = eP_host(i)[0] * eP_host(i)[0] + eP_host(i)[1] * eP_host(i)[1]
301 + eP_host(i)[2] * eP_host(i)[2];
302 const double gamma = std::sqrt(1.0 + bg2);
307 EXPECT_LE(energy, eMax + 1.0e-12);
315 TEST_F(DecayTest, BoostedMuonsProduceBoostedElectrons) {
316 constexpr size_t nPart = 512;
317 auto muons = makeContainer();
321 createParticles(muons, nPart, 5.0);
327 const size_t marked = decay.apply(*muons, 10.0, 0, 0);
328 const size_t destroyed = muons->deleteInvalidParticles();
329 EXPECT_EQ(destroyed, marked);
330 ASSERT_GT(destroyed, 0u);
331 ASSERT_EQ(electrons->getLocalNum(), destroyed);
333 auto eP_host = electrons->P.getHostMirror();
334 Kokkos::deep_copy(eP_host, electrons->P.getView());
336 for (
size_t i = 0; i < destroyed; ++i) {
337 const double bg2 = eP_host(i)[0] * eP_host(i)[0] + eP_host(i)[1] * eP_host(i)[1]
338 + eP_host(i)[2] * eP_host(i)[2];
339 const double gamma = std::sqrt(1.0 + bg2);
347 EXPECT_GT(energy, 0.0);
348 EXPECT_TRUE(std::isfinite(energy));
352 TEST_F(DecayTest, DaughterReproducibleWithSameSeed) {
353 auto runWithDaughter = [&](
int seed) {
354 auto muons = makeContainer();
358 createParticles(muons, 256, 1.0);
363 decay.apply(*muons, 1.0, 0, 0);
364 muons->deleteInvalidParticles();
367 auto eP_host = electrons->P.getHostMirror();
368 Kokkos::deep_copy(eP_host, electrons->P.getView());
370 std::vector<std::array<double, 3>> momenta(electrons->getLocalNum());
371 for (
size_t i = 0; i < momenta.size(); ++i) {
372 momenta[i] = {eP_host(i)[0], eP_host(i)[1], eP_host(i)[2]};
377 auto first = runWithDaughter(12345);
378 auto second = runWithDaughter(12345);
380 ASSERT_EQ(first.size(), second.size());
381 for (
size_t i = 0; i < first.size(); ++i) {
382 EXPECT_DOUBLE_EQ(first[i][0], second[i][0]);
383 EXPECT_DOUBLE_EQ(first[i][1], second[i][1]);
384 EXPECT_DOUBLE_EQ(first[i][2], second[i][2]);
388 TEST_F(DecayTest, PartialDecayCreatesDaughtersOnlyForDecayed) {
389 constexpr size_t nPart = 1000;
390 auto muons = makeContainer();
394 createParticles(muons, nPart, 0.5);
400 const size_t marked = decay.apply(*muons, 0.5, 0, 0);
401 const size_t destroyed = muons->deleteInvalidParticles();
402 EXPECT_EQ(destroyed, marked);
403 EXPECT_GT(destroyed, 0u);
404 EXPECT_LT(destroyed, nPart);
405 EXPECT_EQ(muons->getTotalNum(), nPart - destroyed);
406 EXPECT_EQ(electrons->getTotalNum(), destroyed);
409 TEST_F(DecayTest, ApplyMarksInvalidMaskBeforeDeletion) {
410 constexpr size_t nPart = 16;
411 auto muons = makeContainer();
413 createParticles(muons, nPart, 0.0);
418 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
419 ASSERT_EQ(marked, nPart);
420 EXPECT_EQ(muons->getTotalNum(), nPart);
422 auto invalidHost = Kokkos::create_mirror_view_and_copy(
423 Kokkos::HostSpace(), muons->InvalidMask.getView());
424 for (
size_t i = 0; i < nPart; ++i) {
425 EXPECT_TRUE(invalidHost(i));
428 const size_t destroyed = muons->deleteInvalidParticles();
429 EXPECT_EQ(destroyed, nPart);
430 EXPECT_EQ(muons->getTotalNum(), 0u);
437 TEST_F(DecayTest, PionDecayDaughterCountMatchesDestroyed) {
438 auto pions = makeContainer();
442 createParticles(pions, 256, 0.0);
448 const size_t marked = decay.apply(*pions, 1.0, 0, 0);
449 const size_t destroyed = pions->deleteInvalidParticles();
450 EXPECT_EQ(destroyed, marked);
451 EXPECT_EQ(destroyed, 256u);
452 EXPECT_EQ(pions->getTotalNum(), 0u);
453 EXPECT_EQ(muons->getTotalNum(), 256u);
456 TEST_F(DecayTest, PionDecayTwoBodyFixedMomentum) {
457 constexpr size_t nPart = 512;
458 auto pions = makeContainer();
462 createParticles(pions, nPart, 0.0);
468 const size_t marked = decay.apply(*pions, 1.0, 0, 0);
469 const size_t destroyed = pions->deleteInvalidParticles();
470 EXPECT_EQ(destroyed, marked);
471 ASSERT_EQ(destroyed, nPart);
472 ASSERT_EQ(muons->getLocalNum(), nPart);
474 auto muP_host = muons->P.getHostMirror();
475 Kokkos::deep_copy(muP_host, muons->P.getView());
483 for (
size_t i = 0; i < nPart; ++i) {
484 const double bg2 = muP_host(i)[0] * muP_host(i)[0] + muP_host(i)[1] * muP_host(i)[1]
485 + muP_host(i)[2] * muP_host(i)[2];
486 const double bg = std::sqrt(bg2);
489 EXPECT_NEAR(bg, bgExpected, 1.0e-12);
493 bool foundDifferent =
false;
494 for (
size_t i = 1; i < nPart; ++i) {
495 if (std::abs(muP_host(i)[0] - muP_host(0)[0]) > 1.0e-14
496 || std::abs(muP_host(i)[1] - muP_host(0)[1]) > 1.0e-14) {
497 foundDifferent =
true;
501 EXPECT_TRUE(foundDifferent);
504 TEST_F(DecayTest, PionDecayBoostedConservesEnergy) {
505 constexpr size_t nPart = 512;
506 auto pions = makeContainer();
510 createParticles(pions, nPart, 3.0);
516 const size_t marked = decay.apply(*pions, 10.0, 0, 0);
517 const size_t destroyed = pions->deleteInvalidParticles();
518 EXPECT_EQ(destroyed, marked);
519 ASSERT_GT(destroyed, 0u);
520 ASSERT_EQ(muons->getLocalNum(), destroyed);
522 auto muP_host = muons->P.getHostMirror();
523 Kokkos::deep_copy(muP_host, muons->P.getView());
525 for (
size_t i = 0; i < destroyed; ++i) {
526 const double bg2 = muP_host(i)[0] * muP_host(i)[0] + muP_host(i)[1] * muP_host(i)[1]
527 + muP_host(i)[2] * muP_host(i)[2];
528 const double gamma = std::sqrt(1.0 + bg2);
535 EXPECT_GT(energy, 0.0);
536 EXPECT_TRUE(std::isfinite(energy));
544 TEST_F(DecayTest, SetDaughterContainerAcceptsCorrectSpecies) {
547 EXPECT_NO_THROW(muonDecay.setDaughterContainer(electrons,
Physics::m_e));
551 EXPECT_NO_THROW(pionDecay.setDaughterContainer(muons,
Physics::m_mu));
554 TEST_F(DecayTest, SetDaughterContainerRejectsWrongSpecies) {
561 EXPECT_THROW(decay.setDaughterContainer(photonDaughter, 0.0),
OpalException);
567 TEST_F(DecayTest, SetDaughterContainerRejectsUnnamedSpecies) {
575 TEST_F(DecayTest, PionDecayRejectsElectronDaughter) {
582 TEST_F(DecayTest, SetDaughterContainerAcceptsNullForDestroyOnly) {
583 auto muons = makeContainer();
585 createParticles(muons, 16, 0.0);
589 EXPECT_NO_THROW(decay.setDaughterContainer(
nullptr, 0.0));
593 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
594 const size_t destroyed = muons->deleteInvalidParticles();
595 EXPECT_EQ(destroyed, marked);
596 EXPECT_EQ(destroyed, 16u);
ippl::FieldLayout< Dim > FieldLayout_t
ippl::UniformCartesian< double, 3 > Mesh_t
TEST_F(MonitorTest, GetType)
Muon decay: mu -> e + nu_e + nu_mu (three-body).
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
Charged pion decay: pi -> mu + nu_mu (two-body).
int seed
The current random seed.
KOKKOS_INLINE_FUNCTION constexpr double maxElectronEnergy()
Maximum electron energy in the muon rest frame [GeV]: .
constexpr double m_pi
The charged pion rest mass in GeV (PDG)
constexpr double m_p
The proton rest mass in GeV.
constexpr double m_e
The electron rest mass in GeV.
constexpr double m_mu
The muon rest mass in GeV.