OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestDecay.cpp
Go to the documentation of this file.
1#include <gtest/gtest.h>
2#include <mpi.h>
3
4#include <array>
5#include <cmath>
6#include <limits>
7#include <memory>
8#include <utility>
9
10#include "Ippl.h"
12#include "Physics/MuonDecay.h"
14#include "Physics/Physics.h"
18#include "Utilities/Options.h"
19
20namespace {
21
23
24 class DecayTest : public ::testing::Test {
25 protected:
26 static void SetUpTestSuite() {
27 int argc = 0;
28 char** argv = nullptr;
29 ippl::initialize(argc, argv);
30 }
31
32 static void TearDownTestSuite() { ippl::finalize(); }
33
34 void SetUp() override {
35 oldSeed_m = Options::seed;
36 oldUseQMAttributes_m = Options::useQMAttributes;
38 }
39
40 void TearDown() override {
41 Options::seed = oldSeed_m;
42 Options::useQMAttributes = oldUseQMAttributes_m;
43 }
44
45 std::shared_ptr<PC_t> makeContainer(ParticleType species = ParticleType::UNNAMED) {
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};
52
53 ippl::NDIndex<3> domain;
54 for (unsigned i = 0; i < 3; ++i) {
55 domain[i] = ippl::Index(nr[i]);
56 }
57
58 Mesh_t<3> mesh(domain, hr, origin);
59 FieldLayout_t<3> fl(MPI_COMM_WORLD, domain, decomp, true);
60 auto pc = std::make_shared<PC_t>(mesh, fl);
61 pc->Sp = static_cast<short>(species);
62 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
63 return pc;
64 }
65
66 void createParticles(std::shared_ptr<PC_t>& pc, size_t nPart, double pz) {
67 if (nPart == 0) {
68 return;
69 }
70
71 pc->createParticles(nPart);
72 auto R_host = pc->R.getHostMirror();
73 auto P_host = pc->P.getHostMirror();
74
75 for (size_t i = 0; i < nPart; ++i) {
76 R_host(i)[0] = 0.0;
77 R_host(i)[1] = 0.0;
78 R_host(i)[2] = 0.0;
79 P_host(i)[0] = 0.0;
80 P_host(i)[1] = 0.0;
81 P_host(i)[2] = pz;
82 }
83
84 Kokkos::deep_copy(pc->R.getView(), R_host);
85 Kokkos::deep_copy(pc->P.getView(), P_host);
86 Kokkos::fence();
87 }
88
89 void createParticlesWithPositions(std::shared_ptr<PC_t>& pc, size_t nPart, double pz) {
90 if (nPart == 0) {
91 return;
92 }
93
94 pc->createParticles(nPart);
95 auto R_host = pc->R.getHostMirror();
96 auto P_host = pc->P.getHostMirror();
97
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);
102 P_host(i)[0] = 0.0;
103 P_host(i)[1] = 0.0;
104 P_host(i)[2] = pz;
105 }
106
107 Kokkos::deep_copy(pc->R.getView(), R_host);
108 Kokkos::deep_copy(pc->P.getView(), P_host);
109 Kokkos::fence();
110 }
111
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();
117
119 MuonDecay decay(tau0, containerIndex, Physics::m_mu, -1);
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();
123
124 EXPECT_EQ(marked, destroyed);
125 EXPECT_EQ(before, after + destroyed);
126 return {destroyed, after};
127 }
128
129 private:
130 int oldSeed_m = -1;
131 bool oldUseQMAttributes_m = false;
132 };
133
134 // =====================================================================
135 // Original destroy-only tests
136 // =====================================================================
137
138 TEST_F(DecayTest, ApplyReturnsZeroForNonPositiveDt) {
139 auto pc = makeContainer();
140 createParticles(pc, 64, 0.0);
141 const size_t before = pc->getTotalNum();
142
143 MuonDecay decay(1.0, 0, Physics::m_mu, -1);
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);
147 }
148
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()};
153
154 for (double tau0 : invalidTau) {
155 auto pc = makeContainer();
156 createParticles(pc, 32, 0.0);
157 const size_t before = pc->getTotalNum();
158
159 MuonDecay decay(tau0, 1, Physics::m_mu, -1);
160 EXPECT_EQ(decay.apply(*pc, 1.0, 0, 1), 0u);
161 EXPECT_EQ(pc->getTotalNum(), before);
162 }
163 }
164
165 TEST_F(DecayTest, ApplyReturnsZeroForEmptyContainer) {
166 auto pc = makeContainer();
167 ASSERT_EQ(pc->getLocalNum(), 0u);
168
169 MuonDecay decay(1.0, 0, Physics::m_mu, -1);
170 EXPECT_EQ(decay.apply(*pc, 1.0, 0, 0), 0u);
171 EXPECT_EQ(pc->getTotalNum(), 0u);
172 }
173
174 TEST_F(DecayTest, LargeDtDecaysAllRestParticles) {
175 auto [destroyed, remaining] = runDecay(256, 0.0, 1234, 0, 1.0e-12, 1.0);
176
177 EXPECT_EQ(destroyed, 256u);
178 EXPECT_EQ(remaining, 0u);
179 }
180
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);
186
187 EXPECT_GT(lowMomentumDestroyed, highMomentumDestroyed);
188 EXPECT_GT(lowMomentumDestroyed, nPart / 3);
189 EXPECT_LT(highMomentumDestroyed, nPart / 3);
190 EXPECT_LT(lowMomentumRemaining, highMomentumRemaining);
191 }
192
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);
196
197 EXPECT_EQ(firstDestroyed, secondDestroyed);
198 EXPECT_EQ(firstRemaining, secondRemaining);
199 }
200
201 // =====================================================================
202 // Destroy-only mode preserved when no daughter container is set
203 // =====================================================================
204
205 TEST_F(DecayTest, NoDaughterContainerStillDestroysOnly) {
206 auto muons = makeContainer();
207 muons->setM(Physics::m_mu);
208 createParticles(muons, 128, 0.0);
209
210 Options::seed = 42;
211 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
212 // No setDaughterContainer call.
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);
218 }
219
220 // =====================================================================
221 // Multi-container daughter generation tests (muon -> electron)
222 // =====================================================================
223
224 TEST_F(DecayTest, DaughterContainerReceivesDecayedParticles) {
225 auto muons = makeContainer();
226 auto electrons = makeContainer(ParticleType::ELECTRON);
227 muons->setM(Physics::m_mu);
228 electrons->setM(Physics::m_e);
229 createParticles(muons, 256, 0.0);
230
231 Options::seed = 1234;
232 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
233 decay.setDaughterContainer(electrons, Physics::m_e);
234
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);
241 }
242
243 TEST_F(DecayTest, DaughterPositionMatchesParent) {
244 constexpr size_t nPart = 64;
245 auto muons = makeContainer();
246 auto electrons = makeContainer(ParticleType::ELECTRON);
247 muons->setM(Physics::m_mu);
248 electrons->setM(Physics::m_e);
249 createParticlesWithPositions(muons, nPart, 0.0);
250
251 // Copy muon positions before decay.
252 auto muR_host = muons->R.getHostMirror();
253 Kokkos::deep_copy(muR_host, muons->R.getView());
254
255 Options::seed = 999;
256 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
257 decay.setDaughterContainer(electrons, Physics::m_e);
258
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);
264
265 auto eR_host = electrons->R.getHostMirror();
266 Kokkos::deep_copy(eR_host, electrons->R.getView());
267
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]);
272 }
273 }
274
275 TEST_F(DecayTest, DaughterMomentumIsPhysicalForRestMuons) {
276 constexpr size_t nPart = 1024;
277 auto muons = makeContainer();
278 auto electrons = makeContainer(ParticleType::ELECTRON);
279 muons->setM(Physics::m_mu);
280 electrons->setM(Physics::m_e);
281 createParticles(muons, nPart, 0.0); // muons at rest
282
283 Options::seed = 555;
284 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
285 decay.setDaughterContainer(electrons, Physics::m_e);
286
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);
292
293 auto eP_host = electrons->P.getHostMirror();
294 Kokkos::deep_copy(eP_host, electrons->P.getView());
295
296 const double eMax = Physics::MuonDecay::maxElectronEnergy(); // m_mu / 2
297
298 for (size_t i = 0; i < nPart; ++i) {
299 // P is stored as beta*gamma. Physical momentum = P * m_e.
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);
303 const double energy = gamma * Physics::m_e; // [GeV]
304
305 // Electron energy must be between m_e and m_mu/2.
306 EXPECT_GE(energy, Physics::m_e - 1.0e-12);
307 EXPECT_LE(energy, eMax + 1.0e-12);
308
309 // Verify E^2 = p^2 + m_e^2 (consistency of beta*gamma storage).
310 const double p2 = bg2 * Physics::m_e * Physics::m_e;
311 EXPECT_NEAR(energy * energy, p2 + Physics::m_e * Physics::m_e, 1.0e-15);
312 }
313 }
314
315 TEST_F(DecayTest, BoostedMuonsProduceBoostedElectrons) {
316 constexpr size_t nPart = 512;
317 auto muons = makeContainer();
318 auto electrons = makeContainer(ParticleType::ELECTRON);
319 muons->setM(Physics::m_mu);
320 electrons->setM(Physics::m_e);
321 createParticles(muons, nPart, 5.0); // boosted muons (pz = 5 in beta*gamma)
322
323 Options::seed = 777;
324 MuonDecay decay(1.0e-6, 0, Physics::m_mu, -1);
325 decay.setDaughterContainer(electrons, Physics::m_e);
326
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);
332
333 auto eP_host = electrons->P.getHostMirror();
334 Kokkos::deep_copy(eP_host, electrons->P.getView());
335
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);
340 const double energy = gamma * Physics::m_e;
341
342 // E^2 = p^2 + m_e^2 must hold.
343 const double p2 = bg2 * Physics::m_e * Physics::m_e;
344 EXPECT_NEAR(energy * energy, p2 + Physics::m_e * Physics::m_e, 1.0e-15);
345
346 // Electron energy should be positive and finite.
347 EXPECT_GT(energy, 0.0);
348 EXPECT_TRUE(std::isfinite(energy));
349 }
350 }
351
352 TEST_F(DecayTest, DaughterReproducibleWithSameSeed) {
353 auto runWithDaughter = [&](int seed) {
354 auto muons = makeContainer();
355 auto electrons = makeContainer(ParticleType::ELECTRON);
356 muons->setM(Physics::m_mu);
357 electrons->setM(Physics::m_e);
358 createParticles(muons, 256, 1.0);
359
361 MuonDecay decay(1.0e-6, 0, Physics::m_mu, -1);
362 decay.setDaughterContainer(electrons, Physics::m_e);
363 decay.apply(*muons, 1.0, 0, 0);
364 muons->deleteInvalidParticles();
365
366 // Read back electron momenta.
367 auto eP_host = electrons->P.getHostMirror();
368 Kokkos::deep_copy(eP_host, electrons->P.getView());
369
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]};
373 }
374 return momenta;
375 };
376
377 auto first = runWithDaughter(12345);
378 auto second = runWithDaughter(12345);
379
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]);
385 }
386 }
387
388 TEST_F(DecayTest, PartialDecayCreatesDaughtersOnlyForDecayed) {
389 constexpr size_t nPart = 1000;
390 auto muons = makeContainer();
391 auto electrons = makeContainer(ParticleType::ELECTRON);
392 muons->setM(Physics::m_mu);
393 electrons->setM(Physics::m_e);
394 createParticles(muons, nPart, 0.5);
395
396 Options::seed = 303;
397 MuonDecay decay(2.0, 0, Physics::m_mu, -1); // long lifetime, moderate dt -> partial decay
398 decay.setDaughterContainer(electrons, Physics::m_e);
399
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);
407 }
408
409 TEST_F(DecayTest, ApplyMarksInvalidMaskBeforeDeletion) {
410 constexpr size_t nPart = 16;
411 auto muons = makeContainer();
412 muons->setM(Physics::m_mu);
413 createParticles(muons, nPart, 0.0);
414
415 Options::seed = 404;
416 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
417
418 const size_t marked = decay.apply(*muons, 1.0, 0, 0);
419 ASSERT_EQ(marked, nPart);
420 EXPECT_EQ(muons->getTotalNum(), nPart);
421
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));
426 }
427
428 const size_t destroyed = muons->deleteInvalidParticles();
429 EXPECT_EQ(destroyed, nPart);
430 EXPECT_EQ(muons->getTotalNum(), 0u);
431 }
432
433 // =====================================================================
434 // Pion decay tests (2-body: pi -> mu + nu)
435 // =====================================================================
436
437 TEST_F(DecayTest, PionDecayDaughterCountMatchesDestroyed) {
438 auto pions = makeContainer();
439 auto muons = makeContainer(ParticleType::MUON);
440 pions->setM(Physics::m_pi);
441 muons->setM(Physics::m_mu);
442 createParticles(pions, 256, 0.0);
443
444 Options::seed = 1234;
445 PionDecay decay(1.0e-12, 0, Physics::m_pi, +1); // very short lifetime
446 decay.setDaughterContainer(muons, Physics::m_mu);
447
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);
454 }
455
456 TEST_F(DecayTest, PionDecayTwoBodyFixedMomentum) {
457 constexpr size_t nPart = 512;
458 auto pions = makeContainer();
459 auto muons = makeContainer(ParticleType::MUON);
460 pions->setM(Physics::m_pi);
461 muons->setM(Physics::m_mu);
462 createParticles(pions, nPart, 0.0); // pions at rest
463
464 Options::seed = 888;
465 PionDecay decay(1.0e-12, 0, Physics::m_pi, +1);
466 decay.setDaughterContainer(muons, Physics::m_mu);
467
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);
473
474 auto muP_host = muons->P.getHostMirror();
475 Kokkos::deep_copy(muP_host, muons->P.getView());
476
477 // Expected fixed momentum in pion rest frame: p = (m_pi^2 - m_mu^2) / (2 m_pi)
478 const double pExpected = (Physics::m_pi * Physics::m_pi - Physics::m_mu * Physics::m_mu)
479 / (2.0 * Physics::m_pi);
480 // In beta*gamma units: bg = p / m_mu
481 const double bgExpected = pExpected / Physics::m_mu;
482
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);
487
488 // All daughters must have the same |beta*gamma| (monochromatic).
489 EXPECT_NEAR(bg, bgExpected, 1.0e-12);
490 }
491
492 // Verify directions are not all identical (isotropic).
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;
498 break;
499 }
500 }
501 EXPECT_TRUE(foundDifferent);
502 }
503
504 TEST_F(DecayTest, PionDecayBoostedConservesEnergy) {
505 constexpr size_t nPart = 512;
506 auto pions = makeContainer();
507 auto muons = makeContainer(ParticleType::MUON);
508 pions->setM(Physics::m_pi);
509 muons->setM(Physics::m_mu);
510 createParticles(pions, nPart, 3.0); // boosted pions
511
512 Options::seed = 999;
513 PionDecay decay(1.0e-8, 0, Physics::m_pi, +1);
514 decay.setDaughterContainer(muons, Physics::m_mu);
515
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);
521
522 auto muP_host = muons->P.getHostMirror();
523 Kokkos::deep_copy(muP_host, muons->P.getView());
524
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);
529 const double energy = gamma * Physics::m_mu;
530
531 // E^2 = p^2 + m_mu^2 must hold.
532 const double p2 = bg2 * Physics::m_mu * Physics::m_mu;
533 EXPECT_NEAR(energy * energy, p2 + Physics::m_mu * Physics::m_mu, 1.0e-15);
534
535 EXPECT_GT(energy, 0.0);
536 EXPECT_TRUE(std::isfinite(energy));
537 }
538 }
539
540 // =====================================================================
541 // Species validation in setDaughterContainer
542 // =====================================================================
543
544 TEST_F(DecayTest, SetDaughterContainerAcceptsCorrectSpecies) {
545 auto electrons = makeContainer(ParticleType::ELECTRON);
546 MuonDecay muonDecay(1.0e-6, 0, Physics::m_mu, -1);
547 EXPECT_NO_THROW(muonDecay.setDaughterContainer(electrons, Physics::m_e));
548
549 auto muons = makeContainer(ParticleType::MUON);
550 PionDecay pionDecay(1.0e-8, 0, Physics::m_pi, +1);
551 EXPECT_NO_THROW(pionDecay.setDaughterContainer(muons, Physics::m_mu));
552 }
553
554 TEST_F(DecayTest, SetDaughterContainerRejectsWrongSpecies) {
555 MuonDecay decay(1.0e-6, 0, Physics::m_mu, -1);
556
557 auto protonDaughter = makeContainer(ParticleType::PROTON);
558 EXPECT_THROW(decay.setDaughterContainer(protonDaughter, Physics::m_p), OpalException);
559
560 auto photonDaughter = makeContainer(ParticleType::PHOTON);
561 EXPECT_THROW(decay.setDaughterContainer(photonDaughter, 0.0), OpalException);
562
563 auto muonDaughter = makeContainer(ParticleType::MUON);
564 EXPECT_THROW(decay.setDaughterContainer(muonDaughter, Physics::m_mu), OpalException);
565 }
566
567 TEST_F(DecayTest, SetDaughterContainerRejectsUnnamedSpecies) {
568 // Default-constructed container has Sp = 0 (PHOTON) via makeContainer()
569 // with UNNAMED->0 cast; use explicit UNNAMED here which is -1 (sentinel).
570 auto unnamed = makeContainer(ParticleType::UNNAMED);
571 MuonDecay decay(1.0e-6, 0, Physics::m_mu, -1);
572 EXPECT_THROW(decay.setDaughterContainer(unnamed, Physics::m_e), OpalException);
573 }
574
575 TEST_F(DecayTest, PionDecayRejectsElectronDaughter) {
576 auto electrons = makeContainer(ParticleType::ELECTRON);
577 PionDecay decay(1.0e-8, 0, Physics::m_pi, +1);
578 // PionDecay's allowed daughter is MUON, not ELECTRON.
579 EXPECT_THROW(decay.setDaughterContainer(electrons, Physics::m_e), OpalException);
580 }
581
582 TEST_F(DecayTest, SetDaughterContainerAcceptsNullForDestroyOnly) {
583 auto muons = makeContainer();
584 muons->setM(Physics::m_mu);
585 createParticles(muons, 16, 0.0);
586
587 MuonDecay decay(1.0e-12, 0, Physics::m_mu, -1);
588 // Null daughter = destroy-only mode; must not throw regardless of species.
589 EXPECT_NO_THROW(decay.setDaughterContainer(nullptr, 0.0));
590
591 // Decay still runs in destroy-only mode.
592 Options::seed = 11;
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);
597 }
598
599} // namespace
const int nr
ippl::FieldLayout< Dim > FieldLayout_t
Definition PBunchDefs.h:25
ippl::UniformCartesian< double, 3 > Mesh_t
Definition PBunchDefs.h:18
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).
Definition PionDecay.h:14
bool useQMAttributes
Definition Options.cpp:109
int seed
The current random seed.
Definition Options.cpp:37
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)
Definition Physics.h:138
constexpr double m_p
The proton rest mass in GeV.
Definition Physics.h:105
constexpr double m_e
The electron rest mass in GeV.
Definition Physics.h:93
constexpr double m_mu
The muon rest mass in GeV.
Definition Physics.h:129