File indexing completed on 2025-08-05 08:19:08
0001
0002
0003
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
0027 CHECK( std::distance(nucleus->begin(), nucleus->end()) == 1 );
0028 CHECK( std::distance(nucleus->cbegin(), nucleus->cend()) == 1 );
0029
0030
0031 CHECK( nucleus->radius() == 0. );
0032
0033
0034 double offset = random::canonical<>();
0035 nucleus->sample_nucleons(offset);
0036 auto&& proton = *(nucleus->begin());
0037
0038
0039 CHECK( proton.x() == offset );
0040 CHECK( proton.y() == 0. );
0041 CHECK( proton.z() == 0. );
0042
0043
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
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
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
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
0212
0213
0214
0215
0216
0217
0218
0219
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
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
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
0277
0278
0279
0280
0281
0282
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 }