LDMX Software
TrackersTrackingGeometry.cxx
1#include "Tracking/geo/TrackersTrackingGeometry.h"
2
3#include <G4Box.hh>
4#include <G4VisExtent.hh>
5#include <algorithm>
6
7#include "Framework/Exception/Exception.h"
8
9namespace tracking::geo {
10
11const std::string TrackersTrackingGeometry::NAME = "TrackersTrackingGeometry";
12
13TrackersTrackingGeometry::TrackersTrackingGeometry(
14 const Acts::GeometryContext& gctx, const std::string& gdml,
15 double tracker_y_length, double tracker_z_length)
16 : TrackingGeometry(NAME, gctx, gdml) {
17 std::vector<Acts::CuboidVolumeBuilder::VolumeConfig> vol_builder_configs;
18
19 tagger_ = findDaughterByName(f_world_phys_vol_, "tagger_PV");
20 if (tagger_) {
21 buildTaggerLayoutMap(tagger_, "tagger");
22 vol_builder_configs.push_back(buildVolumeConfig(
23 tagger_, tagger_layout_, tracker_y_length, tracker_z_length, "Tagger"));
24 } else {
25 ldmx_log(warn) << "No tagger_PV found in detector — skipping tagger "
26 "tracking volume";
27 }
28
29 recoil_ = findDaughterByName(f_world_phys_vol_, "recoil_PV");
30 if (recoil_) {
31 buildRecoilLayoutMap(recoil_, "recoil");
32 auto recoil_volume_cfg = buildVolumeConfig(
33 recoil_, recoil_layout_, tracker_y_length, tracker_z_length, "Recoil");
34
35 // Extend the recoil volume upstream so the target (x=0) and the target
36 // scoring planes sit inside it for the Navigator. Thick targets (Ti
37 // 3.56mm, Al 8.89mm) put the upstream scoring plane several mm before
38 // x=0; a start point outside every volume fails truth-track propagation.
39 // Clamp to the tagger volume edge so the two configs never overlap.
40 // This only ever grows the volume: reduced geometries whose recoil
41 // already extends past low_x are left alone.
42 {
43 double low_x = -8.0; // mm
44 if (!vol_builder_configs.empty()) {
45 // tagger config was pushed first when present
46 const auto& tagger_cfg = vol_builder_configs.front();
47 low_x = std::max(
48 low_x, tagger_cfg.position[0] + tagger_cfg.length[0] / 2.0 + 1.0);
49 }
50 double upstream_x =
51 recoil_volume_cfg.position[0] - recoil_volume_cfg.length[0] / 2.0;
52 double downstream_x =
53 recoil_volume_cfg.position[0] + recoil_volume_cfg.length[0] / 2.0;
54 if (upstream_x > low_x) {
55 recoil_volume_cfg.length[0] = downstream_x - low_x;
56 recoil_volume_cfg.position[0] = (downstream_x + low_x) / 2.0;
57 }
58 }
59
60 vol_builder_configs.push_back(recoil_volume_cfg);
61 } else {
62 ldmx_log(warn) << "No recoil_PV found in detector — skipping recoil "
63 "tracking volume";
64 }
65
66 if (vol_builder_configs.empty()) {
67 ldmx_log(warn) << "No tracker volumes found — tracking geometry will be "
68 "empty";
69 return;
70 }
71
72 // Create the builder
73 Acts::CuboidVolumeBuilder cvb;
74
75 Acts::CuboidVolumeBuilder::Config config;
76 config.position = {-200, 0., 0.};
77 // acts x = global z
78 // global z: -200 - 900/2 = -650 to -200 + 900/2 = 250 mm
79 // global y: -70 to 70
80 // global x: -240 to 240
81 config.length = {900, tracker_y_length, tracker_z_length};
82 config.volumeCfg = vol_builder_configs;
83
84 cvb.setConfig(config);
85
86 Acts::TrackingGeometryBuilder::Config tgb_cfg;
87 tgb_cfg.trackingVolumeBuilders.push_back(
88 [=](const auto& cxt, const auto& inner, const auto&) {
89 return cvb.trackingVolume(cxt, inner, nullptr);
90 });
91
92 Acts::TrackingGeometryBuilder tgb(tgb_cfg);
93 t_geometry_ = tgb.trackingGeometry(gctx_);
94
95 // dumpGeometry("./");
96 makeLayerSurfacesMap();
97}
98
99void TrackersTrackingGeometry::buildRecoilLayoutMap(G4VPhysicalVolume* pvol,
100 std::string surfacename) {
101 ldmx_log(trace) << "Building layout for the " << pvol->GetName()
102 << " tracker";
103 getAllDaughters(pvol);
104
105 // Get the global transform
106 Acts::Transform3 tracker_transform = getTransform(*pvol);
107
108 G4LogicalVolume* l_vol = pvol->GetLogicalVolume();
109 for (G4int i = 0; i < l_vol->GetNoDaughters(); i++) {
110 std::string sln = l_vol->GetDaughter(i)->GetName();
111 if (sln.find(surfacename) != std::string::npos) {
112 G4VPhysicalVolume* component0_volume{nullptr};
113 G4VPhysicalVolume* active_sensor{nullptr};
114 Acts::Transform3 ref1_transform = getTransform(*(l_vol->GetDaughter(i)));
115 int sensor_copy_nr = -999;
116
117 // recoil_l1(4)_axial(stereo)->LDMXRecoilL14ModuleVolume_component0_physvol
118 // This works for v12
119 if (sln.find("axial") != std::string::npos ||
120 sln.find("stereo") != std::string::npos) {
121 component0_volume =
122 findDaughterByName(l_vol->GetDaughter(i),
123 "LDMXRecoilL14ModuleVolume_component0_physvol");
124 if (!component0_volume)
125 EXCEPTION_RAISE("BadGeometry",
126 "Could not find component0 volume for L14 Recoil");
127 active_sensor = findDaughterByName(
128 component0_volume,
129 "LDMXRecoilL14ModuleVolume_component0Sensor0_physvol");
130
131 }
132
133 // recoil_l5_sensorX->LDMXRecoilL56ModuleVolume_component0_physvol
134 // This works for v12
135 else if (sln.find("l5_sensor") != std::string::npos ||
136 sln.find("l6_sensor") != std::string::npos) {
137 component0_volume =
138 findDaughterByName(l_vol->GetDaughter(i),
139 "LDMXRecoilL56ModuleVolume_component0_physvol");
140 if (!component0_volume)
141 EXCEPTION_RAISE("BadGeometry",
142 "Could not find component0 volume for L56 Recoil");
143 active_sensor = findDaughterByName(
144 component0_volume,
145 "LDMXRecoilL56ModuleVolume_component0Sensor0_physvol");
146 }
147
148 // recoil_PV tracker->recoil_l14_sensor_vol_PV->recoil_l14_active_sensor
149 // This works for v14
150 else if (sln.find("sensor_vol")) {
151 active_sensor =
152 findDaughterByName(l_vol->GetDaughter(i), "active_sensor");
153 sensor_copy_nr = l_vol->GetDaughter(i)->GetCopyNo();
154 }
155
156 else
157 EXCEPTION_RAISE("BadGeometry", "Could not build recoil layout");
158
159 if (!active_sensor)
160 EXCEPTION_RAISE("BadGeometry",
161 "Could not find ActiveSensor for recoil volume");
162
163 Acts::Transform3 ref2_transform = Acts::Transform3::Identity();
164
165 if (component0_volume)
166 ref2_transform = getTransform(*(component0_volume));
167
168 std::shared_ptr<Acts::PlaneSurface> sensor_surface = getSurfacePtr(
169 active_sensor, tracker_transform * ref1_transform * ref2_transform);
170
171 // Build the layout
172 if (sln == "recoil_l1_axial" || sln == "recoil_l1_stereo" ||
173 sensor_copy_nr == 10 || sensor_copy_nr == 20)
174 recoil_layout_["recoil_tracker_L1"].push_back(sensor_surface);
175
176 if (sln == "recoil_l2_axial" || sln == "recoil_l2_stereo" ||
177 sensor_copy_nr == 30 || sensor_copy_nr == 40)
178 recoil_layout_["recoil_tracker_L2"].push_back(sensor_surface);
179
180 if (sln == "recoil_l3_axial" || sln == "recoil_l3_stereo" ||
181 sensor_copy_nr == 50 || sensor_copy_nr == 60)
182 recoil_layout_["recoil_tracker_L3"].push_back(sensor_surface);
183
184 if (sln == "recoil_l4_axial" || sln == "recoil_l4_stereo" ||
185 sensor_copy_nr == 70 || sensor_copy_nr == 80)
186 recoil_layout_["recoil_tracker_L4"].push_back(sensor_surface);
187
188 if (sln == "recoil_l5_sensor1" || sln == "recoil_l5_sensor2" ||
189 sln == "recoil_l5_sensor3" || sln == "recoil_l5_sensor4" ||
190 sln == "recoil_l5_sensor5" || sln == "recoil_l5_sensor6" ||
191 sln == "recoil_l5_sensor7" || sln == "recoil_l5_sensor8" ||
192 sln == "recoil_l5_sensor9" || sln == "recoil_l5_sensor10" ||
193 (sensor_copy_nr >= 90 && sensor_copy_nr <= 99))
194
195 recoil_layout_["recoil_tracker_L5"].push_back(sensor_surface);
196
197 if (sln == "recoil_l6_sensor1" || sln == "recoil_l6_sensor2" ||
198 sln == "recoil_l6_sensor3" || sln == "recoil_l6_sensor4" ||
199 sln == "recoil_l6_sensor5" || sln == "recoil_l6_sensor6" ||
200 sln == "recoil_l6_sensor7" || sln == "recoil_l6_sensor8" ||
201 sln == "recoil_l6_sensor9" || sln == "recoil_l6_sensor10" ||
202 (sensor_copy_nr >= 100 && sensor_copy_nr <= 109))
203 recoil_layout_["recoil_tracker_L6"].push_back(sensor_surface);
204
205 } // found the daughter
206 } // loop on daughters
207} // BuildRecoilLayoutMap
208
209// This function gets the surfaces from the trackers and orders them in
210// ascending z_.
211
212void TrackersTrackingGeometry::buildTaggerLayoutMap(G4VPhysicalVolume* pvol,
213 std::string surfacename) {
214 ldmx_log(trace) << "Building layout for the " << pvol->GetName()
215 << " tracker";
216 // getAllDaughters(pvol);
217
218 // Get the global transform
219 Acts::Transform3 tracker_transform = getTransform(*pvol);
220
221 G4LogicalVolume* l_vol = pvol->GetLogicalVolume();
222 for (G4int i = 0; i < l_vol->GetNoDaughters(); i++) {
223 std::string sln = l_vol->GetDaughter(i)->GetName();
224
225 // To distinguish which layers need to be selected
226 if (sln.find(surfacename) != std::string::npos) {
227 // v12
228
229 // Box for the module_ (slightly bigger than the sensor)
230 // LDMXTaggerModuleVolume_physvol -> LDMXTaggerModuleVolume_component0Box,
231 // this is the sensor + inactive region
232 // LDMXTaggerModuleVolume_component0Box->
233 // LDMXTaggerModuleVolume_component0Sensor0Box, this is the sensor itself
234 // (active region)
235
236 // tagger_ -> LDMXTaggerModuleVolume_physvol1 ->
237 // LDMXTaggerModuleVolume_component0_physvol
238 // ->LDMXTaggerModuleVolume_component0Sensor0_physvol
239 // -> Get Box for the dimension: GetLogical->GetSolid
240 // For the positioning of the sensor:
241 // A position of physvol1 + position of the component0_physvol +
242 // position of the sensor physvol. Thickness I can grab it from the box,
243 // or I just hardcode it. S transform1 * transform2 * transform3
244 // ....
245
246 // v14
247 // tagger_PV->tagger_sensor_vol_PV->tagger_active_sensor
248 // Then use the copyNumber..
249
250 // Get the sensor volume placement
251 Acts::Transform3 ref1_transform = getTransform(*(l_vol->GetDaughter(i)));
252
253 G4VPhysicalVolume* component0_volume = findDaughterByName(
254 l_vol->GetDaughter(i), "LDMXTaggerModuleVolume_component0_physvol");
255
256 Acts::Transform3 ref2_transform = Acts::Transform3::Identity();
257 G4VPhysicalVolume* active_sensor = nullptr;
258 int sensor_copy_nr = -999;
259
260 // Get Component0 transform. v12
261 if (component0_volume) {
262 ref2_transform = getTransform(*(component0_volume));
263 active_sensor = findDaughterByName(
264 component0_volume,
265 "LDMXTaggerModuleVolume_component0Sensor0_physvol");
266 }
267 // v14
268 else {
269 active_sensor =
270 findDaughterByName(l_vol->GetDaughter(i), "active_sensor");
271 sensor_copy_nr = (l_vol->GetDaughter(i))->GetCopyNo();
272 }
273
274 if (!active_sensor) {
275 ldmx_log(fatal) << "Could not find the ActiveSensor for tagger volume "
276 << l_vol->GetDaughter(i)->GetName();
277 }
278
279 // Get the surface
280 std::shared_ptr<Acts::PlaneSurface> sensor_surface = getSurfacePtr(
281 active_sensor, tracker_transform * ref1_transform * ref2_transform);
282
283 if (sln == "LDMXTaggerModuleVolume_physvol1" ||
284 sln == "LDMXTaggerModuleVolume_physvol2" || sensor_copy_nr == 130 ||
285 sensor_copy_nr == 140)
286 tagger_layout_["tagger_tracker_L1"].push_back(sensor_surface);
287
288 if (sln == "LDMXTaggerModuleVolume_physvol3" ||
289 sln == "LDMXTaggerModuleVolume_physvol4" || sensor_copy_nr == 110 ||
290 sensor_copy_nr == 120)
291 tagger_layout_["tagger_tracker_L2"].push_back(sensor_surface);
292
293 if (sln == "LDMXTaggerModuleVolume_physvol5" ||
294 sln == "LDMXTaggerModuleVolume_physvol6" || sensor_copy_nr == 90 ||
295 sensor_copy_nr == 100)
296 tagger_layout_["tagger_tracker_L3"].push_back(sensor_surface);
297
298 if (sln == "LDMXTaggerModuleVolume_physvol7" ||
299 sln == "LDMXTaggerModuleVolume_physvol8" || sensor_copy_nr == 70 ||
300 sensor_copy_nr == 80)
301 tagger_layout_["tagger_tracker_L4"].push_back(sensor_surface);
302
303 if (sln == "LDMXTaggerModuleVolume_physvol9" ||
304 sln == "LDMXTaggerModuleVolume_physvol10" || sensor_copy_nr == 50 ||
305 sensor_copy_nr == 60)
306 tagger_layout_["tagger_tracker_L5"].push_back(sensor_surface);
307
308 if (sln == "LDMXTaggerModuleVolume_physvol11" ||
309 sln == "LDMXTaggerModuleVolume_physvol12" || sensor_copy_nr == 30 ||
310 sensor_copy_nr == 40)
311 tagger_layout_["tagger_tracker_L6"].push_back(sensor_surface);
312
313 if (sln == "LDMXTaggerModuleVolume_physvol13" ||
314 sln == "LDMXTaggerModuleVolume_physvol14" || sensor_copy_nr == 10 ||
315 sensor_copy_nr == 20)
316 tagger_layout_["tagger_tracker_L7"].push_back(sensor_surface);
317
318 } // found a silicon surface
319 } // loop on daughters
320} // build the layout
321
322std::shared_ptr<Acts::PlaneSurface> TrackersTrackingGeometry::getSurfacePtr(
323 G4VPhysicalVolume* pvol, Acts::Transform3 ref_trans) {
324 if (!pvol) {
325 ldmx_log(fatal) << "pvol is nullptr";
326 }
327
328 // Get the surface transform
329 Acts::Transform3 surface_transform = getTransform(*pvol);
330 // Compose the sensor_transform with the reference transform
331 surface_transform = ref_trans * surface_transform;
332
333 // Now transform to the tracker frame
334 Acts::Transform3 surface_transform_tracker = toTracker(surface_transform);
335
336 ldmx_log(trace) << "THE SENSOR TRANSFORM - TRANSLATION";
337 ldmx_log(trace) << surface_transform.translation()(0);
338 ldmx_log(trace) << surface_transform.translation()(1);
339 ldmx_log(trace) << surface_transform.translation()(2);
340 ldmx_log(trace) << "THE SENSOR TRANSFORM - ROTATION";
341 ldmx_log(trace) << surface_transform.rotation();
342
343 ldmx_log(trace) << "TO THE TRACKER FRAME";
344 ldmx_log(trace) << surface_transform_tracker.translation()(0);
345 ldmx_log(trace) << surface_transform_tracker.translation()(1);
346 ldmx_log(trace) << surface_transform_tracker.translation()(2);
347 ldmx_log(trace) << "THE SENSOR TRANSFORM - ROTATION";
348 ldmx_log(trace) << surface_transform_tracker.rotation();
349
350 // This material is defined in different units with respect what acts expects.
351 // I decided to hardcode here. TODO: fix this
352
353 /*
354 G4Material* sens_mat = _ActiveSensor->GetLogicalVolume()->GetMaterial();
355 ldmx_log(trace)<<"Checking the material of
356 "<<l_vol->GetDaughter(i)->GetName()<<std::endl; ldmx_log(trace)<<"With
357 sensor::"<<_ActiveSensor->GetName()<<std::endl;
358 ldmx_log(trace)<<sens_mat->GetName()<<std::endl;
359 ldmx_log(trace)<<"RL="<<sens_mat->GetRadlen()<<"
360 lambda="<<sens_mat->GetNuclearInterLength()<<std::endl;
361 ldmx_log(trace)<<"A="<<sens_mat->GetA()<<" Z="<<sens_mat->GetZ()<<"
362 rho="<<sens_mat->GetDensity()<<std::endl;
363
364
365 Acts::Material silicon =
366 Acts::Material::fromMassDensity(sens_mat->GetRadlen(),
367 sens_mat->GetNuclearInterLength(),
368 sens_mat->GetA(),
369 sens_mat->GetZ(),
370 sens_mat->GetDensity());
371 */
372
373 // Define the silicon material
374 Acts::Material silicon = Acts::Material::fromMassDensity(
375 95.7 * Acts::UnitConstants::mm, 465.2 * Acts::UnitConstants::mm, 28.03,
376 14., 2.32 * Acts::UnitConstants::g / Acts::UnitConstants::cm3);
377
378 // Get the active sensor box
379 G4Box* surface_solid = (G4Box*)(pvol->GetLogicalVolume()->GetSolid());
380
381 ldmx_log(trace) << "Sensor Dimensions";
382 ldmx_log(trace) << surface_solid->GetXHalfLength() << " "
383 << surface_solid->GetYHalfLength() << " "
384 << surface_solid->GetZHalfLength() << " ";
385
386 // Form the material slab
387 double thickness =
388 2 * surface_solid->GetZHalfLength() * Acts::UnitConstants::mm;
389 Acts::MaterialSlab silicon_slab(silicon, thickness);
390
391 // Get the bounds
392 std::shared_ptr<const Acts::RectangleBounds> rect_bounds =
393 std::make_shared<const Acts::RectangleBounds>(Acts::RectangleBounds(
394 surface_solid->GetXHalfLength() * Acts::UnitConstants::mm,
395 surface_solid->GetYHalfLength() * Acts::UnitConstants::mm));
396
397 // Form the active sensor surface
398 std::shared_ptr<Acts::PlaneSurface> surface =
399 Acts::Surface::makeShared<Acts::PlaneSurface>(surface_transform_tracker,
400 rect_bounds);
401 surface->assignSurfaceMaterial(
402 std::make_shared<Acts::HomogeneousSurfaceMaterial>(silicon_slab));
403
404 // Create an alignable detector element and assign it to the surface.
405 // The default transformation is the surface parsed transformation
406
407 auto det_element = std::make_shared<tracking::geo::DetectorElement>(
408 std::static_pointer_cast<Acts::Surface>(surface),
409 surface_transform_tracker, thickness);
410
411 // This is the call that modify the behaviour of surface->transform(gctx)
412 // After this call each surface will use the underlying detectorElement
413 // transformation which will take care of effectively reading the gctx
414
415 surface->assignSurfacePlacement(*det_element);
416 det_elements_.push_back(det_element);
417
418 return surface;
419}
420
421Acts::CuboidVolumeBuilder::VolumeConfig
422TrackersTrackingGeometry::buildVolumeConfig(
423 const G4VPhysicalVolume* detector,
424 const std::map<std::string,
425 std::vector<std::shared_ptr<const Acts::Surface>>>
426 layout,
427 double tracker_y_length, double tracker_z_length,
428 const std::string& volumeName) {
429 Acts::CuboidVolumeBuilder::VolumeConfig sub_det_volume_config;
430
431 // Get the transform wrt the world volume in tracker frame
432 Acts::Transform3 sub_det_transform = getTransform(*detector, true);
433
434 // Add 1mm to not make it sit on the first layer_ surface
435 Acts::Vector3 sub_det_position = {
436 sub_det_transform.translation()(0) - 1,
437 sub_det_transform.translation()(1),
438 sub_det_transform.translation()(2),
439 };
440
441 ldmx_log(trace) << sub_det_position;
442 // Get the size of the volume along the beam axis (G4 z -> ACTS x).
443 // The solid may be a G4Box or a boolean solid (e.g. G4SubtractionSolid
444 // for the reduced-geometry recoil), so use GetExtent() for generality.
445 G4VSolid* sub_det_solid = detector->GetLogicalVolume()->GetSolid();
446 double z_half;
447 auto* sub_det_box = dynamic_cast<G4Box*>(sub_det_solid);
448 if (sub_det_box) {
449 z_half = sub_det_box->GetZHalfLength();
450 } else {
451 G4VisExtent extent = sub_det_solid->GetExtent();
452 z_half = (extent.GetZmax() - extent.GetZmin()) / 2.0;
453 }
454
455 // In tracker coordinates. Add 1mm to compensate for the movement above
456 double x_length = 2 * (z_half + 1) * Acts::UnitConstants::mm;
457 ldmx_log(info) << "x_length = " << x_length
458 << " y_length = " << tracker_y_length
459 << " z_length = " << tracker_z_length;
460
461 sub_det_volume_config.position = sub_det_position;
462 sub_det_volume_config.length = {x_length, tracker_y_length, tracker_z_length};
463 sub_det_volume_config.name = volumeName;
464
465 // Vacuum material
466 Acts::Material subdet_mat = Acts::Material::Vacuum();
467 sub_det_volume_config.volumeMaterial =
468 std::make_shared<Acts::HomogeneousVolumeMaterial>(subdet_mat);
469
470 std::vector<Acts::CuboidVolumeBuilder::LayerConfig> layer_config;
471
472 // Prepare the layers
473 for (auto& layer : layout) {
474 ldmx_log(trace) << layer.first << " : surfaces==>" << layer.second.size();
475
476 Acts::CuboidVolumeBuilder::LayerConfig lcfg;
477 lcfg.surfaces = layer.second;
478
479 // Get the surface thickness
480 double clearance = 1.0; // mm
481 double thickness = layer.second.front()
482 ->surfaceMaterial()
483 ->materialSlab(Acts::Vector2{0., 0.})
484 .thickness();
485
486 lcfg.envelopeX = std::array<double, 2>{thickness / 2. + clearance,
487 thickness / 2. + clearance};
488 lcfg.active = true;
489 layer_config.push_back(lcfg);
490 }
491
492 sub_det_volume_config.layerCfg = layer_config;
493
494 return sub_det_volume_config;
495}
496
497} // namespace tracking::geo
Visualization.