diff --git a/CMakeLists.txt b/CMakeLists.txt index 112e3b5..3c11c60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ project( LANGUAGES CXX ) -# Set C++20 standard -set(CMAKE_CXX_STANDARD 20) +# C++23: required by Phlex +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/subsystems/Calorimeter/include/Calorimeter/CaloBarLayer.h b/subsystems/Calorimeter/include/Calorimeter/CaloBarLayer.h index 8dd3207..e04bb46 100644 --- a/subsystems/Calorimeter/include/Calorimeter/CaloBarLayer.h +++ b/subsystems/Calorimeter/include/Calorimeter/CaloBarLayer.h @@ -4,6 +4,7 @@ #pragma once #include +#include class GeoVPhysVol; class GeoLogVol; @@ -20,11 +21,12 @@ enum class BarAxis { AlongX, AlongY }; * centre-to-centre, centred on the mother origin in the transverse plane, * and all placed at @p zCenter_mm along Z (local coordinates). */ -class CaloBarLayer { - public: - static void place(GeoVPhysVol* mother, GeoLogVol* barLog, double pitch_mm, int nBars, - double zCenter_mm, const char* tagPrefix, int layerIndex, BarAxis axis, - const std::string& nameSuffix = ""); -}; +namespace CaloBar { + +void placeLayer(GeoVPhysVol* mother, GeoLogVol* barLog, double pitch_mm, int nBars, + double zCenter_mm, std::string_view tagPrefix, int layerIndex, BarAxis axis, + const std::string& nameSuffix = ""); + +} // namespace CaloBar } // namespace SHiPGeometry diff --git a/subsystems/Calorimeter/include/Calorimeter/CaloFibreHPLayer.h b/subsystems/Calorimeter/include/Calorimeter/CaloFibreHPLayer.h index 46a35b9..5e7cbad 100644 --- a/subsystems/Calorimeter/include/Calorimeter/CaloFibreHPLayer.h +++ b/subsystems/Calorimeter/include/Calorimeter/CaloFibreHPLayer.h @@ -17,12 +17,13 @@ namespace SHiPGeometry { * cylindrical fibres (cladding + core). Fibres run along Y when * @p fibresAlongY is true, along X otherwise. */ -class CaloFibreHPLayer { - public: - static void build(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, GeoMaterial* fibreMat, - const std::string& layerTag, double zCenter_mm, int layerIndex, - double casingXY_mm, double casingZ_mm, double fiberDiam_mm, - double fiberCoreDiam_mm, bool fibresAlongY, const std::string& nameSuffix); -}; +namespace CaloFibreHP { + +void buildLayer(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, GeoMaterial* fibreMat, + const std::string& layerTag, double zCenter_mm, int layerIndex, double casingXY_mm, + double casingZ_mm, double fiberDiam_mm, double fiberCoreDiam_mm, bool fibresAlongY, + const std::string& nameSuffix); + +} // namespace CaloFibreHP } // namespace SHiPGeometry diff --git a/subsystems/Calorimeter/include/Calorimeter/CalorimeterConfig.h b/subsystems/Calorimeter/include/Calorimeter/CalorimeterConfig.h index a6d102f..177439a 100644 --- a/subsystems/Calorimeter/include/Calorimeter/CalorimeterConfig.h +++ b/subsystems/Calorimeter/include/Calorimeter/CalorimeterConfig.h @@ -8,6 +8,18 @@ namespace SHiPGeometry { +/// Layer type codes used in the ECAL/HCAL layer sequences. +enum class LayerCode : int { + WidePVT_H = 1, ///< Wide PVT bar layer, bars along X (H orientation) + WidePVT_V = 2, ///< Wide PVT bar layer, bars along Y (V orientation) + ThinPS_H = 3, ///< Thin PS bar layer, bars along X (H orientation) + ThinPS_V = 4, ///< Thin PS bar layer, bars along Y (V orientation) + FibreHPL_Y = 5, ///< HPL fibre layer, fibres along Y + FibreHPL_X = 6, ///< HPL fibre layer, fibres along X + Absorber = 7, ///< Absorber plate (Lead in ECAL, Iron in HCAL) + AirGap = 8, ///< Air gap (no volume, just advances z cursor) +}; + /** * @brief Configuration for the SHiP calorimeter geometry. * diff --git a/subsystems/Calorimeter/include/Calorimeter/CalorimeterFactory.h b/subsystems/Calorimeter/include/Calorimeter/CalorimeterFactory.h index fd39aab..e5cb6f6 100644 --- a/subsystems/Calorimeter/include/Calorimeter/CalorimeterFactory.h +++ b/subsystems/Calorimeter/include/Calorimeter/CalorimeterFactory.h @@ -49,7 +49,7 @@ class CalorimeterFactory { std::string m_configPath; /** Place one NX×NY tiled stack of layers inside @p container. */ - void buildStack(GeoPhysVol* container, const CalorimeterConfig& cfg, int mx, int my, + void buildStack(GeoPhysVol* container, const CalorimeterConfig& cfg, int moduleX, int moduleY, double offsetX, double offsetY) const; // ── Fixed container dimensions (mm) ───────────────────────────────── diff --git a/subsystems/Calorimeter/src/CaloBarLayer.cpp b/subsystems/Calorimeter/src/CaloBarLayer.cpp index fb49757..885cbcc 100644 --- a/subsystems/Calorimeter/src/CaloBarLayer.cpp +++ b/subsystems/Calorimeter/src/CaloBarLayer.cpp @@ -9,29 +9,29 @@ #include #include +#include #include namespace SHiPGeometry { using namespace GeoModelKernelUnits; -void CaloBarLayer::place(GeoVPhysVol* mother, GeoLogVol* barLog, double pitch_mm, int nBars, - double zCenter_mm, const char* tagPrefix, int layerIndex, BarAxis axis, - const std::string& nameSuffix) { +void CaloBar::placeLayer(GeoVPhysVol* mother, GeoLogVol* barLog, double pitch_mm, int nBars, + double zCenter_mm, std::string_view tagPrefix, int layerIndex, + BarAxis axis, const std::string& nameSuffix) { const double pitch = pitch_mm * mm; - const double s0 = -0.5 * (nBars - 1) * pitch; + const double firstBarCenter = -0.5 * (nBars - 1) * pitch; for (int i = 0; i < nBars; ++i) { - const double s = s0 + i * pitch; + const double barCenter = firstBarCenter + i * pitch; double x = 0.0, y = 0.0; if (axis == BarAxis::AlongX) - x = s; + x = barCenter; else - y = s; + y = barCenter; - const std::string name = std::string(tagPrefix) + "_L" + std::to_string(layerIndex) + "_B" + - std::to_string(i) + nameSuffix; + const auto name = std::format("{}_L{}_B{}{}", tagPrefix, layerIndex, i, nameSuffix); auto* barPhys = new GeoPhysVol(barLog); mother->add(new GeoNameTag(name.c_str())); diff --git a/subsystems/Calorimeter/src/CaloFibreHPLayer.cpp b/subsystems/Calorimeter/src/CaloFibreHPLayer.cpp index a89024b..b61b9bb 100644 --- a/subsystems/Calorimeter/src/CaloFibreHPLayer.cpp +++ b/subsystems/Calorimeter/src/CaloFibreHPLayer.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -20,7 +21,7 @@ namespace SHiPGeometry { using namespace GeoModelKernelUnits; -void CaloFibreHPLayer::build(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, GeoMaterial* fibreMat, +void CaloFibreHP::buildLayer(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, GeoMaterial* fibreMat, const std::string& layerTag, double zCenter_mm, int layerIndex, double casingXY_mm, double casingZ_mm, double fiberDiam_mm, double fiberCoreDiam_mm, bool fibresAlongY, @@ -43,7 +44,7 @@ void CaloFibreHPLayer::build(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, Geo if (rCore <= 0.0 || rCore > rOuter) throw std::runtime_error( - "CaloFibreHPLayer: invalid core diameter " + "CaloFibreHP::buildLayer: invalid core diameter " "(must be >0 and <= outer diameter)"); // GeoTube axis is Z; rotate so fibres run along Y or X @@ -70,25 +71,24 @@ void CaloFibreHPLayer::build(GeoVPhysVol* mother, GeoMaterial* aluminiumMat, Geo const std::string orient = fibresAlongY ? "V_L" : "H_L"; - for (int s = 0; s < 3; ++s) { + for (int sublayer = 0; sublayer < 3; ++sublayer) { for (int i = 0; i < nFib; ++i) { - const double pack = x0 + i * pitch + dx[s]; - const double zl = zLocal[s]; + const double fibrePosition = x0 + i * pitch + dx[sublayer]; + const double sublayerZ = zLocal[sublayer]; - const std::string baseName = layerTag + "_HPL_" + orient + std::to_string(layerIndex) + - "_S" + std::to_string(s) + "_F" + std::to_string(i) + - nameSuffix; + const auto baseName = std::format("{}_HPL_{}{}_S{}_F{}{}", layerTag, orient, layerIndex, + sublayer, i, nameSuffix); // cladding physical volume; core is a child of it auto* cladPhys = new GeoPhysVol(cladLog); - cladPhys->add(new GeoNameTag((baseName + "_Core").c_str())); + cladPhys->add(new GeoNameTag(std::format("{}_Core", baseName).c_str())); cladPhys->add(new GeoPhysVol(coreLog)); - const double xPos = fibresAlongY ? pack : 0.0; - const double yPos = fibresAlongY ? 0.0 : pack; + const double xPos = fibresAlongY ? fibrePosition : 0.0; + const double yPos = fibresAlongY ? 0.0 : fibrePosition; - casingPhys->add(new GeoNameTag((baseName + "_Clad").c_str())); - casingPhys->add(new GeoTransform(GeoTrf::Translate3D(xPos, yPos, zl) * rotAxis)); + casingPhys->add(new GeoNameTag(std::format("{}_Clad", baseName).c_str())); + casingPhys->add(new GeoTransform(GeoTrf::Translate3D(xPos, yPos, sublayerZ) * rotAxis)); casingPhys->add(cladPhys); } } diff --git a/subsystems/Calorimeter/src/CalorimeterConfig.cpp b/subsystems/Calorimeter/src/CalorimeterConfig.cpp index 080c178..783e45e 100644 --- a/subsystems/Calorimeter/src/CalorimeterConfig.cpp +++ b/subsystems/Calorimeter/src/CalorimeterConfig.cpp @@ -26,41 +26,72 @@ #include "Calorimeter/CalorimeterConfig.h" +#include +#include #include -#include #include #include #include +#include #include namespace SHiPGeometry { namespace { -// Recognised top-level keys. Anything outside this set triggers a warning. -const std::set kKnownKeys = { - "layers", - "layers2", - "plate_xy_mm", - "lead_thickness_mm", - "scint_thickness_mm", - "hpl_thickness_mm", - "fiber_diameter_mm", - "fiber_core_diameter_mm", - "airgap_mm", - "iron_thickness_mm", - "gap_ecal_hcal_mm", - "module_nx", - "module_ny", - "module_pitch_x_mm", - "module_pitch_y_mm", - "tol_x_mm", - "tol_y_mm", - "tol_z_mm", - "detector_offset_x_mm", - "detector_offset_y_mm", - "detector_offset_z_mm", - "center_stack", +using namespace std::string_view_literals; + +// Recognised top-level keys (sorted for binary search). Anything outside this +// set triggers a warning. +static constexpr std::array kKnownKeys = { + "airgap_mm"sv, + "center_stack"sv, + "detector_offset_x_mm"sv, + "detector_offset_y_mm"sv, + "detector_offset_z_mm"sv, + "fiber_core_diameter_mm"sv, + "fiber_diameter_mm"sv, + "gap_ecal_hcal_mm"sv, + "hpl_thickness_mm"sv, + "iron_thickness_mm"sv, + "layers"sv, + "layers2"sv, + "lead_thickness_mm"sv, + "module_nx"sv, + "module_ny"sv, + "module_pitch_x_mm"sv, + "module_pitch_y_mm"sv, + "plate_xy_mm"sv, + "scint_thickness_mm"sv, + "tol_x_mm"sv, + "tol_y_mm"sv, + "tol_z_mm"sv, +}; + +// Mapping from TOML key name to CalorimeterConfig double member pointer. +struct NumericField { + const char* key; + double CalorimeterConfig::* member; +}; + +static constexpr NumericField kNumericFields[] = { + {"plate_xy_mm", &CalorimeterConfig::plate_xy_mm}, + {"lead_thickness_mm", &CalorimeterConfig::lead_thickness_mm}, + {"scint_thickness_mm", &CalorimeterConfig::scint_thickness_mm}, + {"hpl_thickness_mm", &CalorimeterConfig::hpl_thickness_mm}, + {"fiber_diameter_mm", &CalorimeterConfig::fiber_diameter_mm}, + {"fiber_core_diameter_mm", &CalorimeterConfig::fiber_core_diameter_mm}, + {"airgap_mm", &CalorimeterConfig::airgap_mm}, + {"iron_thickness_mm", &CalorimeterConfig::iron_thickness_mm}, + {"gap_ecal_hcal_mm", &CalorimeterConfig::gap_ecal_hcal_mm}, + {"module_pitch_x_mm", &CalorimeterConfig::module_pitch_x_mm}, + {"module_pitch_y_mm", &CalorimeterConfig::module_pitch_y_mm}, + {"tol_x_mm", &CalorimeterConfig::tol_x_mm}, + {"tol_y_mm", &CalorimeterConfig::tol_y_mm}, + {"tol_z_mm", &CalorimeterConfig::tol_z_mm}, + {"detector_offset_x_mm", &CalorimeterConfig::detector_offset_x_mm}, + {"detector_offset_y_mm", &CalorimeterConfig::detector_offset_y_mm}, + {"detector_offset_z_mm", &CalorimeterConfig::detector_offset_z_mm}, }; // Read an integer-list value: either a TOML array of ints, or a string @@ -79,7 +110,6 @@ std::vector readIntList(const toml::node_view& node, const std: std::stringstream ss(*s); std::string token; while (std::getline(ss, token, ',')) { - // strip whitespace auto first = token.find_first_not_of(" \t\r\n"); auto last = token.find_last_not_of(" \t\r\n;"); if (first == std::string::npos) @@ -102,6 +132,15 @@ double readNumeric(const toml::node_view& node, const std::string& k throw std::runtime_error("CalorimeterConfig: '" + key + "' must be a number"); } +// Read a positive integer from TOML, validating range. +int readPositiveInt(const toml::node_view& node, const char* key) { + auto i = node.value(); + if (!i || *i <= 0 || *i > std::numeric_limits::max()) + throw std::runtime_error(std::string("CalorimeterConfig: '") + key + + "' must be a positive integer"); + return static_cast(*i); +} + } // namespace CalorimeterConfig readCaloConfig(const std::string& path) { @@ -117,79 +156,35 @@ CalorimeterConfig readCaloConfig(const std::string& path) { // First pass: warn about unknown keys. for (const auto& [k, _] : table) { - const std::string key{k}; - if (!kKnownKeys.count(key)) { - std::cerr << "CalorimeterConfig: warning: unknown key '" << key << "' in " << path + if (!std::ranges::binary_search(kKnownKeys, std::string_view{k})) { + std::cerr << "CalorimeterConfig: warning: unknown key '" << k << "' in " << path << " (typo? stale field? — value will be ignored)\n"; } } - // Second pass: read each known key if present. + // Read layer sequences. if (auto n = table["layers"]; n) cfg.layers = readIntList(n, "layers"); if (auto n = table["layers2"]; n) cfg.layers2 = readIntList(n, "layers2"); - if (auto n = table["plate_xy_mm"]; n) - cfg.plate_xy_mm = readNumeric(n, "plate_xy_mm"); - if (auto n = table["lead_thickness_mm"]; n) - cfg.lead_thickness_mm = readNumeric(n, "lead_thickness_mm"); - if (auto n = table["scint_thickness_mm"]; n) - cfg.scint_thickness_mm = readNumeric(n, "scint_thickness_mm"); - if (auto n = table["hpl_thickness_mm"]; n) - cfg.hpl_thickness_mm = readNumeric(n, "hpl_thickness_mm"); - if (auto n = table["fiber_diameter_mm"]; n) - cfg.fiber_diameter_mm = readNumeric(n, "fiber_diameter_mm"); - if (auto n = table["fiber_core_diameter_mm"]; n) - cfg.fiber_core_diameter_mm = readNumeric(n, "fiber_core_diameter_mm"); - if (auto n = table["airgap_mm"]; n) - cfg.airgap_mm = readNumeric(n, "airgap_mm"); - if (auto n = table["iron_thickness_mm"]; n) - cfg.iron_thickness_mm = readNumeric(n, "iron_thickness_mm"); - if (auto n = table["gap_ecal_hcal_mm"]; n) - cfg.gap_ecal_hcal_mm = readNumeric(n, "gap_ecal_hcal_mm"); - - if (auto n = table["module_nx"]; n) { - auto i = n.value(); - if (!i || *i <= 0 || *i > std::numeric_limits::max()) - throw std::runtime_error("CalorimeterConfig: 'module_nx' must be a positive integer"); - cfg.module_nx = static_cast(*i); - } - if (auto n = table["module_ny"]; n) { - auto i = n.value(); - if (!i || *i <= 0 || *i > std::numeric_limits::max()) - throw std::runtime_error("CalorimeterConfig: 'module_ny' must be a positive integer"); - cfg.module_ny = static_cast(*i); - } + // Read all numeric (double) fields via pointer-to-member table. + for (const auto& [key, member] : kNumericFields) + if (auto n = table[key]; n) + cfg.*member = readNumeric(n, key); - if (auto n = table["module_pitch_x_mm"]; n) - cfg.module_pitch_x_mm = readNumeric(n, "module_pitch_x_mm"); - if (auto n = table["module_pitch_y_mm"]; n) - cfg.module_pitch_y_mm = readNumeric(n, "module_pitch_y_mm"); - - if (auto n = table["tol_x_mm"]; n) - cfg.tol_x_mm = readNumeric(n, "tol_x_mm"); - if (auto n = table["tol_y_mm"]; n) - cfg.tol_y_mm = readNumeric(n, "tol_y_mm"); - if (auto n = table["tol_z_mm"]; n) - cfg.tol_z_mm = readNumeric(n, "tol_z_mm"); - - if (auto n = table["detector_offset_x_mm"]; n) - cfg.detector_offset_x_mm = readNumeric(n, "detector_offset_x_mm"); - if (auto n = table["detector_offset_y_mm"]; n) - cfg.detector_offset_y_mm = readNumeric(n, "detector_offset_y_mm"); - if (auto n = table["detector_offset_z_mm"]; n) - cfg.detector_offset_z_mm = readNumeric(n, "detector_offset_z_mm"); + // Integer fields requiring positive-value validation. + if (auto n = table["module_nx"]; n) + cfg.module_nx = readPositiveInt(n, "module_nx"); + if (auto n = table["module_ny"]; n) + cfg.module_ny = readPositiveInt(n, "module_ny"); + // Boolean field — TOML native bool or integer fallback. if (auto n = table["center_stack"]; n) { - if (auto b = n.value(); b) { + if (auto b = n.value(); b) cfg.center_stack = *b; - } else if (auto i = n.value(); i) { + else if (auto i = n.value(); i) cfg.center_stack = (*i != 0); - } else if (auto s = n.value(); s) { - const std::string& v = *s; - cfg.center_stack = (v == "1" || v == "true" || v == "yes" || v == "on"); - } } if (cfg.layers.empty()) diff --git a/subsystems/Calorimeter/src/CalorimeterFactory.cpp b/subsystems/Calorimeter/src/CalorimeterFactory.cpp index 79ca77b..1d26701 100644 --- a/subsystems/Calorimeter/src/CalorimeterFactory.cpp +++ b/subsystems/Calorimeter/src/CalorimeterFactory.cpp @@ -17,7 +17,9 @@ #include #include -#include +#include +#include +#include #include #include @@ -35,18 +37,22 @@ namespace SHiPGeometry { using namespace GeoModelKernelUnits; +// Bar pitch (physical constant, independent of plate size) +static constexpr double kWidePVTBarPitch_mm = 60.0; +static constexpr double kThinPSBarPitch_mm = 10.0; + // ── file-scope helper ──────────────────────────────────────────────────────── static std::string resolveTomlPath(const std::string& path) { if (!path.empty() && path[0] == '/') return path; // already absolute - if (std::ifstream(path).good()) + if (std::filesystem::exists(path)) return path; // found relative to CWD const std::string srcFallback = CALO_TOML_DEFAULT_PATH; - if (std::ifstream(srcFallback).good()) + if (std::filesystem::exists(srcFallback)) return srcFallback; // source-tree fallback const std::string installFallback = CALO_TOML_INSTALL_PATH; - if (!installFallback.empty() && std::ifstream(installFallback).good()) + if (!installFallback.empty() && std::filesystem::exists(installFallback)) return installFallback; // installed data dir return path; // give up — readCaloConfig will emit the error } @@ -63,35 +69,35 @@ std::string CalorimeterFactory::resolvedConfigPath() const { // ── totalStackZ ────────────────────────────────────────────────────────────── double CalorimeterFactory::totalStackZ(const CalorimeterConfig& cfg) { - double total = 0.0; - for (int code : cfg.layers) { - if (code == 7) - total += cfg.lead_thickness_mm; - else if (code >= 1 && code <= 4) - total += cfg.scint_thickness_mm; - else if (code == 5 || code == 6) - total += cfg.hpl_thickness_mm; - else if (code == 8) - total += cfg.airgap_mm; - else - throw std::runtime_error("CalorimeterFactory: unknown layer code in 'layers': " + - std::to_string(code)); - } - total += cfg.gap_ecal_hcal_mm; - for (int code : cfg.layers2) { - if (code == 7) - total += cfg.iron_thickness_mm; - else if (code >= 1 && code <= 4) - total += cfg.scint_thickness_mm; - else if (code == 5 || code == 6) - total += cfg.hpl_thickness_mm; - else if (code == 8) - total += cfg.airgap_mm; - else - throw std::runtime_error("CalorimeterFactory: unknown layer code in 'layers2': " + - std::to_string(code)); - } - return total; + auto sectionZ = [&](const std::vector& codes, double absorberThickness) { + double z = 0.0; + for (int code : codes) { + switch (static_cast(code)) { + case LayerCode::Absorber: + z += absorberThickness; + break; + case LayerCode::WidePVT_H: + case LayerCode::WidePVT_V: + case LayerCode::ThinPS_H: + case LayerCode::ThinPS_V: + z += cfg.scint_thickness_mm; + break; + case LayerCode::FibreHPL_Y: + case LayerCode::FibreHPL_X: + z += cfg.hpl_thickness_mm; + break; + case LayerCode::AirGap: + z += cfg.airgap_mm; + break; + default: + throw std::runtime_error( + std::format("CalorimeterFactory: unknown layer code: {}", code)); + } + } + return z; + }; + return sectionZ(cfg.layers, cfg.lead_thickness_mm) + cfg.gap_ecal_hcal_mm + + sectionZ(cfg.layers2, cfg.iron_thickness_mm); } // ── build ──────────────────────────────────────────────────────────────────── @@ -116,20 +122,20 @@ GeoPhysVol* CalorimeterFactory::build() { const double maxHalfX = 0.5 * cfg.plate_xy_mm + 0.5 * (cfg.module_nx - 1) * pitchX; const double maxHalfY = 0.5 * cfg.plate_xy_mm + 0.5 * (cfg.module_ny - 1) * pitchY; if (stackZ > 2.0 * s_containerHalfZ) - throw std::runtime_error("CalorimeterFactory: calo.toml total stack Z (" + - std::to_string(stackZ) + " mm) exceeds container half-Z*2 (" + - std::to_string(2.0 * s_containerHalfZ) + - " mm). Reduce layer thicknesses or number of layers."); + throw std::runtime_error(std::format( + "CalorimeterFactory: calo.toml total stack Z ({} mm) exceeds container Z ({} mm). " + "Reduce layer thicknesses or number of layers.", + stackZ, 2.0 * s_containerHalfZ)); if (maxHalfX > s_containerHalfX) - throw std::runtime_error("CalorimeterFactory: calo.toml module array half-X (" + - std::to_string(maxHalfX) + " mm) exceeds container half-X (" + - std::to_string(s_containerHalfX) + - " mm). Reduce plate_xy_mm, module_nx, or module_pitch_x_mm."); + throw std::runtime_error(std::format( + "CalorimeterFactory: calo.toml module array half-X ({} mm) exceeds container half-X " + "({} mm). Reduce plate_xy_mm, module_nx, or module_pitch_x_mm.", + maxHalfX, s_containerHalfX)); if (maxHalfY > s_containerHalfY) - throw std::runtime_error("CalorimeterFactory: calo.toml module array half-Y (" + - std::to_string(maxHalfY) + " mm) exceeds container half-Y (" + - std::to_string(s_containerHalfY) + - " mm). Reduce plate_xy_mm, module_ny, or module_pitch_y_mm."); + throw std::runtime_error(std::format( + "CalorimeterFactory: calo.toml module array half-Y ({} mm) exceeds container half-Y " + "({} mm). Reduce plate_xy_mm, module_ny, or module_pitch_y_mm.", + maxHalfY, s_containerHalfY)); const double x0 = -0.5 * (cfg.module_nx - 1) * pitchX; const double y0 = -0.5 * (cfg.module_ny - 1) * pitchY; @@ -143,8 +149,21 @@ GeoPhysVol* CalorimeterFactory::build() { // ── buildStack ─────────────────────────────────────────────────────────────── -void CalorimeterFactory::buildStack(GeoPhysVol* container, const CalorimeterConfig& cfg, int mx, - int my, double offX, double offY) const { +void CalorimeterFactory::buildStack(GeoPhysVol* container, const CalorimeterConfig& cfg, + int moduleX, int moduleY, double offsetX, + double offsetY) const { + // Compute bar counts from configurable plate size + if (std::fmod(cfg.plate_xy_mm, kWidePVTBarPitch_mm) != 0.0) + throw std::runtime_error(std::format( + "CalorimeterFactory: plate_xy_mm ({}) is not divisible by wide PVT bar pitch ({} mm)", + cfg.plate_xy_mm, kWidePVTBarPitch_mm)); + if (std::fmod(cfg.plate_xy_mm, kThinPSBarPitch_mm) != 0.0) + throw std::runtime_error(std::format( + "CalorimeterFactory: plate_xy_mm ({}) is not divisible by thin PS bar pitch ({} mm)", + cfg.plate_xy_mm, kThinPSBarPitch_mm)); + const int widePVTBarCount = static_cast(cfg.plate_xy_mm / kWidePVTBarPitch_mm); + const int thinPSBarCount = static_cast(cfg.plate_xy_mm / kThinPSBarPitch_mm); + GeoMaterial* leadMat = m_materials.requireMaterial("Lead"); GeoMaterial* ironMat = m_materials.requireMaterial("Iron"); GeoMaterial* pvtMat = m_materials.requireMaterial("PVT"); @@ -159,23 +178,23 @@ void CalorimeterFactory::buildStack(GeoPhysVol* container, const CalorimeterConf const double ironZ = cfg.iron_thickness_mm * mm; const double airGapZ = cfg.airgap_mm * mm; - const double wideW = 60.0 * mm; - const double thinW = 10.0 * mm; + const double wideW = kWidePVTBarPitch_mm * mm; + const double thinW = kThinPSBarPitch_mm * mm; - const std::string mtag = "_MX" + std::to_string(mx) + "Y" + std::to_string(my); + const std::string moduleTag = std::format("_MX{}Y{}", moduleX, moduleY); // Reusable LogVols — GeoModel shares them across GeoPhysVol instances - auto* leadLog = new GeoLogVol("/SHiP/calorimeter/lead_plate" + mtag, + auto* leadLog = new GeoLogVol("/SHiP/calorimeter/lead_plate" + moduleTag, new GeoBox(0.5 * plateXY, 0.5 * plateXY, 0.5 * leadZ), leadMat); - auto* ironLog = new GeoLogVol("/SHiP/calorimeter/iron_plate" + mtag, + auto* ironLog = new GeoLogVol("/SHiP/calorimeter/iron_plate" + moduleTag, new GeoBox(0.5 * plateXY, 0.5 * plateXY, 0.5 * ironZ), ironMat); - auto* wideHLog = new GeoLogVol("/SHiP/calorimeter/wide_pvt_h" + mtag, + auto* wideHLog = new GeoLogVol("/SHiP/calorimeter/wide_pvt_h" + moduleTag, new GeoBox(0.5 * plateXY, 0.5 * wideW, 0.5 * scintZ), pvtMat); - auto* wideVLog = new GeoLogVol("/SHiP/calorimeter/wide_pvt_v" + mtag, + auto* wideVLog = new GeoLogVol("/SHiP/calorimeter/wide_pvt_v" + moduleTag, new GeoBox(0.5 * wideW, 0.5 * plateXY, 0.5 * scintZ), pvtMat); - auto* thinHLog = new GeoLogVol("/SHiP/calorimeter/thin_ps_h" + mtag, + auto* thinHLog = new GeoLogVol("/SHiP/calorimeter/thin_ps_h" + moduleTag, new GeoBox(0.5 * plateXY, 0.5 * thinW, 0.5 * scintZ), psMat); - auto* thinVLog = new GeoLogVol("/SHiP/calorimeter/thin_ps_v" + mtag, + auto* thinVLog = new GeoLogVol("/SHiP/calorimeter/thin_ps_v" + moduleTag, new GeoBox(0.5 * thinW, 0.5 * plateXY, 0.5 * scintZ), psMat); // z cursor: start at -halfContainerZ if centering, else 0 @@ -187,210 +206,163 @@ void CalorimeterFactory::buildStack(GeoPhysVol* container, const CalorimeterConf // Lambda: wrap a layer in a named air-box envelope inside the container auto makeEnv = [&](const std::string& name, double halfZ, double zCenter) -> GeoPhysVol* { - auto* s = new GeoBox(0.5 * plateXY, 0.5 * plateXY, halfZ); - auto* l = new GeoLogVol(name + "_env", s, airMat); - auto* p = new GeoPhysVol(l); + auto* envShape = new GeoBox(0.5 * plateXY, 0.5 * plateXY, halfZ); + auto* envLog = new GeoLogVol(name + "_env", envShape, airMat); + auto* envPhys = new GeoPhysVol(envLog); container->add(new GeoNameTag(name.c_str())); container->add(new GeoIdentifierTag(layerId++)); - container->add(new GeoTransform(GeoTrf::Translate3D(offX, offY, zCenter))); - container->add(p); - return p; + container->add(new GeoTransform(GeoTrf::Translate3D(offsetX, offsetY, zCenter))); + container->add(envPhys); + return envPhys; }; int iWideH = 0, iWideV = 0, iThinH = 0, iThinV = 0, iHPL = 0; - int gl = 0, sl = 0; - - // ══════════════════════════════════════════ - // ECAL section (code 7 → Lead) - // ══════════════════════════════════════════ - for (int code : cfg.layers) { - if (code == 7) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_lead" + mtag; - auto* env = makeEnv(n, 0.5 * leadZ, zCursor + 0.5 * leadZ); - env->add(new GeoNameTag(n.c_str())); - env->add(new GeoIdentifierTag(0)); - env->add(new GeoTransform(GeoTrf::Translate3D(0, 0, 0))); - env->add(new GeoPhysVol(leadLog)); - zCursor += leadZ; - ++gl; - - } else if (code == 1) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_wide_pvt_h" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, wideHLog, 60.0, 36, 0.0, n.c_str(), iWideH, BarAxis::AlongY, - mtag); - zCursor += scintZ; - ++iWideH; - ++gl; - ++sl; - - } else if (code == 2) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_wide_pvt_v" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, wideVLog, 60.0, 36, 0.0, n.c_str(), iWideV, BarAxis::AlongX, - mtag); - zCursor += scintZ; - ++iWideV; - ++gl; - ++sl; - - } else if (code == 3) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_thin_ps_h" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, thinHLog, 10.0, 216, 0.0, n.c_str(), iThinH, BarAxis::AlongY, - mtag); - zCursor += scintZ; - ++iThinH; - ++gl; - ++sl; - - } else if (code == 4) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_thin_ps_v" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, thinVLog, 10.0, 216, 0.0, n.c_str(), iThinV, BarAxis::AlongX, - mtag); - zCursor += scintZ; - ++iThinV; - ++gl; - ++sl; - - } else if (code == 5) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_hpl_y" + mtag; - auto* env = makeEnv(n, 0.5 * hplZ, zCursor + 0.5 * hplZ); - CaloFibreHPLayer::build(env, alMat, psMat, n, 0.0, iHPL, cfg.plate_xy_mm, - cfg.hpl_thickness_mm, cfg.fiber_diameter_mm, - cfg.fiber_core_diameter_mm, true, mtag); - zCursor += hplZ; - ++iHPL; - ++gl; - ++sl; - - } else if (code == 6) { - const std::string n = "/SHiP/calorimeter/ecal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_hpl_x" + mtag; - auto* env = makeEnv(n, 0.5 * hplZ, zCursor + 0.5 * hplZ); - CaloFibreHPLayer::build(env, alMat, psMat, n, 0.0, iHPL, cfg.plate_xy_mm, - cfg.hpl_thickness_mm, cfg.fiber_diameter_mm, - cfg.fiber_core_diameter_mm, false, mtag); - zCursor += hplZ; - ++iHPL; - ++gl; - ++sl; - - } else if (code == 8) { - zCursor += airGapZ; - - } else { - throw std::runtime_error("CalorimeterFactory: unknown layer code in 'layers': " + - std::to_string(code)); + + // Describes one calorimeter section (ECAL or HCAL) + struct SectionDescriptor { + std::string_view prefix; // "ecal" or "hcal" + const std::vector& layerCodes; + GeoLogVol* absorberLog; + double absorberHalfZ; + bool absorberNeedsEnvelope; // ECAL lead: true, HCAL iron: false + bool incrementGlobalOnAirGap; // ECAL: false, HCAL: true + }; + + auto processSection = [&](const SectionDescriptor& sec) { + int globalLayerIdx = 0, scintLayerIdx = 0; + int absorberIdx = 0; + + for (int code : sec.layerCodes) { + const auto basePath = + std::format("/SHiP/calorimeter/{}/gl{}", sec.prefix, globalLayerIdx); + + switch (static_cast(code)) { + case LayerCode::Absorber: { + if (sec.absorberNeedsEnvelope) { + const auto volumeName = std::format("{}_lead{}", basePath, moduleTag); + auto* env = + makeEnv(volumeName, sec.absorberHalfZ, zCursor + sec.absorberHalfZ); + env->add(new GeoNameTag(volumeName.c_str())); + env->add(new GeoIdentifierTag(0)); + env->add(new GeoTransform(GeoTrf::Translate3D(0, 0, 0))); + env->add(new GeoPhysVol(sec.absorberLog)); + } else { + const auto volumeName = + std::format("{}_iron_{}{}", basePath, absorberIdx, moduleTag); + container->add(new GeoNameTag(volumeName.c_str())); + container->add(new GeoIdentifierTag(layerId++)); + container->add(new GeoTransform( + GeoTrf::Translate3D(offsetX, offsetY, zCursor + sec.absorberHalfZ))); + container->add(new GeoPhysVol(sec.absorberLog)); + } + zCursor += 2.0 * sec.absorberHalfZ; + ++absorberIdx; + ++globalLayerIdx; + break; + } + case LayerCode::WidePVT_H: { + const auto volumeName = + std::format("{}_sl{}_wide_pvt_h{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * scintZ, zCursor + 0.5 * scintZ); + CaloBar::placeLayer(env, wideHLog, kWidePVTBarPitch_mm, widePVTBarCount, 0.0, + volumeName.c_str(), iWideH, BarAxis::AlongY, moduleTag); + zCursor += scintZ; + ++iWideH; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::WidePVT_V: { + const auto volumeName = + std::format("{}_sl{}_wide_pvt_v{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * scintZ, zCursor + 0.5 * scintZ); + CaloBar::placeLayer(env, wideVLog, kWidePVTBarPitch_mm, widePVTBarCount, 0.0, + volumeName.c_str(), iWideV, BarAxis::AlongX, moduleTag); + zCursor += scintZ; + ++iWideV; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::ThinPS_H: { + const auto volumeName = + std::format("{}_sl{}_thin_ps_h{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * scintZ, zCursor + 0.5 * scintZ); + CaloBar::placeLayer(env, thinHLog, kThinPSBarPitch_mm, thinPSBarCount, 0.0, + volumeName.c_str(), iThinH, BarAxis::AlongY, moduleTag); + zCursor += scintZ; + ++iThinH; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::ThinPS_V: { + const auto volumeName = + std::format("{}_sl{}_thin_ps_v{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * scintZ, zCursor + 0.5 * scintZ); + CaloBar::placeLayer(env, thinVLog, kThinPSBarPitch_mm, thinPSBarCount, 0.0, + volumeName.c_str(), iThinV, BarAxis::AlongX, moduleTag); + zCursor += scintZ; + ++iThinV; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::FibreHPL_Y: { + const auto volumeName = + std::format("{}_sl{}_hpl_y{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * hplZ, zCursor + 0.5 * hplZ); + CaloFibreHP::buildLayer(env, alMat, psMat, volumeName, 0.0, iHPL, + cfg.plate_xy_mm, cfg.hpl_thickness_mm, + cfg.fiber_diameter_mm, cfg.fiber_core_diameter_mm, true, + moduleTag); + zCursor += hplZ; + ++iHPL; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::FibreHPL_X: { + const auto volumeName = + std::format("{}_sl{}_hpl_x{}", basePath, scintLayerIdx, moduleTag); + auto* env = makeEnv(volumeName, 0.5 * hplZ, zCursor + 0.5 * hplZ); + CaloFibreHP::buildLayer(env, alMat, psMat, volumeName, 0.0, iHPL, + cfg.plate_xy_mm, cfg.hpl_thickness_mm, + cfg.fiber_diameter_mm, cfg.fiber_core_diameter_mm, + false, moduleTag); + zCursor += hplZ; + ++iHPL; + ++globalLayerIdx; + ++scintLayerIdx; + break; + } + case LayerCode::AirGap: + zCursor += airGapZ; + if (sec.incrementGlobalOnAirGap) + ++globalLayerIdx; + break; + default: + throw std::runtime_error( + std::format("CalorimeterFactory: unknown layer code: {}", code)); + } } - } + }; + + processSection({.prefix = "ecal", + .layerCodes = cfg.layers, + .absorberLog = leadLog, + .absorberHalfZ = 0.5 * leadZ, + .absorberNeedsEnvelope = true, + .incrementGlobalOnAirGap = false}); - // ECAL–HCAL gap zCursor += cfg.gap_ecal_hcal_mm * mm; - // ══════════════════════════════════════════ - // HCAL section (code 7 → Iron) - // ══════════════════════════════════════════ - int iIron = 0; - gl = 0; - sl = 0; - - for (int code : cfg.layers2) { - if (code == 7) { - const std::string tag = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_iron_" + - std::to_string(iIron) + mtag; - container->add(new GeoNameTag(tag.c_str())); - container->add(new GeoIdentifierTag(layerId++)); - container->add( - new GeoTransform(GeoTrf::Translate3D(offX, offY, zCursor + 0.5 * ironZ))); - container->add(new GeoPhysVol(ironLog)); - zCursor += ironZ; - ++iIron; - ++gl; - - } else if (code == 1) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_wide_pvt_h" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, wideHLog, 60.0, 36, 0.0, n.c_str(), iWideH, BarAxis::AlongY, - mtag); - zCursor += scintZ; - ++iWideH; - ++gl; - ++sl; - - } else if (code == 2) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_wide_pvt_v" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, wideVLog, 60.0, 36, 0.0, n.c_str(), iWideV, BarAxis::AlongX, - mtag); - zCursor += scintZ; - ++iWideV; - ++gl; - ++sl; - - } else if (code == 3) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_thin_ps_h" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, thinHLog, 10.0, 216, 0.0, n.c_str(), iThinH, BarAxis::AlongY, - mtag); - zCursor += scintZ; - ++iThinH; - ++gl; - ++sl; - - } else if (code == 4) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_thin_ps_v" + mtag; - auto* env = makeEnv(n, 0.5 * scintZ, zCursor + 0.5 * scintZ); - CaloBarLayer::place(env, thinVLog, 10.0, 216, 0.0, n.c_str(), iThinV, BarAxis::AlongX, - mtag); - zCursor += scintZ; - ++iThinV; - ++gl; - ++sl; - - } else if (code == 5) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_hpl_y" + mtag; - auto* env = makeEnv(n, 0.5 * hplZ, zCursor + 0.5 * hplZ); - CaloFibreHPLayer::build(env, alMat, psMat, n, 0.0, iHPL, cfg.plate_xy_mm, - cfg.hpl_thickness_mm, cfg.fiber_diameter_mm, - cfg.fiber_core_diameter_mm, true, mtag); - zCursor += hplZ; - ++iHPL; - ++gl; - ++sl; - - } else if (code == 6) { - const std::string n = "/SHiP/calorimeter/hcal/gl" + std::to_string(gl) + "_sl" + - std::to_string(sl) + "_hpl_x" + mtag; - auto* env = makeEnv(n, 0.5 * hplZ, zCursor + 0.5 * hplZ); - CaloFibreHPLayer::build(env, alMat, psMat, n, 0.0, iHPL, cfg.plate_xy_mm, - cfg.hpl_thickness_mm, cfg.fiber_diameter_mm, - cfg.fiber_core_diameter_mm, false, mtag); - zCursor += hplZ; - ++iHPL; - ++gl; - ++sl; - - } else if (code == 8) { - zCursor += airGapZ; - ++gl; - - } else { - throw std::runtime_error("CalorimeterFactory: unknown layer code in 'layers2': " + - std::to_string(code)); - } - } + processSection({.prefix = "hcal", + .layerCodes = cfg.layers2, + .absorberLog = ironLog, + .absorberHalfZ = 0.5 * ironZ, + .absorberNeedsEnvelope = false, + .incrementGlobalOnAirGap = true}); } } // namespace SHiPGeometry