BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSPSPopulationScaled.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 "BDSDebug.hh"
19#include "BDSException.hh"
20#include "BDSHistBinMapper.hh"
21#include "BDSScorerConversionLoader.hh"
22#include "BDSPSPopulationScaled.hh"
23#include "BDSUtilities.hh"
24
25#include "G4IonTable.hh"
26#include "G4PhysicsVector.hh"
27#include "G4String.hh"
28#include "G4Types.hh"
29#include "G4VSolid.hh"
30#include "G4VPVParameterisation.hh"
31
32#include <cmath>
33#include <fstream>
34#include <iterator>
35#include <string>
36#include <dirent.h>
37
38#ifdef USE_GZSTREAM
39#include "src-external/gzstream/gzstream.h"
40#endif
41
43 G4int depth):
44 G4VPrimitiveScorer(scorerName, depth),
45 HCID(-1),
46 EvtMap(nullptr)
47{;}
48
50 const G4String& pathname,
51 G4int depth):
52 BDSPSPopulationScaled(scorerName, depth)
53{
54 G4String directory = BDS::GetFullPath(pathname);
55 if (directory.back() != '/')
56 {directory += '/';}
57
58 std::vector<G4String> dirsAngle;
59 std::vector<G4String> filesParticle;
60
61 dirsAngle = LoadDirectoryContents(directory);
62
63 G4cout << "Scorer \"" << GetName() << "\" - adding conversionFiles from: " + directory << G4endl;
64
65 for (const auto& dirnameAng : dirsAngle)
66 {
67 G4String dirAng = directory + dirnameAng;
68 filesParticle = LoadDirectoryContents(dirAng);
69
71
72 G4double ang = (G4double)std::stod(dirnameAng);
73 angles.push_back(ang); // Store the angle for interpolation
74 G4int angleIndex = (G4int)angles.size() - 1;
75
76 std::vector<G4int> ionPIDs; // Store the ion particle ids vs. angle for interpolation
77
78 for (const auto& filePDG : filesParticle)
79 {
80 G4String filepathPDG = dirAng + '/' + filePDG;
81 G4int pid = (G4int)std::stoi(filePDG);
82
83 if (filePDG.substr((filePDG.find_last_of(".") + 1)) == "gz" && BDS::FileExists(filepathPDG))
84 {
85#ifdef USE_GZSTREAM
87 conversionFactors[angleIndex][pid] = loaderC.Load(filepathPDG, true);
88#else
89 throw BDSException(__METHOD_NAME__, "Compressed file loading - but BDSIM not compiled with ZLIB.");
90#endif
91 }
92 else if (BDS::FileExists(filepathPDG))
93 {conversionFactors[angleIndex][pid] = loader.Load(filepathPDG, true);}
94
95 if (pid > 1e7)
96 {ionPIDs.push_back(pid);}
97 }
98 ionParticleIDs[angleIndex] = ionPIDs;
99 }
100
101 G4cout << "Scorer \"" << GetName() << " Loaded data for " << angles.size() << " angles: [";
102 for (auto a : angles)
103 {
104 G4cout << a;
105 if (a == angles.back())
106 {break;}
107 G4cout << ", ";
108 }
109 G4cout << "]*rad" << G4endl;
110}
111
112BDSPSPopulationScaled::~BDSPSPopulationScaled()
113{
114 for (const auto& af : conversionFactors)
115 {
116 for (auto cf : af.second)
117 {delete cf.second;}
118 }
119}
120
121void BDSPSPopulationScaled::Initialize(G4HCofThisEvent* HCE)
122{
123 EvtMap = new G4THitsMap<G4double>(detector->GetName(), GetName());
124 if (HCID < 0)
125 {HCID = GetCollectionID(0);}
126
127 HCE->AddHitsCollection(HCID, EvtMap);
128}
129
130void BDSPSPopulationScaled::EndOfEvent(G4HCofThisEvent* /*HEC*/)
131{
132 fCellTrackLogger.clear();
133}
134
135void BDSPSPopulationScaled::clear()
136{
137 EvtMap->clear();
138 fCellTrackLogger.clear();
139}
140
141G4bool BDSPSPopulationScaled::ProcessHits(G4Step* aStep, G4TouchableHistory*)
142{
143 G4double stepLength = aStep->GetStepLength();
144
145 if (!BDS::IsFinite(stepLength))
146 {return false;}
147
148 G4double radiationQuantity = 0;
149 G4int index = GetIndex(aStep);
150 G4TrackLogger& tlog = fCellTrackLogger[index];
151
152 if (tlog.FirstEnterance(aStep->GetTrack()->GetTrackID()))
153 {
154 const G4VTouchable* touchable = aStep->GetPreStepPoint()->GetTouchable();
155 auto rot = touchable->GetRotation();
156 G4ThreeVector unitZ = G4ThreeVector(0, 0, 1);
157 G4ThreeVector blmUnitZ = unitZ.transform(*rot);
158
159 auto momDirection = aStep->GetPreStepPoint()->GetMomentumDirection();
160
161 // The angle used for the lookup is sign-independent and spans the range 0 to pi/2
162 G4double angle = std::fmod(std::abs(momDirection.angle(blmUnitZ)), CLHEP::pi);
163 if (angle > CLHEP::pi/2.)
164 {angle = CLHEP::pi - angle;}
165
166 G4double kineticEnergy = aStep->GetPreStepPoint()->GetKineticEnergy();
167 G4double weight = aStep->GetPreStepPoint()->GetWeight();
168 G4double factor = GetConversionFactor(aStep->GetTrack()->GetDefinition()->GetPDGEncoding(),
169 kineticEnergy,
170 angle);
171 radiationQuantity = weight * factor;
172
173 EvtMap->add(index, radiationQuantity);
174 }
175
176 return true;
177}
178
179G4double BDSPSPopulationScaled::GetConversionFactor(G4int particleID,
180 G4double kineticEnergy,
181 G4double angle) const
182{
183 G4double angleNearestIndex = NearestNeighbourAngleIndex(angles, angle);
184
185 if (angleNearestIndex < 0)
186 {return 0;}
187
188 std::map<G4int, G4PhysicsVector*> conversionFactorsPart;
189 auto searchAngle = conversionFactors.find(angleNearestIndex);
190 if (searchAngle != conversionFactors.end())
191 {conversionFactorsPart = searchAngle->second;}
192 else
193 {return 0;}
194
195 if (particleID < 1e7)
196 {
197 auto search = conversionFactorsPart.find(particleID);
198 if (search != conversionFactorsPart.end())
199 {return search->second->Value(kineticEnergy);}
200 else
201 {return 0;}
202 }
203 else
204 {
205 // Get the nearest neighbour ion particle ID based on the ion Z
206 G4int particleIDNearest = NearestNeighbourIonPID(ionParticleIDs.find(angleNearestIndex)->second, particleID);
207 if (particleIDNearest < 0)
208 {return 0;}
209
210 // Get the ion Z in order to normalise the kinetic energy for table look-up
211 G4double nearestIonZ = (G4double)GetZFromParticleID(particleID);
212
213 auto searchIon = conversionFactorsPart.find(particleIDNearest);
214 if (searchIon != conversionFactorsPart.end())
215 {return searchIon->second->Value(kineticEnergy / nearestIonZ);}
216 else
217 {return 0;}
218 }
219}
220
221std::vector<G4String> BDSPSPopulationScaled::LoadDirectoryContents(const G4String& dirname)
222{
223 std::vector<G4String> contents;
224
225 struct dirent* entry = nullptr;
226 DIR* dp = nullptr;
227
228 dp = opendir(dirname.c_str());
229 if (dp == nullptr)
230 {throw BDSException(__METHOD_NAME__, "Cannot open directory " + dirname);}
231 else
232 {
233 while ((entry = readdir(dp)) != nullptr)
234 {
235 std::string name_string(entry->d_name);
236 if (name_string.front() != '.')
237 {contents.emplace_back(name_string);}
238 }
239 }
240 closedir(dp);
241 return contents;
242}
243
244G4int BDSPSPopulationScaled::NearestNeighbourAngleIndex(const std::vector<G4double>& vec, G4double value) const
245{
246 if (vec.empty())
247 {return -1;}
248
249 G4double nearestNeighbourAngle;
250
251 auto const it = std::upper_bound(vec.begin(), vec.end(), value) - 1;
252 if (it == vec.end())
253 {nearestNeighbourAngle = vec.back();}
254 else
255 {nearestNeighbourAngle = *it;}
256
257 auto const itr = std::find(vec.begin(), vec.end(), nearestNeighbourAngle);
258 auto index = (G4int) std::distance(vec.begin(), itr);
259
260 return index;
261}
262
263G4int BDSPSPopulationScaled::NearestNeighbourIonPID(const std::vector<G4int>& vec, G4int value) const
264{
265 // Make a vector of the ion Z for all ion particles
266 std::vector<G4int> vecZ;
267 for (auto pid: vec)
268 {vecZ.push_back(GetZFromParticleID(pid));}
269
270 if (vecZ.empty())
271 {return -1;}
272
273 G4int nearestNeighbourZ;
274 // Perform nearest neighbour interpolation on the ion Z
275 auto const it = std::upper_bound(vecZ.begin(), vecZ.end(), GetZFromParticleID(value)) - 1;
276 if (it == vecZ.end())
277 {nearestNeighbourZ = vecZ.back();}
278 else
279 {nearestNeighbourZ = *it;}
280
281 // Find the index of the nearest neighbour Z - used to look up the full particle ID
282 auto const itr = std::find(vecZ.begin(), vecZ.end(), nearestNeighbourZ);
283 int index = (int)std::distance(vecZ.begin(), itr);
284
285 return vec.at(index);
286}
287
288G4int BDSPSPopulationScaled::GetZFromParticleID(G4int particleID) const
289{
290 G4int Z = 0;
291 G4int A = 0;
292 G4double E = 0.0;
293 G4int lvl = 0;
294 G4IonTable::GetNucleusByEncoding(particleID, Z, A, E, lvl);
295 return Z;
296}
297
298void BDSPSPopulationScaled::PrintAll()
299{
300 G4cout << " MultiFunctionalDet " << detector->GetName() << G4endl;
301 G4cout << " PrimitiveScorer " << GetName() << G4endl;
302 G4cout << " Number of entries " << EvtMap->entries() << G4endl;
303 auto itr = EvtMap->GetMap()->begin();
304 for (; itr != EvtMap->GetMap()->end(); itr++)
305 {
306 G4cout << " copy no.: " << itr->first
307 << " population: " << *(itr->second) / GetUnitValue()
308 << " [quantity]"
309 << G4endl;
310 }
311}
General exception with possible name of object and message.
Primitive scorer for population in a volume with a conversion factor based on angle and kinetic energ...
BDSPSPopulationScaled(const G4String &name, G4int depth=0)
Loader for scoring conversion tables as function of energy.
G4PhysicsVector * Load(const G4String &fileName, G4bool silent=false)
Load the file.
G4String GetFullPath(G4String filename, bool excludeNameFromPath=false, bool useCWDForPrefix=false)
G4bool FileExists(const G4String &filename)
Checks if filename exists.
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())
const char * GetName(int)
Name of element.
Definition parserROOT.cc:37