Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:19:08

0001 // TRENTO: Reduced Thickness Event-by-event Nuclear Topology
0002 // Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
0003 // MIT License
0004 
0005 #include "../src/nucleus.h"
0006 
0007 #include <algorithm>
0008 #include <cmath>
0009 #include <iomanip>
0010 #include <iterator>
0011 #include <map>
0012 
0013 #include "catch.hpp"
0014 #include "util.h"
0015 
0016 #include "../src/hdf5_utils.h"
0017 #include "../src/random.h"
0018 
0019 using namespace trento;
0020 
0021 TEST_CASE( "proton" ) {
0022   auto nucleus = Nucleus::create("p");
0023 
0024   CHECK( dynamic_cast<Proton*>(nucleus.get()) != nullptr );
0025 
0026   // proton contains one nucleon
0027   CHECK( std::distance(nucleus->begin(), nucleus->end()) == 1 );
0028   CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == 1 );
0029 
0030   // and has zero radius
0031   CHECK( nucleus->radius() == 0. );
0032 
0033   // sample position with random offset
0034   double offset = random::canonical<>();
0035   nucleus->sample_nucleons(offset);
0036   auto&& proton = *(nucleus->begin());
0037 
0038   // check correct position
0039   CHECK( proton.x() == offset );
0040   CHECK( proton.y() == 0. );
0041   CHECK( proton.z() == 0. );
0042 
0043   // not a participant initially
0044   CHECK( !proton.is_participant() );
0045 }
0046 
0047 TEST_CASE( "deuteron" ) {
0048   auto nucleus = Nucleus::create("d");
0049 
0050   CHECK( dynamic_cast<Deuteron*>(nucleus.get()) != nullptr );
0051 
0052   CHECK( std::distance(nucleus->begin(), nucleus->end()) == 2 );
0053   CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == 2 );
0054 
0055   nucleus->sample_nucleons(0);
0056 
0057   CHECK( nucleus->cbegin()->x() == -std::next(nucleus->cbegin())->x() );
0058   CHECK( nucleus->cbegin()->y() == -std::next(nucleus->cbegin())->y() );
0059   CHECK( nucleus->cbegin()->z() == -std::next(nucleus->cbegin())->z() );
0060 
0061   CHECK( nucleus->radius() == Approx(-std::log(.01)/(2*0.457)) );
0062 }
0063 
0064 TEST_CASE( "lead nucleus" ) {
0065   auto nucleus = Nucleus::create("Pb");
0066 
0067   CHECK( dynamic_cast<WoodsSaxonNucleus*>(nucleus.get()) != nullptr );
0068 
0069   constexpr int A = 208;
0070   CHECK( std::distance(nucleus->begin(), nucleus->end()) == A );
0071   CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == A );
0072 
0073   CHECK( nucleus->radius() > 6. );
0074 
0075   double offset = nucleus->radius() * random::canonical<>();
0076   nucleus->sample_nucleons(offset);
0077 
0078   // average nucleon position
0079   double x = 0., y = 0., z = 0.;
0080   for (const auto& nucleon : *nucleus) {
0081     x += nucleon.x();
0082     y += nucleon.y();
0083     z += nucleon.z();
0084   }
0085   x /= A;
0086   y /= A;
0087   z /= A;
0088   auto tolerance = .7;
0089   CHECK( std::abs(x - offset) < tolerance );
0090   CHECK( std::abs(y) < tolerance );
0091   CHECK( std::abs(z) < tolerance );
0092 
0093   // no initial participants
0094   bool initial_participants = std::any_of(
0095       nucleus->cbegin(), nucleus->cend(),
0096       [](decltype(*nucleus->cbegin())& n) {
0097         return n.is_participant();
0098       });
0099   CHECK( !initial_participants );
0100 }
0101 
0102 TEST_CASE( "copper nucleus" ) {
0103   auto nucleus = Nucleus::create("Cu");
0104   auto def_nucleus = Nucleus::create("Cu2");
0105 
0106   CHECK( dynamic_cast<WoodsSaxonNucleus*>(nucleus.get()) != nullptr );
0107   CHECK( dynamic_cast<DeformedWoodsSaxonNucleus*>(def_nucleus.get()) != nullptr );
0108 
0109   constexpr int A = 63;
0110   CHECK( std::distance(nucleus->begin(), nucleus->end()) == A );
0111   CHECK( std::distance(def_nucleus->cbegin(), def_nucleus->cend()) == A );
0112 
0113   CHECK( nucleus->radius() > 4. );
0114   CHECK( def_nucleus->radius() > nucleus->radius() );
0115 }
0116 
0117 TEST_CASE( "gold nucleus" ) {
0118   auto nucleus = Nucleus::create("Au");
0119   auto def_nucleus = Nucleus::create("Au2");
0120 
0121   CHECK( dynamic_cast<WoodsSaxonNucleus*>(nucleus.get()) != nullptr );
0122   CHECK( dynamic_cast<DeformedWoodsSaxonNucleus*>(def_nucleus.get()) != nullptr );
0123 
0124   constexpr int A = 197;
0125   CHECK( std::distance(nucleus->begin(), nucleus->end()) == A );
0126   CHECK( std::distance(def_nucleus->cbegin(), def_nucleus->cend()) == A );
0127 
0128   CHECK( nucleus->radius() > 6. );
0129   CHECK( def_nucleus->radius() > nucleus->radius() );
0130 }
0131 
0132 TEST_CASE( "uranium nucleus" ) {
0133   auto nucleus = Nucleus::create("U");
0134 
0135   CHECK( dynamic_cast<DeformedWoodsSaxonNucleus*>(nucleus.get()) != nullptr );
0136 
0137   constexpr int A = 238;
0138   CHECK( std::distance(nucleus->begin(), nucleus->end()) == A );
0139   CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == A );
0140 
0141   CHECK( nucleus->radius() > 8. );
0142 }
0143 
0144 #ifdef TRENTO_HDF5
0145 
0146 TEST_CASE( "manual nucleus" ) {
0147   std::vector<float> positions = {
0148      0.,  0., 0.,
0149      1.,  0., 0.,
0150     -1.,  0., 0.,
0151      3.,  0., 0.,
0152      0.,  2., 0.,
0153      0., -2., 0.,
0154   };
0155 
0156   const auto A = positions.size() / 3;
0157 
0158   temporary_path temp{".hdf5"};
0159 
0160   {
0161     auto dataspace = hdf5::make_dataspace(std::array<hsize_t, 3>{1, A, 3});
0162     const auto& datatype = hdf5::type<decltype(positions)::value_type>();
0163 
0164     H5::H5File file{temp.path.string(), H5F_ACC_EXCL};
0165 
0166     auto dataset = file.createDataSet("test", datatype, dataspace);
0167     dataset.write(positions.data(), datatype);
0168   }
0169 
0170   auto nucleus = Nucleus::create(temp.path.string());
0171 
0172   CHECK( dynamic_cast<ManualNucleus*>(nucleus.get()) != nullptr );
0173 
0174   CHECK( std::distance(nucleus->begin(), nucleus->end()) == A );
0175   CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == A );
0176 
0177   CHECK( nucleus->radius() == Approx(3.) );
0178 
0179   nucleus->sample_nucleons(0.);
0180 
0181   auto nucleon = nucleus->cbegin();
0182 
0183   CHECK( nucleon->x() == Approx(0.) );
0184   CHECK( nucleon->y() == Approx(0.) );
0185   CHECK( nucleon->z() == Approx(0.) );
0186 
0187   std::advance(nucleon, 1);
0188   CHECK( nucleon->x() == Approx(-std::next(nucleon)->x()) );
0189   CHECK( nucleon->y() == Approx(-std::next(nucleon)->y()) );
0190   CHECK( nucleon->z() == Approx(-std::next(nucleon)->z()) );
0191 
0192   CHECK( nucleon->x() == Approx(std::next(nucleon, 2)->x()/3) );
0193   CHECK( nucleon->y() == Approx(std::next(nucleon, 2)->y()/3) );
0194   CHECK( nucleon->z() == Approx(std::next(nucleon, 2)->z()/3) );
0195 
0196   std::advance(nucleon, 3);
0197   CHECK( nucleon->x() == Approx(-std::next(nucleon)->x()) );
0198   CHECK( nucleon->y() == Approx(-std::next(nucleon)->y()) );
0199   CHECK( nucleon->z() == Approx(-std::next(nucleon)->z()) );
0200 
0201   CHECK_THROWS_AS( Nucleus::create("nonexistent.hdf"), std::invalid_argument );
0202 }
0203 
0204 #endif  // TRENTO_HDF5
0205 
0206 TEST_CASE( "woods-saxon sampling" ) {
0207   int A = 200;
0208   double R = 6., a = .5;
0209   NucleusPtr nucleus{new WoodsSaxonNucleus{static_cast<std::size_t>(A), R, a}};
0210 
0211   // Test Woods-Saxon sampling.
0212   // This is honestly not a great test; while it does prove that the code
0213   // basically works as intended, it does not rigorously show that the generated
0214   // numbers are actually Woods-Saxon distributed.  Personally I am much more
0215   // convinced by plotting a histogram and visually comparing it to the smooth
0216   // curve.  The script 'plot-woods-saxon.py' in the 'woods-saxon' subdirectory
0217   // does this.
0218 
0219   // sample a bunch of nuclei and bin all the nucleon positions
0220   auto nevents = 5000;
0221   auto nsamples = nevents * A;
0222   std::map<int, int> hist{};
0223   for (auto i = 0; i < nevents; ++i) {
0224     nucleus->sample_nucleons(0.);
0225     for (const auto& nucleon : *nucleus) {
0226       auto x = nucleon.x();
0227       auto y = nucleon.y();
0228       auto z = nucleon.z();
0229       auto r = std::sqrt(x*x + y*y + z*z);
0230       ++hist[static_cast<int>(r)];
0231     }
0232   }
0233 
0234   // integrate the W-S dist from rmin to rmax
0235   auto integrate_woods_saxon = [R, a](double rmin, double rmax) -> double {
0236     auto nbins = static_cast<int>((rmax - rmin)/.001);
0237     auto dr = (rmax - rmin)/nbins;
0238     double result = 0.;
0239     for (auto n = 0; n <= nbins; ++n) {
0240       auto r = rmin + n*dr;
0241       auto f = r*r/(1. + std::exp((r - R)/a));
0242       if (n == 0 || n == nbins)
0243         f /= 2;
0244       result += f;
0245     }
0246     return result * dr;
0247   };
0248 
0249   double ws_norm = integrate_woods_saxon(0, hist.size());
0250 
0251   // check all histogram bins agree with numerical integration
0252   auto all_bins_correct = true;
0253   std::ostringstream bin_output{};
0254   bin_output << std::fixed << std::boolalpha
0255              << "rmin rmax  prob      cprob     ratio     pass\n";
0256   for (const auto& bin : hist) {
0257     auto rmin = bin.first;
0258     auto rmax = rmin + 1;
0259     auto prob = static_cast<double>(bin.second) / nsamples;
0260     auto correct_prob = integrate_woods_saxon(rmin, rmax) / ws_norm;
0261     bool within_tol = prob == Approx(correct_prob).epsilon(.1).margin(1e-4);
0262     if (!within_tol)
0263       all_bins_correct = false;
0264     bin_output << std::setw(4) << rmin << ' '
0265                << std::setw(4) << rmax << "  "
0266                << prob << "  "
0267                << correct_prob << "  "
0268                << prob/correct_prob << "  "
0269                << within_tol << '\n';
0270   }
0271   INFO( bin_output.str() );
0272   CHECK( all_bins_correct );
0273 }
0274 
0275 TEST_CASE( "deformed woods-saxon sampling" ) {
0276   // This is tough to test because the sampled positions are randomly rotated
0277   // and the z-coordinate is discarded.  However, the authors have visually
0278   // checked the results by histogramming the samples (not done here, but easy
0279   // to reproduce).
0280 
0281   // As a not-very-stringent test, check that the mean ellipticity of a deformed
0282   // W-S nucleus is significantly larger than a symmetric nucleus.
0283 
0284   std::size_t A = 200;
0285   double R = 6., a = .5, beta2 = .3, beta4 = .1;
0286 
0287   auto nucleus_sym = WoodsSaxonNucleus{A, R, a};
0288   auto nucleus_def = DeformedWoodsSaxonNucleus{A, R, a, beta2, beta4};
0289 
0290   auto mean_ecc2 = [](Nucleus& nucleus) {
0291     double e2 = 0.;
0292     int nevents = 500;
0293 
0294     for (int n = 0; n < nevents; ++n) {
0295       nucleus.sample_nucleons(0.);
0296       double numer = 0.;
0297       double denom = 0.;
0298       for (const auto& nucleon : nucleus) {
0299         double x2 = std::pow(nucleon.x(), 2);
0300         double y2 = std::pow(nucleon.y(), 2);
0301         numer += x2 - y2;
0302         denom += x2 + y2;
0303       }
0304 
0305       e2 += std::fabs(numer) / denom;
0306     }
0307 
0308     return e2 / nevents;
0309   };
0310 
0311   CHECK( mean_ecc2(nucleus_def) > 2.*mean_ecc2(nucleus_sym) );
0312 }
0313 
0314 TEST_CASE( "nuclear radius" ) {
0315   constexpr auto R = 5., a = .5;
0316   constexpr auto radius = R + 3*a;
0317 
0318   WoodsSaxonNucleus nucleus{100, R, a};
0319   DeformedWoodsSaxonNucleus dummy_def_nucleus{100, R, a, 0., 0.};
0320 
0321   CHECK( nucleus.radius() == Approx(radius) );
0322   CHECK( dummy_def_nucleus.radius() == Approx(radius) );
0323 
0324   DeformedWoodsSaxonNucleus def_nucleus{100, R, a, .2, .1};
0325   CHECK( def_nucleus.radius() > radius );
0326 }
0327 
0328 TEST_CASE( "minimum distance" ) {
0329   for (const auto& species : {"Pb", "U"}) {
0330     for (auto repeat = 0; repeat < 10; ++repeat) {
0331       const auto target_dmin = .2 + .4*random::canonical<>();
0332       auto nucleus = Nucleus::create("Pb", target_dmin);
0333       nucleus->sample_nucleons(10*random::canonical<>());
0334       auto dminsq = 100.;
0335       for (auto n1 = nucleus->cbegin(); n1 != nucleus->cend(); ++n1) {
0336         for (auto n2 = n1 + 1; n2 != nucleus->cend(); ++n2) {
0337           auto dx = n1->x() - n2->x();
0338           auto dy = n1->y() - n2->y();
0339           auto dz = n1->z() - n2->z();
0340           dminsq = std::fmin(dx*dx + dy*dy + dz*dz, dminsq);
0341         }
0342       }
0343       auto dmin = std::sqrt(dminsq);
0344       INFO( "species " << species << " repeat " << repeat );
0345       CHECK( dmin >= target_dmin );
0346     }
0347   }
0348 }
0349 
0350 TEST_CASE( "unknown nucleus species" ) {
0351   CHECK_THROWS_AS( Nucleus::create("hello"), std::invalid_argument );
0352 }