46#include <gtest/gtest.h>
72 double michelNormalization(
double a) {
return 1.0 - 2.0 * a * a * a + a * a * a * a; }
76 double angularAsymmetryNumerator(
double a) {
77 const double a3 = a * a * a;
78 const double a4 = a3 * a;
79 return 1.0 / 3.0 - a4 + (2.0 / 3.0) * a3;
83 double integratedAsymmetry(
double a) {
84 return angularAsymmetryNumerator(a) / michelNormalization(a);
87 class MuonDecaySpectrumTest :
public ::testing::Test {
89 static void SetUpTestSuite() {
91 char** argv =
nullptr;
92 ippl::initialize(argc, argv);
95 static void TearDownTestSuite() { ippl::finalize(); }
97 void SetUp()
override {
103 void TearDown()
override {
108 std::shared_ptr<PC_t> makeContainer(
110 ippl::Vector<int, 3>
nr = 8;
111 ippl::Vector<double, 3> rmin = -4.0;
112 ippl::Vector<double, 3> rmax = 4.0;
113 ippl::Vector<double, 3> origin = rmin;
114 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(
nr);
115 std::array<bool, 3> decomp = {
true,
true,
true};
117 ippl::NDIndex<3> domain;
118 for (
unsigned i = 0; i < 3; ++i) {
119 domain[i] = ippl::Index(
nr[i]);
124 auto pc = std::make_shared<PC_t>(mesh, fl, spinEnabled);
125 pc->Sp =
static_cast<short>(species);
126 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
130 void createParticlesAtRest(std::shared_ptr<PC_t>& pc, std::size_t nPart) {
131 pc->createParticles(nPart);
132 auto R_host = pc->R.getHostMirror();
133 auto P_host = pc->P.getHostMirror();
134 for (std::size_t i = 0; i < nPart; ++i) {
142 Kokkos::deep_copy(pc->R.getView(), R_host);
143 Kokkos::deep_copy(pc->P.getView(), P_host);
148 void setUniformPolarization(std::shared_ptr<PC_t>& pc,
const std::array<float, 3>& pol) {
149 using spin_t =
typename PC_t::spin_vector_type;
150 auto Pol_host = pc->Pol.getHostMirror();
151 const std::size_t n = pc->getLocalNum();
152 for (std::size_t i = 0; i < n; ++i) {
153 Pol_host(i) = spin_t{pol[0], pol[1], pol[2]};
155 Kokkos::deep_copy(pc->Pol.getView(), Pol_host);
161 void runAngularCase(
int parentSign,
const std::string& csvPath) {
162 constexpr std::size_t nMuons = 100000;
163 constexpr std::size_t nBins = 50;
164 constexpr double polMag = 1.0;
170 createParticlesAtRest(muons, nMuons);
171 setUniformPolarization(muons, {0.0f, 0.0f,
static_cast<float>(polMag)});
179 const std::size_t marked = decay.apply(*muons, 1.0, 0, 0);
180 const std::size_t destroyed = muons->deleteInvalidParticles();
181 ASSERT_EQ(marked, nMuons);
182 ASSERT_EQ(destroyed, nMuons);
183 ASSERT_EQ(electrons->getLocalNum(), nMuons);
185 auto eP_host = electrons->P.getHostMirror();
186 Kokkos::deep_copy(eP_host, electrons->P.getView());
190 std::vector<double> cosSamples;
191 cosSamples.reserve(nMuons);
192 for (std::size_t i = 0; i < nMuons; ++i) {
193 const double px = eP_host(i)[0];
194 const double py = eP_host(i)[1];
195 const double pz = eP_host(i)[2];
196 const double mag = std::sqrt(px * px + py * py + pz * pz);
198 cosSamples.push_back(pz / mag);
203 const double asymSign = (parentSign < 0) ? +1.0 : -1.0;
204 const double A = asymSign * polMag * integratedAsymmetry(xMin);
207 std::vector<double> analyticPdf(nBins, 0.0);
208 for (std::size_t i = 0; i < nBins; ++i) {
209 analyticPdf[i] = 0.5 * (1.0 + A * hist.center(i));
215 const double meanRef = A / 3.0;
217 std::cout <<
"[MuonAngularSpectrum sign=" << parentSign <<
"] L1=" << l1
218 <<
" mean=" << meanCos <<
" (analytic=" << meanRef <<
") area=" << area
222 EXPECT_NEAR(meanCos, meanRef, 0.01);
223 EXPECT_NEAR(area, 1.0, 0.01);
226 csvPath, hist, analyticPdf,
"cos(theta) w.r.t. muon spin");
227 std::cout <<
"[MuonAngularSpectrum] CSV written: " << csvPath <<
'\n';
232 bool oldUseQMAttributes_m =
false;
235 TEST_F(MuonDecaySpectrumTest, MuMinusAngularSpectrumMatchesPolarization) {
236 runAngularCase(-1,
"muon_angular_spectrum_mu_minus.csv");
239 TEST_F(MuonDecaySpectrumTest, MuPlusAngularSpectrumMatchesPolarization) {
240 runAngularCase(+1,
"muon_angular_spectrum_mu_plus.csv");
ippl::FieldLayout< Dim > FieldLayout_t
ippl::UniformCartesian< double, 3 > Mesh_t
Lightweight histogram + CSV utilities shared by decay spectrum tests.
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.
int seed
The current random seed.
KOKKOS_INLINE_FUNCTION constexpr double minElectronX()
Minimum reduced energy .
constexpr double m_e
The electron rest mass in GeV.
constexpr double m_mu
The muon rest mass in GeV.
void writeSpectrumCsv(const std::string &path, const Histogram1D &h, const std::vector< double > &analyticPdf, const std::string &xLabel)
Histogram1D makeHistogram(const std::vector< double > &samples, double low, double high, std::size_t nBins)
double histogramArea(const Histogram1D &h)
Riemann sum of the histogram density (should be ~1 if all samples were in range).
double l1Distance(const Histogram1D &sampled, const std::vector< double > &analyticPdf)
double sampleMean(const std::vector< double > &samples)