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