diff --git a/src/SHiPMaterials.cpp b/src/SHiPMaterials.cpp index a644ea0..add541a 100644 --- a/src/SHiPMaterials.cpp +++ b/src/SHiPMaterials.cpp @@ -179,6 +179,32 @@ void SHiPMaterials::createMaterials() { scintillator->add(m_elements["Hydrogen"], 0.085); scintillator->lock(); m_materials["Scintillator"] = scintillator; + + // Mylar / PET (density 1.39 g/cm³): C10H8O4 → C 62.50%, H 4.20%, O 33.30% + GeoMaterial* mylar = + new GeoMaterial("Mylar", 1.39 * GeoModelKernelUnits::g / GeoModelKernelUnits::cm3); + mylar->add(m_elements["Carbon"], 0.6250); + mylar->add(m_elements["Hydrogen"], 0.0420); + mylar->add(m_elements["Oxygen"], 0.3330); + mylar->lock(); + m_materials["Mylar"] = mylar; + + // ArCO2 70/30 drift gas (density 1.842e-3 g/cm³): Ar 67.93%, C 8.75%, O 23.32% + GeoMaterial* arco2 = + new GeoMaterial("ArCO2", 1.842e-3 * GeoModelKernelUnits::g / GeoModelKernelUnits::cm3); + arco2->add(m_elements["Argon"], 0.6793); + arco2->add(m_elements["Carbon"], 0.0875); + arco2->add(m_elements["Oxygen"], 0.2332); + arco2->lock(); + m_materials["ArCO2"] = arco2; + + // Polystyrene (density 1.06 g/cm³): C8H8 → C 92.26%, H 7.74% + GeoMaterial* polystyrene = + new GeoMaterial("Polystyrene", 1.06 * GeoModelKernelUnits::g / GeoModelKernelUnits::cm3); + polystyrene->add(m_elements["Carbon"], 0.9226); + polystyrene->add(m_elements["Hydrogen"], 0.0774); + polystyrene->lock(); + m_materials["Polystyrene"] = polystyrene; } } // namespace SHiPGeometry diff --git a/subsystems/UpstreamTagger/README.md b/subsystems/UpstreamTagger/README.md index 19a2d1b..de36f55 100644 --- a/subsystems/UpstreamTagger/README.md +++ b/subsystems/UpstreamTagger/README.md @@ -1,36 +1,124 @@ # UpstreamTagger -Upstream veto tagger. +Upstream Background Tagger (UBT) — segmented drift tube and scintillator tile detector. ## Description -The UpstreamTagger subsystem implements a scintillator slab upstream of the decay vessel, used to veto charged particles entering from the target region. Currently modelled as a single monolithic scintillator box. The full implementation requires bar segmentation with SiPM readout. +The UpstreamTagger implements the UBT as a segmented 2×3 m² tracking plane placed at +z = 32720 mm, upstream of the decay vessel. It consists of: -The volume is created as a `GeoFullPhysVol` (rather than `GeoPhysVol`) to allow sensitive detector registration via `SHiPUBTManager`. +- **Drift tubes** — mylar straw tubes (5 mm diameter, 15 µm wall, ArCO2 70/30 fill, 1.2 m long) + arranged in double-staggered layers +- **Scintillator tiles** — 1×1×1 cm³ polystyrene tiles in two 40×40 blocks ## Geometry Tree ``` -Upstream_Tagger (Scintillator, 4400×6400×160 mm) +UBT_Envelope_LV (Air, 2000×3000×16 mm) +├── UBT_Top_Left_LOG (Air, outer top band, left half-plane) +│ ├── UBT_Top_Left_S0_T*_Wall (Mylar, hollow tube annulus) +│ └── UBT_Top_Left_S0_T*_Gas (ArCO2, solid cylinder) [sensitive] +│ ├── UBT_Top_Left_S1_T*_Wall +│ └── UBT_Top_Left_S1_T*_Gas [sensitive] +├── UBT_Top_Right_LOG (Air, outer top band, right half-plane) +│ └── ... +├── UBT_Bottom_Left_LOG (Air, outer bottom band, left half-plane) +│ └── ... +├── UBT_Bottom_Right_LOG (Air, outer bottom band, right half-plane) +│ └── ... +├── UBT_Central_LOG (Air, central tube strip x=[-600,+600]) +│ ├── UBT_Central_S*_T*_Wall +│ └── UBT_Central_S*_T*_Gas [sensitive] +├── UBT_TileLeft_LOG (Air, left tile block x=[-1000,-600]) +│ └── UBT_TileLeft_T*_* [sensitive] +└── UBT_TileRight_LOG (Air, right tile block x=[+600,+1000]) + └── UBT_TileRight_T*_* [sensitive] ``` -Position in world: z = 32720 mm. +## Detector Layout + +``` + Y + +1500 ┌──────────────────────────────────────────┐ + │ Top_Left tubes + Top_Right tubes │ y = [+200, +1500] mm + +200 ├───────────┬──────────────────┬────────────┤ + │ PS tiles │ Central tubes │ PS tiles │ y = [-200, +200] mm + │ (left) │ x=[-600,+600] │ (right) │ + -200 ├───────────┴──────────────────┴────────────┤ + │ Bottom_Left tubes + Bottom_Right tubes │ y = [-1500, -200] mm + -1500 └──────────────────────────────────────────┘ + -1000 -600 +600 +1000 → X (mm) +``` + +### Drift tubes + +| Region | X extent (mm) | Y extent (mm) | Z centre (mm) | +|--------|--------------|---------------|---------------| +| Top_Left | [-1000, +200] | [+200, +1500] | -5 | +| Top_Right | [-200, +1000] | [+200, +1500] | +5 | +| Bottom_Left | [-1000, +200] | [-1500, -200] | -5 | +| Bottom_Right | [-200, +1000] | [-1500, -200] | +5 | +| Central | [-600, +600] | [-200, +200] | 0 | + +Each region uses a double-staggered layer: two sub-layers separated by one tube +radius in Z and half a pitch in Y, giving full azimuthal coverage. + +Tube gas and wall volumes are placed as **siblings** in the envelope (not nested) +to avoid Geo2G4 copy-number corruption with shared logical volumes. + +### Scintillator tiles + +Two 40×40 blocks of 1×1×1 cm³ polystyrene tiles, flush with the tube layer in Z. + +## Sensitive Volumes + +All sensitive volumes are `GeoFullPhysVol` and are registered with `SHiPUBTManager`: + +| Collection | LV name pattern | Count | +|---|---|---| +| Tube gas | `UBT_*_TubeGas_LV` | ~1000+ | +| Tiles | `UBT_Tile_LV` | 3200 | ## Materials -| Material | Density | Usage | -|--------------|------------|-----------------| -| Scintillator | 1.023 g/cm³ | Detector slab | -## Status +| Material | Density | Usage | +|---|---|---| +| Mylar | 1.39 g/cm³ | Tube wall (15 µm) | +| ArCO2 | 1.842×10⁻³ g/cm³ | Drift gas (70% Ar, 30% CO2) | +| Polystyrene | 1.06 g/cm³ | Scintillator tiles | +| Air | 1.29×10⁻³ g/cm³ | Envelopes | + + +## Position in World + +z = 32720 mm (centre of 32520–32920 mm range, from subsystem_envelopes.csv). -- [x] C++ implementation (monolithic slab) -- [ ] Implement bar segmentation with SiPM readout -- [ ] Verification against GDML +## Geant4 Integration -## TODO +To register sensitive detectors in a Geant4 application: + +```cpp +SHiPUBTManager ubtManager; +UpstreamTaggerFactory factory(materials); +factory.build(&ubtManager); + +// After Geo2G4 conversion, iterate the G4LogicalVolumeStore and register +// your SD on volumes whose name contains "TubeGas_LV" or "Tile_LV": +auto* sd = new MyUBTSD("UBTSD"); +for (auto* lv : *G4LogicalVolumeStore::GetInstance()) { + const auto& name = lv->GetName(); + if (name.find("TubeGas_LV") != std::string::npos || + name.find("Tile_LV") != std::string::npos) + lv->SetSensitiveDetector(sd); +} +``` + +## Status -- Implement individual scintillator bar segmentation -- Add SiPM readout geometry -- Register individual bars as sensitive volumes (currently the whole slab is one volume) -- Verify slab dimensions against GDML reference +- [x] Full segmented geometry (drift tubes + tiles) +- [x] Double-staggered tube layers per region +- [x] GeoFullPhysVol sensitive volume registration via SHiPUBTManager +- [x] New materials (Mylar, ArCO2, Polystyrene) added to SHiPMaterials +- [ ] Verification of tube count against GDML reference +- [ ] SiPM readout geometry diff --git a/subsystems/UpstreamTagger/include/UpstreamTagger/SHiPUBTManager.h b/subsystems/UpstreamTagger/include/UpstreamTagger/SHiPUBTManager.h index 42f7796..19816f5 100644 --- a/subsystems/UpstreamTagger/include/UpstreamTagger/SHiPUBTManager.h +++ b/subsystems/UpstreamTagger/include/UpstreamTagger/SHiPUBTManager.h @@ -7,28 +7,65 @@ #include #include +#include + namespace SHiPGeometry { /** * @brief Detector manager for the Upstream Background Tagger (UBT). * - * Stores the single GeoFullPhysVol sensitive slab and satisfies the - * GeoVDetectorManager interface for downstream Geant4 integration. + * Stores the sensitive volumes of the UBT: drift tube gas volumes and + * scintillator tile volumes, all registered as GeoFullPhysVol. + * + * Two sets of sensitive volumes: + * - Drift tube gas cylinders (ArCO2 70/30) + * - Polystyrene scintillator tiles (1×1×1 cm³) + * + * The legacy single-slab interface (setSlabVolume / getFullPV) is kept + * for backwards compatibility with the monolithic placeholder. */ class SHiPUBTManager : public GeoVDetectorManager { public: SHiPUBTManager() = default; ~SHiPUBTManager() override = default; + // ---- Legacy slab interface (monolithic placeholder) -------------------- void setSlabVolume(GeoFullPhysVol* fpv) { m_slab = fpv; } GeoFullPhysVol* getFullPV() const { return m_slab; } - unsigned int getNumTreeTops() const override { return m_slab ? 1u : 0u; } + // ---- Segmented detector interface -------------------------------------- + void addTubeGasVolume(GeoFullPhysVol* fpv) { m_tubeGasVolumes.push_back(fpv); } + void addTileVolume(GeoFullPhysVol* fpv) { m_tileVolumes.push_back(fpv); } + + const std::vector& getTubeGasVolumes() const { return m_tubeGasVolumes; } + const std::vector& getTileVolumes() const { return m_tileVolumes; } + + std::size_t numTubeGasVolumes() const { return m_tubeGasVolumes.size(); } + std::size_t numTileVolumes() const { return m_tileVolumes.size(); } + + // ---- GeoVDetectorManager interface ------------------------------------ + unsigned int getNumTreeTops() const override { + // If segmented volumes exist use them, otherwise fall back to slab + const auto n = m_tubeGasVolumes.size() + m_tileVolumes.size(); + return n > 0 ? static_cast(n) : (m_slab ? 1u : 0u); + } - PVConstLink getTreeTop(unsigned int /*i*/) const override { return PVConstLink(m_slab); } + PVConstLink getTreeTop(unsigned int i) const override { + const auto nTubes = m_tubeGasVolumes.size(); + const auto nTiles = m_tileVolumes.size(); + if (nTubes + nTiles > 0) { + if (i < nTubes) + return PVConstLink(m_tubeGasVolumes[i]); + if (i < nTubes + nTiles) + return PVConstLink(m_tileVolumes[i - nTubes]); + } + return PVConstLink(m_slab); + } private: GeoFullPhysVol* m_slab{nullptr}; + std::vector m_tubeGasVolumes; + std::vector m_tileVolumes; }; } // namespace SHiPGeometry diff --git a/subsystems/UpstreamTagger/include/UpstreamTagger/UpstreamTaggerFactory.h b/subsystems/UpstreamTagger/include/UpstreamTagger/UpstreamTaggerFactory.h index ccd82dd..487068f 100644 --- a/subsystems/UpstreamTagger/include/UpstreamTagger/UpstreamTaggerFactory.h +++ b/subsystems/UpstreamTagger/include/UpstreamTagger/UpstreamTaggerFactory.h @@ -11,12 +11,35 @@ class SHiPMaterials; class SHiPUBTManager; /** - * @brief Factory for the UpstreamTagger (upstream veto tagger) geometry + * @brief Factory for the Upstream Background Tagger (UBT) geometry. * - * Creates a scintillator slab as a GeoFullPhysVol (sensitive volume). - * Based on GDML reference: Box 440×640×16 cm (full dimensions). - * Z: 32.52 to 32.92 m → centre: 32.72 m, half-length: 0.20 m - * Half-width: 2.20 m, half-height: 3.20 m + * Builds a segmented 2×3 m² tracker plane of drift tubes and scintillator tiles. + * + * ## Detector layout + * + * ``` + * Y + * +1500 ┌──────────────────────────────────────────┐ + * │ Top_Left tubes + Top_Right tubes │ y = [+200, +1500] + * +200 ├───────────┬──────────────────┬────────────┤ + * │ PS tiles │ Central tubes │ PS tiles │ y = [-200, +200] + * │ (left) │ x=[-600,+600] │ (right) │ + * -200 ├───────────┴──────────────────┴────────────┤ + * │ Bottom_Left tubes + Bottom_Right tubes │ y = [-1500, -200] + * -1500 └──────────────────────────────────────────┘ + * -1000 -600 +600 +1000 → X (mm) + * ``` + * + * ## Sensitive volumes + * All tube gas volumes (GeoFullPhysVol, LV name contains "TubeGas_LV") and + * tile volumes (GeoFullPhysVol, LV name "UBT_Tile_LV") are registered with + * the supplied SHiPUBTManager for downstream Geant4 SD registration. + * + * ## Envelope dimensions (half-extents, mm) + * halfX = 1000, halfY = 1500, halfZ = 8 + * + * ## Position in world + * z = 32720 mm (centre of the 32520–32920 mm z range) */ class UpstreamTaggerFactory { public: @@ -24,19 +47,25 @@ class UpstreamTaggerFactory { ~UpstreamTaggerFactory() = default; /** - * @brief Build the UpstreamTagger geometry. - * @param manager Optional manager to register the sensitive slab; may be null. - * @return Pointer to the GeoFullPhysVol scintillator slab. + * @brief Build the full segmented UBT plane. + * @param manager Optional manager to register sensitive volumes; may be null. + * @return Pointer to the envelope GeoPhysVol. */ GeoVPhysVol* build(SHiPUBTManager* manager = nullptr); + // Envelope half-dimensions (mm) — used by the test to check against CSV limits + // Envelope half-dimensions match CSV: half_width=750mm, half_height=1600mm + static constexpr double s_halfX = 750.0; + static constexpr double s_halfY = 1600.0; + static constexpr double s_halfZ = 8.0; + private: SHiPMaterials& m_materials; - // Dimensions from GDML: Box 440×640×16 cm → half: 220×320×8 cm (mm) - static constexpr double s_halfX = 2200.0; - static constexpr double s_halfY = 3200.0; - static constexpr double s_halfZ = 80.0; + static constexpr double s_tubeROuter_mm = 2.5; + static constexpr double s_tubeWall_mm = 0.015; + static constexpr double s_tubeHalfLen_mm = 600.0; + static constexpr double s_tileSide_mm = 10.0; }; } // namespace SHiPGeometry diff --git a/subsystems/UpstreamTagger/src/UpstreamTaggerFactory.cpp b/subsystems/UpstreamTagger/src/UpstreamTaggerFactory.cpp index 5390036..31bfc34 100644 --- a/subsystems/UpstreamTagger/src/UpstreamTaggerFactory.cpp +++ b/subsystems/UpstreamTagger/src/UpstreamTaggerFactory.cpp @@ -7,25 +7,222 @@ #include "UpstreamTagger/SHiPUBTManager.h" #include +#include #include #include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace GeoModelKernelUnits; namespace SHiPGeometry { +namespace { + +// ============================================================================ +// placeTubeSubLayer (file-scope helper) +// +// Wall and gas are placed as SIBLINGS in the envelope (not nested) to avoid +// Geo2G4 copy-number corruption that occurs when a GeoPhysVol is a daughter +// of another GeoPhysVol sharing the same GeoLogVol across many instances. +// ============================================================================ +void placeTubeSubLayer(GeoPhysVol* envelope, GeoLogVol* wallLog, GeoLogVol* gasLog, + double halfExtentY, double zLocal, double yShift, double rOuter_mm, + const std::string& tag, int subIdx, SHiPUBTManager* manager) { + const double rOuter = rOuter_mm * mm; + const double pitch = 2.0 * rOuter; + + const double usable = 2.0 * halfExtentY - std::abs(yShift); + const int nTubes = std::max(1, static_cast(std::floor(usable / pitch)) + 1); + const double yFirst = -0.5 * (nTubes - 1) * pitch + yShift; + + const GeoTrf::Transform3D rotToX = GeoTrf::RotateY3D(-90.0 * deg); + + for (int i = 0; i < nTubes; ++i) { + const double yPos = yFirst + i * pitch; + if (std::abs(yPos) > halfExtentY + 1e-6) + continue; + + const std::string name = tag + "_S" + std::to_string(subIdx) + "_T" + std::to_string(i); + const GeoTrf::Transform3D trf = GeoTrf::Translate3D(0.0, yPos, zLocal) * rotToX; + + // Wall (not sensitive) — placed first + auto* wallPV = new GeoPhysVol(wallLog); + envelope->add(new GeoNameTag((name + "_Wall").c_str())); + envelope->add(new GeoTransform(trf)); + envelope->add(wallPV); + + // Gas (sensitive) — sibling of wall, same transform + auto* gasFPV = new GeoFullPhysVol(gasLog); + envelope->add(new GeoNameTag((name + "_Gas").c_str())); + envelope->add(new GeoTransform(trf)); + envelope->add(gasFPV); + + if (manager) + manager->addTubeGasVolume(gasFPV); + } +} + +void placeDoubleStaggeredLayer(GeoPhysVol* envelope, GeoLogVol* wallLog, GeoLogVol* gasLog, + double halfExtentY, double zCenter, double rOuter_mm, + const std::string& tag, SHiPUBTManager* manager) { + const double rOuter = rOuter_mm * mm; + placeTubeSubLayer(envelope, wallLog, gasLog, halfExtentY, zCenter - rOuter, 0.0, rOuter_mm, tag, + 0, manager); + placeTubeSubLayer(envelope, wallLog, gasLog, halfExtentY, zCenter + rOuter, rOuter, rOuter_mm, + tag, 1, manager); +} + +GeoPhysVol* makeEnvelope(GeoPhysVol* mother, const GeoMaterial* mat, const std::string& tag, + double hx, double hy, double hz, double cx, double cy, double cz) { + auto* log = new GeoLogVol((tag + "_LOG").c_str(), new GeoBox(hx, hy, hz), + const_cast(mat)); + auto* pv = new GeoPhysVol(log); + mother->add(new GeoNameTag(tag.c_str())); + mother->add(new GeoTransform(GeoTrf::Translate3D(cx, cy, cz))); + mother->add(pv); + return pv; +} + +} // anonymous namespace + +// ============================================================================ +// UpstreamTaggerFactory +// ============================================================================ UpstreamTaggerFactory::UpstreamTaggerFactory(SHiPMaterials& materials) : m_materials(materials) {} GeoVPhysVol* UpstreamTaggerFactory::build(SHiPUBTManager* manager) { - const GeoMaterial* scint = m_materials.requireMaterial("Scintillator"); + // ---- Materials ---------------------------------------------------------- + const GeoMaterial* air = m_materials.requireMaterial("Air"); + const GeoMaterial* mylar = m_materials.requireMaterial("Mylar"); + const GeoMaterial* arco2 = m_materials.requireMaterial("ArCO2"); + const GeoMaterial* poly = m_materials.requireMaterial("Polystyrene"); + + // ---- Tube cross-section ------------------------------------------------- + const double rOuter = s_tubeROuter_mm * mm; + const double rInner = (s_tubeROuter_mm - s_tubeWall_mm) * mm; + const double rGas = rInner - 0.001 * mm; // 1 µm clearance + const double halfLen = s_tubeHalfLen_mm * mm; + + if (rInner <= 0.0 || rInner >= rOuter) + throw std::runtime_error("UBT: invalid tube wall thickness"); + + // ---- Tile half-size ----------------------------------------------------- + const double tileHalf = 0.5 * s_tileSide_mm * mm; + + // ---- Shared logical volumes (one wall+gas pair per region) -------------- + auto makeTubeLVs = [&](const std::string& region) -> std::pair { + auto* wLV = + new GeoLogVol(("UBT_" + region + "_TubeWall_LV").c_str(), + new GeoTube(rInner, rOuter, halfLen), const_cast(mylar)); + auto* gLV = new GeoLogVol(("UBT_" + region + "_TubeGas_LV").c_str(), + new GeoTube(0.0, rGas, halfLen), const_cast(arco2)); + return {wLV, gLV}; + }; + + auto* tileLV = new GeoLogVol("UBT_Tile_LV", new GeoBox(tileHalf, tileHalf, tileHalf), + const_cast(poly)); + + // ---- Layout constants scaled to CSV envelope (all mm) ------------------- + // CSV: half_width=750mm, half_height=1600mm, halfZ(length/2)=200mm + // + // Outer band: y=[+200,+1600] top / y=[-1600,-200] bottom + constexpr double outerHalfY_mm = 700.0; // (1600-200)/2 + constexpr double outerCtrY_mm = 900.0; // (200+1600)/2 + // Left half-plane: x=[-750,+200] → halfX=475, ctrX=-275 + constexpr double leftHalfX_mm = 475.0; + constexpr double leftCtrX_mm = -275.0; + // Right half-plane: x=[-200,+750] → halfX=475, ctrX=+275 + constexpr double rightHalfX_mm = 475.0; + constexpr double rightCtrX_mm = +275.0; + // Z offset between left/right half-planes + constexpr double zLeft_mm = -2.0 * s_tubeROuter_mm; + constexpr double zRight_mm = +2.0 * s_tubeROuter_mm; + constexpr double outerEnvHalfZ_mm = 3.0 * s_tubeROuter_mm + 0.5; + // Central strip: x=[-600,+600], y=[-200,+200] + constexpr double ctrHalfX_mm = 600.0; + constexpr double ctrHalfY_mm = 200.0; + constexpr double ctrEnvHalfZ_mm = s_tubeROuter_mm + 0.5; + // Tile blocks: x=[-750,-600] left, x=[+600,+750] right + constexpr double tileBlkHalfX_mm = 75.0; // (750-600)/2 + constexpr double tileBlkHalfY_mm = 200.0; + constexpr double tileBlkCtrX_mm = 675.0; // 600+75 - auto* box = new GeoBox(s_halfX, s_halfY, s_halfZ); - auto* log = new GeoLogVol("Upstream_Tagger", box, scint); - auto* fpv = new GeoFullPhysVol(log); + // ---- Top-level envelope ------------------------------------------------- + auto* envPV = new GeoPhysVol(new GeoLogVol("UBT_Envelope_LV", + new GeoBox(s_halfX * mm, s_halfY * mm, s_halfZ * mm), + const_cast(air))); - if (manager) { - manager->setSlabVolume(fpv); + // ---- Outer bands (top + bottom) ----------------------------------------- + for (int sign : {+1, -1}) { + const std::string band = (sign > 0) ? "Top" : "Bottom"; + const double yCtr = sign * outerCtrY_mm * mm; + + // Left half-plane + { + auto [wLV, gLV] = makeTubeLVs(band + "_Left"); + auto* env = makeEnvelope(envPV, air, "UBT_" + band + "_Left", leftHalfX_mm * mm, + outerHalfY_mm * mm, outerEnvHalfZ_mm * mm, leftCtrX_mm * mm, + yCtr, zLeft_mm * mm); + placeDoubleStaggeredLayer(env, wLV, gLV, outerHalfY_mm * mm, 0.0, s_tubeROuter_mm, + "UBT_" + band + "_Left", manager); + } + // Right half-plane + { + auto [wLV, gLV] = makeTubeLVs(band + "_Right"); + auto* env = makeEnvelope(envPV, air, "UBT_" + band + "_Right", rightHalfX_mm * mm, + outerHalfY_mm * mm, outerEnvHalfZ_mm * mm, rightCtrX_mm * mm, + yCtr, zRight_mm * mm); + placeDoubleStaggeredLayer(env, wLV, gLV, outerHalfY_mm * mm, 0.0, s_tubeROuter_mm, + "UBT_" + band + "_Right", manager); + } + } + + // ---- Central tube strip ------------------------------------------------- + { + auto [wLV, gLV] = makeTubeLVs("Central"); + auto* env = makeEnvelope(envPV, air, "UBT_Central", ctrHalfX_mm * mm, ctrHalfY_mm * mm, + ctrEnvHalfZ_mm * mm, 0.0, 0.0, 0.0); + placeDoubleStaggeredLayer(env, wLV, gLV, ctrHalfY_mm * mm, 0.0, s_tubeROuter_mm, + "UBT_Central", manager); } - return fpv; + // ---- Tile blocks -------------------------------------------------------- + auto placeTileBlock = [&](const std::string& blkTag, double ctrX) { + auto* blkEnv = makeEnvelope(envPV, air, blkTag, tileBlkHalfX_mm * mm, tileBlkHalfY_mm * mm, + tileHalf, ctrX, 0.0, 0.0); + + const int nX = static_cast(std::round(2.0 * tileBlkHalfX_mm / s_tileSide_mm)); + const int nY = static_cast(std::round(2.0 * tileBlkHalfY_mm / s_tileSide_mm)); + const double xFirst = -(nX - 1) * 0.5 * s_tileSide_mm * mm; + const double yFirst = -(nY - 1) * 0.5 * s_tileSide_mm * mm; + + for (int ix = 0; ix < nX; ++ix) { + for (int iy = 0; iy < nY; ++iy) { + const std::string tname = + blkTag + "_T" + std::to_string(ix) + "_" + std::to_string(iy); + auto* tileFPV = new GeoFullPhysVol(tileLV); + blkEnv->add(new GeoNameTag(tname.c_str())); + blkEnv->add(new GeoTransform(GeoTrf::Translate3D( + xFirst + ix * s_tileSide_mm * mm, yFirst + iy * s_tileSide_mm * mm, 0.0))); + blkEnv->add(tileFPV); + if (manager) + manager->addTileVolume(tileFPV); + } + } + }; + + placeTileBlock("UBT_TileLeft", -tileBlkCtrX_mm * mm); + placeTileBlock("UBT_TileRight", +tileBlkCtrX_mm * mm); + + return envPV; } } // namespace SHiPGeometry diff --git a/subsystems/UpstreamTagger/test_upstreamtagger.cpp b/subsystems/UpstreamTagger/test_upstreamtagger.cpp index 5996857..d9265a5 100644 --- a/subsystems/UpstreamTagger/test_upstreamtagger.cpp +++ b/subsystems/UpstreamTagger/test_upstreamtagger.cpp @@ -2,6 +2,7 @@ // Copyright (C) CERN for the benefit of the SHiP Collaboration #include "SHiPGeometry/SHiPMaterials.h" +#include "UpstreamTagger/SHiPUBTManager.h" #include "UpstreamTagger/UpstreamTaggerFactory.h" #include @@ -12,25 +13,44 @@ #include using SHiPGeometry::SHiPMaterials; +using SHiPGeometry::SHiPUBTManager; +using SHiPGeometry::UpstreamTaggerFactory; -// CSV limits: UpstreamTagger halfX ≤ 2200, halfY ≤ 3200, halfZ ≤ 200 -TEST_CASE("UpstreamTaggerWithinEnvelope", "[upstreamtagger]") { +// CSV row: Upstream background tagger, half_width=0.75m=750mm, half_height=1.60m=1600mm +TEST_CASE("UBTEnvelopeWithinCSVLimits", "[upstreamtagger]") { SHiPMaterials materials; - SHiPGeometry::UpstreamTaggerFactory factory(materials); + UpstreamTaggerFactory factory(materials); GeoVPhysVol* ubt = factory.build(); REQUIRE(ubt != nullptr); + auto* box = dynamic_cast(ubt->getLogVol()->getShape()); REQUIRE(box != nullptr); - CHECK(box->getXHalfLength() <= 2200.0); - CHECK(box->getYHalfLength() <= 3200.0); + CHECK(box->getXHalfLength() <= 750.0); + CHECK(box->getYHalfLength() <= 1600.0); CHECK(box->getZHalfLength() <= 200.0); } -// UpstreamTagger slab must be a GeoVFullPhysVol (sensitive volume) -TEST_CASE("UBTHasSensitiveVolume", "[upstreamtagger]") { +TEST_CASE("UBTMaterialsExist", "[upstreamtagger]") { SHiPMaterials materials; - SHiPGeometry::UpstreamTaggerFactory factory(materials); - GeoVPhysVol* ubt = factory.build(); - REQUIRE(ubt != nullptr); - CHECK(dynamic_cast(ubt) != nullptr); + CHECK(materials.getMaterial("Mylar") != nullptr); + CHECK(materials.getMaterial("ArCO2") != nullptr); + CHECK(materials.getMaterial("Polystyrene") != nullptr); +} + +TEST_CASE("UBTManagerReceivesSensitiveVolumes", "[upstreamtagger]") { + SHiPMaterials materials; + UpstreamTaggerFactory factory(materials); + SHiPUBTManager manager; + factory.build(&manager); + + // 2 tile blocks × 15 tiles in X × 40 tiles in Y = 1200 + CHECK(manager.numTubeGasVolumes() > 0); + CHECK(manager.numTileVolumes() == 1200); + CHECK(manager.getNumTreeTops() == manager.numTubeGasVolumes() + manager.numTileVolumes()); +} + +TEST_CASE("UBTBuildWithoutManager", "[upstreamtagger]") { + SHiPMaterials materials; + UpstreamTaggerFactory factory(materials); + CHECK(factory.build(nullptr) != nullptr); }