Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PHG4MicromegasDetector.cc
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file PHG4MicromegasDetector.cc
1 
8 
10 #include "PHG4MicromegasSurvey.h"
11 
12 #include <phparameter/PHParameters.h>
13 
15 
16 #include <g4main/PHG4Detector.h>
17 #include <g4main/PHG4Subsystem.h>
18 
20 
21 #include <phool/PHCompositeNode.h>
22 #include <phool/PHIODataNode.h>
23 #include <phool/PHNode.h> // for PHNode
24 #include <phool/PHNodeIterator.h> // for PHNodeIterator
25 #include <phool/PHObject.h> // for PHObject
26 #include <phool/getClass.h>
27 #include <phool/recoConsts.h>
28 
29 #include <Geant4/G4Box.hh>
30 #include <Geant4/G4Color.hh>
31 #include <Geant4/G4LogicalVolume.hh>
32 #include <Geant4/G4Material.hh>
33 #include <Geant4/G4PVPlacement.hh>
34 #include <Geant4/G4String.hh> // for G4String
35 #include <Geant4/G4SystemOfUnits.hh>
36 #include <Geant4/G4ThreeVector.hh> // for G4ThreeVector
37 #include <Geant4/G4Tubs.hh>
38 #include <Geant4/G4Types.hh> // for G4double
39 #include <Geant4/G4VPhysicalVolume.hh> // for G4VPhysicalVolume
40 #include <Geant4/G4VSolid.hh> // for G4VSolid
41 
42 #include <cmath>
43 #include <iostream>
44 #include <numeric>
45 #include <tuple> // for make_tuple, tuple
46 #include <utility> // for pair, make_pair
47 #include <vector> // for vector
48 
49 namespace
50 {
51  template <class T>
52  inline constexpr T square(const T& x)
53  {
54  return x * x;
55  }
56  template <class T>
57  inline T get_r(const T& x, const T& y)
58  {
59  return std::sqrt(square(x) + square(y));
60  }
61 } // namespace
62 
63 //____________________________________________________________________________..
65  : PHG4Detector(subsys, Node, dnam)
66  , m_DisplayAction(dynamic_cast<PHG4MicromegasDisplayAction*>(subsys->GetDisplayAction()))
67  , m_Params(parameters)
68  , m_ActiveFlag(m_Params->get_int_param("active"))
69  , m_SupportActiveFlag(m_Params->get_int_param("supportactive"))
70 {
71  setup_tiles();
72 }
73 
74 //_______________________________________________________________
76 {
77  if (m_ActiveFlag)
78  {
79  if (m_activeVolumes.find(volume) != m_activeVolumes.end())
80  {
81  return 1;
82  }
83  }
85  {
86  if (m_passiveVolumes.find(volume) != m_passiveVolumes.end())
87  {
88  return -1;
89  }
90  }
91  return 0;
92 }
93 
94 //_______________________________________________________________
96 {
97  const auto iter = m_activeVolumes.find(volume);
98  return iter == m_activeVolumes.end() ? -1 : iter->second;
99 }
100 
101 //_______________________________________________________________
103 {
104  const auto iter = m_tiles_map.find(volume);
105  return iter == m_tiles_map.end() ? -1 : iter->second;
106 }
107 
108 //_______________________________________________________________
109 void PHG4MicromegasDetector::ConstructMe(G4LogicalVolume* logicWorld)
110 {
112  construct_micromegas(logicWorld);
114 }
115 
116 //_______________________________________________________________
118 {
119  std::cout << "PHG4Micromegas Detector:" << std::endl;
120  if (what == "ALL" || what == "VOLUME")
121  {
122  std::cout << "Version 0.1" << std::endl;
123  std::cout << "Parameters:" << std::endl;
124  m_Params->Print();
125  }
126  return;
127 }
128 
129 //_______________________________________________________________
131 {
132  // TODO: replace with more realistic description from latest engineering drawings
133  m_tiles.clear();
134 
135  // tile dimensions
136  /* they correspond to the total micromegas PCB size. They must match the definitions in construct_micromegas_tile */
137  static constexpr double tile_length = 54.2; // cm
138  static constexpr double tile_width = 31.6; // cm
139 
140  {
141  // bottom most sector 3pi/2 has 4 modules
142  static constexpr double phi = 3. * M_PI / 2;
143 
144  // tiles z position from CAD model (T-POT DETECTOR ASSEMBLY 7-13-2022-w-new-trays-modules)
145  for (const double& tile_z : {-84.6, -28.2, 28.2, 84.6})
146  {
147  m_tiles.emplace_back(phi, tile_z, tile_width / CylinderGeomMicromegas::reference_radius, tile_length);
148  }
149  }
150 
151  {
152  // neighbor sectors have two modules, separated by 10cm
153  for (const double& phi : {4. * M_PI / 3, 5. * M_PI / 3})
154  {
155  // tiles z position from CAD model (T-POT DETECTOR ASSEMBLY 7-13-2022-w-new-trays-modules)
156  for (const double& tile_z : {-37.1, 37.1})
157  {
158  m_tiles.emplace_back(phi, tile_z, tile_width / CylinderGeomMicromegas::reference_radius, tile_length);
159  }
160  }
161  }
162 }
163 
164 //_______________________________________________________________
166 {
167  // get the list of NIST materials
168  // ---------------------------------
169  auto G4_N = GetDetectorMaterial("G4_N");
170  auto G4_O = GetDetectorMaterial("G4_O");
171  auto G4_C = GetDetectorMaterial("G4_C");
172  auto G4_H = GetDetectorMaterial("G4_H");
173  auto G4_Si = GetDetectorMaterial("G4_Si");
174  auto G4_Ar = GetDetectorMaterial("G4_Ar");
175  auto G4_Cr = GetDetectorMaterial("G4_Cr");
176  auto G4_Fe = GetDetectorMaterial("G4_Fe");
177  auto G4_Mn = GetDetectorMaterial("G4_Mn");
178  auto G4_Ni = GetDetectorMaterial("G4_Ni");
179  auto G4_Cu = GetDetectorMaterial("G4_Cu");
180 
181  // combine elements
182  // ----------------
183  static const G4double temperature = 298.15 * kelvin;
184  static const G4double pressure = 1. * atmosphere;
185 
186  // FR4
187  if (!GetDetectorMaterial("mmg_FR4", false))
188  {
189  auto mmg_FR4 = new G4Material("mmg_FR4", 1.860 * g / cm3, 4, kStateSolid);
190  mmg_FR4->AddMaterial(G4_C, 0.43550);
191  mmg_FR4->AddMaterial(G4_H, 0.03650);
192  mmg_FR4->AddMaterial(G4_O, 0.28120);
193  mmg_FR4->AddMaterial(G4_Si, 0.24680);
194  }
195 
196  // Kapton
197  if (!GetDetectorMaterial("mmg_Kapton", false))
198  {
199  auto mmg_Kapton = new G4Material("mmg_Kapton", 1.420 * g / cm3, 4, kStateSolid);
200  mmg_Kapton->AddMaterial(G4_C, 0.6911330);
201  mmg_Kapton->AddMaterial(G4_H, 0.0263620);
202  mmg_Kapton->AddMaterial(G4_N, 0.0732700);
203  mmg_Kapton->AddMaterial(G4_O, 0.2092350);
204  }
205 
206  // MMgas
207  if (!GetDetectorMaterial("mmg_Gas", false))
208  {
209  auto mmg_Gas = new G4Material("mmg_Gas", 0.00170335 * g / cm3, 3, kStateGas, temperature, pressure);
210  mmg_Gas->AddMaterial(G4_Ar, 0.900);
211  mmg_Gas->AddMaterial(G4_C, 0.0826586);
212  mmg_Gas->AddMaterial(G4_H, 0.0173414);
213  }
214 
215  // MMMesh
216  if (!GetDetectorMaterial("mmg_Mesh", false))
217  {
218  auto mmg_Mesh = new G4Material("mmg_Mesh", 2.8548 * g / cm3, 5, kStateSolid);
219  mmg_Mesh->AddMaterial(G4_Cr, 0.1900);
220  mmg_Mesh->AddMaterial(G4_Fe, 0.6800);
221  mmg_Mesh->AddMaterial(G4_Mn, 0.0200);
222  mmg_Mesh->AddMaterial(G4_Ni, 0.1000);
223  mmg_Mesh->AddMaterial(G4_Si, 0.0100);
224  }
225 
226  // MMStrips
227  if (!GetDetectorMaterial("mmg_Strips", false))
228  {
229  new G4Material("mmg_Strips", 5.248414 * g / cm3, G4_Cu, kStateSolid);
230  }
231 
232  // MMResistivePaste
233  if (!GetDetectorMaterial("mmg_ResistPaste", false))
234  {
235  new G4Material("mmg_ResistPaste", 0.77906 * g / cm3, G4_C, kStateSolid);
236  }
237 }
238 
239 //_______________________________________________________________
240 void PHG4MicromegasDetector::construct_micromegas(G4LogicalVolume* logicWorld)
241 {
242  // start seting up volumes
243  // Micromegas detector radius
244  /* it corresponds to the radial position of the innermost surface of a Micromegas module , as measured in CAD model (THREE PANELS UPDATE 1-18-21) */
245  static constexpr double inner_radius = 83.197 * cm;
246 
247  /*
248  * this is the radius at the center of a detector.
249  * it is updated when constructing the first tile, assuming that all tiles are identical
250  */
251  double radius_phi_mean = 0;
252  double radius_z_mean = 0;
253 
254  // load survey data
255  PHG4MicromegasSurvey micromegas_survey;
256  static constexpr bool apply_survey = true;
257 
258  // create detector
259  // loop over tiles
260  for (size_t tileid = 0; tileid < m_tiles.size(); ++tileid)
261  {
262  // get relevant tile
263  const auto& tile = m_tiles[tileid];
264 
265  // create phi tile master volume
267  const double tile_thickness_phi = static_cast<G4Box*>(tile_logic_phi->GetSolid())->GetXHalfLength() * 2;
268 
269  {
270  // place tile in master volume
271  const double centerZ = tile.m_centerZ * cm;
272  const double centerPhi = tile.m_centerPhi;
273 
274  G4RotationMatrix rotation;
275  rotation.rotateZ(centerPhi * radian);
276 
277  // calculate radius at tile center
278  double radius_phi = inner_radius + tile_thickness_phi / 2;
279  const G4ThreeVector center(
280  radius_phi * std::cos(centerPhi),
281  radius_phi * std::sin(centerPhi),
282  centerZ);
283 
284  G4Transform3D transform(rotation, center);
285 
286  if (apply_survey)
287  {
288  // get transformation from survey
289  const auto survey_transform = micromegas_survey.get_transformation(m_FirstLayer, tileid);
290  transform = survey_transform * transform;
291 
292  // adjust radius at tile center to account for survey
293  const auto translation = transform.getTranslation();
294  radius_phi = get_r(translation.x(), translation.y());
295  }
296 
297  // update mean radius
298  radius_phi_mean += radius_phi;
299 
300  const auto tilename = GetName() + "_tile_" + std::to_string(tileid) + "_phi";
301  new G4PVPlacement(transform, tile_logic_phi, tilename + "_phys", logicWorld, false, 0, OverlapCheck());
302  }
303 
304  // create z tile master volume
306  const double tile_thickness_z = static_cast<G4Box*>(tile_logic_z->GetSolid())->GetXHalfLength() * 2;
307 
308  {
309  // place tile in master volume
310  const double centerZ = tile.m_centerZ * cm;
311  const double centerPhi = tile.m_centerPhi;
312 
313  G4RotationMatrix rotation;
314  rotation.rotateZ(centerPhi * radian);
315 
316  double radius_z = inner_radius + tile_thickness_phi + tile_thickness_z / 2;
317  const G4ThreeVector center(
318  radius_z * std::cos(centerPhi),
319  radius_z * std::sin(centerPhi),
320  centerZ);
321 
322  G4Transform3D transform(rotation, center);
323 
324  if (apply_survey)
325  {
326  // get transformation from survey
327  const auto survey_transform = micromegas_survey.get_transformation(m_FirstLayer + 1, tileid);
328  transform = survey_transform * transform;
329 
330  // adjust radius at tile center to account for survey
331  const auto translation = transform.getTranslation();
332  radius_z = get_r(translation.x(), translation.y());
333  }
334 
335  radius_z_mean += radius_z;
336 
337  const auto tilename = GetName() + "_tile_" + std::to_string(tileid) + "_z";
338  new G4PVPlacement(transform, tile_logic_z, tilename + "_phys", logicWorld, false, 0, OverlapCheck());
339  }
340  }
341 
342  // adjust active volume radius to account for world placement
343  radius_phi_mean /= m_tiles.size();
344  m_layer_radius.at(m_FirstLayer) += radius_phi_mean / cm;
345 
346  radius_z_mean /= m_tiles.size();
347  m_layer_radius.at(m_FirstLayer + 1) += radius_z_mean / cm;
348 
349  if (Verbosity())
350  {
351  std::cout << "PHG4MicromegasDetector::ConstructMe - first layer: " << m_FirstLayer << std::endl;
352  for (const auto& pair : m_activeVolumes)
353  {
354  std::cout << "PHG4MicromegasDetector::ConstructMe - layer: " << pair.second << " volume: " << pair.first->GetName() << std::endl;
355  }
356  }
357 
358  return;
359 }
360 
361 //_______________________________________________________________
363 {
364  // components enumeration
365  /*
366  this describes all the detector onion layers for a single side
367  note that the detector is two sided
368  */
369  enum class Component
370  {
371  PCB,
372  CuStrips,
373  KaptonStrips,
374  ResistiveStrips,
375  Gas1,
376  Mesh,
377  Gas2,
378  DriftCuElectrode,
379  DriftKapton,
380  DriftCarbon,
381  FeeSupport
382  };
383 
384  // layer definition
385  struct LayerStruct
386  {
387  // constructor
388  LayerStruct(float thickness, G4Material* material, const G4Colour& color, double dy, double dz, double y_offset, double z_offset)
389  : m_thickness(thickness)
390  , m_material(material)
391  , m_color(color)
392  , m_dy(dy)
393  , m_dz(dz)
394  , m_y_offset(y_offset)
395  , m_z_offset(z_offset)
396  {
397  }
398 
399  // thickness
400  float m_thickness = 0;
401 
402  // material
403  G4Material* m_material = nullptr;
404 
405  // color
406  G4Colour m_color = 0;
407 
408  // dimension along y
409  double m_dy = 0;
410 
411  // dimension along z
412  double m_dz = 0;
413 
414  // center offset along y
415  double m_y_offset = 0;
416 
417  // center offset along z
418  double m_z_offset = 0;
419  };
420 
421  // define all layers
422  const std::map<Component, LayerStruct> layer_map =
423  {
424  /* adjusted PCB thickness so that total thickness up to Gas2 is 1.6mm, consistently with CAD model */
425  // { Component::PCB, LayerStruct(1.384*mm, GetDetectorMaterial("mmg_FR4"), G4Colour::Green(), 316*mm, 542*mm, 0, 0 )},
426  {Component::PCB, LayerStruct(1.0 * mm, GetDetectorMaterial("mmg_FR4"), G4Colour::Green(), 316 * mm, 542 * mm, 0, 0)},
427  {Component::CuStrips, LayerStruct(12. * micrometer, GetDetectorMaterial("mmg_Strips"), G4Colour::Brown(), 256 * mm, 512 * mm, -15 * mm, 0)},
428  {Component::KaptonStrips, LayerStruct(50. * micrometer, GetDetectorMaterial("mmg_Kapton"), G4Colour::Brown(), 256 * mm, 512 * mm, -15 * mm, 0)},
429  {Component::ResistiveStrips, LayerStruct(20. * micrometer, GetDetectorMaterial("mmg_ResistPaste"), G4Colour::Black(), 256 * mm, 512 * mm, -15 * mm, 0)},
430  {Component::Gas1, LayerStruct(120. * micrometer, GetDetectorMaterial("mmg_Gas"), G4Colour::Grey(), 256 * mm, 512 * mm, -15 * mm, 0)},
431  /* 0.8 correction factor to thickness is to account for the mesh denstity@18/45 */
432  {Component::Mesh, LayerStruct(18. * 0.8 * micrometer, GetDetectorMaterial("mmg_Mesh"), G4Colour::White(), 256 * mm, 512 * mm, -15 * mm, 0)},
433  {Component::Gas2, LayerStruct(3. * mm, GetDetectorMaterial("mmg_Gas"), G4Colour::Grey(), 256 * mm, 512 * mm, -15 * mm, 0)},
434  {Component::DriftCuElectrode, LayerStruct(15. * micrometer, GetDetectorMaterial("G4_Cu"), G4Colour::Brown(), 256 * mm, 512 * mm, -15 * mm, 0)},
435  {Component::DriftKapton, LayerStruct(50. * micrometer, GetDetectorMaterial("mmg_Kapton"), G4Colour::Brown(), 256 * mm, 512 * mm, -15 * mm, 0)},
436  {Component::DriftCarbon, LayerStruct(1. * mm, GetDetectorMaterial("G4_C"), G4Colour(150 / 255., 75 / 255., 0), 256 * mm, 512 * mm, -15 * mm, 0)},
437  {Component::FeeSupport, LayerStruct(2.38 * mm, GetDetectorMaterial("G4_Al"), G4Colour::Grey(), 182 * mm, 542 * mm, -67 * mm, 0)}};
438 
439  // setup layers in the correct order, going outwards from beam axis
440  using LayerDefinition = std::tuple<Component, std::string>;
441  const std::vector<LayerDefinition> layer_stack_phi =
442  {
443  // inner side
444  std::make_tuple(Component::DriftCarbon, "DriftCarbon_inner"),
445  std::make_tuple(Component::DriftKapton, "DriftKapton_inner"),
446  std::make_tuple(Component::DriftCuElectrode, "DriftCuElectrode_inner"),
447  std::make_tuple(Component::Gas2, "Gas2_inner"),
448  std::make_tuple(Component::Mesh, "Mesh_inner"),
449  std::make_tuple(Component::Gas1, "Gas1_inner"),
450  std::make_tuple(Component::ResistiveStrips, "ResistiveStrips_inner"),
451  std::make_tuple(Component::KaptonStrips, "KaptonStrips_inner"),
452  std::make_tuple(Component::CuStrips, "CuStrips_inner"),
453  std::make_tuple(Component::PCB, "PCB_inner")};
454 
455  // layer stack for z view, same as phi view, but mirrored
456  const std::vector<LayerDefinition> layer_stack_z =
457  {
458  // outer side (= inner side, mirrored)
459  std::make_tuple(Component::PCB, "PCB_outer"),
460  std::make_tuple(Component::CuStrips, "CuStrips_outer"),
461  std::make_tuple(Component::KaptonStrips, "KaptonStrips_outer"),
462  std::make_tuple(Component::ResistiveStrips, "ResistiveStrips_outer"),
463  std::make_tuple(Component::Gas1, "Gas1_outer"),
464  std::make_tuple(Component::Mesh, "Mesh_outer"),
465  std::make_tuple(Component::Gas2, "Gas2_outer"),
466  std::make_tuple(Component::DriftCuElectrode, "DriftCuElectrode_outer"),
467  std::make_tuple(Component::DriftKapton, "DriftKapton_outer"),
468  std::make_tuple(Component::DriftCarbon, "DriftCarbon_outer"),
469 
470  // FEE support plate
471  std::make_tuple(Component::FeeSupport, "FEE_support")};
472 
473  const bool is_z = (segmentation_type == MicromegasDefs::SegmentationType::SEGMENTATION_Z);
474 
475  // get the right layer stack
476  const auto& layer_stack = is_z ? layer_stack_z : layer_stack_phi;
477 
478  // for z view, create two FEE boards up front, to get their total thickness and add to master volume
479  std::array<G4LogicalVolume*, 2> fee_board_logic =
480  {
481  is_z ? construct_fee_board(0) : nullptr,
482  is_z ? construct_fee_board(1) : nullptr};
483 
484  const double fee_thickness = is_z ? static_cast<G4Box*>(fee_board_logic[0]->GetSolid())->GetXHalfLength() * 2 : 0;
485 
486  // calculate total tile thickness
487  double tile_thickness = std::accumulate(
488  layer_stack.begin(), layer_stack.end(), 0.,
489  [&layer_map](double value, const LayerDefinition& layer)
490  { return value + layer_map.at(std::get<0>(layer)).m_thickness; });
491 
492  // for z tile, adds fee thickness
493  if (is_z)
494  {
495  tile_thickness += fee_thickness;
496  }
497 
498  // tile dimensions match that of the PCB layer
499  const double tile_dy = layer_map.at(Component::PCB).m_dy;
500  const double tile_dz = layer_map.at(Component::PCB).m_dz;
501 
502  // get world material to define parent volume
503  auto rc = recoConsts::instance();
504  auto world_material = GetDetectorMaterial(rc->get_StringFlag("WorldMaterial"));
505 
506  // define tile name
507  const auto tilename = GetName() + "_tile_" + std::to_string(tileid) + (is_z ? "_z" : "_phi");
508 
509  auto tile_solid = new G4Box(tilename + "_solid", tile_thickness / 2, tile_dy / 2, tile_dz / 2);
510  auto tile_logic = new G4LogicalVolume(tile_solid, world_material, "invisible_" + tilename + "_logic");
511  GetDisplayAction()->AddVolume(tile_logic, G4Colour::Grey());
512 
513  /* we loop over registered layers and create volumes for each as daughter of the tile volume */
514  auto current_radius_local = -tile_thickness / 2;
515  for (const auto& [type, name] : layer_stack)
516  {
517  // layer name
518  /*
519  * for the Gas2 layers, which are the active components, we use a different volume name,
520  * that match the old geometry implementation. This maximizes compatibility with previous versions
521  */
522  const G4String cname = (type == Component::Gas2) ? "micromegas_measurement_" + name : G4String(GetName()) + "_" + name;
523 
524  // get thickness, material and name
525  const auto& component(layer_map.at(type));
526  const auto& thickness = component.m_thickness;
527  const auto& material = component.m_material;
528  const auto& color = component.m_color;
529 
530  const auto& dy = component.m_dy;
531  const auto& dz = component.m_dz;
532 
533  const auto& y_offset = component.m_y_offset;
534  const auto& z_offset = component.m_z_offset;
535 
536  auto component_solid = new G4Box(cname + "_solid", thickness / 2, dy / 2, dz / 2);
537  auto component_logic = new G4LogicalVolume(component_solid, material, cname + "_logic");
538  GetDisplayAction()->AddVolume(component_logic, color);
539 
540  const G4ThreeVector center((current_radius_local + thickness / 2), y_offset, z_offset);
541  auto component_phys = new G4PVPlacement(nullptr, center, component_logic, cname + "_phys", tile_logic, false, 0, OverlapCheck());
542 
543  if (type == Component::Gas2)
544  {
545  // store active volume
546  // define layer from name
547  const int layer_index = is_z ? m_FirstLayer + 1 : m_FirstLayer;
548  m_activeVolumes.insert(std::make_pair(component_phys, layer_index));
549  m_tiles_map.insert(std::make_pair(component_phys, tileid));
550 
551  // store radius associated to this layer
552  m_layer_radius.insert(std::make_pair(layer_index, (current_radius_local + thickness / 2) / cm));
553  m_layer_thickness.insert(std::make_pair(layer_index, thickness / cm));
554  }
555  else
556  {
557  m_passiveVolumes.insert(component_phys);
558  }
559 
560  // update radius
561  current_radius_local += thickness;
562  }
563 
564  if (is_z)
565  {
566  // add FEE boards
567  /* offsets measured from CAD drawings */
568  static constexpr double fee_y_offset = (316. / 2 - 44.7 - 141.5 / 2) * mm;
569  static constexpr double fee_x_offset = (542. / 2 - 113.1 - 140. / 2) * mm;
570  new G4PVPlacement(nullptr, {current_radius_local + fee_thickness / 2, -fee_y_offset, -fee_x_offset}, fee_board_logic[0], GetName() + "_fee_0_phys", tile_logic, false, 0, OverlapCheck());
571  new G4PVPlacement(nullptr, {current_radius_local + fee_thickness / 2, -fee_y_offset, fee_x_offset}, fee_board_logic[1], GetName() + "_fee_1_phys", tile_logic, false, 0, OverlapCheck());
572  }
573 
574  // return master logical volume
575  return tile_logic;
576 }
577 
578 //_______________________________________________________________
580 {
581  // layer definition
582  struct LayerStruct
583  {
584  // constructor
585  LayerStruct(const std::string& name, float thickness, G4Material* material, const G4Colour& color)
586  : m_name(name)
587  , m_thickness(thickness)
588  , m_material(material)
589  , m_color(color)
590  {
591  }
592 
593  // name
595 
596  // thickness
597  float m_thickness = 0;
598 
599  // material
600  G4Material* m_material = nullptr;
601 
602  // color
603  G4Colour m_color = 0;
604  };
605 
606  static constexpr double inch_to_cm = 2.54;
607 
608  /*
609  * FEE board consists of FR4 PCB, a coper layer, and an aluminium layer, for cooling.
610  * FR4 and Cu layer thickness taken from TPC description (PHG4TpcEndCapSubsystem::SetDefaultParameters)
611  * Al layer correspond to cooling plate. Thickness from mechanical drawings
612  */
613  const std::vector<LayerStruct> layer_stack = {
614  LayerStruct("fee_pcb", 0.07 * inch_to_cm * cm, GetDetectorMaterial("mmg_FR4"), G4Colour::Green()),
615  LayerStruct("fee_cu", 35e-4 * 10 * 0.8 * cm, GetDetectorMaterial("G4_Cu"), G4Colour::Brown()),
616  LayerStruct("fee_al", 0.25 * inch_to_cm * cm, GetDetectorMaterial("G4_Al"), G4Colour::Grey())};
617 
618  // calculate total tile thickness
619  const double fee_thickness = std::accumulate(
620  layer_stack.begin(), layer_stack.end(), 0.,
621  [](double value, const LayerStruct& layer)
622  { return value + layer.m_thickness; });
623 
624  // fee dimensions match CAD drawings
625  const double fee_dy = 141.51 * mm;
626  const double fee_dz = 140 * mm;
627 
628  // get world material to define parent volume
629  auto rc = recoConsts::instance();
630  auto world_material = GetDetectorMaterial(rc->get_StringFlag("WorldMaterial"));
631 
632  // define tile name
633  const auto feename = GetName() + "_fee_" + std::to_string(id);
634 
635  auto fee_solid = new G4Box(feename + "_solid", fee_thickness / 2, fee_dy / 2, fee_dz / 2);
636  auto fee_logic = new G4LogicalVolume(fee_solid, world_material, "invisible_" + feename + "_logic");
637  GetDisplayAction()->AddVolume(fee_logic, G4Colour::Grey());
638 
639  /* we loop over registered layers and create volumes for each as daughter of the fee volume */
640  auto current_radius_local = -fee_thickness / 2;
641  for (const auto& layer : layer_stack)
642  {
643  // layer name
644  /*
645  * for the Gas2 layers, which are the active components, we use a different volume name,
646  * that match the old geometry implementation. This maximizes compatibility with previous versions
647  */
648  const G4String cname = G4String(GetName()) + "_" + layer.m_name;
649 
650  // get thickness, material and name
651  const auto& thickness = layer.m_thickness;
652  const auto& material = layer.m_material;
653  const auto& color = layer.m_color;
654 
655  auto component_solid = new G4Box(cname + "_solid", thickness / 2, fee_dy / 2, fee_dz / 2);
656  auto component_logic = new G4LogicalVolume(component_solid, material, cname + "_logic");
657  GetDisplayAction()->AddVolume(component_logic, color);
658 
659  const G4ThreeVector center((current_radius_local + thickness / 2), 0, 0);
660  auto component_phys = new G4PVPlacement(nullptr, center, component_logic, cname + "_phys", fee_logic, false, 0, OverlapCheck());
661 
662  // store as passive
663  m_passiveVolumes.insert(component_phys);
664 
665  // update radius
666  current_radius_local += thickness;
667  }
668 
669  // return master logical volume
670  return fee_logic;
671 }
672 
673 //_______________________________________________________________
675 {
676  // do nothing if detector is inactive
677  if (!m_Params->get_int_param("active"))
678  {
679  return;
680  }
681 
682  // find or create geometry node
683  const auto geonode_name = std::string("CYLINDERGEOM_") + m_SuperDetector + "_FULL";
684  auto geonode = findNode::getClass<PHG4CylinderGeomContainer>(topNode(), geonode_name);
685  if (!geonode)
686  {
687  geonode = new PHG4CylinderGeomContainer;
688  PHNodeIterator iter(topNode());
689  auto runNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "RUN"));
690  auto newNode = new PHIODataNode<PHObject>(geonode, geonode_name, "PHObject");
691  runNode->addNode(newNode);
692  }
693 
694  // cylinder maximal length
695  static constexpr double length = 220;
696 
697  // add cylinder objects
698  /* one cylinder is added per physical volume. The dimention correspond to the drift volume */
699  bool is_first = true;
700  for (const auto& [layer_index, radius] : m_layer_radius)
701  {
702  // create cylinder and match geometry
703  /* note: cylinder segmentation type and pitch is set in PHG4MicromegasHitReco */
704  auto cylinder = new CylinderGeomMicromegas(layer_index);
705  cylinder->set_radius(radius);
706  cylinder->set_thickness(m_layer_thickness.at(layer_index));
707  cylinder->set_zmin(-length / 2);
708  cylinder->set_zmax(length / 2);
709 
710  // tiles
711  cylinder->set_tiles(m_tiles);
712 
713  /*
714  * asign segmentation type and pitch
715  * assume first layer in phi, other(s) are z
716  */
718 
719  /*
720  * assign drift direction
721  * assume first layer is outward, with readout plane at the top, and others are inward, with readout plane at the bottom
722  * this is used to properly implement transverse diffusion in ::process_event
723  */
724  cylinder->set_drift_direction(is_first ? MicromegasDefs::DriftDirection::OUTWARD : MicromegasDefs::DriftDirection::INWARD);
725 
726  // pitch
727  // first layer (phi) has 1mm pitch. second layer (z) has 2mm pitch
728  cylinder->set_pitch(is_first ? 0.1 : 0.2);
729 
730  // if( Verbosity() )
731  {
732  cylinder->identify(std::cout);
733  }
734 
735  geonode->AddLayerGeom(layer_index, cylinder);
736 
737  is_first = false;
738  }
739 }