BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
BDSCrystalFactory.cc
1/*
2Beam Delivery Simulation (BDSIM) Copyright (C) Royal Holloway,
3University of London 2001 - 2022.
4
5This file is part of BDSIM.
6
7BDSIM is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published
9by the Free Software Foundation version 3 of the License.
10
11BDSIM is distributed in the hope that it will be useful, but
12WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with BDSIM. If not, see <http://www.gnu.org/licenses/>.
18*/
19#include "BDSAcceleratorModel.hh"
20#include "BDSColours.hh"
21#include "BDSCrystal.hh"
22#include "BDSCrystalFactory.hh"
23#include "BDSCrystalInfo.hh"
24#include "BDSDebug.hh"
25#include "BDSException.hh"
26#include "BDSGlobalConstants.hh"
27#include "BDSMaterials.hh"
28#include "BDSUtilities.hh"
29
30#include "G4Box.hh"
31#include "G4DisplacedSolid.hh"
32#include "G4ExtrudedSolid.hh"
33#include "G4LogicalVolume.hh"
34#include "G4Material.hh"
35#include "G4RotationMatrix.hh"
36#include "G4String.hh"
37#include "G4ThreeVector.hh"
38#include "G4Torus.hh"
39#include "G4Tubs.hh"
40#include "G4TwoVector.hh"
41#include "G4Types.hh"
42#include "G4UserLimits.hh"
43#include "G4VisAttributes.hh"
44#include "G4Version.hh"
45
46#include "CLHEP/Units/SystemOfUnits.h"
47
48#include <algorithm>
49#include <cmath>
50#include <memory>
51#include <set>
52#include <vector>
53
54// only use the crystal extensions if using 10.4.p00 upwards
55#if G4VERSION_NUMBER > 1039
56#include "G4ExtendedMaterial.hh"
57#include "G4CrystalExtension.hh"
58#include "G4CrystalUnitCell.hh"
59#include "G4ChannelingMaterialData.hh"
60#include "G4LogicalCrystalVolume.hh"
61#endif
62
63BDSCrystalFactory::BDSCrystalFactory():
64 maxStepFactor(1.1),
65 nPoints(30)
66{
67 CleanUp();
68}
69
71{
73 crystalSolid = nullptr;
74 crystalLV = nullptr;
75 placementOffset = G4ThreeVector();
76 placementRotation = nullptr;
77}
78
80 const BDSCrystalInfo* recipe)
81{
82 CleanUp();
83
84 BDSCrystal* result = nullptr;
85 switch(recipe->shape.underlying())
86 {
87 case BDSCrystalType::box:
88 {result = CreateCrystalBox(name, recipe); break;}
89 case BDSCrystalType::cylinder:
90 {result = CreateCrystalCylinder(name, recipe); break;}
91 case BDSCrystalType::torus:
92 {result = CreateCrystalTorus(name, recipe); break;}
93 default:
94 {break;}
95 }
96 return result;
97}
98
99void BDSCrystalFactory::CommonConstruction(const G4String& nameIn,
100 const BDSCrystalInfo* recipe)
101{
102 allSolids.insert(crystalSolid);
103
104 // only in g4.10.4 onwards do we build the crystal extensions - otherwise regular LV
105#if G4VERSION_NUMBER > 1039
106 G4ExtendedMaterial* crystalMat = new G4ExtendedMaterial("crystal.material", recipe->material);
107
108 crystalMat->RegisterExtension(std::unique_ptr<G4CrystalExtension>(new G4CrystalExtension(crystalMat)));
109 G4CrystalExtension* crystalExtension = dynamic_cast<G4CrystalExtension*>(crystalMat->RetrieveExtension("crystal"));
110
111 G4CrystalUnitCell* uCell = new G4CrystalUnitCell(recipe->sizeA,
112 recipe->sizeB,
113 recipe->sizeC,
114 recipe->alpha,
115 recipe->beta,
116 recipe->gamma,
117 recipe->spaceGroup);
118 crystalExtension->SetUnitCell(uCell);
119
120 crystalMat->RegisterExtension(std::unique_ptr<G4ChannelingMaterialData>(new G4ChannelingMaterialData("channeling")));
121
122 G4ChannelingMaterialData* crystalChannelingData = (G4ChannelingMaterialData*)crystalMat->RetrieveExtension("channeling");
123 G4String fileName = BDS::GetFullPath(recipe->data);
124 if (!BDS::FileExists(fileName + "_pot.txt"))
125 {throw BDSException(__METHOD_NAME__, "No such crystal data files beginning with: \"" + fileName + "\"");}
126#ifdef BDSDEBUG
127 G4cout << __METHOD_NAME__ << "Raw data path: " << recipe->data << G4endl;
128 G4cout << __METHOD_NAME__ << "Using crystal data: " << fileName << G4endl;
129#endif
130 crystalChannelingData->SetFilename(fileName);
131
132 // -ve here due to right handed coordinate system + convention of +ve bending angle bends
133 // away from beam axis. This convention is also implied by when we do a -ve x axis rotation
134 // for cylindrical crystals (including the start and sweep angle).
135 G4double bendingRadius = -recipe->BendingRadiusHorizontal();
136 crystalChannelingData->SetBR(bendingRadius);
137
138 crystalLV = new G4LogicalCrystalVolume(crystalSolid,
139 crystalMat,
140 nameIn + "_crystal_lv",
141 nullptr, nullptr, nullptr,
142 true, 0, 0, 0, recipe->miscutAngleY);
143
144 BDSAcceleratorModel::Instance()->VolumeSet("crystals")->insert(crystalLV);
145#else
146 // build logical volumes
147 crystalLV = new G4LogicalVolume(crystalSolid,
148 recipe->material,
149 nameIn + "_crystal_lv");
150#endif
151 allLogicalVolumes.insert(crystalLV);
152
154 SetUserLimits(recipe->lengthZ);
155}
156
158{
159 G4VisAttributes* crysVisAttr = new G4VisAttributes(*BDSColours::Instance()->GetColour("crystal"));
160 crysVisAttr->SetVisibility(true);
161 crysVisAttr->SetForceLineSegmentsPerCircle(200);
162 allVisAttributes.insert(crysVisAttr);
163 crystalLV->SetVisAttributes(crysVisAttr);
164}
165
167{
168 auto defaultUL = BDSGlobalConstants::Instance()->DefaultUserLimits();
169 //copy the default and update with the length of the object rather than the default 1m
170 G4UserLimits* ul = BDS::CreateUserLimits(defaultUL, length*maxStepFactor);
171
172 if (ul != defaultUL) // if it's not the default register it
173 {allUserLimits.insert(ul);}
174 crystalLV->SetUserLimits(ul);
175}
176
178 const BDSExtent& extent)
179{
180 // build the BDSCrystal instance and return it
181 BDSCrystal* aCrystal = new BDSCrystal(recipe, crystalSolid, crystalLV,
182 extent, placementOffset, placementRotation);
183
184 // register objects
185 aCrystal->RegisterSolid(allSolids);
186 aCrystal->RegisterSensitiveVolume(crystalLV, BDSSDType::collimatorcomplete);
187 aCrystal->RegisterUserLimits(allUserLimits);
188 aCrystal->RegisterVisAttributes(allVisAttributes);
189 aCrystal->ExcludeLogicalVolumeFromBiasing(crystalLV); // can't double bias one LV ie with generic biasing
190
191 return aCrystal;
192}
193
195 const BDSCrystalInfo* recipe)
196{
197 crystalSolid = new G4Box(nameIn + "_solid",
198 recipe->lengthX * 0.5,
199 recipe->lengthY * 0.5,
200 recipe->lengthZ * 0.5);
201
202 CommonConstruction(nameIn, recipe);
203
204 placementOffset = G4ThreeVector(0, 0, 0);
205 BDSExtent ext = BDSExtent(recipe->lengthX * 0.5,
206 recipe->lengthY * 0.5,
207 recipe->lengthZ * 0.5);
208
209 return BuildCrystalObject(recipe, ext); // no placement offset - leave as default
210}
211
213 G4double& startAngle,
214 G4double& sweepAngle) const
215{
216 if (bendingAngle >= 0)
217 {
218 startAngle = CLHEP::twopi - 0.5 * bendingAngle;
219 sweepAngle = bendingAngle;
220 }
221 else
222 {
223 startAngle = CLHEP::pi - 0.5*std::abs(bendingAngle);
224 sweepAngle = std::abs(bendingAngle);
225 }
226}
227
229 G4double xBendingRadius,
230 G4double xThickness,
231 const BDSCrystalInfo* recipe) const
232{
233 // calculate horizontal extents - do in +ve version and flip for -ve
234 G4double xHi = xBendingRadius - (xBendingRadius - xThickness*0.5)*std::cos(std::abs(xBendingAngle)*0.5);
235 G4double xLow = -(xThickness * 0.5);
236 if (xBendingAngle > 0)
237 {
238 std::swap(xHi, xLow);
239 xHi *= -1;
240 xLow *= -1;
241 }
242 G4double dz = (xBendingRadius + xThickness*0.5) * std::sin(std::abs(xBendingAngle)*0.5);
243
244 // note this is the extent the partial cylinder would have with its intended placement.
245 // convenient for now but doesn't strictly match the solid which is built along the z axis.
246 BDSExtent ext = BDSExtent(xLow, xHi,
247 recipe->lengthY * 0.5, recipe->lengthY * 0.5,
248 -dz, dz);
249 return ext;
250}
251
253 const BDSCrystalInfo* recipe)
254{
255 G4double ba = recipe->bendingAngleYAxis; // bending angle
256
257 // if no bending angle, create a box as that's all we can create
258 if (!BDS::IsFinite(ba))
259 {return CreateCrystalBox(nameIn, recipe);}
260
261 G4double xBR = std::abs(recipe->BendingRadiusHorizontal());
262 G4double thickness = recipe->lengthX;
263
264 // calculate start angle and sweep angle
265 G4double startAngle;
266 G4double sweepAngle;
267 CalculateSolidAngles(ba, startAngle, sweepAngle);
268
269 G4Tubs* rawShape = new G4Tubs(nameIn + "_solid",
270 xBR - 0.5*thickness,
271 xBR + 0.5*thickness,
272 recipe->lengthY*0.5,
273 startAngle,
274 sweepAngle);
275
276 // crystal channelling only works in local unitX of a given solid which
277 // makes it impossible to use a cylinder. we cheat by using a G4DisplacedSolid
278 // that's a class used internally by geant4's boolean solids to rotate and translate
279 // the frame of a solid. another option was an intersection with a big box, but
280 // geant4 can't handle this.
281 G4RotationMatrix* relativeRotation = new G4RotationMatrix();
282 relativeRotation->rotateX(-CLHEP::halfpi);
283 G4double bendingRadiusH = recipe->BendingRadiusHorizontal(); // includes sign
284 G4ThreeVector offset(-bendingRadiusH,0,0);
285 crystalSolid = new G4DisplacedSolid(nameIn + "_shifted_solid",
286 rawShape,
287 relativeRotation,
288 offset);
289
290 CommonConstruction(nameIn, recipe);
291 placementOffset = G4ThreeVector();
292 BDSExtent ext = CalculateExtents(ba, xBR, thickness, recipe);
293 return BuildCrystalObject(recipe, ext);
294}
295
297 const BDSCrystalInfo* recipe)
298{
299 G4double xBA = recipe->bendingAngleYAxis; // bending angle horizontal
300 G4double xBR = std::abs(recipe->BendingRadiusHorizontal());
301 G4double zBA = recipe->bendingAngleZAxis;
302 G4double zBR = std::abs(recipe->BendingRadiusVertical());
303
304 // if no bending angle, create a box as that's all we can create
305 if (!BDS::IsFinite(xBA))
306 {return CreateCrystalBox(nameIn, recipe);}
307
308 G4double thickness = recipe->lengthX;
309
310 // calculate start angle and sweep angle
311 G4double startAngle;
312 G4double sweepAngle;
313 CalculateSolidAngles(xBA, startAngle, sweepAngle);
314
315 G4double angFraction = sweepAngle / (G4double)nPoints;
316 std::vector<G4TwoVector> points;
317
318 G4double innerRadius = xBR - thickness*0.5;
319 for (G4int i = 0; i < nPoints; i++)
320 {
321 G4double ang = startAngle + (G4double)i * angFraction;
322 G4double x = innerRadius * std::cos(ang);
323 G4double y = innerRadius * std::sin(ang);
324 points.emplace_back(G4TwoVector(x,y));
325 }
326 G4double outerRadius = xBR + thickness*0.5;
327 for (G4int i = nPoints; i > 0; i--)
328 {
329 G4double ang = startAngle + (G4double)i * angFraction;
330 G4double x = outerRadius * std::cos(ang);
331 G4double y = outerRadius * std::sin(ang);
332 points.emplace_back(G4TwoVector(x,y));
333 }
334
335 G4double zStartAngle = CLHEP::twopi - 0.5 * zBA;
336 G4double zSweepAngle = zBA;
337
338 // create vertical z sections, accumulate max/min shift at same time
339 G4double xmin = 0;
340 G4double xmax = 0;
341 G4double zAngFraction = zSweepAngle / (G4double)nPoints;
342 std::vector<G4ExtrudedSolid::ZSection> zSections;
343 for (G4int i = 0; i < nPoints; i++)
344 {
345 G4double zAng = zStartAngle + (G4double)i * zAngFraction;
346 G4double z = zBR * std::sin(zAng);
347 G4double x = zBR - (zBR * std::cos(zAng));
348 if (zBA < 0)
349 {
350 z *= -1;
351 x *= -1;
352 }
353 xmin = std::min(xmin, x);
354 xmax = std::max(xmax, x);
355 zSections.emplace_back(G4ExtrudedSolid::ZSection(z, G4TwoVector(x, 0), 1));
356 }
357
358 G4ExtrudedSolid* rawShape = new G4ExtrudedSolid(nameIn + "_solid",
359 points,
360 zSections);
361
362 // crystal channelling only works in local unitX of a given solid which
363 // makes it impossible to use a cylinder. we cheat by using a G4DisplacedSolid
364 // that's a class used internally by geant4's boolean solids to rotate and translate
365 // the frame of a solid. another option was an intersection with a big box, but
366 // geant4 can't handle this.
367 G4RotationMatrix* relativeRotation = new G4RotationMatrix();
368 relativeRotation->rotateX(-CLHEP::halfpi);
369 G4ThreeVector offset(0,0,0);
370 crystalSolid = new G4DisplacedSolid(nameIn + "_shifted_solid",
371 rawShape,
372 relativeRotation,
373 offset);
374
375 CommonConstruction(nameIn, recipe);
376
377 // placement offset - no rotation as we've rotated the solid internally
378 placementOffset = G4ThreeVector(-1*recipe->BendingRadiusHorizontal(), 0, 0);
379
380 BDSExtent ext = CalculateExtents(xBA, xBR, thickness, recipe);
381 G4double xLow = ext.XNeg() + xmin;
382 G4double xHi = ext.XPos() + xmax;
383 BDSExtent extTorus = BDSExtent(xLow, xHi, ext.YNeg(), ext.YPos(), ext.ZNeg(), ext.ZPos());
384
385 return BuildCrystalObject(recipe, extTorus);
386}
std::set< G4LogicalVolume * > * VolumeSet(const G4String &name)
Returns pointer to a set of logical volumes. If no set by that name exits, create it.
static BDSColours * Instance()
singleton pattern
Definition: BDSColours.cc:33
void CommonConstruction(const G4String &nameIn, const BDSCrystalInfo *recipe)
Common construction tasks.
const G4int nPoints
Number of points to split torus into.
void CalculateSolidAngles(G4double bendingAngle, G4double &startAngle, G4double &sweepAngle) const
const G4double maxStepFactor
Fraction of length for maximum step in user limits.
BDSCrystal * CreateCrystalBox(const G4String &nameIn, const BDSCrystalInfo *recipe)
Create box geometry for a crystal.
BDSCrystal * CreateCrystalCylinder(const G4String &nameIn, const BDSCrystalInfo *recipe)
Create cylinder geometry for a crystal.
BDSExtent CalculateExtents(G4double bendingAngle, G4double xBendingRadius, G4double xThickness, const BDSCrystalInfo *recipe) const
Produce an extent for a curved crystal.
BDSCrystal * CreateCrystalTorus(const G4String &nameIn, const BDSCrystalInfo *recipe)
Create torus geometry for a cyrstal.
BDSCrystal * CreateCrystal(const G4String &nameIn, const BDSCrystalInfo *recipe)
Main interface to create a crystal.
BDSCrystal * BuildCrystalObject(const BDSCrystalInfo *recipe, const BDSExtent &extent)
build beampipe and register logical volumes
void SetUserLimits(G4double length)
Set user limits.
void SetVisAttributes()
Set visual attributes.
Holder for all information required to create a crystal.
G4String data
Potential data path.
G4double lengthZ
Z dimension.
G4Material * material
Material.
G4double bendingAngleZAxis
Bending angle about Z axis.
G4double lengthX
X dimension.
BDSCrystalType shape
Shape of geometry.
G4double bendingAngleYAxis
Bending angle about Y axis.
G4double lengthY
Y dimension.
An object for both a crystal from a factory.
Definition: BDSCrystal.hh:40
General exception with possible name of object and message.
Definition: BDSException.hh:35
Holder for +- extents in 3 dimensions.
Definition: BDSExtent.hh:39
G4double XPos() const
Accessor.
Definition: BDSExtent.hh:66
G4double ZPos() const
Accessor.
Definition: BDSExtent.hh:70
G4double XNeg() const
Accessor.
Definition: BDSExtent.hh:65
G4double YNeg() const
Accessor.
Definition: BDSExtent.hh:67
G4double YPos() const
Accessor.
Definition: BDSExtent.hh:68
G4double ZNeg() const
Accessor.
Definition: BDSExtent.hh:69
virtual void FactoryBaseCleanUp()
Empty containers for next use - factories are never deleted so can't rely on scope.
void RegisterUserLimits(G4UserLimits *userLimit)
virtual void ExcludeLogicalVolumeFromBiasing(G4LogicalVolume *lv)
void RegisterVisAttributes(G4VisAttributes *visAttribute)
void RegisterSolid(G4VSolid *solid)
void RegisterSensitiveVolume(G4LogicalVolume *sensitiveVolume, BDSSDType sensitivityType)
static BDSGlobalConstants * Instance()
Access method.
type underlying() const
return underlying value (can be used in switch statement)
G4String GetFullPath(G4String filename, bool excludeNameFromPath=false, bool useCWDForPrefix=false)
G4UserLimits * CreateUserLimits(G4UserLimits *defaultUL, G4double length, G4double fraction=1.6)
G4bool FileExists(const G4String &filename)
Checks if filename exists.
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())