BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSWrapperMuonSplitting.cc
1/*
2Beam Delivery Simulation (BDSIM) Copyright (C) BDSIM Collaboration, 2001 - 2026.
3
4This file is part of BDSIM.
5
6BDSIM is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published
8by the Free Software Foundation version 3 of the License.
9
10BDSIM is distributed in the hope that it will be useful, but
11WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with BDSIM. If not, see <http://www.gnu.org/licenses/>.
17*/
18#include "BDSPhysicsVectorLinear.hh"
19#include "BDSWrapperMuonSplitting.hh"
20
21#include "G4ParticleDefinition.hh"
22#include "G4Track.hh"
23#include "G4Types.hh"
24#include "G4VParticleChange.hh"
25#include "G4VProcess.hh"
26
27#include <cmath>
28#include <limits>
29#include <vector>
30
32
33BDSWrapperMuonSplitting::BDSWrapperMuonSplitting(G4VProcess* originalProcess,
34 G4int splittingFactorIn,
35 G4double splittingThresholdEKIn,
36 G4int splittingFactor2In,
37 G4double splittingThresholdEK2In,
38 G4bool excludeWeight1ParticlesIn,
39 G4double muonSplittingExclusionWeightIn):
40 BDSWrapperProcess("MuonSplittingWrapper"),
41 splittingFactor(splittingFactorIn),
42 splittingThresholdEK(splittingThresholdEKIn),
43 splittingFactor2(splittingFactor2In),
44 splittingThresholdEK2(splittingThresholdEK2In),
45 excludeWeight1Particles(excludeWeight1ParticlesIn),
46 muonSplittingExclusionWeight(muonSplittingExclusionWeightIn),
47 splitting(nullptr)
48{
49 RegisterProcess(originalProcess);
50 theProcessSubType = originalProcess->GetProcessSubType();
51 theProcessName = "MuonSplittingWrapper("+originalProcess->GetProcessName()+")";
52 if (splittingFactor2 < splittingFactor)
53 {splittingFactor2 = splittingFactor;} // always want more high energy
54 if (splittingThresholdEK2 < splittingThresholdEK)
55 {splittingThresholdEK2 = splittingThresholdEK;}
56
57 std::vector<G4double> eK = {0.8*splittingThresholdEK, splittingThresholdEK};
58 std::vector<G4double> va = {2.0, static_cast<G4double>(splittingFactorIn)};
59 if (splittingThresholdEK2 > splittingThresholdEK)
60 {
61 eK.push_back(splittingThresholdEK2);
62 va.push_back((G4double)splittingFactor2In);
63 }
64 splitting = new BDSPhysicsVectorLinear(eK, va);
65}
66
67BDSWrapperMuonSplitting::~BDSWrapperMuonSplitting()
68{
69 delete splitting;
70}
71
72G4VParticleChange* BDSWrapperMuonSplitting::PostStepDoIt(const G4Track& track,
73 const G4Step& step)
74{
75 G4VParticleChange* particleChange = pRegProcess->PostStepDoIt(track, step);
76
77 if (splittingFactor == 1)
78 {return particleChange;}
79
80 G4double parentEk = track.GetKineticEnergy();
81 if (parentEk < 0.8*splittingThresholdEK) // smaller of the two thresholds by design
82 {return particleChange;}
83
84 G4int nSecondaries = particleChange->GetNumberOfSecondaries();
85 if (nSecondaries == 0)
86 {return particleChange;}
87
88 // if weight == 1
89 if (excludeWeight1Particles && std::abs(track.GetWeight() - 1.0) > std::numeric_limits<double>::epsilon())
90 {return particleChange;}
91
92 if (track.GetWeight() > muonSplittingExclusionWeight)
93 {return particleChange;}
94
95 G4bool muonPresent = false;
96 std::vector<G4int> secondaryPDGIDs;
97 for (G4int i = 0; i < nSecondaries; i++)
98 {
99 G4int secondaryPDGID = particleChange->GetSecondary(i)->GetDefinition()->GetPDGEncoding();
100 muonPresent = std::abs(secondaryPDGID) == 13 || muonPresent;
101 }
102 if (!muonPresent)
103 {return particleChange;}
104
105 // we keep hold of the tracks and manage their memory
106 std::vector<G4Track*> originalSecondaries;
107 std::vector<G4Track*> originalMuons;
108 for (G4int i = 0; i < nSecondaries; i++)
109 {
110 G4Track* secondary = particleChange->GetSecondary(i);
111 if (std::abs(secondary->GetDefinition()->GetPDGEncoding()) != 13)
112 {originalSecondaries.push_back(secondary);}
113 else
114 {originalMuons.push_back(secondary);}
115 }
116
117 G4int nOriginalSecondaries = nSecondaries;
118
119 particleChange->Clear(); // doesn't delete the secondaries
120
121 G4double spf2 = splitting->Value(parentEk);
122 G4int thisTimeSplittingFactor = static_cast<G4int>(std::round(spf2));
123
124 // Attempt to generate more muons. This might be difficult or rare, so we must
125 // tolerate this and go for up to a number.
126 G4int maxTrials = 10 * thisTimeSplittingFactor;
127 G4int nSuccessfulMuonSplits = 0;
128 G4int iTry = 0;
129 std::vector<G4Track*> newMuons;
130 while (iTry < maxTrials && nSuccessfulMuonSplits < thisTimeSplittingFactor-1)
131 {
132 iTry++;
133 particleChange->Clear(); // wipes the vector of tracks, but doesn't delete them
134 particleChange = pRegProcess->PostStepDoIt(track, step);
135 G4bool success = false;
136 for (G4int i = 0; i < particleChange->GetNumberOfSecondaries(); i++)
137 {
138 G4Track* sec = particleChange->GetSecondary(i);
139 if (std::abs(sec->GetDefinition()->GetPDGEncoding()) == 13)
140 {
141 newMuons.push_back(sec);
142 success = true;
143 }
144 else
145 {delete sec;}
146 }
147 particleChange->Clear();
148 if (success)
149 {nSuccessfulMuonSplits++;}
150 }
151
152 particleChange->Clear();
153 particleChange->SetNumberOfSecondaries(nOriginalSecondaries + static_cast<G4int>(newMuons.size()));
154 // cache this flag so we can reset it back afterwards
155 G4bool originalSetSecondaryWeightByProcess = particleChange->IsSecondaryWeightSetByProcess();
156 particleChange->SetSecondaryWeightByProcess(true);
157 if (nSuccessfulMuonSplits == 0)
158 {// we've cleared the original ones, so we have to put them back
159 for (auto secondary : originalSecondaries)
160 {particleChange->AddSecondary(secondary);}
161 for (auto muon : originalMuons)
162 {particleChange->AddSecondary(muon);}
163 return particleChange;
164 }
165
166 // the original muon(s) count as 1 even if there are 2 of them as it's 1x call to the process
167 G4double weightFactor = 1.0 / (static_cast<G4double>(nSuccessfulMuonSplits) + 1.0);
168 // put in the original secondaries with an unmodified weight
169 for (auto aSecondary : originalSecondaries)
170 {particleChange->AddSecondary(aSecondary);}
171 for (auto originalMuon : originalMuons)
172 {
173 G4double existingWeight = originalMuon->GetWeight();
174 G4double newWeight = existingWeight * weightFactor;
175 originalMuon->SetWeight(newWeight);
176 particleChange->AddSecondary(originalMuon);
177 }
178 for (auto newMuon : newMuons)
179 {
180 G4double existingWeight = newMuon->GetWeight();
181 G4double newWeight = existingWeight * weightFactor;
182 newMuon->SetWeight(newWeight);
183 particleChange->AddSecondary(newMuon);
184 }
185 // IMPORTANT - we must reset this back to its default value as the process owns
186 // (usually) a single particle change object that it reuses, so setting this flag
187 // will change that process's behaviour forever more in the run even if this wrapper
188 // decides not to act. We must reset it after AddSecondary().
189 particleChange->SetSecondaryWeightByProcess(originalSetSecondaryWeightByProcess);
190
192 return particleChange;
193}
A vector of some values vs Ek that are linearly interpolated.
G4double Value(G4double eK) const
Get the linearly interpolated value for a given kinetic energy.
static G4int nCallsThisEvent
Counter for understanding occurence.
virtual G4VParticleChange * PostStepDoIt(const G4Track &track, const G4Step &step)
Do the splitting operation.