BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSParallelWorldSampler.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 "BDSAcceleratorModel.hh"
19#include "BDSApertureInfo.hh"
20#include "BDSBeamline.hh"
21#include "BDSBeamlineElement.hh"
22#include "BDSBeamPipeInfo.hh"
23#include "BDSDebug.hh"
24#include "BDSDetectorConstruction.hh"
25#include "BDSExtent.hh"
26#include "BDSException.hh"
27#include "BDSGlobalConstants.hh"
28#include "BDSOutput.hh"
29#include "BDSParallelWorldSampler.hh"
30#include "BDSParser.hh"
31#include "BDSSampler.hh"
32#include "BDSSamplerCustom.hh"
33#include "BDSSamplerCylinder.hh"
34#include "BDSSamplerInfo.hh"
35#include "BDSSamplerPlane.hh"
36#include "BDSSamplerRegistry.hh"
37#include "BDSSamplerSphere.hh"
38#include "BDSSamplerType.hh"
39#include "BDSSDSampler.hh"
40#include "BDSUtilities.hh"
41#include "BDSWarning.hh"
42
43#include "parser/samplerplacement.h"
44
45#include "globals.hh" // geant4 types / globals
46#include "G4LogicalVolume.hh"
47#include "G4PVPlacement.hh"
48#include "G4Transform3D.hh"
49#include "G4VisAttributes.hh"
50#include "G4VPhysicalVolume.hh"
51#include "G4VUserParallelWorld.hh"
52
53#include <map>
54#include <set>
55#include <vector>
56
57
59 G4VUserParallelWorld("SamplerWorld_" + name),
60 suffix(name),
61 samplerWorldVis(nullptr),
62 generalPlane(nullptr),
63 samplerWorldLV(nullptr)
64{;}
65
66BDSParallelWorldSampler::~BDSParallelWorldSampler()
67{
68 // we leak all the placements as g4 is incredibly slow to delete
69 // them as it deregisters them - g4 will clean up
70 //for (auto placement : placements)
71 // {delete placement;}
72 delete samplerWorldVis;
73 // samplers are deleted by the sampler registry
74}
75
77{
79 G4VPhysicalVolume* samplerWorld = GetWorld();
80 samplerWorldLV = samplerWorld->GetLogicalVolume();
81
82 samplerWorldVis = new G4VisAttributes(*(globals->VisibleDebugVisAttr()));
83 samplerWorldVis->SetForceWireframe(true);//just wireframe so we can see inside it
84 samplerWorldLV->SetVisAttributes(samplerWorldVis);
85
86 const G4double samplerRadius = 0.5*globals->SamplerDiameter();
87 const BDSBeamline* beamline = BDSAcceleratorModel::Instance()->BeamlineMain();
88 const G4bool checkOverlaps = globals->CheckOverlaps();
89
90 // Construct the one sampler typically used for a general sampler.
91 generalPlane = new BDSSamplerPlane("Plane_sampler", samplerRadius);
92 samplerInstances[-1] = generalPlane;
93 // Construct any others we need.
94 std::map<int, std::set<int>> filterIDToSet = BDSParser::Instance()->GetSamplerFilterIDToSet();
95 for (const auto& kv : filterIDToSet)
96 {
97 BDSSamplerPlane* samp = new BDSSamplerPlane("Plane_sampler_w_filter_set_" + std::to_string(kv.first),
98 samplerRadius,
99 (G4int)kv.first);
100 samplerInstances[(G4int)kv.first] = samp;
101 }
102
103 // For each element in the beamline (if a beamline has been defined) construct and place the
104 // appropriate type of sampler if required. Info encoded in BDSBeamlineElement instance
105 if (beamline != nullptr)
106 {
107 for (auto element : *beamline)
108 {Place(element, samplerRadius);}
109 }
110
111 // Now user customised samplers
112 std::vector<GMAD::SamplerPlacement> samplerPlacements = BDSParser::Instance()->GetSamplerPlacements();
113
114 for (const auto& samplerPlacement : samplerPlacements)
115 {
116 G4String sn = samplerPlacement.name;
117 G4cout << "User placed sampler: \"" << sn << "\"" << G4endl;
118
119 if (BDSOutput::InvalidSamplerName(samplerPlacement.name))
120 {
121 G4cerr << __METHOD_NAME__ << "invalid sampler name \"" << sn << "\"" << G4endl;
123 throw BDSException(__METHOD_NAME__, "");
124 }
125
126 // use main beamline - in future, multiple beam lines
127 G4Transform3D transform = BDSDetectorConstruction::CreatePlacementTransform(samplerPlacement, beamline);
128
129 BDSSamplerType st = BDS::DetermineSamplerType(samplerPlacement.samplerType);
130 AdjustTransform(transform, st); // for 'forward' samplers we add an extra rotation
131 G4double radius = 0;
132 BDSSampler* sampler = BuildSampler(samplerPlacement, st, radius);
133 G4int samplerID = BDSSamplerRegistry::Instance()->RegisterSampler(sn,
134 sampler,
135 transform,
136 -1000,
137 nullptr,
138 st,
139 radius);
140
141 G4String uniqueName = BDSSamplerRegistry::Instance()->GetNameUnique(samplerID);
142 if (uniqueName != sn)
143 {BDS::Warning("Sampler placement with name \"" + sn + "\" will be named \"" + uniqueName + "\" in the output");}
144
145 G4PVPlacement* pl = new G4PVPlacement(transform,
146 sampler->GetContainerLogicalVolume(),
147 sn + "_pv",
148 samplerWorldLV,
149 false,
150 samplerID,
151 checkOverlaps);
152 placements.push_back(pl);
153 }
154}
155
157 const G4String& variableName,
158 const G4String& objectName) const
159{
160 if (value <= 0)
161 {throw BDSException("\"" + variableName + "\" is <= 0 and must be > 0 in samplerplacement \"" + objectName + "\"");}
162}
163
166 G4double& radius) const
167{
168 BDSSampler* result = nullptr;
169 G4String samplerName = G4String(samplerPlacement.name);
170
171 switch (st.underlying())
172 {
173 case BDSSamplerType::plane:
174 {
175 BDSApertureInfo* shape;
176 if (samplerPlacement.apertureModel.empty())
177 {
178 shape = new BDSApertureInfo(samplerPlacement.shape,
179 samplerPlacement.aper1 * CLHEP::m,
180 samplerPlacement.aper2 * CLHEP::m,
181 samplerPlacement.aper3 * CLHEP::m,
182 samplerPlacement.aper4 * CLHEP::m,
183 samplerName);
184 }
185 else
186 {shape = BDSAcceleratorModel::Instance()->Aperture(samplerPlacement.apertureModel);}
187
188 result = new BDSSamplerCustom(samplerName, *shape, samplerPlacement.partIDSetID);
189 break;
190 }
191 case BDSSamplerType::cylinder:
192 {
193 ErrorNonPositive(samplerPlacement.aper1, "aper1", samplerName);
194 ErrorNonPositive(samplerPlacement.aper2, "aper2", samplerName);
195 G4double startAnglePhi = samplerPlacement.startAnglePhi * CLHEP::rad;
196 G4double sweepAnglePhi = samplerPlacement.sweepAnglePhi * CLHEP::rad;
197 if (sweepAnglePhi <= 0) // default in parser is -1 to flag we should use 2pi now we have units
198 {sweepAnglePhi = CLHEP::twopi;}
199 else if (sweepAnglePhi > CLHEP::twopi + 1e-6)
200 {throw BDSException(__METHOD_NAME__, "\"sweepAnglePhi\" must be in range (0 to pi] in samplerplacement \"" + samplerName + "\"");}
201
202 result = new BDSSamplerCylinder(samplerName,
203 samplerPlacement.aper1 * CLHEP::m,
204 2 * samplerPlacement.aper2 * CLHEP::m,
205 startAnglePhi,
206 sweepAnglePhi,
207 samplerPlacement.partIDSetID);
208 radius = samplerPlacement.aper1 * CLHEP::m;
209 break;
210 }
211 case BDSSamplerType::cylinderforward:
212 {
213 ErrorNonPositive(samplerPlacement.aper1, "aper1", samplerName);
214 ErrorNonPositive(samplerPlacement.aper2, "aper2", samplerName);
215 if (BDS::IsFinite(samplerPlacement.startAnglePhi))
216 {BDS::Warning("\"startAnglePhi\" in samplerplacement \""+samplerName+"\" != 0 -> this has no effect for a cylinderforward sampler");}
217
218 G4double sweepAnglePhi = samplerPlacement.sweepAnglePhi * CLHEP::rad;
219 if (sweepAnglePhi <= 0) // default in parser is -1 to flag we should use 2pi now we have units
220 {sweepAnglePhi = CLHEP::twopi;}
221 else if (sweepAnglePhi > CLHEP::twopi + 1e-6)
222 {throw BDSException(__METHOD_NAME__, "\"sweepAnglePhi\" must be in range (0 to pi] in samplerplacement \"" + samplerName + "\"");}
223 G4double startAnglePhi = -0.5*sweepAnglePhi;
224
225 result = new BDSSamplerCylinder(samplerName,
226 samplerPlacement.aper1 * CLHEP::m,
227 2 * samplerPlacement.aper2 * CLHEP::m,
228 startAnglePhi,
229 sweepAnglePhi,
230 samplerPlacement.partIDSetID);
231 radius = samplerPlacement.aper1 * CLHEP::m;
232 break;
233 }
234 case BDSSamplerType::sphere:
235 {
236 ErrorNonPositive(samplerPlacement.aper1, "aper1", samplerName);
237 G4double startAnglePhi = samplerPlacement.startAnglePhi * CLHEP::rad;
238 G4double sweepAnglePhi = samplerPlacement.sweepAnglePhi * CLHEP::rad;
239 if (sweepAnglePhi <= 0) // default in parser is -1 to flag we should use 2pi now we have units
240 {sweepAnglePhi = CLHEP::twopi;}
241 else if (sweepAnglePhi > CLHEP::twopi + 1e-6)
242 {throw BDSException(__METHOD_NAME__, "\"sweepAnglePhi\" must be in range (0 to 2 pi] in samplerplacement \"" + samplerName + "\"");}
243 G4double startAngleTheta = samplerPlacement.startAngleTheta * CLHEP::rad;
244 if (startAngleTheta < 0)
245 {throw BDSException(__METHOD_NAME__, "\"startAngleTheta\" must be in the range [0 to pi] in samplerplacement \"" + samplerName + "\"");}
246 G4double sweepAngleTheta = samplerPlacement.sweepAngleTheta * CLHEP::rad;
247 if (sweepAngleTheta <= 0) // default in parser is -1 to flag we should use 2pi now we have units
248 {sweepAngleTheta = CLHEP::pi;}
249 else if (sweepAngleTheta > CLHEP::pi + 1e-6)
250 {throw BDSException(__METHOD_NAME__, "\"sweepAngleTheta\" must be in range (0 to pi] in samplerplacement \"" + samplerName + "\"");}
251 else if (sweepAngleTheta - startAngleTheta > CLHEP::pi + 1e-6)
252 {throw BDSException(__METHOD_NAME__, "\"startAngleTheta\" + \"sweepAngleTheta\" must be in range (0 to pi] in samplerplacement \"" + samplerName + "\"");}
253
254 result = new BDSSamplerSphere(samplerName,
255 samplerPlacement.aper1 * CLHEP::m,
256 startAnglePhi,
257 sweepAnglePhi,
258 startAngleTheta,
259 sweepAngleTheta,
260 samplerPlacement.partIDSetID);
261 radius = samplerPlacement.aper1 * CLHEP::m;
262 break;
263 }
264 case BDSSamplerType::sphereforward:
265 {
266 ErrorNonPositive(samplerPlacement.aper1, "aper1", samplerName);
267 if (BDS::IsFinite(samplerPlacement.startAnglePhi))
268 {BDS::Warning("\"startAnglePhi\" in samplerplacement \""+samplerName+"\" != 0 -> this has no effect for a sphereforward sampler");}
269 if (BDS::IsFinite(samplerPlacement.startAngleTheta))
270 {BDS::Warning("\"startAngleTheta\" in samplerplacement \""+samplerName+"\" != 0 -> this has no effect for a sphereforward sampler");}
271
272 G4double sweepAnglePhi = samplerPlacement.sweepAnglePhi * CLHEP::rad;
273 if (sweepAnglePhi <= 0) // default in parser is -1 to flag we should use 2pi now we have units
274 {sweepAnglePhi = CLHEP::twopi;}
275 else if (sweepAnglePhi > CLHEP::twopi + 1e-6)
276 {throw BDSException(__METHOD_NAME__, "\"sweepAnglePhi\" must be in range (0 to 2 pi] in samplerplacement \"" + samplerName + "\"");}
277 G4double sweepAngleTheta = samplerPlacement.sweepAngleTheta * CLHEP::rad;
278 if (sweepAngleTheta <= 0) // default in parser is -1 to flag we should use 2pi now we have units
279 {sweepAngleTheta = CLHEP::pi;}
280 else if (sweepAngleTheta > CLHEP::pi + 1e-6)
281 {throw BDSException(__METHOD_NAME__, "\"sweepAngleTheta\" must be in range (0 to pi] in samplerplacement \"" + samplerName + "\"");}
282
283 G4double startAngleTheta = CLHEP::halfpi -0.5*sweepAngleTheta;
284 G4double startAnglePhi = -0.5*sweepAnglePhi;
285
286 result = new BDSSamplerSphere(samplerName,
287 samplerPlacement.aper1 * CLHEP::m,
288 startAnglePhi,
289 sweepAnglePhi,
290 startAngleTheta,
291 sweepAngleTheta,
292 samplerPlacement.partIDSetID);
293 radius = samplerPlacement.aper1 * CLHEP::m;
294 break;
295 }
296 }
297 return result;
298}
299
301 BDSSamplerType st) const
302{
303 switch (st.underlying())
304 {
305 case BDSSamplerType::cylinderforward:
306 {
307 auto rmExisting = trans.getRotation();
308 G4RotationMatrix rm;
309 rm.rotate(-CLHEP::halfpi, {0,0,1});
310 rm.rotate(-CLHEP::halfpi, {1,0,0});
311 rmExisting *= rm;
312 trans = G4Transform3D(rmExisting, trans.getTranslation());
313 break;
314 }
315 case BDSSamplerType::sphereforward:
316 {
317 auto rmExisting = trans.getRotation();
318 G4RotationMatrix rm;
319 rm.rotate(-CLHEP::halfpi, {0,1,0});
320 rmExisting *= rm;
321 trans = G4Transform3D(rmExisting, trans.getTranslation());
322 break;
323 }
324 default:
325 {break;}
326 }
327}
328
330 G4double samplerRadius)
331{
332 BDSSamplerInfo* samplerInfo = element->GetSamplerInfo();
333 if (!samplerInfo)
334 {return;}
335 BDSSamplerType samplerType = samplerInfo->samplerType;
336 if (samplerType == BDSSamplerType::none)
337 {return;}
338 // else must be a valid sampler
339
340 G4String name = samplerInfo->name;
341 G4double sEnd = element->GetSPositionEnd();
342
343 BDSSampler* sampler = nullptr;
344 switch (samplerType.underlying())
345 {
346 case BDSSamplerType::plane:
347 {sampler = samplerInstances[samplerInfo->pdgSetID]; break;}
348 case BDSSamplerType::cylinder:
349 {// these are built uniquely for each instance so the length matches exactly
350 // for a cylindrical sampler we make it 'fit' the component
351 G4double boundingRadius = element->GetExtent().TransverseBoundingRadius();
352 if (boundingRadius > 0) // protect against bad extent, i.e. 0
353 {samplerRadius = boundingRadius;}
354
355 G4double length = element->GetChordLength();
356 G4double angle = element->GetAngle();
357 if (samplerInfo->startElement && samplerInfo->finishElement)
358 {// to cover a range of split components, e.g. an sbend
359 const auto startElement = samplerInfo->startElement;
360 const auto finishElement = samplerInfo->finishElement;
361 G4ThreeVector connectingVector = finishElement->GetReferencePositionEnd() - startElement->GetReferencePositionStart();
362 G4double chordLength = connectingVector.mag();
363
364 G4ThreeVector directionFrameStart = G4ThreeVector(0,0,1).transform(*(startElement->GetReferenceRotationStart()));
365 G4ThreeVector directionFrameFinish = G4ThreeVector(0,0,1).transform(*(finishElement->GetReferenceRotationEnd()));
366
367 G4ThreeVector dChordFrameStart = connectingVector.unit() - directionFrameStart;
368 G4ThreeVector dChordFrameFinish = connectingVector.unit() - directionFrameFinish;
369
370 // we purposively ignore possible pole face rotations or a different input normal
371 // this is convention for cylindrical samplers where we do the same for drifts
372 G4ThreeVector ipfnCSampler = G4ThreeVector(0,0,-1) + dChordFrameStart;
373 G4ThreeVector opfnCSampler = G4ThreeVector(0,0,1) - dChordFrameFinish;
374
375 sampler = new BDSSamplerCylinder(name,
376 samplerRadius,
377 chordLength,
378 ipfnCSampler,
379 opfnCSampler,
380 0, CLHEP::twopi,
381 samplerInfo->pdgSetID);
382
383 }
384 else if (BDS::IsFinite(angle))
385 {
386 const auto ipfn = element->GetAcceleratorComponent()->InputFaceNormal();
387 const auto opfn = element->GetAcceleratorComponent()->OutputFaceNormal();
388 sampler = new BDSSamplerCylinder(name,
389 samplerRadius,
390 length,
391 ipfn,
392 opfn,
393 0, CLHEP::twopi,
394 samplerInfo->pdgSetID);
395 }
396 else
397 {
398 sampler = new BDSSamplerCylinder(name,
399 samplerRadius,
400 length,
401 0, CLHEP::twopi,
402 samplerInfo->pdgSetID);
403 }
404 break;
405 }
406 default: // no spherical samplers for beam line samplers
407 {break;} // leave as nullptr - shouldn't occur due to if at top
408 }
409
410 if (sampler)
411 {
412 G4Transform3D* pt = new G4Transform3D(*element->GetSamplerPlacementTransform());
413
414 G4int samplerID = BDSSamplerRegistry::Instance()->RegisterSampler(name,
415 sampler,
416 *pt,
417 sEnd,
418 element,
419 samplerType,
420 samplerRadius);
421
422 G4VPhysicalVolume* samplerWorld = GetWorld();
423 samplerWorldLV = samplerWorld->GetLogicalVolume();
424 const G4bool checkOverlaps = BDSGlobalConstants::Instance()->CheckOverlaps();
425 // record placements for cleaning up at destruction.
426 G4PVPlacement* pl = new G4PVPlacement(*pt, // placement transform
427 sampler->GetContainerLogicalVolume(), // logical volume
428 name + "_pv", // name of placement
429 samplerWorldLV, // mother volume
430 false, // no boolean operation
431 samplerID, // copy number
432 checkOverlaps);
433
434 placements.push_back(pl);
435 }
436}
G4ThreeVector OutputFaceNormal() const
G4ThreeVector InputFaceNormal() const
BDSApertureInfo * Aperture(G4String name) const
const BDSBeamline * BeamlineMain() const
Accessor.
Holder class for all information required to describe an aperture.
A class that holds a fully constructed BDSAcceleratorComponent as well as any information relevant to...
G4double GetChordLength() const
Accessor.
G4double GetAngle() const
Accessor.
G4ThreeVector GetReferencePositionEnd() const
Accessor.
BDSExtent GetExtent() const
Accessor.
BDSSamplerInfo * GetSamplerInfo() const
Accessor.
G4double GetSPositionEnd() const
Accessor.
BDSAcceleratorComponent * GetAcceleratorComponent() const
Accessor.
G4Transform3D * GetSamplerPlacementTransform() const
Accessor.
A vector of BDSBeamlineElement instances - a beamline.
static G4Transform3D CreatePlacementTransform(const GMAD::Placement &placement, const BDSBeamline *beamLine, G4double *S=nullptr, BDSExtent *placementExtent=nullptr, const G4String &objectTypeForErrorMsg="placement")
General exception with possible name of object and message.
G4double TransverseBoundingRadius() const
Return a radius that would encompass the maximum x,y extent.
Definition BDSExtent.cc:182
G4LogicalVolume * GetContainerLogicalVolume() const
Accessor - see member for more info.
A class that holds global options and constants.
static BDSGlobalConstants * Instance()
Access method.
static void PrintProtectedNames(std::ostream &out)
Feedback for protected names.
Definition BDSOutput.cc:399
static G4bool InvalidSamplerName(const G4String &samplerName)
Test whether a sampler name is invalid or not.
Definition BDSOutput.cc:394
BDSParallelWorldSampler()=delete
No default constructor.
BDSSamplerPlane * generalPlane
General single sampler we use for plane samplers.
void AdjustTransform(G4Transform3D &trans, BDSSamplerType st) const
void ErrorNonPositive(G4double value, const G4String &variableName, const G4String &objectName) const
Utility function to reduce code.
void Place(const BDSBeamlineElement *element, G4double samplerRadius)
Place a sampler from a single element.
std::vector< G4VPhysicalVolume * > placements
Cache of the placements to clean up at the end.
BDSSampler * BuildSampler(const GMAD::SamplerPlacement &samplerPlacement, BDSSamplerType st, G4double &radius) const
Construct the geometry for a sampler. Update 'radius' by reference if applicable.
G4VisAttributes * samplerWorldVis
Visualisation attributes for the sampler world.
static BDSParser * Instance()
Access method.
Definition BDSParser.cc:29
std::vector< GMAD::SamplerPlacement > GetSamplerPlacements() const
Return the parser list of that object.
Definition BDSParser.hh:109
Custom shaped sampler with fixed thickness.
Cylindrical sampler around an object.
All info required to build a sampler but not place it.
Rectangular sampler with fixed thickness but variable x,y.
static BDSSamplerRegistry * Instance()
Accessor for registry.
G4String GetNameUnique(G4int index) const
Accessor.
G4int RegisterSampler(const G4String &name, BDSSampler *sampler, const G4Transform3D &transform=G4Transform3D(), G4double S=-1000, const BDSBeamlineElement *element=nullptr, BDSSamplerType type=BDSSamplerType::plane, G4double radius=0)
Spherical sampler around an object.
Base class and registry of sampler instances.
Definition BDSSampler.hh:35
type underlying() const
return underlying value (can be used in switch statement)
Sampler placement class for parser.
std::string name
Name of this samplerplacement.
int partIDSetID
The unique ID of the particle set given by the parser.
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())