Geant4 11.4.0
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4FermiSplitter.cc
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26//
27// G4FermiBreakUpAN alternative de-excitation model
28// by A. Novikov (January 2025)
29//
30
31#include "G4FermiSplitter.hh"
32
33#include "G4FermiDataTypes.hh"
36#include "G4VFermiFragmentAN.hh"
37
39
40#include <iterator>
41#include <numeric>
42#include <optional>
43#include <functional>
44
45namespace
46{
47// Kappa = V/V_0 it is used in calculation of Coulomb energy, Kappa is dimensionless
48constexpr G4double Kappa = 1.0;
49
50// Nuclear radius R0 (is a model parameter)
51constexpr G4double R0 = 1.3 * CLHEP::fermi;
52
54{
55 // Coulomb Barrier (MeV) for given channel with K fragments.
56 static const G4double COEF =
57 (3. / 5.) * (CLHEP::elm_coupling / R0) * std::cbrt(1. / (1. + Kappa));
58
59 std::uint32_t atomicMassSum = 0.;
60 std::uint32_t chargeSum = 0.;
61 G4double CoulombEnergy = 0.;
62 for (const auto fragmentPtr : split) {
63 auto mass = static_cast<std::uint32_t>(fragmentPtr->GetAtomicMass());
64 auto charge = static_cast<std::uint32_t>(fragmentPtr->GetChargeNumber());
65 CoulombEnergy += std::pow(charge, 2) / std::cbrt(static_cast<G4double>(mass));
66 atomicMassSum += mass;
67 chargeSum += charge;
68 }
69
70 CoulombEnergy -=
71 std::pow(static_cast<G4double>(chargeSum), 2) / std::cbrt(static_cast<G4double>(atomicMassSum));
72 return -COEF * CoulombEnergy;
73}
74
75G4double SpinFactor(const G4FermiFragmentVector& split)
76{
77 G4double factor = 1;
78
79 for (const auto fragmentPtr : split) {
80 factor *= fragmentPtr->GetPolarization();
81 }
82
83 return factor;
84}
85
87{
88 auto kineticEnergy = totalEnergy;
89 for (const auto fragmentPtr : split) {
90 kineticEnergy -= fragmentPtr->GetTotalEnergy();
91 }
92
93 // skip columb calculation for optimization purposes
94 if (kineticEnergy <= 0.) {
95 return kineticEnergy;
96 }
97
98 return kineticEnergy - CoulombBarrier(split);
99}
100
101G4double MassFactor(const G4FermiFragmentVector& split)
102{
103 G4double massSum = 0.;
104 G4double massProduct = 1.;
105 for (const auto fragmentPtr : split) {
106 const auto fragmentMass = fragmentPtr->GetMass();
107 massProduct *= fragmentMass;
108 massSum += fragmentMass;
109 }
110 if (0.0 >= massSum) { return 1.0; }
111 auto massFactor = massProduct / massSum;
112 massFactor *= std::sqrt(massFactor);
113 return massFactor;
114}
115
116std::size_t Factorial(const std::size_t n)
117{
118 std::size_t factorial = 1;
119 for (std::size_t i = 2; i <= n; ++i) {
120 factorial *= i;
121 }
122 return factorial;
123}
124
125G4double ConfigurationFactor(const G4FermiFragmentVector& split)
126{
127 // get all mass numbers and count repetitions
128 std::vector<G4FermiAtomicMass> masses(split.size());
129 std::transform(split.begin(), split.end(), masses.begin(),
131 std::sort(masses.begin(), masses.end());
132
133 // avoid overflow with floats
134 // TODO: optimize with ints maybe
135 G4double factor = 1;
136
137 std::size_t repeatCount = 1; // we skip first, so start with 1
138 for (std::size_t i = 1; i < masses.size(); ++i) {
139 if (masses[i] != masses[i - 1]) {
140 factor *= static_cast<G4double>(Factorial(repeatCount));
141 repeatCount = 0;
142 }
143 ++repeatCount;
144 }
145 factor *= static_cast<G4double>(Factorial(repeatCount));
146
147 return factor;
148}
149
150G4double ConstFactor(G4FermiAtomicMass atomicMass, std::size_t fragmentsCount)
151{
152 static const G4double COEF =
153 std::pow(R0 / CLHEP::hbarc, 3) * Kappa * std::sqrt(2.0 / CLHEP::pi) / 3.0;
154
155 return std::pow(COEF * static_cast<G4double>(atomicMass), fragmentsCount - 1);
156}
157
158G4double GammaFactor(std::size_t fragmentsCount)
159{
160 G4double gamma = 1.0;
161 G4double arg = 3.0 * static_cast<G4double>(fragmentsCount - 1) / 2.0 - 1.0;
162 while (arg > 1.1) {
163 gamma *= arg;
164 arg -= 1;
165 }
166
167 if (fragmentsCount % 2 == 0) {
168 gamma *= std::sqrt(CLHEP::pi);
169 }
170
171 return gamma;
172}
173} // namespace
174
176 G4FermiAtomicMass atomicMass, G4double totalEnergy)
177{
178 const auto kineticEnergy = KineticEnergy(split, totalEnergy); // in MeV
179
180 // Check that there is enough energy to produce K fragments
181 if (kineticEnergy <= 0.) {
182 return 0.;
183 }
184
185 const auto power = 3.0 * static_cast<G4double>(split.size() - 1) / 2.0 - 1.;
186 const auto kineticFactor = std::pow(kineticEnergy, power);
187
188 // Spin factor S_n
189 const auto spinFactor = SpinFactor(split);
190
191 // Calculate MassFactor
192 const auto massFactor = MassFactor(split);
193
194 // This is the constant (doesn't depend on energy) part
195 const auto coef = ConstFactor(atomicMass, split.size());
196
197 // Calculation of 1/gamma(3(k-1)/2)
198 const auto gamma = GammaFactor(split.size());
199
200 // Permutation Factor G_n
201 const auto permutationFactor = ConfigurationFactor(split);
202
203 return coef * kineticFactor * massFactor * spinFactor / (permutationFactor * gamma);
204}
205
206namespace
207{
208constexpr std::size_t ExpectedSplitSize = 100;
209
210void ThrowOnInvalidInputs(G4FermiNucleiData nucleiData)
211{
212 FERMI_ASSERT_MSG(nucleiData.atomicMass > 0_m && nucleiData.chargeNumber >= 0_c,
213 "Non valid arguments A = " << nucleiData.atomicMass
214 << " Z = " << nucleiData.chargeNumber);
215
216 FERMI_ASSERT_MSG(static_cast<std::uint32_t>(nucleiData.chargeNumber)
217 <= static_cast<std::uint32_t>(nucleiData.atomicMass),
218 "Non physical arguments = " << nucleiData.atomicMass
219 << " Z = " << nucleiData.chargeNumber);
220}
221
222std::vector<G4FermiFragmentVector> PossibleSplits(const G4FermiPartition& massPartition,
223 const G4FermiPartition& chargePartition)
224{
225 auto& fragmentPool = G4FermiFragmentPoolAN::Instance();
226 const auto fragmentCount = massPartition.size();
227
228 // count all possible splits due to multiplicity of fragments
229 std::size_t splitsCount = 1;
230 for (std::size_t fragmentIdx = 0; fragmentIdx < fragmentCount; ++fragmentIdx) {
231 splitsCount *= fragmentPool.Count(G4FermiAtomicMass(massPartition[fragmentIdx]),
232 G4FermiChargeNumber(chargePartition[fragmentIdx]));
233 if (splitsCount == 0) {
234 return {};
235 }
236 }
237
238 // allocate in advance
239 std::vector<G4FermiFragmentVector> splits(splitsCount, G4FermiFragmentVector(fragmentCount));
240
241 // incrementally build splits
242 // !! chosen order matters, because later there we need to remove duplicates
243 std::size_t groupSize = splitsCount;
244 for (std::size_t fragmentIdx = 0; fragmentIdx < fragmentCount; ++fragmentIdx) {
245 const auto fragmentRange =
246 fragmentPool.GetFragments(G4FermiAtomicMass(massPartition[fragmentIdx]),
247 G4FermiChargeNumber(chargePartition[fragmentIdx]));
248 // no remainder here!
249 const std::size_t multiplicity = std::distance(fragmentRange.begin(), fragmentRange.end());
250 groupSize /= multiplicity;
251
252 for (std::size_t offset = 0; offset < splitsCount;) {
253 for (const auto fragmentPtr : fragmentRange) {
254 for (std::size_t pos = 0; pos < groupSize; ++pos) {
255 splits[offset + pos][fragmentIdx] = fragmentPtr;
256 }
257 offset += groupSize;
258 }
259 }
260 }
261
262 // remove duplicate splits
263 for (auto& split : splits) {
264 std::sort(split.begin(), split.end(), std::greater<>());
265 // greater, because they already partially sorted as greater due to integer partition
266 }
267 const auto uniqueEndIt = std::unique(splits.begin(), splits.end());
268 splits.resize(uniqueEndIt - splits.begin());
269
270 return splits;
271}
272} // namespace
273
274std::vector<G4FermiFragmentVector> G4FermiSplitter::GenerateSplits(G4FermiNucleiData nucleiData)
275{
276 std::vector<G4FermiFragmentVector> splits;
277 GenerateSplits(nucleiData, splits);
278 return splits;
279}
280
282 std::vector<G4FermiFragmentVector>& splits)
283{
284 ThrowOnInvalidInputs(nucleiData);
285
286 splits.reserve(ExpectedSplitSize);
287
288 // let's split nucleus into 2, ..., A fragments
289 const auto maxFragmentsCount = static_cast<std::uint32_t>(nucleiData.atomicMass);
290
291 for (std::uint32_t fragmentCount = 2; fragmentCount <= maxFragmentsCount; ++fragmentCount) {
292 // Form all possible partition by combination of A partitions and Z partitions (Z partitions
293 // include null parts)
294 for (auto& massPartition : G4integerPartition(nucleiData.atomicMass, fragmentCount, 1)) {
295 for (auto& chargePartition : G4integerPartition(nucleiData.chargeNumber, fragmentCount, 0)) {
296 // Some splits are invalid, some nuclei doesn't exist
297 if (auto partitionSplits = PossibleSplits(massPartition, chargePartition);
298 !partitionSplits.empty()) {
299 splits.insert(splits.end(), std::make_move_iterator(partitionSplits.begin()),
300 std::make_move_iterator(partitionSplits.end()));
301 }
302 }
303 }
304 }
305}
#define FERMI_ASSERT_MSG(COND, MSG)
std::vector< std::uint32_t > G4FermiPartition
G4ThreadLocal T * G4GeomSplitter< T >::offset
double G4double
Definition G4Types.hh:83
std::vector< const G4VFermiFragmentAN * > G4FermiFragmentVector
static G4FermiFragmentPoolAN & Instance()
static void GenerateSplits(G4FermiNucleiData nucleiData, std::vector< G4FermiFragmentVector > &splits)
static G4double DecayWeight(const G4FermiFragmentVector &split, G4FermiAtomicMass atomicMass, G4double totalEnergy)
G4FermiAtomicMass GetAtomicMass() const
G4double CoulombBarrier(const G4int Z1, const G4int A1, const G4int Z2, const G4int A2, const G4double exc)
G4FermiChargeNumber chargeNumber
G4FermiAtomicMass atomicMass