aegir
Phlex-based simulation framework for the SHiP experiment.
Loading...
Searching...
No Matches
detector_construction.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// detector_construction.hpp — Bridges IGeometrySource to
6// G4VUserDetectorConstruction
7//
8// Delegates Construct() to IGeometrySource::construct() and assigns the
9// configured SD type to volumes matching IGeometrySource::sensitiveVolumes().
10
11#pragma once
12
13#include <G4FieldManager.hh>
14#include <G4LogicalVolumeStore.hh>
15#include <G4ProductionCuts.hh>
16#include <G4Region.hh>
17#include <G4SDManager.hh>
18#include <G4String.hh>
19#include <G4VUserDetectorConstruction.hh>
20#include <mutex>
21#include <stdexcept>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "FieldService/G4MagFieldAdapter.h"
27#include "FieldService/IFieldSource.h"
28#include "geant4_sim_core.hpp"
29#include "geometry_source.hpp"
30
31namespace SHiP::g4 {
32
33enum class SDMode { scoring, crossing };
34
35class ConfigurableDetectorConstruction : public G4VUserDetectorConstruction {
36 IGeometrySource const*
37 source_; // non-owning; Job-layer product outlives the G4 run
38 ship::IFieldSource const* field_source_; // non-owning; may have no regions
39 SDMode sd_mode_;
40 double ke_threshold_; // GeV, used by CrossingSD
41 std::vector<std::pair<std::string, double>> regions_; // pattern -> cut in mm
42 std::once_flag regions_flag_;
43
44 public:
46 IGeometrySource const& source, ship::IFieldSource const& field_source,
47 SDMode sd_mode = SDMode::scoring, double ke_threshold = 0.0,
48 std::vector<std::pair<std::string, double>> regions = {})
49 : source_{&source},
50 field_source_{&field_source},
51 sd_mode_{sd_mode},
52 ke_threshold_{ke_threshold},
53 regions_{std::move(regions)} {}
54
55 // Called once, master thread only
56 G4VPhysicalVolume* Construct() override { return source_->construct(); }
57
58 void ConstructSDandField() override {
59 auto const& sv_names = source_->sensitiveVolumes();
60 DetectorIdMap detector_ids;
61
62 for (auto* lv : *G4LogicalVolumeStore::GetInstance()) {
63 for (int i = 0; i < static_cast<int>(sv_names.size()); ++i) {
64 if (G4StrUtil::contains(lv->GetName(), std::string_view{sv_names[i]})) {
65 detector_ids.emplace(lv, i);
66 break;
67 }
68 }
69 }
70
71 G4VSensitiveDetector* sd = nullptr;
72 if (sd_mode_ == SDMode::crossing) {
73 sd = new CrossingSD("CrossingSD", detector_ids, ke_threshold_);
74 } else {
75 sd = new ScoringSD("ScoringSD", detector_ids);
76 }
77 G4SDManager::GetSDMpointer()->AddNewDetector(sd);
78
79 for (auto [lv, _] : detector_ids) {
80 lv->SetSensitiveDetector(sd);
81 }
82
83 // Install per-magnet G4FieldManagers on every logical volume whose name
84 // contains the configured pattern. Outside these volumes Geant4 never
85 // invokes the field, so drift regions stay cost-free. An unmatched
86 // pattern is treated as a hard configuration error (wrong field setup is
87 // a silent physics bug otherwise).
88 for (auto const& fr : field_source_->regions()) {
89 auto* adapter = new ship::G4MagFieldAdapter(fr.field);
90 auto* fmgr = new G4FieldManager(adapter);
91 bool matched = false;
92 for (auto* lv : *G4LogicalVolumeStore::GetInstance()) {
93 if (G4StrUtil::contains(lv->GetName(),
94 std::string_view{fr.volume_pattern})) {
95 lv->SetFieldManager(fmgr, /*forceToAllDaughters=*/true);
96 matched = true;
97 }
98 }
99 if (!matched)
100 throw std::runtime_error("Field region '" + fr.name +
101 "': volume_pattern '" + fr.volume_pattern +
102 "' matches no logical volumes");
103 }
104
105 // Create G4Regions with custom production cuts (once only)
106 std::call_once(regions_flag_, [this]() {
107 for (auto const& [pattern, cut_mm] : regions_) {
108 auto* region = new G4Region(pattern);
109 auto* cuts = new G4ProductionCuts();
110 cuts->SetProductionCut(cut_mm * mm);
111 region->SetProductionCuts(cuts);
112 for (auto* lv : *G4LogicalVolumeStore::GetInstance()) {
113 if (G4StrUtil::contains(lv->GetName(), std::string_view{pattern}))
114 region->AddRootLogicalVolume(lv);
115 }
116 }
117 });
118 }
119};
120
121} // namespace SHiP::g4
Definition geometry_source.hpp:20
virtual G4VPhysicalVolume * construct() const =0
Build or return the cached G4 world volume.
virtual std::vector< std::string > const & sensitiveVolumes() const =0
Volume names to assign sensitive detectors to.
Definition detector_construction.hpp:35
G4VPhysicalVolume * Construct() override
Definition detector_construction.hpp:56
void ConstructSDandField() override
Definition detector_construction.hpp:58
ConfigurableDetectorConstruction(IGeometrySource const &source, ship::IFieldSource const &field_source, SDMode sd_mode=SDMode::scoring, double ke_threshold=0.0, std::vector< std::pair< std::string, double > > regions={})
Definition detector_construction.hpp:45
Definition geant4_sim_core.hpp:73
Definition geant4_sim_core.hpp:50
Definition detector_construction.hpp:31
SDMode
Definition detector_construction.hpp:33
std::unordered_map< G4LogicalVolume *, int > DetectorIdMap
Definition geant4_sim_core.hpp:30