diff --git a/.github/workflows/key4hep-build.yaml b/.github/workflows/key4hep-build.yaml
new file mode 100644
index 000000000..3f9007f09
--- /dev/null
+++ b/.github/workflows/key4hep-build.yaml
@@ -0,0 +1,23 @@
+name: Key4hep build
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ build_type: ["release", "nightly"]
+ image: ["alma9", "ubuntu22", "centos7"]
+ fail-fast: false
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: key4hep/key4hep-actions/key4hep-build@main
+ with:
+ build_type: ${{ matrix.build_type }}
+ image: ${{ matrix.image }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5a13ada8a..d467551ae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -54,6 +54,7 @@ find_package( Geant4 REQUIRED )
find_package( LCIO REQUIRED)
add_subdirectory(detectorSegmentations)
+add_subdirectory(detectorCommon)
file(GLOB sources
./detector/tracker/*.cpp
@@ -84,8 +85,8 @@ add_library(lcgeo ALIAS k4geo)
target_include_directories(${PackageName} PRIVATE ${PROJECT_SOURCE_DIR}/detector/include )
target_include_directories(${PackageName}G4 PRIVATE ${PROJECT_SOURCE_DIR}/detector/include )
-target_link_libraries(${PackageName} DD4hep::DDCore DD4hep::DDRec DD4hep::DDParsers ROOT::Core LCIO::LCIO detectorSegmentations)
-target_link_libraries(${PackageName}G4 DD4hep::DDCore DD4hep::DDRec DD4hep::DDParsers DD4hep::DDG4 ROOT::Core ${Geant4_LIBRARIES} LCIO::LCIO)
+target_link_libraries(${PackageName} DD4hep::DDCore DD4hep::DDRec DD4hep::DDParsers ROOT::Core LCIO::lcio detectorSegmentations)
+target_link_libraries(${PackageName}G4 DD4hep::DDCore DD4hep::DDRec DD4hep::DDParsers DD4hep::DDG4 ROOT::Core ${Geant4_LIBRARIES} LCIO::lcio)
#Create this_package.sh file, and install
dd4hep_instantiate_package(${PackageName})
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ALLEGRO_o1_v02.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ALLEGRO_o1_v02.xml
index 49c76c53d..bf4b652fe 100644
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ALLEGRO_o1_v02.xml
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ALLEGRO_o1_v02.xml
@@ -3,14 +3,14 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xs:noNamespaceSchemaLocation="http://www.lcsim.org/schemas/compact/1.0/compact.xsd">
-
- Master compact file describing the latest developments of the FCCee IDEA detector concept with a LAr calorimeter.
+ Master compact file describing the latest developments of the FCCee ALLEGRO detector concept.
@@ -28,7 +28,7 @@
-
+
@@ -36,11 +36,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_DectDimensions.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DectDimensions.xml
similarity index 96%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_DectDimensions.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DectDimensions.xml
index 439e20cdd..237223204 100644
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_DectDimensions.xml
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DectDimensions.xml
@@ -84,9 +84,9 @@
-
-
-
+
+
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_DectEmptyMaster.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DectEmptyMaster.xml
similarity index 100%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_DectEmptyMaster.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DectEmptyMaster.xml
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DriftChamber.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DriftChamber.xml
new file mode 100644
index 000000000..b24fa784f
--- /dev/null
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/DriftChamber.xml
@@ -0,0 +1,110 @@
+
+
+
+ Detector description for the IDEA Drift Chamber. To understand the free parameters below, look e.g. at https://indico.cern.ch/event/932973/contributions/4041314/attachments/2139657/3664808/primavera_FCCworkshop_2020.pdf
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ system:4,superLayer:5,layer:5,phi:11,hitorigin:3,stereo:1,layerInCell:2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_ECalBarrel_thetamodulemerged.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged.xml
similarity index 90%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_ECalBarrel_thetamodulemerged.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged.xml
index 4fd78287a..9cf284bf0 100644
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_ECalBarrel_thetamodulemerged.xml
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged.xml
@@ -23,11 +23,11 @@
-
-
+
+
-
+
@@ -42,19 +42,22 @@
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
@@ -107,10 +110,14 @@
+
+
+
+
-
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_calibration.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_calibration.xml
new file mode 100644
index 000000000..d69ba26f9
--- /dev/null
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_calibration.xml
@@ -0,0 +1,139 @@
+
+
+
+
+ Settings for the inclined EM calorimeter.
+ The barrel is filled with liquid argon. Passive material includes lead in the middle and steal on the outside, glued together.
+ Passive plates are inclined by a certain angle from the radial direction.
+ In between of two passive plates there is a readout.
+ Space between the plate and readout is of trapezoidal shape and filled with liquid argon.
+ Definition of sizes, visualization settings, readout and longitudinal segmentation are specified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ system:4,cryo:1,type:3,subtype:3,layer:8,module:11,theta:10
+
+
+
+
+
+ system:4,cryo:1,type:3,subtype:3,layer:8,module:11,theta:10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_upstream.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_upstream.xml
new file mode 100644
index 000000000..ee39e57a5
--- /dev/null
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalBarrel_thetamodulemerged_upstream.xml
@@ -0,0 +1,139 @@
+
+
+
+
+ Settings for the inclined EM calorimeter.
+ The barrel is filled with liquid argon. Passive material includes lead in the middle and steal on the outside, glued together.
+ Passive plates are inclined by a certain angle from the radial direction.
+ In between of two passive plates there is a readout.
+ Space between the plate and readout is of trapezoidal shape and filled with liquid argon.
+ Definition of sizes, visualization settings, readout and longitudinal segmentation are specified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ system:4,cryo:1,type:3,subtype:3,layer:8,module:11,theta:10
+
+
+
+
+
+ system:4,cryo:1,type:3,subtype:3,layer:8,module:11,theta:10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_EcalEndcaps_coneCryo.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalEndcaps_coneCryo.xml
similarity index 100%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_EcalEndcaps_coneCryo.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ECalEndcaps_coneCryo.xml
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_HCalBarrel_TileCal.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/HCalBarrel_TileCal.xml
similarity index 88%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_HCalBarrel_TileCal.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/HCalBarrel_TileCal.xml
index 0acecdb1d..714fffbe1 100644
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_HCalBarrel_TileCal.xml
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/HCalBarrel_TileCal.xml
@@ -45,22 +45,22 @@
-
- system:4,layer:5,row:9,eta:9,phi:10
+
+ system:4,layer:5,row:9,theta:9,phi:10
-
-
- system:4,layer:5,eta:9,phi:10
+
+
+ system:4,layer:5,theta:9,phi:10
-
-
- system:4,layer:5,row:9,eta:0,phi:10
+
+
+ system:4,layer:5,row:9,theta:0,phi:10
-
+
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_HCalEndcaps_ThreeParts_TileCal.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/HCalEndcaps_ThreeParts_TileCal.xml
similarity index 100%
rename from FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/FCCee_HCalEndcaps_ThreeParts_TileCal.xml
rename to FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/HCalEndcaps_ThreeParts_TileCal.xml
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/SimplifiedDriftChamber.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/SimplifiedDriftChamber.xml
deleted file mode 100644
index 11586a5df..000000000
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/SimplifiedDriftChamber.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
- A simplified implementation of the drift chamber for the FCCee-IDEA concept
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${GlobalTrackerReadoutID_DCH}
-
-
-
-
-
-
-
- Dimensions for the drift chamber
-
-
-
-
-
-
-
diff --git a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/materials.xml b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/materials.xml
index a899266c3..7fe008147 100644
--- a/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/materials.xml
+++ b/FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/materials.xml
@@ -225,14 +225,118 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/ALLEGRO/compact/README.md b/FCCee/ALLEGRO/compact/README.md
index 9a6ded62d..aecc08265 100644
--- a/FCCee/ALLEGRO/compact/README.md
+++ b/FCCee/ALLEGRO/compact/README.md
@@ -2,4 +2,6 @@ ALLEGRO
========================
ALLEGRO_o1_v01: it is a liquid Noble gas based detector. This version picked from the latest version in FCCDetectors repo.
-ALLEGRO_o1_v02: evolves from o1_v01, replacing the barrel ECAL. This version has a constant cell size in theta for the ECAL barrel (instead of eta as in o1_v01) and now it is possible to have a different number of cells merged for each longitudinal layer.
+ALLEGRO_o1_v02: evolves from o1_v01, replacing the barrel ECAL and adding a detailed version drift chamber.
+This version has a constant cell size in theta for the ECAL barrel (instead of eta as in o1_v01) and now it is possible to have a different number of cells merged for each longitudinal layer.
+Known caveat: the drift chamber has a larger z extent than in the IDEA detector but the wire spacing was not re-optimized. It is ok software-wise but the currently implemented design is not fully compliant with R&D considerations, will need a new drift chamber layout from the detector concept team.
diff --git a/FCCee/CLD/compact/CLD_o4_v05/LAr_ECalBarrel.xml b/FCCee/CLD/compact/CLD_o4_v05/LAr_ECalBarrel.xml
index 5f940463f..55519bd5a 100644
--- a/FCCee/CLD/compact/CLD_o4_v05/LAr_ECalBarrel.xml
+++ b/FCCee/CLD/compact/CLD_o4_v05/LAr_ECalBarrel.xml
@@ -96,7 +96,7 @@
-
+
diff --git a/FCCee/IDEA/compact/IDEA_o1_v02/DectDimensions_IDEA_o1_v01.xml b/FCCee/IDEA/compact/IDEA_o1_v02/DectDimensions_IDEA_o1_v01.xml
index 8226c97f4..27a5bb4fc 100644
--- a/FCCee/IDEA/compact/IDEA_o1_v02/DectDimensions_IDEA_o1_v01.xml
+++ b/FCCee/IDEA/compact/IDEA_o1_v02/DectDimensions_IDEA_o1_v01.xml
@@ -89,8 +89,8 @@
-
-
+
+
diff --git a/FCCee/IDEA/compact/IDEA_o1_v02/DriftChamber_o1_v01.xml b/FCCee/IDEA/compact/IDEA_o1_v02/DriftChamber_o1_v01.xml
new file mode 100644
index 000000000..addb730d3
--- /dev/null
+++ b/FCCee/IDEA/compact/IDEA_o1_v02/DriftChamber_o1_v01.xml
@@ -0,0 +1,110 @@
+
+
+
+ Detector description for the IDEA Drift Chamber. To understand the free parameters below, look e.g. at https://indico.cern.ch/event/932973/contributions/4041314/attachments/2139657/3664808/primavera_FCCworkshop_2020.pdf
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ system:4,superLayer:5,layer:5,phi:11,hitorigin:3,stereo:1,layerInCell:2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/IDEA/compact/IDEA_o1_v02/IDEA_o1_v02.xml b/FCCee/IDEA/compact/IDEA_o1_v02/IDEA_o1_v02.xml
index b6dea340e..5ea3592ad 100644
--- a/FCCee/IDEA/compact/IDEA_o1_v02/IDEA_o1_v02.xml
+++ b/FCCee/IDEA/compact/IDEA_o1_v02/IDEA_o1_v02.xml
@@ -41,6 +41,9 @@
+
+
+
diff --git a/FCCee/IDEA/compact/IDEA_o1_v02/materials_o1_v01.xml b/FCCee/IDEA/compact/IDEA_o1_v02/materials_o1_v01.xml
index 878cddd00..f68afa8b4 100644
--- a/FCCee/IDEA/compact/IDEA_o1_v02/materials_o1_v01.xml
+++ b/FCCee/IDEA/compact/IDEA_o1_v02/materials_o1_v01.xml
@@ -299,14 +299,6 @@
-
-
-
-
-
-
-
-
@@ -354,4 +346,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FCCee/IDEA/compact/README.md b/FCCee/IDEA/compact/README.md
index b088aec4c..532f3335f 100644
--- a/FCCee/IDEA/compact/README.md
+++ b/FCCee/IDEA/compact/README.md
@@ -1,3 +1,4 @@
IDEA
====
IDEA_o1_v01: IDEA version picked from the latest version in FCCDetectors repo
+IDEA_o1_v02: Based on o1_v01 but with a detailed description of the vertex detector and the drift chamber
diff --git a/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v01_geo.cpp b/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v01_geo.cpp
index ab11803e4..dece89dde 100644
--- a/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v01_geo.cpp
+++ b/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v01_geo.cpp
@@ -626,8 +626,8 @@ static dd4hep::detail::Ref_t createECalBarrelInclined(dd4hep::Detector& aLcdd,
caloLayer.outer_thickness = difference_bet_r1r2 / 2;
caloLayer.absorberThickness = absorberThickness;
- caloLayer.cellSize0 = 2;
- caloLayer.cellSize1 = 2;
+ caloLayer.cellSize0 = 2 * dd4hep::mm;
+ caloLayer.cellSize1 = 2 * dd4hep::mm;
caloData->layers.push_back(caloLayer);
}
diff --git a/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v02_geo.cpp b/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v02_geo.cpp
index 9f62dec88..9c2c27b2b 100644
--- a/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v02_geo.cpp
+++ b/detector/calorimeter/ECalBarrel_NobleLiquid_InclinedTrapezoids_o1_v02_geo.cpp
@@ -108,8 +108,10 @@ static dd4hep::detail::Ref_t createECalBarrelInclined(dd4hep::Detector& aLcdd,
double passiveThickness = passiveInnerThicknessMin + passiveOuterThickness + passiveGlueThickness;
double angle = passive.rotation().angle();
- double bathRmin = caloDim.rmin(); // - margin for inclination
- double bathRmax = caloDim.rmax(); // + margin for inclination
+ dd4hep::xml::DetElement bath = aXmlElement.child(_Unicode(bath));
+ dd4hep::xml::Dimension bathDim(bath.dimensions());
+ double bathRmin = bathDim.rmin();
+ double bathRmax = bathDim.rmax();
dd4hep::Tube bathOuterShape(bathRmin, bathRmax, caloDim.dz()); // make it 4 volumes + 5th for detector envelope
dd4hep::Tube bathAndServicesOuterShape(cryoDim.rmin2(), cryoDim.rmax1(), caloDim.dz()); // make it 4 volumes + 5th for detector envelope
if (cryoThicknessFront > 0) {
diff --git a/detector/calorimeter/README.md b/detector/calorimeter/README.md
index e99fa66f4..3bcd9a375 100644
--- a/detector/calorimeter/README.md
+++ b/detector/calorimeter/README.md
@@ -12,7 +12,9 @@ The documentation about its usage is [here](../../doc/detector/calorimeter/ECalB
Original version taken from [FCCDetectors](https://github.com/HEP-FCC/FCCDetectors/blob/main/Detector/DetFCChhECalInclined/src/ECalBarrelInclined_geo.cpp).
### o1_v02
-New version adapted to the theta segmentation with the possibility to have different number of cell merged per layer. The main difference is that now one has to set `ECalBarrelNumLayers` and `ECalBarrelNumPlanes` in the xml while before it was just dynamically computed based on other xml parameters (also, to avoid silent mistakes, the number from the xml and the one computed dynamically must match).
+New version, with module-theta based segmentation. In each layer adjacent cells along theta or module directions can be grouped together, with possibly different merging per layer. This is specified with the `mergedCells_Theta` and `mergedModules` vectors in the `segmentation` tag of the xml file. The baseline grouping in theta is by four in all layers except L1 (the strip layer). The baseline grouping in module direction is by two in all layers. The LAr gap has also been slightly adjusted to bring back the number of modules to 1536 (it was 1545 before). The segmentation class needs to know the number of modules, which is passed via the `nModules` parameter of the `segmentation` tag. To ensure that number of modules and layers (length of the mergedXXX vectors) are consistent with number of modules and layers of the detector, the xml defines `ECalBarrelNumLayers` and `ECalBarrelNumPlanes`, and the c++ file doing the detector construction checks that the number of planes and layers calculated dynamically from other parameters matches that in the xml (if not, the code will crash).
+
+Overlaps in the LAr bath volume fixed.
## CaloDisks
This sub-detector makes calorimeter endcaps (original and reflected). It is used in ALLEGRO detector concept.
diff --git a/detector/tracker/DriftChamber_o1_v01.cpp b/detector/tracker/DriftChamber_o1_v01.cpp
new file mode 100644
index 000000000..6023596f6
--- /dev/null
+++ b/detector/tracker/DriftChamber_o1_v01.cpp
@@ -0,0 +1,829 @@
+/***************************************************************************************\
+* DD4hep geometry code for the central drift chamber of the IDEA detector *
+* Author: Lorenzo Capriotti, Modified by Brieuc Francois to have sensitive cell volumes *
+\***************************************************************************************/
+#include "DD4hep/DetFactoryHelper.h"
+#include "DD4hep/Printout.h"
+#include "DD4hep/detail/DetectorInterna.h"
+#include "TClass.h"
+#include "TMath.h"
+#include "XML/Utilities.h"
+#include
+#include
+#include
+
+using namespace std;
+using namespace dd4hep;
+
+struct wire
+{
+ dd4hep::Volume mother_volume;
+ string type; // F = field wire, S = sense wire, G = guard wire
+ int num; // how many wires for the layer being considered
+ double radius; // radial position of the wire at z = 0
+ double delta_phi; // delta phi between two wires of this type
+ double phioffset; // phi angle of the first wire of this type
+ double stereo; // stereo angle
+ double thickness; // thickness of the core of the wire (no coating)
+ double halflength; // wire z extent before stereo angle rotation
+ dd4hep::Volume volume; // actual volume of the wire
+ string name;
+};
+
+namespace {
+
+struct CDCHBuild : public dd4hep::xml::tools::VolumeBuilder {
+ std::vector deSuperLayer, deLayer, deSWire;
+
+ CDCHBuild(dd4hep::Detector& description, xml_elt_t e, dd4hep::SensitiveDetector sens);
+
+ double diff_of_squares(double a, double b);
+ void apply_wire_coating(struct wire& w, double outwrap, double halflength, string material);
+ void PlaceGuardWires(struct wire& w, double outwrap, double halflength, int SL, int ilayer);
+ void build_layer(DetElement parent, Volume parentVol, dd4hep::SensitiveDetector sens);
+};
+
+// ******************************************************
+// Initializing constructor
+// ******************************************************
+
+CDCHBuild::CDCHBuild(dd4hep::Detector& dsc, xml_elt_t e, dd4hep::SensitiveDetector sens)
+ : dd4hep::xml::tools::VolumeBuilder(dsc, e, sens) {}
+
+double CDCHBuild::diff_of_squares(double a, double b) {
+
+ double diff = pow(a, 2) - pow(b, 2);
+ return diff;
+}
+
+void CDCHBuild::apply_wire_coating(struct wire& w, double outwrap, double halflength, string material = "Silver"){
+ dd4hep::Tube WrapTube(w.thickness, w.thickness + 0.5 * outwrap, halflength);
+ dd4hep::Volume WireWrapVol(w.name + "_coating", WrapTube, description.material(material));
+ dd4hep::Tube TotalWire(0.0, w.thickness + 0.5 * outwrap, halflength);
+ dd4hep::Volume WireVol(w.name + "_totalWire", TotalWire, description.material("Air"));
+ WireVol.placeVolume(w.volume, dd4hep::Position(0.0, 0.0, 0.0));
+ WireVol.placeVolume(WireWrapVol, dd4hep::Position(0.0, 0.0, 0.0));
+ w.volume = WireVol;
+}
+
+void CDCHBuild::PlaceGuardWires(struct wire& w, double outwrap, double halflength, int SL = 999,
+ int ilayer = 999) {
+
+ dd4hep::RotationZYX rot(0., 0., w.stereo);
+ dd4hep::RotationX rot_stereo(w.stereo);
+ dd4hep::Translation3D transl(w.radius, 0., 0.);
+
+ dd4hep::Transform3D T(transl * rot_stereo);
+
+ string wirewrapname = "WireWrap_SL";
+ wirewrapname += std::to_string(SL);
+ wirewrapname += "_layer";
+ wirewrapname += std::to_string(ilayer);
+ wirewrapname += "_type";
+ wirewrapname += w.type;
+ wirewrapname += "_stereo";
+ wirewrapname += std::to_string(w.stereo);
+
+ string wirename = "Wire_SL";
+ wirename += std::to_string(SL);
+ wirename += "_layer";
+ wirename += std::to_string(ilayer);
+ wirename += "_type";
+ wirename += w.type;
+ wirename += "_stereo";
+ wirename += std::to_string(w.stereo);
+
+ apply_wire_coating(w, outwrap, halflength);
+
+ // repeat the placement of wires over phi
+ for (int n = 0; n < w.num; n++) {
+ dd4hep::RotationZ iRot(w.phioffset + w.delta_phi * n);
+ if (n % 1 == 0) w.mother_volume.placeVolume(w.volume, dd4hep::Transform3D(iRot * T));
+ }
+}
+
+void CDCHBuild::build_layer(DetElement parent, Volume parentVol, dd4hep::SensitiveDetector sens_det) {
+
+ // ******************************************************
+ // Loading parameters
+ // ******************************************************
+
+ double halfalpha = 0.5 * dd4hep::_toDouble("CDCH:alpha");
+ double inner_radius = dd4hep::_toDouble("CDCH:inner_radius");
+ double outer_radius = dd4hep::_toDouble("CDCH:outer_radius");
+ double zHalfExtentWithServices = dd4hep::_toDouble("CDCH:zHalfExtentWithServices");
+ double CarbonInnerWallThick = dd4hep::_toDouble("CDCH:CarbonInnerWallThick");
+ double CopperInnerWallThick = dd4hep::_toDouble("CDCH:CopperInnerWallThick");
+ double GasInnerWallThick = dd4hep::_toDouble("CDCH:GasInnerWallThick");
+ double Carbon1OuterWallThick = dd4hep::_toDouble("CDCH:Carbon1OuterWallThick");
+ double Carbon2OuterWallThick = dd4hep::_toDouble("CDCH:Carbon2OuterWallThick");
+ double CopperOuterWallThick = dd4hep::_toDouble("CDCH:CopperOuterWallThick");
+ double FoamOuterWallThick = dd4hep::_toDouble("CDCH:FoamOuterWallThick");
+ double GasEndcapWallThick = dd4hep::_toDouble("CDCH:GasEndcapWallThick");
+ double CopperEndcapWallThick = dd4hep::_toDouble("CDCH:CopperEndcapWallThick");
+ double KaptonEndcapWallThick = dd4hep::_toDouble("CDCH:KaptonEndcapWallThick");
+ double CarbonEndcapWallThick = dd4hep::_toDouble("CDCH:CarbonEndcapWallThick");
+ double FWireShellThickIn = dd4hep::_toDouble("CDCH:FWireShellThickIn");
+ double FWireShellThickOut = dd4hep::_toDouble("CDCH:FWireShellThickOut");
+ double centerFWireShellThickIn = dd4hep::_toDouble("CDCH:centerFWireShellThickIn");
+ double centerFWireShellThickOut = dd4hep::_toDouble("CDCH:centerFWireShellThickOut");
+ double SWireShellThickIn = dd4hep::_toDouble("CDCH:SWireShellThickIn");
+ double SWireShellThickOut = dd4hep::_toDouble("CDCH:SWireShellThickOut");
+ double InGWireShellThickIn = dd4hep::_toDouble("CDCH:InGWireShellThickIn");
+ double InGWireShellThickOut = dd4hep::_toDouble("CDCH:InGWireShellThickOut");
+ double OutGWireShellThickIn = dd4hep::_toDouble("CDCH:OutGWireShellThickIn");
+ double OutGWireShellThickOut = dd4hep::_toDouble("CDCH:OutGWireShellThickOut");
+ //------------------------------------------------------------------------
+ // The wireThicknessDilution parameter is used to see the wires in the rendering. NB: not propagated to the envelope volumes --> you will have volume extrusions if != 1
+ //------------------------------------------------------------------------
+ double wireThicknessDilution = dd4hep::_toDouble("CDCH:wireThicknessDilution");
+ double secure = dd4hep::_toDouble("CDCH:secure");
+ double capGasLayer = dd4hep::_toDouble("CDCH:capGasLayer");
+ double extShiftFW = dd4hep::_toDouble("CDCH:extShiftFW");
+ double cellDimension = dd4hep::_toDouble("CDCH:cellDimension");
+ double inGuardRad = dd4hep::_toDouble("CDCH:inGuardRad");
+ double outGuardRad = dd4hep::_toDouble("CDCH:outGuardRad");
+ int nSDeltaWire = dd4hep::_toInt("CDCH:nSDeltaWire");
+ int nSWireFirstLayer = dd4hep::_toInt("CDCH:nSWireFirstLayer");
+ int nFtoSWireRatio = dd4hep::_toInt("CDCH:nFtoSWireRatio");
+ int nCenterFWirePerCell = dd4hep::_toInt("CDCH:nCenterFWirePerCell");
+ int nSuperLayer = dd4hep::_toInt("CDCH:nSuperLayer");
+ int nLayer = dd4hep::_toInt("CDCH:nLayer");
+ //bool setWireSensitive = true; // FIXME: add the possibility to have wires sensitive (parameter in the xml) which could be useful for detailed chamber behavior studies
+ double halflength = zHalfExtentWithServices - (GasEndcapWallThick + CopperEndcapWallThick + KaptonEndcapWallThick + CarbonEndcapWallThick); // this will be the sensitive volume z extent
+
+ double epsilon = 0.0;
+ double phi_offset = 0.0;
+ double delta_phi_top_bottom = 0.0;
+ int nFWireTopAndBottom = 0;
+ int nFWireTopOrBottom = 0;
+ int nSWire = 0;
+ int nHorizontalFtoSWireRatio = nFtoSWireRatio - nCenterFWirePerCell; // nHorizontalFtoSWireRatio is, per cell, how many wires do we have in top and bottom layer (summed), knowing that the rightmost wires are not in the current cell
+ int sign_epsilon = -1;
+ double sense_wire_delta_phi = 0.0;
+ double scaleFactor = 0.0;
+ double dropFactor = 0.0;
+ double epsilonFactor = 0.0;
+ double delta_radius_layer = cellDimension;
+ double sense_wire_radius_at_z_0 = 0.0;
+ double iradius = 0.0;
+ double idelta_radius = 0.0;
+
+ double envelop_Inner_thickness = CarbonInnerWallThick + CopperInnerWallThick + GasInnerWallThick;
+ double envelop_Outer_thickness = Carbon1OuterWallThick + Carbon2OuterWallThick + CopperOuterWallThick + FoamOuterWallThick;
+ double FWireDiameter = FWireShellThickIn + FWireShellThickOut;
+ double FWradii = 0.5 * FWireDiameter;
+ double inGWireDiameter = InGWireShellThickIn + InGWireShellThickOut;
+ double inGWradii = 0.5 * inGWireDiameter;
+ double fakeLayerInIWthick = -0.0001 + GasInnerWallThick;
+ double guard_layer_inner_radius_at_z_0 = inner_radius + envelop_Inner_thickness - fakeLayerInIWthick;
+
+ double radius_layer_0 = inner_radius + envelop_Inner_thickness + FWradii + secure + capGasLayer;
+ double layer_outer_radius_at_z_0 = radius_layer_0 - FWradii - secure;
+ double layer_inner_radius_at_z_0 = 0.0;
+ double layer_inner_radius_at_z_end = 0.0;
+ double layer_outer_radius_at_z_end = 0.0;
+
+ double drop = 0.0;
+ double radius_layer = 0.0;
+ double epsilonIn = 0.0;
+ double epsilonOut = 0.0;
+ double phi_offset_cell_start = 0.0;
+ double zlength = 0.0;
+ double cellStaggering = 0.0;
+ double epsilonInGwRing = 0.0;
+ double epsilonOutGwRing = 0.0;
+ double layer_inner_radius_at_z_end_whole_cell = 0.0;
+ double epsilonIn_whole_cell = 0.0;
+
+ //------------------------------------------------------------------------
+ // Build the inner, outer and endcap walls first
+ //------------------------------------------------------------------------
+
+ dd4hep::Tube Endcap_Gas(inner_radius, outer_radius, 0.5 * GasEndcapWallThick);
+ dd4hep::Tube Endcap_Copper(inner_radius, outer_radius, 0.5 * CopperEndcapWallThick);
+ dd4hep::Tube Endcap_Kapton(inner_radius, outer_radius, 0.5 * KaptonEndcapWallThick);
+ dd4hep::Tube Endcap_Carbon(inner_radius, outer_radius, 0.5 * CarbonEndcapWallThick);
+
+ dd4hep::Volume lvEndcapWallGas =
+ dd4hep::Volume("lvEndcapWallGasVol", Endcap_Gas, description.material("GasHe_90Isob_10"));
+ dd4hep::Volume lvEndcapWallCopper =
+ dd4hep::Volume("lvEndcapWallCopperVol", Endcap_Copper, description.material("G4_Cu"));
+ dd4hep::Volume lvEndcapWallKapton =
+ dd4hep::Volume("lvEndcapWallKaptonVol", Endcap_Kapton, description.material("Kapton"));
+ dd4hep::Volume lvEndcapWallCarbon =
+ dd4hep::Volume("lvEndcapWallCarbonVol", Endcap_Carbon, description.material("CarbonFiber"));
+
+ dd4hep::Tube InnerWall_Carbon(inner_radius, inner_radius + CarbonInnerWallThick, halflength);
+ dd4hep::Tube InnerWall_Copper(inner_radius + CarbonInnerWallThick,
+ inner_radius + CarbonInnerWallThick + CopperInnerWallThick, halflength);
+ dd4hep::Tube InnerWall_Gas(inner_radius + CarbonInnerWallThick + CopperInnerWallThick,
+ inner_radius + envelop_Inner_thickness, halflength);
+
+ dd4hep::Volume lvInnerWallCarbon =
+ dd4hep::Volume("lvInnerWallCarbonVol", InnerWall_Carbon, description.material("CarbonFiber"));
+ dd4hep::Volume lvInnerWallCopper =
+ dd4hep::Volume("lvInnerWallCopperVol", InnerWall_Copper, description.material("G4_Cu"));
+ dd4hep::Volume lvInnerWallGas =
+ dd4hep::Volume("lvInnerWallGasVol", InnerWall_Gas, description.material("GasHe_90Isob_10"));
+
+ dd4hep::Tube OuterWall_Copper(outer_radius - envelop_Outer_thickness,
+ outer_radius - Carbon1OuterWallThick - Carbon2OuterWallThick - FoamOuterWallThick,
+ halflength);
+ dd4hep::Tube OuterWall_Carbon1(outer_radius - Carbon1OuterWallThick - Carbon2OuterWallThick - FoamOuterWallThick,
+ outer_radius - Carbon2OuterWallThick - FoamOuterWallThick, halflength);
+ dd4hep::Tube OuterWall_Foam(outer_radius - Carbon2OuterWallThick - FoamOuterWallThick,
+ outer_radius - Carbon2OuterWallThick, halflength);
+ dd4hep::Tube OuterWall_Carbon2(outer_radius - Carbon2OuterWallThick, outer_radius, halflength);
+
+ dd4hep::Volume lvOuterWallCarbon1 =
+ dd4hep::Volume("lvOuterWallCarbon1Vol", OuterWall_Carbon1, description.material("CarbonFiber"));
+ dd4hep::Volume lvOuterWallCarbon2 =
+ dd4hep::Volume("lvOuterWallCarbon2Vol", OuterWall_Carbon2, description.material("CarbonFiber"));
+ dd4hep::Volume lvOuterWallCopper =
+ dd4hep::Volume("lvOuterWallCopperVol", OuterWall_Copper, description.material("G4_Cu"));
+ dd4hep::Volume lvOuterWallFoam =
+ dd4hep::Volume("lvOuterWallFoamVol", OuterWall_Foam, description.material("GasHe_90Isob_10"));
+
+
+ //------------------------------------------------------------------------
+ // Now we are ready to loop over the SuperLayers and fill the gas volume!
+ //------------------------------------------------------------------------
+
+ std::vector lvLayerVol;
+ std::vector lvFwireVol, lvGwireVol;
+
+ string wirecol, gascol, wholeHyperboloidVolumeName;
+ string lvFwireName, lvSwireName;
+
+ struct wire guard_wires{}, field_wires_bottom{}, field_wires_center{}, field_wires_top{}, sense_wires{};
+
+ for (int SL = 0; SL < nSuperLayer; ++SL) {
+
+ nSWire = nSWireFirstLayer + SL * nSDeltaWire;
+ sense_wire_delta_phi = 2. * TMath::Pi() / nSWire;
+ nFWireTopAndBottom = nHorizontalFtoSWireRatio * nSWire;
+ phi_offset = 2. * TMath::Pi() / nFWireTopAndBottom;
+ nFWireTopOrBottom = nFWireTopAndBottom / 2;
+ if (ceilf(nFWireTopOrBottom) != nFWireTopOrBottom)
+ throw std::runtime_error("Error: Failed to build CDCH. Please make sure that '(nFtoSWireRatio - nCenterFWirePerCell) * (nSWireFirstLayer + SuperLayerIndex * nSDeltaWire)' is always an even number");
+ delta_phi_top_bottom = 2.0 * phi_offset;
+ scaleFactor = (1.0 + TMath::Pi() / nSWire) / (1.0 - TMath::Pi() / nSWire); // used to scale the radial extent of each layer which grows towards outer radius
+ dropFactor = (1.0 / cos(halfalpha) - 1.0); // used to determine the radius of the hyperboloid in z = +- halflength with r_out = r_min + r_min * dropFactor
+ epsilonFactor = sin(halfalpha) / halflength;
+ phi_offset_cell_start = -0.5 * sense_wire_delta_phi;
+
+ gascol = "vCDCH:Gas1";
+ if (SL % 3 == 0)
+ gascol = "vCDCH:Gas1";
+ else if ((SL + 1) % 3 == 0)
+ gascol = "vCDCH:Gas2";
+ else if ((SL + 2) % 3 == 0)
+ gascol = "vCDCH:Gas3";
+
+ if (SL % 3 == 0)
+ wirecol = "vCDCH:Wire1";
+ else if ((SL + 1) % 3 == 0)
+ wirecol = "vCDCH:Wire2";
+ else if ((SL + 2) % 3 == 0)
+ wirecol = "vCDCH:Wire3";
+
+ if (SL == 0) {// SL = 0 is special due to the guard wires and the first field wires that lie outside of the sensitive volume
+ double stereoOut0 = atan(sqrt(diff_of_squares((inGuardRad - inGWradii) + (inGuardRad - inGWradii) * dropFactor, inGuardRad - inGWradii)) / halflength);
+
+ dd4hep::Hyperboloid HypeLayer0(guard_layer_inner_radius_at_z_0, 0.0, layer_outer_radius_at_z_0 - secure, stereoOut0, halflength);
+ lvLayerVol.push_back(dd4hep::Volume("hyperboloid_inner_guard_layer", HypeLayer0, description.material("GasHe_90Isob_10")));
+ lvLayerVol.back().setVisAttributes(description, gascol);
+
+ epsilonInGwRing = atan(inGuardRad * (1.0 + dropFactor) * epsilonFactor);
+ zlength = halflength;
+ zlength -= sin(epsilonInGwRing) * inGWradii;
+ zlength /= cos(epsilonInGwRing);
+
+ guard_wires.mother_volume = lvLayerVol.back();
+ guard_wires.type = "G";
+ guard_wires.num = nFWireTopOrBottom; //(#guard wires == # field wires)
+ guard_wires.radius = inGuardRad - inGWradii;
+ guard_wires.delta_phi = 2. * TMath::Pi() / guard_wires.num;
+ guard_wires.phioffset = phi_offset_cell_start;
+ guard_wires.stereo = epsilonInGwRing;
+ guard_wires.thickness = 0.5 * InGWireShellThickIn * wireThicknessDilution; // half the inner thickness as radius of tube
+ guard_wires.halflength = zlength;
+ guard_wires.name = string("Gwire_inner_stereoplus");
+
+ dd4hep::Tube Gwire(0.0, guard_wires.thickness, halflength);
+ lvGwireVol.push_back(dd4hep::Volume("Gwire_inner", Gwire, description.material("G4_Al")));
+ lvGwireVol.back().setVisAttributes(description, wirecol);
+
+ guard_wires.volume = lvGwireVol.back();
+ CDCHBuild::PlaceGuardWires(guard_wires, FWireShellThickOut, halflength, SL, -1);
+
+ guard_wires.volume = lvGwireVol.back(); // needed because applyWireCoating acts on it
+ guard_wires.radius = inGuardRad + inGWradii + extShiftFW;
+ guard_wires.phioffset = phi_offset_cell_start + phi_offset;
+ guard_wires.stereo = -1.0 * epsilonInGwRing;
+ guard_wires.name = string("Gwire_inner_stereominus");
+ CDCHBuild::PlaceGuardWires(guard_wires, FWireShellThickOut, halflength, SL, -1);
+
+ drop = radius_layer_0 * dropFactor;
+ radius_layer = radius_layer_0 + drop;
+ epsilon = atan(radius_layer * epsilonFactor);
+ layer_inner_radius_at_z_0 = radius_layer_0 - FWradii - 2.0 * secure;
+ layer_inner_radius_at_z_end = layer_inner_radius_at_z_0 + drop;
+ layer_outer_radius_at_z_0 = radius_layer_0 + FWradii;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonIn = atan(sqrt(pow(layer_inner_radius_at_z_end, 2) - pow(layer_inner_radius_at_z_0, 2)) / halflength);
+ epsilonOut = atan(sqrt(pow(layer_outer_radius_at_z_end, 2) - pow(layer_outer_radius_at_z_0, 2)) / halflength);
+
+
+ zlength = halflength;
+ zlength -= sin(epsilon) * FWradii;
+ zlength /= cos(epsilon);
+
+ field_wires_top.type = "F";
+ field_wires_top.num = nFWireTopOrBottom;
+ field_wires_top.radius = layer_inner_radius_at_z_0 - FWradii - extShiftFW;
+ field_wires_top.delta_phi = 2. * TMath::Pi() /field_wires_top.num;;
+ field_wires_top.phioffset = phi_offset_cell_start + cellStaggering - phi_offset;
+ field_wires_top.stereo = sign_epsilon * epsilon;
+ field_wires_top.thickness = 0.5 * FWireShellThickIn * wireThicknessDilution;
+ field_wires_top.halflength = zlength;
+
+ dd4hep::Hyperboloid HypeLayer1(layer_inner_radius_at_z_0, epsilonIn, layer_outer_radius_at_z_0 + field_wires_top.thickness + 0.5 * FWireShellThickOut, epsilonOut, halflength);
+ lvLayerVol.push_back(dd4hep::Volume("hyperboloid_first_field_wire_ring", HypeLayer1, description.material("GasHe_90Isob_10")));
+ lvLayerVol.back().setVisAttributes(description, gascol);
+ field_wires_top.mother_volume = lvLayerVol.back();
+
+ lvFwireName = dd4hep::_toString(SL, "lvFwire_%d_init");
+ field_wires_top.name = string(lvFwireName);
+
+ dd4hep::Tube Fwire(0.0, field_wires_top.thickness, halflength);
+ lvFwireVol.push_back(dd4hep::Volume(lvFwireName, Fwire, description.material("G4_Al")));
+ lvFwireVol.back().setVisAttributes(description, wirecol);
+
+ field_wires_top.volume = lvFwireVol.back();
+ CDCHBuild::PlaceGuardWires(field_wires_top, FWireShellThickOut, halflength, SL, -1);
+
+ radius_layer_0 += FWradii;
+
+ } else {
+ delta_radius_layer = 2. * TMath::Pi() * layer_outer_radius_at_z_0 / (nSWire - TMath::Pi());
+ }
+
+ //------------------------------------------------------------------------
+ // Starting the layer loop. nLayer=8
+ //------------------------------------------------------------------------
+
+ for (int ilayer = 0; ilayer < nLayer; ilayer++) {
+ //------------------------------------------------------------------------
+ // Fill the geometry parameters of the layer. Each layer lies
+ // on top of the following one, so new layerIn = old layerOut
+ //------------------------------------------------------------------------
+
+ sense_wire_radius_at_z_0 = radius_layer_0 + 0.5 * delta_radius_layer;
+ sign_epsilon *= -1;
+
+ layer_inner_radius_at_z_0 = layer_outer_radius_at_z_0;
+ layer_inner_radius_at_z_end = layer_outer_radius_at_z_end;
+ epsilonIn = epsilonOut;
+
+ layer_outer_radius_at_z_0 = layer_inner_radius_at_z_0 + FWireDiameter + 2.0 * secure;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonOut = atan(sqrt(diff_of_squares(layer_outer_radius_at_z_end, layer_outer_radius_at_z_0)) / halflength);
+
+ zlength = halflength;
+
+ // save bottom layer inner radius and epsilon before they are modified to build the whole layer hyperboloid volume
+ layer_inner_radius_at_z_end_whole_cell = layer_inner_radius_at_z_0;
+ epsilonIn_whole_cell = epsilonIn;
+
+ //------------------------------------------------------------------------
+ // Reduce zlength to avoid volume extrusions and check the staggering
+ //------------------------------------------------------------------------
+
+ zlength -= sin(epsilon) * FWradii;
+ zlength /= cos(epsilon);
+
+ if (ilayer % 2 == 1)
+ cellStaggering = phi_offset;
+ else
+ cellStaggering = 0.0;
+
+ //------------------------------------------------------------------------
+ // Fill the field wire struct with all the relevant information
+ // This is the bottom of the cell.
+ //------------------------------------------------------------------------
+
+ field_wires_bottom.type = "F";
+ field_wires_bottom.num = nFWireTopOrBottom;
+ field_wires_bottom.radius = layer_inner_radius_at_z_0 + FWradii + extShiftFW;
+ field_wires_bottom.delta_phi = delta_phi_top_bottom;
+ field_wires_bottom.phioffset = phi_offset_cell_start + cellStaggering;
+ field_wires_bottom.stereo = sign_epsilon * epsilon;
+ field_wires_bottom.thickness = 0.5 * FWireShellThickIn * wireThicknessDilution;
+ field_wires_bottom.halflength = zlength;
+ field_wires_bottom.name = dd4hep::_toString(SL, "Wire_SL%d") + dd4hep::_toString(ilayer, "_layer%d") + string("_type") + field_wires_bottom.type + dd4hep::_toString(field_wires_bottom.stereo, "_stereo%f_bottom");
+
+ //------------------------------------------------------------------------
+ // Define the field wire name and build the field wire volume
+ //------------------------------------------------------------------------
+
+ lvFwireName = dd4hep::_toString(SL, "lvFwire_%d") + dd4hep::_toString(ilayer, "_%d");
+
+ dd4hep::Tube Fwire(0.0, field_wires_bottom.thickness, halflength);
+ dd4hep::Volume FwireVol(lvFwireName, Fwire, description.material("G4_Al"));
+ FwireVol.setVisAttributes(description, wirecol);
+
+ //------------------------------------------------------------------------
+ // Add the field wire volume to the struct
+ //------------------------------------------------------------------------
+
+ field_wires_bottom.volume = FwireVol;
+ apply_wire_coating(field_wires_bottom, FWireShellThickOut, halflength);
+
+ //------------------------------------------------------------------------
+ // Next, fill the geometry parameters of the central layer.
+ //------------------------------------------------------------------------
+
+ iradius = radius_layer_0;
+ radius_layer_0 += delta_radius_layer;
+ drop = radius_layer_0 * dropFactor;
+
+ layer_inner_radius_at_z_0 = layer_outer_radius_at_z_0;
+ layer_inner_radius_at_z_end = layer_outer_radius_at_z_end;
+ epsilonIn = epsilonOut;
+ layer_outer_radius_at_z_0 = radius_layer_0 - FWireDiameter - 2.0 * secure;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonOut = atan(sqrt(diff_of_squares(layer_outer_radius_at_z_end, layer_outer_radius_at_z_0)) / halflength);
+ zlength = halflength;
+
+ //------------------------------------------------------------------------
+ // Reduce zlength to avoid volume extrusions
+ //------------------------------------------------------------------------
+
+ zlength -= sin(epsilon) * 0.5 * FWireShellThickIn;
+ zlength /= cos(epsilon);
+
+
+ ////------------------------------------------------------------------------
+ //// Tune the radius and epsilon of the central field wires
+ ////------------------------------------------------------------------------
+
+ idelta_radius = delta_radius_layer * 0.5;
+ iradius += idelta_radius;
+ //epsilon = atan(iradius * (1.0 + dropFactor) * epsilonFactor);
+
+ //------------------------------------------------------------------------
+ // Fill the sense wire struct with all the relevant information
+ //------------------------------------------------------------------------
+
+ sense_wires.type = "S";
+ sense_wires.num = nSWire;
+ sense_wires.radius = sense_wire_radius_at_z_0;
+ sense_wires.delta_phi = sense_wire_delta_phi;
+ sense_wires.phioffset = cellStaggering;
+ sense_wires.stereo = sign_epsilon * epsilon;
+ sense_wires.thickness = 0.5 * SWireShellThickIn * wireThicknessDilution;
+ sense_wires.halflength = zlength;
+ sense_wires.name = dd4hep::_toString(SL, "Wire_SL%d") + dd4hep::_toString(ilayer, "_layer%d") + string("_type") + sense_wires.type + dd4hep::_toString(sense_wires.stereo, "_stereo%f");
+
+ //------------------------------------------------------------------------
+ // Define the sense wire name and build the sense wire volume
+ //------------------------------------------------------------------------
+
+ lvSwireName = dd4hep::_toString(SL, "lvSwire_%d") + dd4hep::_toString(ilayer, "_%d");
+
+ dd4hep::Tube Swire(0.0, sense_wires.thickness, halflength);
+ dd4hep::Volume lvSwireVol(lvSwireName, Swire, description.material("G4_W"));
+ lvSwireVol.setVisAttributes(description, wirecol);
+ sense_wires.volume = lvSwireVol;
+ apply_wire_coating(sense_wires, SWireShellThickOut, halflength, "G4_Au");
+
+ //------------------------------------------------------------------------
+ // Fill the central field wire struct with all the relevant information
+ //------------------------------------------------------------------------
+
+ field_wires_center.type = "F";
+ field_wires_center.num = nSWire * nCenterFWirePerCell;
+ field_wires_center.radius = sense_wire_radius_at_z_0; //iradius;
+ field_wires_center.delta_phi = 2. * TMath::Pi() / field_wires_center.num;
+ field_wires_center.phioffset = phi_offset_cell_start + cellStaggering;
+ field_wires_center.stereo = sign_epsilon * epsilon;
+ field_wires_center.thickness = 0.5 * centerFWireShellThickIn * wireThicknessDilution;
+ field_wires_center.halflength = zlength;
+ field_wires_center.volume = FwireVol;
+ field_wires_center.name = dd4hep::_toString(SL, "Wire_SL%d") + dd4hep::_toString(ilayer, "_layer%d") + string("_type") + field_wires_center.type + dd4hep::_toString(field_wires_center.stereo, "_stereo%f_center");
+ apply_wire_coating(field_wires_center, centerFWireShellThickOut, halflength);
+
+ // derive a phi offset to englobe the 'left' (clockwise) wires inside the sensitive volume (center field wires are the thickest)
+ double phi_offset_to_englobe_wires = atan((field_wires_center.thickness + 0.5 * centerFWireShellThickOut) / field_wires_center.radius);
+
+ //------------------------------------------------------------------------
+ // Next, fill the geometry parameters of the upper layer.
+ //------------------------------------------------------------------------
+
+ layer_inner_radius_at_z_0 = layer_outer_radius_at_z_0;
+ layer_inner_radius_at_z_end = layer_outer_radius_at_z_end;
+ epsilonIn = epsilonOut;
+ layer_outer_radius_at_z_0 = layer_inner_radius_at_z_0 + FWireDiameter + 2.0 * secure;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonOut = atan(sqrt(diff_of_squares(layer_outer_radius_at_z_end, layer_outer_radius_at_z_0)) / halflength);
+ zlength = halflength;
+
+ // Create hyperboloid volume of the whole layer for the cell sensitive volume definition, not needed per se but helps having a well balanced volume tree (having too many volumes inside one mother volume harms performance)
+ wholeHyperboloidVolumeName = dd4hep::_toString(SL, "hyperboloid_SL_%d") + dd4hep::_toString(ilayer, "_layer_%d");
+ double radial_inner_offset_to_englobe_wires = field_wires_bottom.thickness + 0.5 * FWireShellThickOut;
+ double radial_outer_offset_to_englobe_wires = field_wires_top.thickness + 0.5 * FWireShellThickOut;
+ dd4hep::Hyperboloid whole_layer_hyperboloid = dd4hep::Hyperboloid(layer_inner_radius_at_z_end_whole_cell - radial_inner_offset_to_englobe_wires, epsilonIn_whole_cell, layer_outer_radius_at_z_0 + radial_outer_offset_to_englobe_wires, epsilonOut, zlength);
+ dd4hep::Volume whole_layer_hyperboloid_volume = dd4hep::Volume(wholeHyperboloidVolumeName, whole_layer_hyperboloid, description.material("GasHe_90Isob_10"));
+ whole_layer_hyperboloid_volume.setVisAttributes(description, gascol);
+ //registerVolume(wholeHyperboloidVolumeName, whole_layer_hyperboloid_volume);
+ dd4hep::PlacedVolume whole_layer_hyperboloid_placedVolume;
+ whole_layer_hyperboloid_placedVolume = parentVol.placeVolume(whole_layer_hyperboloid_volume);
+ whole_layer_hyperboloid_placedVolume.addPhysVolID("superLayer", SL).addPhysVolID("layer", ilayer);
+ dd4hep::DetElement whole_layer_hyperboloid_detElement(parent, "superLayer_" + dd4hep::_toString(SL) + "_layer_" + dd4hep::_toString(ilayer) + "_hyperboloid", SL * nLayer + ilayer);
+ whole_layer_hyperboloid_detElement.setPlacement(whole_layer_hyperboloid_placedVolume);
+
+ //------------------------------------------------------------------------
+ // Reduce zlength to avoid volume extrusions
+ //------------------------------------------------------------------------
+
+ zlength -= sin(epsilon) * FWradii;
+ zlength /= cos(epsilon);
+
+ //------------------------------------------------------------------------
+ // Fill the field wire struct with all the relevant information
+ // This is the top of the cell.
+ //------------------------------------------------------------------------
+
+ field_wires_top.type = "F";
+ field_wires_top.num = nFWireTopOrBottom;
+ field_wires_top.radius = layer_inner_radius_at_z_0 - FWradii - extShiftFW;
+ field_wires_top.delta_phi = delta_phi_top_bottom;
+ field_wires_top.phioffset = phi_offset_cell_start + cellStaggering;
+ field_wires_top.stereo = sign_epsilon * epsilon;
+ field_wires_top.thickness = 0.5 * FWireShellThickIn * wireThicknessDilution;
+ field_wires_top.halflength = zlength;
+ field_wires_top.volume = FwireVol;
+ field_wires_top.name = dd4hep::_toString(SL, "Wire_SL%d") + dd4hep::_toString(ilayer, "_layer%d") + string("_type") + field_wires_top.type + dd4hep::_toString(field_wires_top.stereo, "_stereo%f_top");
+ apply_wire_coating(field_wires_top, FWireShellThickOut, halflength);
+
+ //if(setWireSensitive){
+ // field_wires_bottom.volume.setSensitiveDetector(sens_det);
+ // field_wires_center.volume.setSensitiveDetector(sens_det);
+ // sense_wires.volume.setSensitiveDetector(sens_det);
+ // field_wires_top.volume.setSensitiveDetector(sens_det);
+ //}
+
+ // Create the tube segment volume to identify the cell sensitive regions
+ // FIXME: this leads to some volume extrusion (corner of the tube going outside of the hyperboloid mother volume)
+ // Using the intersection with the hyperboloid mother volume solves it but severely impacts the perfomance (memory and CPU) of the geometry building
+ // Other paths to investigate are twisted tubes (caveat: no TGeo shape equivalent), extruded volumes or tessalated solids
+ dd4hep::Tube cellID_tube_segment(layer_inner_radius_at_z_end_whole_cell - radial_inner_offset_to_englobe_wires, layer_outer_radius_at_z_0 + radial_outer_offset_to_englobe_wires, halflength, (- sense_wires.delta_phi / 2.0) - phi_offset_to_englobe_wires, (sense_wires.delta_phi / 2.0) - phi_offset_to_englobe_wires);
+
+ // Radial translation
+ dd4hep::Translation3D radial_translation_sense_wire(sense_wires.radius, 0., 0.);
+ // stereo rotation
+ dd4hep::RotationX rot_stereo_sense_wire(sense_wires.stereo);
+ // extract the number of wire ratio to place field wires in the loop for sense wires
+ // it is not very elegant but the sense wire define the sensitive volume in which wires are placed
+ float middle_to_middle_num_wire_ratio = field_wires_center.num/float(sense_wires.num);
+ float middle_to_bottom_num_wire_ratio = field_wires_bottom.num/float(sense_wires.num);
+ float middle_to_top_num_wire_ratio = field_wires_top.num/float(sense_wires.num);
+ if(ceilf(middle_to_middle_num_wire_ratio) != middle_to_middle_num_wire_ratio || ceilf(middle_to_bottom_num_wire_ratio) != middle_to_bottom_num_wire_ratio || ceilf(middle_to_top_num_wire_ratio) != middle_to_top_num_wire_ratio)
+ throw std::runtime_error("Error: Failed to build CDCH. Please make sure that the number of wires in top/center cell rings is always a multiple of the number of wires in the middle of the cell");
+ // loop to arrange the wires in phi, starting with the sense wires
+ for (int phi_index = 0; phi_index < sense_wires.num; phi_index++) {
+ // Place the sensitive volume inside the hyperbioloid with phi and stereo angle rotation
+ string cellID_volume_name = dd4hep::_toString(SL, "cellIDvolume_SL_%d") + dd4hep::_toString(ilayer, "_layer_%d") + dd4hep::_toString(phi_index, "_phi_%d");
+ dd4hep::Volume cellID_volume = dd4hep::Volume(cellID_volume_name, cellID_tube_segment, description.material("GasHe_90Isob_10"));
+ cellID_volume.setSensitiveDetector(sens_det);
+ // phi rotation
+ double phi_angle_sense_wire_rotation = sense_wires.phioffset + sense_wires.delta_phi * phi_index;
+ dd4hep::RotationZ rot_phi_sense_wire(phi_angle_sense_wire_rotation);
+ dd4hep::PlacedVolume cellID_placedvolume = whole_layer_hyperboloid_volume.placeVolume(cellID_volume, dd4hep::Transform3D(rot_phi_sense_wire * rot_stereo_sense_wire));
+ cellID_placedvolume.addPhysVolID("phi", phi_index).addPhysVolID("hitorigin", 0).addPhysVolID("stereo", sense_wires.stereo > 0 ? 0 : 1).addPhysVolID("layerInCell", 0);
+ dd4hep::DetElement cellID_detElement(whole_layer_hyperboloid_detElement, "superLayer_" + dd4hep::_toString(SL) + "_layer_" + dd4hep::_toString(ilayer) + "_phi_" + dd4hep::_toString(phi_index) + "_cellID", phi_index);
+ cellID_detElement.setPlacement(cellID_placedvolume);
+
+ // place the wires
+ // sense wires in the radial middle of the cell
+ dd4hep::PlacedVolume sense_wire_placedvolume = cellID_volume.placeVolume(sense_wires.volume, dd4hep::Transform3D(radial_translation_sense_wire)); // only the radial translation is needed as the cellID volume already had the stereo and phi angle rotation
+ // add the sense wire as detElement to be able to retrive its matrix with DD4hep 1.23 (with later verion we can use Volumes daugthers)
+ dd4hep::DetElement senseWire_detElement(cellID_detElement, "superLayer_" + dd4hep::_toString(SL) + "_layer_" + dd4hep::_toString(ilayer) + "_phi_" + dd4hep::_toString(phi_index) + "_wire", phi_index);
+ senseWire_detElement.setPlacement(sense_wire_placedvolume);
+
+ // field wires: the cellID volume has the stereo angle rotation of the sense wires which is different than the top/bototm wires --> Rotations are defined on top of the already applied cellID volume rotation
+ // bottom field wires
+ for(int sub_phi_index = phi_index * middle_to_bottom_num_wire_ratio; sub_phi_index < (phi_index * middle_to_bottom_num_wire_ratio) + middle_to_bottom_num_wire_ratio; sub_phi_index++){
+ // phi rotation
+ dd4hep::RotationZ rot_phi_bottom_wire((field_wires_bottom.phioffset + field_wires_bottom.delta_phi * sub_phi_index) - phi_angle_sense_wire_rotation);
+ cellID_volume.placeVolume(field_wires_bottom.volume, dd4hep::Transform3D(rot_phi_bottom_wire * dd4hep::Translation3D(field_wires_bottom.radius, 0., 0.) * dd4hep::RotationX(field_wires_bottom.stereo - sense_wires.stereo)));
+ //dd4hep::PlacedVolume field_wire_bottom_placedvolume = cellID_volume.placeVolume(field_wires_bottom.volume, dd4hep::Transform3D(rot_phi_bottom_wire * dd4hep::Translation3D(field_wires_bottom.radius, 0., 0.) * dd4hep::RotationX(field_wires_bottom.stereo - sense_wires.stereo)));
+ //if(setWireSensitive)
+ // field_wire_bottom_placedvolume.addPhysVolID("phi", sub_phi_index).addPhysVolID("hitorigin", 2).addPhysVolID("stereo", field_wires_center.stereo > 0 ? 0 : 1).addPhysVolID("layerInCell", 1);
+ }
+
+ // central field wires
+ for(int sub_phi_index = phi_index * middle_to_middle_num_wire_ratio; sub_phi_index < (phi_index * middle_to_middle_num_wire_ratio) + middle_to_middle_num_wire_ratio; sub_phi_index++){
+ // phi rotation
+ dd4hep::RotationZ rot_phi_center_wire((field_wires_center.phioffset + field_wires_center.delta_phi * sub_phi_index) - phi_angle_sense_wire_rotation);
+ cellID_volume.placeVolume(field_wires_center.volume, dd4hep::Transform3D(rot_phi_center_wire * dd4hep::Translation3D(field_wires_center.radius, 0., 0.) * dd4hep::RotationX(field_wires_center.stereo - sense_wires.stereo)));
+ //dd4hep::PlacedVolume field_wire_center_placedvolume = cellID_volume.placeVolume(field_wires_center.volume, dd4hep::Transform3D(rot_phi_center_wire * dd4hep::Translation3D(field_wires_center.radius, 0., 0.) * dd4hep::RotationX(field_wires_center.stereo - sense_wires.stereo)));
+ //if(setWireSensitive)
+ // field_wire_center_placedvolume.addPhysVolID("phi", sub_phi_index).addPhysVolID("hitorigin", 2).addPhysVolID("stereo", field_wires_center.stereo > 0 ? 0 : 1).addPhysVolID("layerInCell", 2);
+ }
+
+ // top field wires
+ for(int sub_phi_index = phi_index * middle_to_top_num_wire_ratio; sub_phi_index < (phi_index * middle_to_top_num_wire_ratio) + middle_to_top_num_wire_ratio; sub_phi_index++){
+ // phi rotation
+ dd4hep::RotationZ rot_phi_top_wire((field_wires_top.phioffset + field_wires_top.delta_phi * sub_phi_index) - phi_angle_sense_wire_rotation);
+ cellID_volume.placeVolume(field_wires_top.volume, dd4hep::Transform3D(rot_phi_top_wire * dd4hep::Translation3D(field_wires_top.radius, 0., 0.) * dd4hep::RotationX(field_wires_top.stereo - sense_wires.stereo)));
+ //dd4hep::PlacedVolume field_wire_top_placedvolume = cellID_volume.placeVolume(field_wires_top.volume, dd4hep::Transform3D(rot_phi_top_wire * dd4hep::Translation3D(field_wires_top.radius, 0., 0.) * dd4hep::RotationX(field_wires_top.stereo - sense_wires.stereo)));
+ //if(setWireSensitive)
+ // field_wire_top_placedvolume.addPhysVolID("phi", sub_phi_index).addPhysVolID("hitorigin", 2).addPhysVolID("stereo", field_wires_center.stereo > 0 ? 0 : 1).addPhysVolID("layerInCell", 3);
+ }
+ }
+
+ //------------------------------------------------------------------------
+ // Scale the delta radius of the layer for next iteration
+ //------------------------------------------------------------------------
+
+ delta_radius_layer *= scaleFactor;
+ epsilon = atan(iradius * (1.0 + dropFactor) * epsilonFactor); // FIXME the stereo angle is here assumed to be constant for a given cell while it should be different for the bottom, middle and top rings of the cell
+ }
+
+ if (SL == (nSuperLayer - 1)) {// the last super layer is special since we need to add the field wires outside of the sensitive volume and the guard wires
+
+ // Take care of the field wires outside of the sensitive volume
+ layer_inner_radius_at_z_0 = layer_outer_radius_at_z_0;
+ layer_inner_radius_at_z_end = layer_outer_radius_at_z_end;
+ epsilonIn = epsilonOut;
+ layer_outer_radius_at_z_0 = radius_layer_0 + FWireDiameter + 2.0 * secure;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonOut = atan(sqrt(diff_of_squares(layer_outer_radius_at_z_end, layer_outer_radius_at_z_0)) / halflength);
+
+ dd4hep::Hyperboloid HypeLayerOut(layer_inner_radius_at_z_0, epsilonIn, layer_outer_radius_at_z_0, epsilonOut, halflength);
+ lvLayerVol.push_back(dd4hep::Volume("hyperboloid_last_field_wire_ring", HypeLayerOut, description.material("GasHe_90Isob_10")));
+ lvLayerVol.back().setVisAttributes(description, gascol);
+
+ zlength = halflength;
+ zlength -= sin(epsilon) * FWradii;
+ zlength /= cos(epsilon);
+
+ field_wires_bottom.mother_volume = lvLayerVol.back();
+ field_wires_bottom.type = "F";
+ field_wires_bottom.num = nFWireTopOrBottom;
+ field_wires_bottom.radius = layer_inner_radius_at_z_0 + FWradii + extShiftFW;
+ field_wires_bottom.delta_phi = 2. * TMath::Pi() / field_wires_bottom.num;
+ field_wires_bottom.phioffset = phi_offset_cell_start + cellStaggering + phi_offset;
+ field_wires_bottom.stereo = -1. * sign_epsilon * epsilon;
+ field_wires_bottom.thickness = 0.5 * FWireShellThickIn * wireThicknessDilution;
+ field_wires_bottom.halflength = zlength;
+
+ lvFwireName = dd4hep::_toString(SL, "lvFwire_%d_out");
+ field_wires_bottom.name = lvFwireName;
+
+ dd4hep::Tube Fwire(0.0, field_wires_bottom.thickness, halflength);
+ lvFwireVol.push_back(dd4hep::Volume(lvFwireName, Fwire, description.material("G4_Al")));
+ lvFwireVol.back().setVisAttributes(description, wirecol);
+
+ field_wires_bottom.volume = lvFwireVol.back();
+ CDCHBuild::PlaceGuardWires(field_wires_bottom, FWireShellThickOut, halflength, SL, -1);
+
+ //------------------------------------------------------------------------
+ // Start placing the outer layer of guard wires (#guard wires == # field wires)
+ //------------------------------------------------------------------------
+
+ layer_inner_radius_at_z_0 = layer_outer_radius_at_z_0;
+ layer_inner_radius_at_z_end = layer_outer_radius_at_z_end;
+ epsilonIn = epsilonOut;
+ layer_outer_radius_at_z_0 = radius_layer_0 + FWireDiameter + 2.0 * secure;
+ layer_outer_radius_at_z_end = layer_outer_radius_at_z_0 + drop;
+ epsilonOut = atan(sqrt(diff_of_squares(layer_outer_radius_at_z_end, layer_outer_radius_at_z_0)) / halflength);
+
+ dd4hep::Hyperboloid HypeLayerOutG(layer_inner_radius_at_z_0, epsilonOut, outer_radius - envelop_Outer_thickness - 0.0001,
+ 0.0, halflength);
+ lvLayerVol.push_back(dd4hep::Volume("hyperboloid_outer_guard_layer", HypeLayerOutG, description.material("GasHe_90Isob_10")));
+ lvLayerVol.back().setVisAttributes(description, gascol);
+
+ epsilonOutGwRing = atan(outGuardRad * (1.0 + dropFactor) * epsilonFactor);
+ zlength = halflength;
+ zlength -= sin(epsilonOutGwRing) * inGWradii;
+ zlength /= cos(epsilonOutGwRing);
+
+ guard_wires.mother_volume = lvLayerVol.back();
+ guard_wires.type = "G";
+ guard_wires.num = nFWireTopOrBottom;
+ guard_wires.radius = outGuardRad - inGWradii;
+ guard_wires.delta_phi = 2. * TMath::Pi() / guard_wires.num;
+ guard_wires.phioffset = phi_offset_cell_start;
+ guard_wires.stereo = epsilonOutGwRing;
+ guard_wires.thickness = 0.5 * OutGWireShellThickIn * wireThicknessDilution;
+ guard_wires.halflength = zlength;
+ guard_wires.name = string("Gwire_outer_stereominus");
+
+ dd4hep::Tube Gwire(0.0, guard_wires.thickness, halflength);
+ lvGwireVol.push_back(dd4hep::Volume("Gwire_outer", Gwire, description.material("G4_Al")));
+ lvGwireVol.back().setVisAttributes(description, wirecol);
+
+ guard_wires.volume = lvGwireVol.back();
+ CDCHBuild::PlaceGuardWires(guard_wires, OutGWireShellThickOut, halflength, SL, -1);
+
+ guard_wires.volume = lvGwireVol.back(); // needed because applyWireCoating acts on it
+ guard_wires.radius = outGuardRad + inGWradii + extShiftFW;
+ guard_wires.phioffset = phi_offset_cell_start + phi_offset;
+ guard_wires.stereo = -1.0 * epsilonOutGwRing;
+ guard_wires.name = string("Gwire_outer_stereoplus");
+ CDCHBuild::PlaceGuardWires(guard_wires, OutGWireShellThickOut, halflength, SL, -1);
+ }
+ }
+
+
+ Int_t sizeLayer = lvLayerVol.size();
+
+
+ for (Int_t i = 0; i < sizeLayer; i++) {
+ registerVolume(lvLayerVol.at(i).name(), lvLayerVol.at(i));
+ //cout << "Placing Volume: " << lvLayerVol.at(i).name() << endl;
+ //pv = parentVol.placeVolume(volume(lvLayerVol.at(i).name()));
+ //CDCHDetector.setPlacement(pv);
+ parentVol.placeVolume(volume(lvLayerVol.at(i).name()));
+ }
+
+ double PosEndcapGas = halflength + 0.5 * GasEndcapWallThick;
+ double PosEndcapCopper = halflength + GasEndcapWallThick + 0.5 * CopperEndcapWallThick;
+ double PosEndcapKapton = halflength + GasEndcapWallThick + CopperEndcapWallThick + 0.5 * KaptonEndcapWallThick;
+ double PosEndcapCarbon =
+ halflength + GasEndcapWallThick + CopperEndcapWallThick + KaptonEndcapWallThick + 0.5 * CarbonEndcapWallThick;
+
+ parentVol.placeVolume(lvInnerWallCarbon);
+ parentVol.placeVolume(lvInnerWallCopper);
+ parentVol.placeVolume(lvInnerWallGas);
+ parentVol.placeVolume(lvOuterWallCarbon1);
+ parentVol.placeVolume(lvOuterWallCarbon2);
+ parentVol.placeVolume(lvOuterWallCopper);
+ parentVol.placeVolume(lvOuterWallFoam);
+ parentVol.placeVolume(lvEndcapWallGas, dd4hep::Position(0., 0., PosEndcapGas));
+ parentVol.placeVolume(lvEndcapWallCopper, dd4hep::Position(0., 0., PosEndcapCopper));
+ parentVol.placeVolume(lvEndcapWallKapton, dd4hep::Position(0., 0., PosEndcapKapton));
+ parentVol.placeVolume(lvEndcapWallCarbon, dd4hep::Position(0., 0., PosEndcapCarbon));
+ parentVol.placeVolume(lvEndcapWallGas, dd4hep::Position(0., 0., -PosEndcapGas));
+ parentVol.placeVolume(lvEndcapWallCopper, dd4hep::Position(0., 0., -PosEndcapCopper));
+ parentVol.placeVolume(lvEndcapWallKapton, dd4hep::Position(0., 0., -PosEndcapKapton));
+ parentVol.placeVolume(lvEndcapWallCarbon, dd4hep::Position(0., 0., -PosEndcapCarbon));
+}
+} //namespace
+
+static dd4hep::Ref_t create_element(dd4hep::Detector& description, xml_h e, dd4hep::SensitiveDetector sens_det) {
+
+ xml_det_t x_det = e;
+ CDCHBuild builder(description, x_det, sens_det);
+ string det_name = x_det.nameStr();
+
+ dd4hep::printout(dd4hep::DEBUG, "CreateCDCH", "Detector name: %s with ID: %s", det_name.c_str(), x_det.id());
+
+ DetElement CDCH_det = builder.detector; // ( det_name, x_det.id() );
+ dd4hep::Tube CDCH_envelope(dd4hep::_toDouble("CDCH:inner_radius"), dd4hep::_toDouble("CDCH:outer_radius"), dd4hep::_toDouble("CDCH:zHalfExtentWithServices"));
+
+ dd4hep::Volume envelope("lvCDCH", CDCH_envelope, description.air());
+ envelope.setVisAttributes(description, "vCDCH:Air");
+
+ // ******************************************************
+ // Build CDCH cable
+ // ******************************************************
+
+ builder.build_layer(CDCH_det, envelope, sens_det);
+
+ dd4hep::printout(dd4hep::DEBUG, "CreateCDCH", "MotherVolume is: %s", envelope.name());
+ dd4hep::xml::Dimension sdType = x_det.child(_U(sensitive));
+ sens_det.setType(sdType.typeStr());
+
+ builder.buildVolumes(e);
+ builder.placeDaughters(CDCH_det, envelope, e);
+
+ // ******************************************************
+ // Build CDCH cell and beam plug
+ // ******************************************************
+
+ // builder.build_cell();
+ // builder.build_beamplug();
+
+ // ******************************************************
+ // Assemble CDCH
+ // ******************************************************
+
+ // builder.build_CDCH( Ecal_det, envelope );
+
+ // ******************************************************
+ // Place the CDCH in the world
+ // ******************************************************
+
+ PlacedVolume pv;
+ pv = builder.placeDetector(envelope);
+ pv.addPhysVolID("system", x_det.id());
+
+ return CDCH_det;
+}
+
+DECLARE_DETELEMENT(DriftChamber_o1_v01, create_element)
diff --git a/detector/tracker/README.md b/detector/tracker/README.md
index 5800e9f44..3fbe421fc 100644
--- a/detector/tracker/README.md
+++ b/detector/tracker/README.md
@@ -1,4 +1,6 @@
-# VertexBarrel_detailed and VertexDisks_detailed description
+# Vertex detectors
+
+## VertexBarrel_detailed and VertexDisks_detailed description
These two detector constructors were derived from ZPlanarTracker.cpp and from VertexEndcap_o1_v06.cpp and adapted to fit the needs of the [IDEA vertex detector engineering design](https://indico.cern.ch/event/1202105/timetable/#242-mechanical-integration-of).
Both the barrel and the disks are made up of staves, that can feature an arbitrary number of cuboid layers to describe the support, readout and sensor structures. The sensors can be described by individual sensitive and insensitive pieces to form large, complex structures such as quad modules, that have an insensitive periphery.
@@ -7,8 +9,24 @@ More details can be found in the talks at the [FCC week 2023](https://indico.cer
# Trackers
## parametrised_SimplifiedDriftChamber
-This sub-detector implements a simplied drift chamber. It is used in ALLEGRO detector concept.
+This sub-detector implements a simplied drift chamber (there is no actual wire). It is used in ALLEGRO and IDEA detector concepts.
### o1_v01
-Original version taken from [FCCDetectors](https://github.com/HEP-FCC/FCCDetectors/blob/70a989a6fc333610e3b1b979c3596da9c41543d8/Detector/DetFCChhCalDiscs/src/CaloEndcapDiscs_geo.cpp).
+Original version taken from [FCCDetectors](https://github.com/HEP-FCC/FCCDetectors/blob/main/Detector/DetSensitive/src/SimpleDriftChamber.cpp).
+
+## DriftChamber
+Detailed (i.e. including wires) description of a unique-volume, high granularity, fully stereo, low mass, cylindrical drift chamber.
+Originally designed for the IDEA detector, it will be used also for the ALLEGRO concept with different dimensions.
+A detailed description of the detector it tries to model can be found [here](https://indico.cern.ch/event/932973/contributions/4041314/attachments/2139657/3664808/primavera_FCCworkshop_2020.pdf) or [here](https://indico.cern.ch/event/1283129/contributions/5476695/attachments/2682660/4654170/DeFilippis_DCH_CC.pdf).
+### o1_v01
+Very first version of the detailed drift chamber with wires, walls, etc. Taken from an early prototype available in FCCDetectors ([link](https://github.com/HEP-FCC/FCCDetectors/blob/main/Detector/DetFCCeeIDEA/src/DriftChamber.cpp)) and started to debug it, clean it and add the sensitive volume definition.
+It is clearly not finished but made available to enable further technical developments on the digitization, tracking, particle flow, ...
+This version does not rely on any external segmentation, all volumes are defined in the detector builder.
+The sensitive cell volume is a rotated (to get the stereo angle and phi position) tube segment that is put inside a hyperboloid for each layer.
+The hyperboloid is needed for performance, it helps having a well balanced volume tree.
+What to improve:
+- sensitive volume definition: fix volume extrusion/overlaps/wholes which can be partially solved by using "intersection solids" but this in turn makes the geometry building very slow and memory demanding (~10GB RAM). Other shapes like twisted tubes (caveat: no TGeo shape equivalent), tessellated solids or extruded volumes should be investigated for the next iteration.
+- better automation in the C++ to rely less on user defined parameters from the xml
+- better variable naming (started already but could still be improved)
+- make sense out of the many layer radiuses defined
diff --git a/detectorCommon/CMakeLists.txt b/detectorCommon/CMakeLists.txt
new file mode 100644
index 000000000..0982e0a4c
--- /dev/null
+++ b/detectorCommon/CMakeLists.txt
@@ -0,0 +1,21 @@
+################################################################################
+# Package: detectorCommon
+################################################################################
+
+file(GLOB sources src/*.cpp)
+add_dd4hep_plugin(detectorCommon SHARED ${sources})
+target_link_libraries(detectorCommon DD4hep::DDCore DD4hep::DDG4 detectorSegmentations)
+target_include_directories(detectorCommon
+ PUBLIC
+ $
+ $
+)
+
+file(GLOB headers include/detectorCommon/*.h)
+set_target_properties(detectorCommon PROPERTIES PUBLIC_HEADER "${headers}")
+
+install(TARGETS detectorCommon
+ EXPORT ${PROJECT_NAME}Targets
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib
+ PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/detectorCommon" COMPONENT dev)
diff --git a/detectorCommon/include/detectorCommon/DetUtils_k4geo.h b/detectorCommon/include/detectorCommon/DetUtils_k4geo.h
new file mode 100644
index 000000000..04c1f93e6
--- /dev/null
+++ b/detectorCommon/include/detectorCommon/DetUtils_k4geo.h
@@ -0,0 +1,200 @@
+#ifndef DETECTORCOMMON_DETUTILS_H
+#define DETECTORCOMMON_DETUTILS_H
+
+// k4geo
+#include "detectorSegmentations/FCCSWGridPhiEta_k4geo.h"
+#include "detectorSegmentations/FCCSWGridPhiTheta_k4geo.h"
+#include "detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h"
+
+// DD4hep
+#include "DD4hep/DetFactoryHelper.h"
+#include "DD4hep/Segmentations.h"
+#include "DDSegmentation/BitFieldCoder.h"
+// Include GridPhiEta from dd4hep once eta calculation is fixed
+//#include "DDSegmentation/GridPhiEta.h"
+#include "DDSegmentation/CartesianGridXY.h"
+#include "DDSegmentation/CartesianGridXYZ.h"
+#include "DDSegmentation/PolarGridRPhi.h"
+
+// Geant
+#include "G4Step.hh"
+
+// CLHEP
+#include "CLHEP/Vector/ThreeVector.h"
+
+#include "TGeoManager.h"
+
+/** Given a XML element with several daughters with the same name, e.g.
+
+ this method returns the first daughter of type nodeName whose attribute has a given value
+ e.g. returns when called with (detector, "layer", "name", "1") */
+namespace det {
+namespace utils {
+dd4hep::xml::Component getNodeByStrAttr(const dd4hep::xml::Handle_t& mother, const std::string& nodeName,
+ const std::string& attrName, const std::string& attrValue);
+
+/// try to get attribute with double value, return defaultValue if attribute not found
+double getAttrValueWithFallback(const dd4hep::xml::Component& node, const std::string& attrName,
+ const double& defaultValue);
+
+/** Retrieves the cellID based on the position of the step and the detector segmentation.
+ * @param aSeg Handle to the segmentation of the volume.
+ * @param aStep Step in which particle deposited the energy.
+ * @param aPreStepPoint Flag indicating if the position of the deposit is the beginning of the step (default)
+ * or the middle of the step.
+ */
+
+uint64_t cellID(const dd4hep::Segmentation& aSeg, const G4Step& aStep, bool aPreStepPoint = true);
+
+/** Get number of possible combinations of bit fields for determination of neighbours.
+ * @param[in] aN number of field names.
+ * @param[in] aK length of bit fields included for index search.
+ * return vector of possible combinations of field values.
+ */
+
+std::vector> combinations(int N, int K);
+
+/** Get number of possible permutations for certain combination of bit field indeces.
+ * @param[in] aN number of field names.
+ * return vector of permuations for certain field values.
+ */
+
+std::vector> permutations(int K);
+
+/** Get true field value of neighbour in cyclic bit field
+ * @param[in] aCyclicId field value of neighbour
+ * @param[in] aFieldExtremes Minimal and Maximal values of the fields.
+ * return field value for neighbour in cyclic bit field
+ */
+
+int cyclicNeighbour(int aCyclicId, std::pair aFieldExtremes);
+
+/** Get neighbours in many dimensions.
+ * @param[in] aDecoder Handle to the bitfield decoder.
+ * @param[in] aFieldNames Names of the fields for which neighbours are found.
+ * @param[in] aFieldExtremes Minimal and maximal values for the fields.
+ * @param[in] aCellId ID of cell.
+ * @param[in] aDiagonal If diagonal neighbours should be included (all combinations of fields).
+ * return Vector of neighbours.
+ */
+std::vector neighbours(const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames,
+ const std::vector>& aFieldExtremes,
+ uint64_t aCellId,
+ const std::vector& aFieldCyclic = {false, false, false, false},
+ bool aDiagonal = true);
+
+/** Special version of the neighbours function for the readout with module and theta merged cells
+ * Compared to the standard version, it needs a reference to the segmentation class to
+ * access the number of merged cells per layer. The other parameters and return value are the same
+ * @param[in] aSeg Reference to the segmentation object.
+ * @param[in] aDecoder Handle to the bitfield decoder.
+ * @param[in] aFieldNames Names of the fields for which neighbours are found.
+ * @param[in] aFieldExtremes Minimal and maximal values for the fields.
+ * @param[in] aCellId ID of cell.
+ * @param[in] aDiagonal If diagonal neighbours should be included (all combinations of fields).
+ * return Vector of neighbours.
+ */
+std::vector neighbours_ModuleThetaMerged(const dd4hep::DDSegmentation::FCCSWGridModuleThetaMerged_k4geo& aSeg,
+ const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames,
+ const std::vector>& aFieldExtremes,
+ uint64_t aCellId,
+ bool aDiagonal = false);
+
+/** Get minimal and maximal values that can be decoded in the fields of the bitfield.
+ * @param[in] aDecoder Handle to the bitfield decoder.
+ * @param[in] aFieldNames Names of the fields for which extremes are found.
+ * return Vector of pairs (min,max)
+ */
+std::vector> bitfieldExtremes(const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames);
+
+/** Get the half widths of the box envelope (TGeoBBox).
+ * @param[in] aVolumeId The volume ID.
+ * return Half-widths of the volume (x,y,z).
+ */
+CLHEP::Hep3Vector envelopeDimensions(uint64_t aVolumeId);
+
+/** Get the dimensions of a tube (TGeoConeSeg).
+ * @param[in] aVolumeId The volume ID.
+ * return Dimensions of the tube (rmin, rmax, z(half-length)).
+ */
+CLHEP::Hep3Vector tubeDimensions(uint64_t aVolumeId);
+
+/** Get the dimensions of a cone (TGeoCone).
+ * @param[in] aVolumeId The volume ID.
+ * return Dimensions of the cone (rmin, rmax, z(half-length)).
+ */
+CLHEP::Hep3Vector coneDimensions(uint64_t aVolumeId);
+
+/** Get the extrema in pseudorapidity of a tube or cone volume.
+ * @param[in] aVolumeId The volume ID.
+ * return Pseudorapidity extrema (eta_min, eta_max).
+ */
+std::array tubeEtaExtremes(uint64_t aVolumeId);
+
+/** Get the extrema in pseudorapidity of an envelope.
+ * @param[in] aVolumeId The volume ID.
+ * return Pseudorapidity extrema (eta_min, eta_max).
+ */
+std::array envelopeEtaExtremes(uint64_t aVolumeId);
+
+/** Get the extrema in pseudorapidity of a volume. First try to match tube or cone, if it fails use an envelope shape.
+ * @param[in] aVolumeId The volume ID.
+ * return Pseudorapidity extrema (eta_min, eta_max).
+ */
+std::array volumeEtaExtremes(uint64_t aVolumeId);
+
+/** Get the number of cells for the volume and a given Cartesian XY segmentation.
+ * For an example see: Test/TestReconstruction/tests/options/testcellcountingXYZ.py.
+ * @warning No offset in segmentation is currently taken into account.
+ * @param[in] aVolumeId The volume for which the cells are counted.
+ * @param[in] aSeg Handle to the segmentation of the volume.
+ * return Array of the number of cells in (X, Y).
+ */
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::CartesianGridXY& aSeg);
+
+/** Get the number of cells for the volume and a given Cartesian XYZ segmentation.
+ * For an example see: Test/TestReconstruction/tests/options/testcellcountingXYZ.py.
+ * @warning No offset in segmentation is currently taken into account.
+ * @param[in] aVolumeId The volume for which the cells are counted.
+ * @param[in] aSeg Handle to the segmentation of the volume.
+ * return Array of the number of cells in (X, Y, Z).
+ */
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::CartesianGridXYZ& aSeg);
+
+/** Get the number of cells for the volume and a given Phi-Eta / Phi-Theta / Module-Theta segmentation.
+ * It is assumed that the volume has a cylindrical shape (and full azimuthal coverage)
+ * and that it is centred at (0,0,0).
+ * For an example see: Test/TestReconstruction/tests/options/testcellcountingPhiEta.py.
+ * @warning No offset in segmentation is currently taken into account.
+ * @param[in] aVolumeId The volume for which the cells are counted.
+ * @param[in] aSeg Handle to the segmentation of the volume.
+ * return Array of the number of cells in (phi, eta) / (phi, theta) / (module, theta) and the minimum eta / theta ID.
+ */
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridPhiEta_k4geo& aSeg);
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridPhiTheta_k4geo& aSeg);
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridModuleThetaMerged_k4geo& aSeg);
+
+/** Get the number of cells for the volume and a given R-phi segmentation.
+ * It is assumed that the volume has a cylindrical shape - TGeoTube (and full azimuthal coverage)
+ * and that it is centred at (0,0,0).
+ * For an example see: Test/TestReconstruction/tests/options/testcellcountingRPhi.py.
+ * @warning No offset in segmentation is currently taken into account.
+ * @param[in] aVolumeId The volume for which the cells are counted.
+ * @param[in] aSeg Handle to the segmentation of the volume.
+ * return Array of the number of cells in (r, phi).
+ */
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::PolarGridRPhi& aSeg);
+
+/** Get the number of the volumes containing a given name.
+ * For an example see: Test/TestReconstruction/tests/options/testcellcountingXYZ.py.
+ * @param[in] aHighestVolume The top volume in the geometry.
+ * @param[in] aMatchName Name (or its part) of the volume.
+ * return Number of the volumes.
+ */
+unsigned int countPlacedVolumes(TGeoVolume* aHighestVolume, const std::string& aMatchName);
+}
+}
+#endif /* DETCOMMON_DETUTILS_H */
diff --git a/detectorCommon/src/DetUtils_k4geo.cpp b/detectorCommon/src/DetUtils_k4geo.cpp
new file mode 100644
index 000000000..711118248
--- /dev/null
+++ b/detectorCommon/src/DetUtils_k4geo.cpp
@@ -0,0 +1,600 @@
+#include "detectorCommon/DetUtils_k4geo.h"
+
+// DD4hep
+#include "DDG4/Geant4Mapping.h"
+#include "DDG4/Geant4VolumeManager.h"
+
+// Geant
+#include "G4NavigationHistory.hh"
+
+// ROOT
+#include "TGeoBBox.h"
+
+#ifdef HAVE_GEANT4_UNITS
+#define MM_2_CM 1.0
+#else
+#define MM_2_CM 0.1
+#endif
+
+#include
+
+#include
+
+namespace det {
+namespace utils {
+dd4hep::xml::Component getNodeByStrAttr(const dd4hep::xml::Handle_t& mother, const std::string& nodeName,
+ const std::string& attrName, const std::string& attrValue) {
+ for (dd4hep::xml::Collection_t xCompColl(mother, nodeName.c_str()); nullptr != xCompColl; ++xCompColl) {
+ if (xCompColl.attr(attrName.c_str()) == attrValue) {
+ return static_cast(xCompColl);
+ }
+ }
+ // in case there was no xml daughter with matching name
+ return dd4hep::xml::Component(nullptr);
+}
+
+double getAttrValueWithFallback(const dd4hep::xml::Component& node, const std::string& attrName,
+ const double& defaultValue) {
+ if (node.hasAttr(_Unicode(attrName.c_str()))) {
+ return node.attr(attrName.c_str());
+ } else {
+ return defaultValue;
+ }
+}
+
+uint64_t cellID(const dd4hep::Segmentation& aSeg, const G4Step& aStep, bool aPreStepPoint) {
+ dd4hep::sim::Geant4VolumeManager volMgr = dd4hep::sim::Geant4Mapping::instance().volumeManager();
+ dd4hep::VolumeID volID = volMgr.volumeID(aStep.GetPreStepPoint()->GetTouchable());
+ if (aSeg.isValid()) {
+ G4ThreeVector global;
+ if (aPreStepPoint) {
+ global = aStep.GetPreStepPoint()->GetPosition();
+ } else {
+ global = 0.5 * (aStep.GetPreStepPoint()->GetPosition() + aStep.GetPostStepPoint()->GetPosition());
+ }
+ G4ThreeVector local =
+ aStep.GetPreStepPoint()->GetTouchable()->GetHistory()->GetTopTransform().TransformPoint(global);
+ dd4hep::Position loc(local.x() * MM_2_CM, local.y() * MM_2_CM, local.z() * MM_2_CM);
+ dd4hep::Position glob(global.x() * MM_2_CM, global.y() * MM_2_CM, global.z() * MM_2_CM);
+ dd4hep::VolumeID cID = aSeg.cellID(loc, glob, volID);
+ return cID;
+ }
+ return volID;
+}
+
+std::vector> combinations(int N, int K) {
+ std::vector> indexes;
+ std::string bitmask(K, 1); // K leading 1's
+ bitmask.resize(N, 0); // N-K trailing 0's
+ // permute bitmask
+ do {
+ std::vector tmp;
+ for (int i = 0; i < N; ++i) { // [0..N-1] integers
+ if (bitmask[i]) {
+ tmp.push_back(i);
+ }
+ }
+ indexes.push_back(tmp);
+ } while (std::prev_permutation(bitmask.begin(), bitmask.end()));
+ return indexes;
+}
+
+std::vector> permutations(int K) {
+ std::vector> indexes;
+ int N = pow(2, K); // number of permutations with repetition of 2 numbers (-1,1)
+ for (int i = 0; i < N; i++) {
+ // permutation = binary representation of i
+ std::vector tmp;
+ tmp.assign(K, 0);
+ uint res = i;
+ // dec -> bin
+ for (int j = 0; j < K; j++) {
+ tmp[K - 1 - j] = -1 + 2 * (res % 2);
+ res = floor(res / 2);
+ }
+ indexes.push_back(tmp);
+ }
+ return indexes;
+}
+
+// use it for field module/phi
+int cyclicNeighbour(int aCyclicId, std::pair aFieldExtremes) {
+ int nBins = aFieldExtremes.second - aFieldExtremes.first + 1;
+ if (aCyclicId < aFieldExtremes.first) {
+ return (aFieldExtremes.second + 1 - ((aFieldExtremes.first - aCyclicId) % nBins) );
+ } else if (aCyclicId > aFieldExtremes.second) {
+ return ( ((aCyclicId - aFieldExtremes.first) % nBins) + aFieldExtremes.first) ;
+ }
+ return aCyclicId;
+}
+
+std::vector neighbours(const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames,
+ const std::vector>& aFieldExtremes, uint64_t aCellId,
+ const std::vector& aFieldCyclic, bool aDiagonal) {
+ std::vector neighbours;
+ dd4hep::DDSegmentation::CellID cID = aCellId;
+ for (uint itField = 0; itField < aFieldNames.size(); itField++) {
+ const auto& field = aFieldNames[itField];
+ // note: get(..) returns a FieldID i.e. a int64_t
+ // while CellID (used previously) is a uint64_t
+ // similarly, the second argument of set(..)
+ // is signed (FieldID) rather than unsigned (CellID)
+ int id = aDecoder.get(cID,field);
+ if (aFieldCyclic[itField]) {
+ aDecoder[field].set(cID, cyclicNeighbour(id - 1, aFieldExtremes[itField]));
+ neighbours.emplace_back(cID);
+ aDecoder[field].set(cID, cyclicNeighbour(id + 1, aFieldExtremes[itField]));
+ neighbours.emplace_back(cID);
+ } else {
+ if (id > aFieldExtremes[itField].first) {
+ aDecoder[field].set(cID, id - 1);
+ neighbours.emplace_back(cID);
+ }
+ if (id < aFieldExtremes[itField].second) {
+ aDecoder[field].set(cID, id + 1);
+ neighbours.emplace_back(cID);
+ }
+ }
+ aDecoder[field].set(cID, id);
+ }
+ if (aDiagonal) {
+ std::vector fieldIds; // initial IDs
+ fieldIds.assign(aFieldNames.size(), 0);
+ // for each field get current Id
+ for (uint iField = 0; iField < aFieldNames.size(); iField++) {
+ const auto& field = aFieldNames[iField];
+ fieldIds[iField] = aDecoder.get(cID, field);
+ }
+ for (uint iLength = aFieldNames.size(); iLength > 1; iLength--) {
+ // get all combinations for a given length
+ const auto& indexes = combinations(aFieldNames.size(), iLength);
+ for (uint iComb = 0; iComb < indexes.size(); iComb++) {
+ // for current combination get all permutations of +- 1 operation on IDs
+ const auto& calculation = permutations(iLength);
+ // do the increase/decrease of bitfield
+ for (uint iCalc = 0; iCalc < calculation.size(); iCalc++) {
+ // set new Ids for each field combination
+ bool add = true;
+ for (uint iField = 0; iField < indexes[iComb].size(); iField++) {
+ if (aFieldCyclic[indexes[iComb][iField]]) {
+ aDecoder[aFieldNames[indexes[iComb][iField]]].set(cID, cyclicNeighbour(fieldIds[indexes[iComb][iField]] + calculation[iCalc][iField],
+ aFieldExtremes[indexes[iComb][iField]]) );
+ } else if ((calculation[iCalc][iField] > 0 &&
+ fieldIds[indexes[iComb][iField]] < aFieldExtremes[indexes[iComb][iField]].second) ||
+ (calculation[iCalc][iField] < 0 &&
+ fieldIds[indexes[iComb][iField]] > aFieldExtremes[indexes[iComb][iField]].first)) {
+ aDecoder[aFieldNames[indexes[iComb][iField]]].set(cID, fieldIds[indexes[iComb][iField]] + calculation[iCalc][iField]);
+ } else {
+ add = false;
+ }
+ }
+ // add new cellId to neighbours (unless it's beyond extrema)
+ if (add) {
+ neighbours.emplace_back(cID);
+ }
+ // reset ids
+ for (uint iField = 0; iField < indexes[iComb].size(); iField++) {
+ aDecoder[aFieldNames[indexes[iComb][iField]]].set(cID, fieldIds[indexes[iComb][iField]]);
+ }
+ }
+ }
+ }
+ }
+ return neighbours;
+}
+
+// use it for module-theta merged readout (FCCSWGridModuleThetaMerged_k4geo)
+std::vector neighbours_ModuleThetaMerged(const dd4hep::DDSegmentation::FCCSWGridModuleThetaMerged_k4geo& aSeg,
+ const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames,
+ const std::vector>& aFieldExtremes,
+ uint64_t aCellId,
+ bool aDiagonal) {
+
+ std::vector neighbours;
+
+ // check that field names and extremes have the proper length
+ if (aFieldNames.size() != 3 || aFieldExtremes.size()!=3) {
+ std::cout << "ERROR: the vectors aFieldNames and aFieldSizes should be of length = 3, corresponding to the theta/module/layer fields" << std::endl;
+ std::cout << "ERROR: will return empty neighbour map" << std::endl;
+ return neighbours;
+ }
+
+ // find index of layer, module and theta in field extremes vector
+ int idModuleField(-1);
+ int idThetaField(-1);
+ int idLayerField(-1);
+ for (uint itField = 0; itField < aFieldNames.size(); itField++) {
+ if (aFieldNames[itField] == aSeg.fieldNameModule())
+ idModuleField = (int) itField;
+ else if (aFieldNames[itField] == aSeg.fieldNameTheta())
+ idThetaField = (int) itField;
+ else if (aFieldNames[itField] == aSeg.fieldNameLayer())
+ idLayerField = (int) itField;
+
+ }
+ if (idModuleField < 0) {
+ std::cout << "WARNING: module field " << aSeg.fieldNameModule() << " not found in aFieldNames vector" << std::endl;
+ std::cout << "WARNING: will return empty neighbour map" << std::endl;
+ return neighbours;
+ }
+ if (idThetaField < 0) {
+ std::cout << "WARNING: theta field " << aSeg.fieldNameTheta() << " not found in aFieldNames vector" << std::endl;
+ std::cout << "WARNING: will return empty neighbour map" << std::endl;
+ return neighbours;
+ }
+ if (idLayerField < 0) {
+ std::cout << "WARNING: layer field " << aSeg.fieldNameLayer() << " not found in aFieldNames vector" << std::endl;
+ std::cout << "WARNING: will return empty neighbour map" << std::endl;
+ return neighbours;
+ }
+
+ // retrieve layer/module/theta of cell under study
+ int layer_id = aDecoder.get(aCellId, aFieldNames[idLayerField]);
+ int module_id = aDecoder.get(aCellId, aFieldNames[idModuleField]);
+ int theta_id = aDecoder.get(aCellId, aFieldNames[idThetaField]);
+
+ // now find the neighbours
+ dd4hep::DDSegmentation::CellID cID = aCellId;
+
+ // for neighbours across different layers, we have to take into
+ // account that merging along module and/or theta could be different
+ // so one cell in layer N could be neighbour to several in layer N+-1
+ // The cells are classified in a different way whether they are
+ // direct neighbours (common surface), diagonal neighbours (common edge or vertex)
+ // or neither.
+ // To decide this, we need to check how the cells are related in both directions:
+ // neighbours (edge at least partially in common), diagonal neigbours (common vertex),
+ // none
+ int neighbourTypeModuleDir; // 0: not neighbour; 1: diagonal neighbour; 2: neighbour in module direction
+ int neighbourTypeThetaDir; // 0: not neighbour; 1: diagonal neighbour; 2: neighbour in module direction
+
+ for (int deltaLayer = -1; deltaLayer<2; deltaLayer+=2) {
+
+ // no neighbours in layer N-1 for innermost layer
+ if (layer_id == aFieldExtremes[idLayerField].first && deltaLayer<0) continue;
+ // and in layer N+1 for outermost layer
+ if (layer_id == aFieldExtremes[idLayerField].second && deltaLayer>0) continue;
+
+ // set layer field of neighbour cell
+ aDecoder.set(cID, aSeg.fieldNameLayer(), layer_id + deltaLayer);
+
+ // find the neighbour(s) in module and theta
+ // if the numbers of module (theta) merged cells across 2 layers are the
+ // same then we just take the same module (theta) ID
+ // otherwise, we need to do some math to account for the different mergings
+ // note: even if number of merged cells in layer-1 is larger, a cell
+ // in layer could neighbour more than one cell in layer-1 if the merged
+ // cells are not aligned, for example if cells are grouped by 3 in a layer
+ // and by 4 in the next one, cell 435 in the former (which groups together
+ // 435-436-437) will be neighbour to cells 432 and 436 of the latter
+ // this might introduce duplicates, we will remove them later
+ // another issue is that it could add spurious cells beyond the maximum module number
+ // to prevent this we would need to know the max module number in layer -1
+ // which would require modifying this function passing the extrema for all layers
+ // instead of the extrema only for a certain layer
+ // this border effect is also present in the original method..
+ for (int i=-1; i <= aSeg.mergedThetaCells(layer_id); i++) {
+ int theta_id_neighbour = (theta_id + i) - ((theta_id + i) % aSeg.mergedThetaCells(layer_id+deltaLayer));
+ if (theta_id_neighbour >= theta_id && theta_id_neighbour < (theta_id + aSeg.mergedThetaCells(layer_id))) neighbourTypeThetaDir = 2;
+ else if (theta_id_neighbour < theta_id && theta_id_neighbour > (theta_id - aSeg.mergedThetaCells(layer_id+deltaLayer))) neighbourTypeThetaDir = 2;
+ else if (theta_id_neighbour == (theta_id + aSeg.mergedThetaCells(layer_id))) neighbourTypeThetaDir = 1;
+ else if (theta_id_neighbour == (theta_id - aSeg.mergedThetaCells(layer_id+deltaLayer))) neighbourTypeThetaDir = 1;
+ else neighbourTypeThetaDir = 0;
+
+ // if there is no point of contact along theta, no need to check also for module direction
+ if (neighbourTypeThetaDir == 0) continue;
+ // if we are not considering diagonal neighbours, and cells in theta have only an edge in common, then skip
+ if (!aDiagonal && neighbourTypeThetaDir == 1) continue;
+ // otherwise, check status along module direction
+ for (int j=-1; j <= aSeg.mergedModules(layer_id); j++) {
+ int module_id_neighbour = (module_id + j) - ((module_id + j) % aSeg.mergedModules(layer_id+deltaLayer));
+ int module_id_neighbour_cyclic = cyclicNeighbour(module_id_neighbour,
+ aFieldExtremes[idModuleField]
+ );
+
+ if (module_id_neighbour >= module_id && module_id_neighbour < (module_id + aSeg.mergedModules(layer_id))) neighbourTypeModuleDir = 2;
+ else if (module_id_neighbour < module_id && module_id_neighbour > (module_id - aSeg.mergedModules(layer_id+deltaLayer))) neighbourTypeModuleDir = 2;
+ else if (module_id_neighbour == (module_id + aSeg.mergedModules(layer_id))) neighbourTypeModuleDir = 1;
+ else if (module_id_neighbour == (module_id - aSeg.mergedModules(layer_id+deltaLayer))) neighbourTypeModuleDir = 1;
+ else neighbourTypeModuleDir = 0;
+
+ // if there is no point of contact along module, then skip
+ if (neighbourTypeModuleDir == 0) continue;
+ // otherwise: if neighbours along both module and theta, or along one of the two
+ // and we also consider diagonal neighbours, then add cells to list of neighbours
+ if ( (neighbourTypeModuleDir == 2 && neighbourTypeThetaDir==2) ||
+ (aDiagonal && neighbourTypeThetaDir > 0 && neighbourTypeModuleDir>0) ) {
+ aDecoder.set(cID, aSeg.fieldNameModule(), module_id_neighbour_cyclic);
+ aDecoder.set(cID, aSeg.fieldNameTheta(), theta_id_neighbour);
+ neighbours.emplace_back(cID);
+ }
+ }
+ }
+ }
+ // reset cellID
+ // aDecoder.set(cID, aSeg.fieldNameModule(), module_id);
+ // aDecoder.set(cID, aSeg.fieldNameTheta(), theta_id);
+
+ // for neighbours in module/theta direction at same layer_id, do +-nMergedCells instead of +-1
+ aDecoder.set(cID, aSeg.fieldNameLayer(), layer_id);
+ // loop over theta cells
+ for (int i=-1; i<=1; i++) {
+ // calculate theta_id of neighbour
+ int theta_id_neighbour = theta_id + i*aSeg.mergedThetaCells(layer_id);
+ // check that it is within the ranges
+ if (
+ (theta_id_neighbour < aFieldExtremes[idThetaField].first) ||
+ (theta_id_neighbour > aFieldExtremes[idThetaField].second)
+ ) continue;
+ // set theta_id of cell ID
+ aDecoder[aSeg.fieldNameTheta()].set(cID, theta_id + i*aSeg.mergedThetaCells(layer_id));
+ // loop over modules
+ for (int j=-1; j<=1; j++) {
+ // skip the cell under study (i==j==0)
+ if (i==0 && j==0) continue;
+ // calculate module_id of neighbour
+ int newid = cyclicNeighbour(module_id + j*aSeg.mergedModules(layer_id), aFieldExtremes[idModuleField]);
+ newid -= (newid % aSeg.mergedModules(layer_id));
+ // set module_if of cell ID
+ aDecoder[aSeg.fieldNameModule()].set(cID, newid);
+ // add cell to neighbour list
+ if ( i==0 || j==0 || aDiagonal ) { // first two conditions correspond to non diagonal neighbours
+ neighbours.emplace_back(cID);
+ }
+ }
+ }
+
+ // remove duplicates
+ std::unordered_set s;
+ for (uint64_t i : neighbours)
+ s.insert(i);
+ neighbours.assign( s.begin(), s.end() );
+ return neighbours;
+}
+
+std::vector> bitfieldExtremes(const dd4hep::DDSegmentation::BitFieldCoder& aDecoder,
+ const std::vector& aFieldNames) {
+ std::vector> extremes;
+ int width = 0;
+ for (const auto& field : aFieldNames) {
+ width = aDecoder[field].width();
+ if (aDecoder[field].isSigned()) {
+ extremes.emplace_back(std::make_pair(-(1 << (width - 1)), (1 << (width - 1)) - 1));
+ } else {
+ extremes.emplace_back(std::make_pair(0, (1 << width) - 1));
+ }
+ }
+ return extremes;
+}
+
+CLHEP::Hep3Vector envelopeDimensions(uint64_t aVolumeId) {
+ dd4hep::VolumeManager volMgr = dd4hep::Detector::getInstance().volumeManager();
+ auto pvol = volMgr.lookupVolumePlacement(aVolumeId);
+ auto solid = pvol.volume().solid();
+ // get the envelope of the shape
+ TGeoBBox* box = (dynamic_cast(solid.ptr()));
+ // get half-widths
+ return CLHEP::Hep3Vector(box->GetDX(), box->GetDY(), box->GetDZ());
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::CartesianGridXY& aSeg) {
+ // // get half-widths
+ auto halfSizes = envelopeDimensions(aVolumeId);
+ // get segmentation cell widths
+ double xCellSize = aSeg.gridSizeX();
+ double yCellSize = aSeg.gridSizeY();
+ // calculate number of cells, the middle cell is centred at 0 (no offset)
+ uint cellsX = ceil((halfSizes.x() - xCellSize / 2.) / xCellSize) * 2 + 1;
+ uint cellsY = ceil((halfSizes.y() - yCellSize / 2.) / yCellSize) * 2 + 1;
+ return {cellsX, cellsY};
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::CartesianGridXYZ& aSeg) {
+ // // get half-widths
+ auto halfSizes = envelopeDimensions(aVolumeId);
+ // get segmentation cell widths
+ double xCellSize = aSeg.gridSizeX();
+ double yCellSize = aSeg.gridSizeY();
+ double zCellSize = aSeg.gridSizeZ();
+ // calculate number of cells, the middle cell is centred at 0 (no offset)
+ uint cellsX = ceil((halfSizes.x() - xCellSize / 2.) / xCellSize) * 2 + 1;
+ uint cellsY = ceil((halfSizes.y() - yCellSize / 2.) / yCellSize) * 2 + 1;
+ uint cellsZ = ceil((halfSizes.z() - zCellSize / 2.) / zCellSize) * 2 + 1;
+ return {cellsX, cellsY, cellsZ};
+}
+
+CLHEP::Hep3Vector tubeDimensions(uint64_t aVolumeId) {
+ dd4hep::VolumeManager volMgr = dd4hep::Detector::getInstance().volumeManager();
+ auto pvol = volMgr.lookupVolumePlacement(aVolumeId);
+ auto solid = pvol.volume().solid();
+
+ // get the envelope of the shape
+ TGeoTubeSeg* tube = (dynamic_cast(solid.ptr()));
+ if (tube == nullptr) {
+ return CLHEP::Hep3Vector(0, 0, 0);
+ }
+ // get half-widths
+ return CLHEP::Hep3Vector(tube->GetRmin(), tube->GetRmax(), tube->GetDZ());
+}
+
+CLHEP::Hep3Vector coneDimensions(uint64_t aVolumeId) {
+ dd4hep::VolumeManager volMgr = dd4hep::Detector::getInstance().volumeManager();
+ auto pvol = volMgr.lookupVolumePlacement(aVolumeId);
+ auto solid = pvol.volume().solid();
+ // get the envelope of the shape
+ TGeoCone* cone = (dynamic_cast(solid.ptr()));
+ if (cone == nullptr) {
+ return CLHEP::Hep3Vector(0, 0, 0);
+ }
+ // get half-widths
+ return CLHEP::Hep3Vector(cone->GetRmin1(), cone->GetRmax1(), cone->GetDZ());
+}
+
+std::array tubeEtaExtremes(uint64_t aVolumeId) {
+ auto sizes = tubeDimensions(aVolumeId);
+ if (sizes.mag() == 0) {
+ // if it is not a cylinder maybe it is a cone (same calculation for extremes)
+ sizes = coneDimensions(aVolumeId);
+ if (sizes.mag() == 0) {
+ return {0, 0};
+ }
+ }
+ // eta segmentation calculate maximum eta from the inner radius (no offset is taken into account)
+ double maxEta = 0;
+ double minEta = 0;
+
+ double rIn = sizes.x();
+ double rOut = sizes.y();
+ double dZ = sizes.z();
+
+ // check if it is a cylinder centred at z=0
+ dd4hep::VolumeManager volMgr = dd4hep::Detector::getInstance().volumeManager();
+ auto detelement = volMgr.lookupDetElement(aVolumeId);
+ const auto& transformMatrix = detelement.nominal().worldTransformation();
+ double outGlobal[3];
+ double inLocal[] = {0, 0, 0}; // to get middle of the volume
+ transformMatrix.LocalToMaster(inLocal, outGlobal);
+ double zCenter = outGlobal[2];
+ if (fabs(zCenter) < 1e-10) {
+ // this assumes cylinder centred at z=0
+ maxEta = CLHEP::Hep3Vector(rIn, 0, dZ).eta();
+ minEta = -maxEta;
+ } else {
+ maxEta = std::max(
+ CLHEP::Hep3Vector(rIn, 0, zCenter+dZ).eta(),
+ CLHEP::Hep3Vector(rOut, 0, zCenter+dZ).eta()
+ );
+ minEta = std::min(
+ CLHEP::Hep3Vector(rIn, 0, zCenter-dZ).eta(),
+ CLHEP::Hep3Vector(rOut, 0, zCenter-dZ).eta()
+ );
+ }
+ return {minEta, maxEta};
+}
+
+std::array envelopeEtaExtremes (uint64_t aVolumeId) {
+ dd4hep::VolumeManager volMgr = dd4hep::Detector::getInstance().volumeManager();
+ auto detelement = volMgr.lookupDetElement(aVolumeId);
+ const auto& transformMatrix = detelement.nominal().worldTransformation();
+ // calculate values of eta in all possible corners of the envelope
+ auto dim = envelopeDimensions(aVolumeId);
+ double minEta = 0;
+ double maxEta = 0;
+ for (uint i = 0; i < 8; i++) {
+ // coefficients to get all combinations of corners
+ int iX = -1 + 2 * ((i / 2) % 2);
+ int iY = -1 + 2 * (i % 2);
+ int iZ = -1 + 2 * (i / 4);
+ double outDimGlobal[3];
+ double inDimLocal[] = {iX * dim.x(), iY * dim.y(), iZ * dim.z()};
+ transformMatrix.LocalToMaster(inDimLocal, outDimGlobal);
+ double eta = CLHEP::Hep3Vector(outDimGlobal[0], outDimGlobal[1], outDimGlobal[2]).eta();
+ if (i == 0) {
+ minEta = eta;
+ maxEta = eta;
+ }
+ if (eta < minEta) {
+ minEta = eta;
+ }
+ if (eta > maxEta) {
+ maxEta = eta;
+ }
+ }
+ return {minEta, maxEta};
+}
+
+std::array volumeEtaExtremes(uint64_t aVolumeId) {
+ // try if volume is a cylinder/disc
+ auto etaExtremes = tubeEtaExtremes(aVolumeId);
+ if (etaExtremes[0] != 0 or etaExtremes[1] != 0) {
+ return etaExtremes;
+ } else {
+ return envelopeEtaExtremes(aVolumeId);
+ }
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridPhiEta_k4geo& aSeg) {
+ // get segmentation number of bins in phi
+ uint phiCellNumber = aSeg.phiBins();
+ // get segmentation cell width in eta
+ double etaCellSize = aSeg.gridSizeEta();
+ // get min and max eta of the volume
+ auto etaExtremes = volumeEtaExtremes(aVolumeId);
+ // calculate the number of eta volumes
+ // max - min = full eta range, - size = not counting the middle cell centred at 0, + 1 to account for that cell
+ uint cellsEta = ceil(( etaExtremes[1] - etaExtremes[0] - etaCellSize ) / 2 / etaCellSize) * 2 + 1;
+ uint minEtaID = int(floor((etaExtremes[0] + 0.5 * etaCellSize - aSeg.offsetEta()) / etaCellSize));
+ return {phiCellNumber, cellsEta, minEtaID};
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridPhiTheta_k4geo& aSeg) {
+ uint phiCellNumber = aSeg.phiBins();
+ double thetaCellSize = aSeg.gridSizeTheta();
+ double thetaOffset = aSeg.offsetTheta();
+ auto etaExtremes = volumeEtaExtremes(aVolumeId);
+ double thetaMin = 2.*atan(exp(-etaExtremes[1]));
+ double thetaMax = 2.*atan(exp(-etaExtremes[0]));
+ // debug
+ std::cout << "volumeID = " << aVolumeId << std::endl;
+ std::cout << "thetaMin, thetaMax = " << thetaMin << " " << thetaMax << std::endl;
+ //
+ uint minThetaID = int(floor((thetaMin + 0.5 * thetaCellSize - thetaOffset) / thetaCellSize));
+ uint maxThetaID = int(floor((thetaMax + 0.5 * thetaCellSize - thetaOffset) / thetaCellSize));
+ uint nThetaCells = 1 + maxThetaID - minThetaID;
+ return {phiCellNumber, nThetaCells, minThetaID};
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::FCCSWGridModuleThetaMerged_k4geo& aSeg) {
+
+ const dd4hep::DDSegmentation::BitFieldCoder* aDecoder = aSeg.decoder();
+ int nLayer = aDecoder->get(aVolumeId, aSeg.fieldNameLayer());
+ // + 0.5 to avoid integer division
+ uint nModules = aSeg.nModules() / aSeg.mergedModules(nLayer);
+ if (aSeg.nModules() % aSeg.mergedModules(nLayer) != 0) nModules++;
+ // get minimum and maximum theta of volume
+ auto etaExtremes = volumeEtaExtremes(aVolumeId);
+ double thetaMin = 2.*atan(exp(-etaExtremes[1]));
+ double thetaMax = 2.*atan(exp(-etaExtremes[0]));
+
+ // convert to minimum and maximum theta bins
+ double thetaCellSize = aSeg.gridSizeTheta();
+ double thetaOffset = aSeg.offsetTheta();
+ uint minThetaID = int(floor((thetaMin + 0.5 * thetaCellSize - thetaOffset) / thetaCellSize));
+ uint maxThetaID = int(floor((thetaMax + 0.5 * thetaCellSize - thetaOffset) / thetaCellSize));
+ // correct minThetaID and maxThetaID for merging
+ uint mergedThetaCells = aSeg.mergedThetaCells(nLayer);
+ minThetaID -= (minThetaID % mergedThetaCells);
+ maxThetaID -= (maxThetaID % mergedThetaCells);
+ uint nThetaCells = 1 + (maxThetaID - minThetaID)/ mergedThetaCells;
+ return {nModules, nThetaCells, minThetaID};
+}
+
+std::array numberOfCells(uint64_t aVolumeId, const dd4hep::DDSegmentation::PolarGridRPhi& aSeg) {
+ // get half-widths,
+ auto tubeSizes = tubeDimensions(aVolumeId);
+ // get segmentation cell width
+ double rCellSize = aSeg.gridSizeR();
+ double phiCellSize = aSeg.gridSizePhi();
+ uint cellsRout = ceil(tubeSizes.y() / rCellSize);
+ uint cellsRin = floor(tubeSizes.x() / rCellSize);
+ uint cellsR = cellsRout - cellsRin;
+ uint cellsPhi = ceil(2 * M_PI / phiCellSize);
+ return {cellsR, cellsPhi};
+}
+
+unsigned int countPlacedVolumes(TGeoVolume* aHighestVolume, const std::string& aMatchName) {
+ int numberOfPlacedVolumes = 0;
+ TGeoNode* node;
+ TGeoIterator next(aHighestVolume);
+ while ((node = next())) {
+ std::string currentNodeName = node->GetName();
+ if (currentNodeName.find(aMatchName) != std::string::npos) {
+ ++numberOfPlacedVolumes;
+ }
+ }
+ return numberOfPlacedVolumes;
+}
+}
+}
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMergedHandle_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMergedHandle_k4geo.h
index c0b79ea89..cdbb30223 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMergedHandle_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMergedHandle_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DD4HEP_DDCORE_GRIDMODULETHETAMERGED_H
-#define DD4HEP_DDCORE_GRIDMODULETHETAMERGED_H 1
+#ifndef DETECTORSEGMENTATIONS_FCCSWGRIDMODULETHETAMERGEDHANDLE_K4GEO_H
+#define DETECTORSEGMENTATIONS_FCCSWGRIDMODULETHETAMERGEDHANDLE_K4GEO_H
// FCCSW
#include "detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h
index 796ba400d..98fb122ca 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridModuleThetaMerged_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DETSEGMENTATION_FCCSWGridModuleThetaMerged_k4geo_H
-#define DETSEGMENTATION_FCCSWGridModuleThetaMerged_k4geo_H
+#ifndef DETECTORSEGMENTATIONS_FCCSWGridModuleThetaMerged_k4geo_H
+#define DETECTORSEGMENTATIONS_FCCSWGridModuleThetaMerged_k4geo_H
// FCCSW
#include "detectorSegmentations/GridTheta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEtaHandle_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEtaHandle_k4geo.h
index cd53e01fa..286c97f72 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEtaHandle_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEtaHandle_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DD4HEP_DDCORE_GRIDPHIETA_H
-#define DD4HEP_DDCORE_GRIDPHIETA_H 1
+#ifndef DETECTORSEGMENTATIONS_GRIDPHIETAHANDLE_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDPHIETAHANDLE_K4GEO_H
// FCCSW
#include "detectorSegmentations/FCCSWGridPhiEta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEta_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEta_k4geo.h
index 7f80185a3..8c4336262 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEta_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiEta_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DETSEGMENTATION_GRIDPHIETA_H
-#define DETSEGMENTATION_GRIDPHIETA_H
+#ifndef DETECTORSEGMENTATIONS_GRIDPHIETA_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDPHIETA_K4GEO_H
// FCCSW
#include "detectorSegmentations/GridEta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiThetaHandle_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiThetaHandle_k4geo.h
index efbfbf8da..a5546a846 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiThetaHandle_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiThetaHandle_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DD4HEP_DDCORE_GRIDPHITHETA_H
-#define DD4HEP_DDCORE_GRIDPHITHETA_H 1
+#ifndef DETECTORSEGMENTATIONS_GRIDPHITHETAHANDLE_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDPHITHETAHANDLE_K4GEO_H
// FCCSW
#include "detectorSegmentations/FCCSWGridPhiTheta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiTheta_k4geo.h b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiTheta_k4geo.h
index cc1d0c31d..aae4bab10 100644
--- a/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiTheta_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/FCCSWGridPhiTheta_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DETSEGMENTATION_GRIDPHITHETA_H
-#define DETSEGMENTATION_GRIDPHITHETA_H
+#ifndef DETECTORSEGMENTATIONS_GRIDPHITHETA_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDPHITHETA_K4GEO_H
// FCCSW
#include "detectorSegmentations/GridTheta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/GridEtaHandle_k4geo.h b/detectorSegmentations/include/detectorSegmentations/GridEtaHandle_k4geo.h
index 961f46025..787c53e23 100644
--- a/detectorSegmentations/include/detectorSegmentations/GridEtaHandle_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/GridEtaHandle_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DD4HEP_DDCORE_GRIDETA_H
-#define DD4HEP_DDCORE_GRIDETA_H 1
+#ifndef DETECTORSEGMENTATIONS_GRIDETAHANDLE_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDETAHANDLE_K4GEO_H
// FCCSW
#include "detectorSegmentations/GridEta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/GridEta_k4geo.h b/detectorSegmentations/include/detectorSegmentations/GridEta_k4geo.h
index 29cd8dabd..ec4270f79 100644
--- a/detectorSegmentations/include/detectorSegmentations/GridEta_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/GridEta_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DETSEGMENTATION_GRIDETA_H
-#define DETSEGMENTATION_GRIDETA_H
+#ifndef DETECTORSEGMENTATIONS_GRIDETA_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDETA_K4GEO_H
#include "DDSegmentation/Segmentation.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/GridThetaHandle_k4geo.h b/detectorSegmentations/include/detectorSegmentations/GridThetaHandle_k4geo.h
index 923406665..9f97d1b63 100644
--- a/detectorSegmentations/include/detectorSegmentations/GridThetaHandle_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/GridThetaHandle_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DD4HEP_DDCORE_GRIDTHETA_H
-#define DD4HEP_DDCORE_GRIDTHETA_H 1
+#ifndef DETECTORSEGMENTATIONS_GRIDTHETAHANDLE_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDTHETAHANDLE_K4GEO_H
// FCCSW
#include "detectorSegmentations/GridTheta_k4geo.h"
diff --git a/detectorSegmentations/include/detectorSegmentations/GridTheta_k4geo.h b/detectorSegmentations/include/detectorSegmentations/GridTheta_k4geo.h
index 84236c9db..0198e9c7b 100644
--- a/detectorSegmentations/include/detectorSegmentations/GridTheta_k4geo.h
+++ b/detectorSegmentations/include/detectorSegmentations/GridTheta_k4geo.h
@@ -1,5 +1,5 @@
-#ifndef DETSEGMENTATION_GRIDTHETA_H
-#define DETSEGMENTATION_GRIDTHETA_H
+#ifndef DETECTORSEGMENTATIONS_GRIDTHETA_K4GEO_H
+#define DETECTORSEGMENTATIONS_GRIDTHETA_K4GEO_H
#include "DDSegmentation/Segmentation.h"
diff --git a/lcgeoTests/CMakeLists.txt b/lcgeoTests/CMakeLists.txt
index 635a52c7c..909fbdee6 100644
--- a/lcgeoTests/CMakeLists.txt
+++ b/lcgeoTests/CMakeLists.txt
@@ -86,6 +86,13 @@ ADD_TEST( t_${test_name} "${CMAKE_INSTALL_PREFIX}/bin/run_test_${PackageName}.sh
ddsim --compactFile=${CMAKE_CURRENT_SOURCE_DIR}/../FCCee/IDEA/compact/IDEA_o1_v02/IDEA_o1_v02.xml --runType=batch -G -N=1 --outputFile=testIDEA_o1_v02.slcio )
SET_TESTS_PROPERTIES( t_${test_name} PROPERTIES FAIL_REGULAR_EXPRESSION "Exception;EXCEPTION;ERROR;Error" TIMEOUT 240)
+#--------------------------------------------------
+# test for ALLEGRO o1 v02
+SET( test_name "test_ALLEGRO_o1_v02" )
+ADD_TEST( t_${test_name} "${CMAKE_INSTALL_PREFIX}/bin/run_test_${PackageName}.sh"
+ ddsim --compactFile=${CMAKE_CURRENT_SOURCE_DIR}/../FCCee/ALLEGRO/compact/ALLEGRO_o1_v02/ALLEGRO_o1_v02.xml --runType=batch -G -N=1 --outputFile=testALLEGRO_o1_v02.root )
+SET_TESTS_PROPERTIES( t_${test_name} PROPERTIES FAIL_REGULAR_EXPRESSION "Exception;EXCEPTION;ERROR;Error" )
+
#--------------------------------------------------
# test for ARC o1 v01
SET( test_name "test_ARC_o1_v01_run" )