23#include <spdlog/spdlog.h>
26#include <G4LogicalVolume.hh>
27#include <G4LogicalVolumeStore.hh>
28#include <G4Navigator.hh>
29#include <G4NistManager.hh>
30#include <G4PVPlacement.hh>
31#include <G4Point3D.hh>
32#include <G4SDManager.hh>
34#include <G4StepPoint.hh>
36#include <G4ThreeVector.hh>
38#include <G4Transform3D.hh>
39#include <G4TransportationManager.hh>
41#include <G4VPhysicalVolume.hh>
42#include <G4VSensitiveDetector.hh>
44#include <G4VUserParallelWorld.hh>
45#include <SHiP/QuantityView.hpp>
46#include <SHiP/SimHit.hpp>
47#include <SHiP/Units.hpp>
57#include <unordered_map>
58#include <unordered_set>
80 ship::Length
half_x = ship::Length::zero();
81 ship::Length
half_y = ship::Length::zero();
82 ship::Length
r_min = ship::Length::zero();
83 ship::Length
r_max = ship::Length::zero();
84 ship::Length
half_z = ship::Length::zero();
91 std::optional<ship::Vec3<ship::Length>>
position;
93 ship::Length
gap = ship::Length::zero();
110namespace scoring_plane_detail {
114 auto const zero = ship::Length::zero();
116 return "half_z must be positive";
120 return "a box needs positive half_x and half_y";
123 return "r_min/r_max apply to tubes only";
128 return "a tube needs 0 <= r_min < r_max";
131 return "half_x/half_y apply to boxes only";
138 auto const zero = ship::Length::zero();
140 return "set exactly one of 'position' and 'after_volume'";
143 return "'gap' applies to 'after_volume' placement only";
146 return "gap must not be negative";
156 std::vector<ScoringPlaneConfig>
const& planes) {
157 std::unordered_set<std::string> names;
158 std::unordered_set<std::int32_t> ids;
159 for (
auto const& p : planes) {
160 if (p.name.empty()) {
161 throw std::invalid_argument(
"scoring plane without a name");
164 if (!names.insert(p.name).second) {
165 error =
"name used by more than one plane";
166 }
else if (!ids.insert(p.id).second) {
167 error =
"id " + std::to_string(p.id) +
" used by more than one plane";
168 }
else if (p.ke_threshold < ship::Energy::zero()) {
169 error =
"ke_threshold must not be negative";
173 if (!error.empty()) {
174 throw std::invalid_argument(
"scoring plane '" + p.name +
"': " + error);
179namespace scoring_plane_detail {
184 G4VPhysicalVolume*
pv =
nullptr;
192template <
typename Visit>
193void walk(G4VPhysicalVolume* pv, G4VPhysicalVolume* parent,
194 G4Transform3D
const& to_global, Visit& visit) {
195 visit(
Placement{.
pv = pv, .parent = parent, .to_global = to_global});
196 auto* lv = pv->GetLogicalVolume();
197 for (std::size_t i = 0; i < lv->GetNoDaughters(); ++i) {
198 auto* daughter = lv->GetDaughter(
static_cast<G4int
>(i));
199 if (daughter->IsReplicated()) {
203 to_global * G4Transform3D(daughter->GetObjectRotationValue(),
204 daughter->GetObjectTranslation()),
213 std::string
const& lv_name,
214 std::string
const& plane,
char const* role) {
215 std::vector<Placement> found;
217 if (p.pv->GetLogicalVolume()->GetName() == lv_name) {
221 walk(world,
nullptr, G4Transform3D{}, visit);
222 if (found.size() != 1) {
223 throw std::runtime_error(
224 "scoring plane '" + plane +
"': " + role +
" '" + lv_name +
225 "' must be placed exactly once in the geometry, found " +
226 std::to_string(found.size()) +
227 " placement(s) (volumes inside replicas are not searched)");
229 return found.front();
237 p.
pv->GetLogicalVolume()->GetSolid()->BoundingLimits(lo, hi);
238 constexpr double inf = std::numeric_limits<double>::infinity();
239 G4ThreeVector gmin{inf, inf, inf};
240 G4ThreeVector gmax{-inf, -inf, -inf};
241 for (
int corner = 0; corner < 8; ++corner) {
242 G4Point3D
const local{(corner & 1) != 0 ? hi.x() : lo.x(),
243 (corner & 2) != 0 ? hi.y() : lo.y(),
244 (corner & 4) != 0 ? hi.z() : lo.z()};
245 G4Point3D
const global = p.
to_global * local;
246 for (
int axis = 0; axis < 3; ++axis) {
247 gmin[axis] = std::min(gmin[axis], global[axis]);
248 gmax[axis] = std::max(gmax[axis], global[axis]);
256namespace scoring_plane_detail {
261 G4VPhysicalVolume* mass_world) {
265 return {cb::g4(pos[0]), cb::g4(pos[1]), cb::g4(pos[2])};
269 if (ref.parent ==
nullptr) {
270 throw std::runtime_error(
"scoring plane '" + cfg.
name +
271 "': after_volume is the world volume");
274 return {0.5 * (lo.x() + hi.x()), 0.5 * (lo.y() + hi.y()),
275 hi.z() + cb::g4(cfg.
gap) + cb::g4(cfg.
half_z)};
287 return {.shape = cfg.
shape,
289 .half_x = cb::g4(cfg.
half_x),
290 .half_y = cb::g4(cfg.
half_y),
291 .r_min = cb::g4(cfg.
r_min),
292 .r_max = cb::g4(cfg.
r_max),
293 .half_z = cb::g4(cfg.
half_z)};
300 double const dx = std::abs(x -
box.centre.x());
301 double const dy = std::abs(y -
box.centre.y());
303 std::hypot(std::max(dx -
box.half_x, 0.), std::max(dy -
box.half_y, 0.)),
304 std::hypot(dx +
box.half_x, dy +
box.half_y)};
311 double const d = std::hypot(x -
tube.centre.x(), y -
tube.centre.y());
312 double const closest = d <
tube.r_min ?
tube.r_min - d
313 : d <=
tube.r_max ? 0.
315 return {closest, d +
tube.r_max};
333 auto const [closest, farthest] =
337 return closest <
tube.r_max && farthest >
tube.r_min;
343 G4VPhysicalVolume* world) {
346 world->GetLogicalVolume()->GetSolid()->BoundingLimits(lo, hi);
347 lo += world->GetObjectTranslation();
348 hi += world->GetObjectTranslation();
351 G4ThreeVector
const half{hx, hy, e.
half_z};
352 for (
int axis = 0; axis < 3; ++axis) {
353 if (e.
centre[axis] - half[axis] < lo[axis] ||
354 e.
centre[axis] + half[axis] > hi[axis]) {
355 throw std::runtime_error(
"scoring plane '" + name +
356 "' extends beyond the world volume");
369 2 * std::numbers::pi);
382 G4VPhysicalVolume* mass_world, G4VPhysicalVolume* scoring_world,
383 std::vector<ScoringPlaneConfig>
const& planes) {
385 namespace detail = scoring_plane_detail;
389 if (planes.empty()) {
397 std::vector<detail::Extent> extents;
398 extents.reserve(planes.size());
399 for (
auto const& cfg : planes) {
400 if (G4LogicalVolumeStore::GetInstance()->GetVolume(cfg.name,
false) !=
402 throw std::runtime_error(
"scoring plane '" + cfg.name +
403 "': the geometry already has a volume with "
407 detail::extent(cfg, detail::plane_centre(cfg, mass_world)));
408 detail::check_inside_world(cfg.name, extents.back(), scoring_world);
410 for (std::size_t i = 0; i < planes.size(); ++i) {
411 for (std::size_t j = 0; j < i; ++j) {
412 if (detail::overlap(extents[i], extents[j])) {
413 throw std::runtime_error(
"scoring planes '" + planes[j].name +
414 "' and '" + planes[i].name +
"' overlap");
419 auto* vacuum = G4NistManager::Instance()->FindOrBuildMaterial(
"G4_Galactic");
420 G4Transform3D
const world_to_global(scoring_world->GetObjectRotationValue(),
421 scoring_world->GetObjectTranslation());
422 for (std::size_t i = 0; i < planes.size(); ++i) {
423 auto const& cfg = planes[i];
424 G4ThreeVector
const centre = extents[i].centre;
425 auto* lv =
new G4LogicalVolume(detail::make_solid(cfg), vacuum, cfg.name);
428 auto* pv =
new G4PVPlacement(
429 world_to_global.inverse() * G4Translate3D(centre), lv, cfg.name,
430 scoring_world->GetLogicalVolume(),
false, 0);
433 if (pv->CheckOverlaps(10000)) {
434 throw std::runtime_error(
"scoring plane '" + cfg.name +
435 "' extends beyond the world volume");
440 .ke_threshold = cb::g4(cfg.ke_threshold),
442 auto const mm = [](
double v) {
443 return cb::length(v).numerical_value_in(ship::units::mm);
446 "geant4: scoring plane '{}' (id {}) placed, centre ({:.1f}, {:.1f}, "
448 cfg.name, cfg.id, mm(centre.x()), mm(centre.y()), mm(centre.z()),
449 cfg.kill ?
", kills every track stepping into it" :
"");
464 : G4VSensitiveDetector(name), planes_{std::move(planes)} {}
467 auto const* pre = step->GetPreStepPoint();
469 planes_.find(pre->GetTouchable()->GetVolume()->GetLogicalVolume());
470 if (it == planes_.end()) {
473 auto const& plane = it->second;
474 auto* track = step->GetTrack();
482 track->SetTrackStatus(fStopAndKill);
488 if (pre->GetStepStatus() != fGeomBoundary ||
489 pre->GetKineticEnergy() < plane.ke_threshold) {
494 hit.geometryNodeId = plane.id;
495 hit.energyDeposit = 0;
500 ship::view::setPathLength(hit,
521 auto* mass_world = G4TransportationManager::GetTransportationManager()
522 ->GetNavigatorForTracking()
524 auto* scoring_world = GetWorld();
525 world_lv_ = scoring_world->GetLogicalVolume();
530 if (planes_.empty()) {
534 G4SDManager::GetSDMpointer()->AddNewDetector(sd);
535 for (
auto const& [lv, _] : planes_) {
536 lv->SetSensitiveDetector(sd);
543 [[nodiscard]]
bool contains(G4LogicalVolume* lv)
const {
544 return lv == world_lv_ || planes_.contains(lv);
548 std::vector<ScoringPlaneConfig> configs_;
552 G4LogicalVolume* world_lv_ =
nullptr;
The parallel world holding the scoring planes.
Definition scoring_planes.hpp:515
bool contains(G4LogicalVolume *lv) const
Whether lv belongs to this world (a plane or the world volume), so that detector-geometry patterns (s...
Definition scoring_planes.hpp:543
ScoringParallelWorld(std::vector< ScoringPlaneConfig > planes)
Definition scoring_planes.hpp:517
void ConstructSD() override
Definition scoring_planes.hpp:529
void Construct() override
Definition scoring_planes.hpp:520
Records one SimHit per track entering a scoring plane through its surface, regardless of particle typ...
Definition scoring_planes.hpp:461
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition scoring_planes.hpp:466
ScoringPlaneSD(G4String const &name, ScoringPlaneMap planes)
Definition scoring_planes.hpp:463
Extent extent(ScoringPlaneConfig const &cfg, G4ThreeVector centre)
Definition scoring_planes.hpp:285
void check_inside_world(std::string const &name, Extent const &e, G4VPhysicalVolume *world)
Throw if e (plane name) is not inside the world's bounding box.
Definition scoring_planes.hpp:342
std::pair< double, double > distance_range_to_box(double x, double y, Extent const &box)
Closest and farthest distance from the point (x, y) to the points of a box's cross-section,...
Definition scoring_planes.hpp:298
void walk(G4VPhysicalVolume *pv, G4VPhysicalVolume *parent, G4Transform3D const &to_global, Visit &visit)
Depth-first visit of every placement below (and including) pv.
Definition scoring_planes.hpp:193
std::string placement_error(ScoringPlaneConfig const &p)
What is wrong with a plane's placement settings, or an empty string.
Definition scoring_planes.hpp:137
Placement find_unique(G4VPhysicalVolume *world, std::string const &lv_name, std::string const &plane, char const *role)
The single placement of the logical volume named lv_name.
Definition scoring_planes.hpp:212
bool overlap(Extent const &a, Extent const &b)
Whether two axis-aligned planes share any volume; planes that only touch do not overlap.
Definition scoring_planes.hpp:323
G4ThreeVector plane_centre(ScoringPlaneConfig const &cfg, G4VPhysicalVolume *mass_world)
A plane's centre in the global frame: its position, or behind its after_volume in the detector geomet...
Definition scoring_planes.hpp:260
std::pair< G4ThreeVector, G4ThreeVector > global_bounds(Placement const &p)
Global axis-aligned bounding box of a placed volume.
Definition scoring_planes.hpp:233
G4VSolid * make_solid(ScoringPlaneConfig const &cfg)
Definition scoring_planes.hpp:361
std::string shape_error(ScoringPlaneConfig const &p)
What is wrong with a plane's shape and size, or an empty string.
Definition scoring_planes.hpp:113
std::pair< double, double > distance_range_to_tube(double x, double y, Extent const &tube)
Closest and farthest distance from the point (x, y) to the points of a tube's cross-section,...
Definition scoring_planes.hpp:309
Definition detector_construction.hpp:33
constexpr std::int32_t kScoringPlaneDetectorId
SimHit::detectorId of every scoring-plane hit.
Definition scoring_plane_id.hpp:21
thread_local std::vector< SimHit > tl_hits
Definition geant4_sim_core.hpp:29
ScoringPlaneMap place_scoring_planes(G4VPhysicalVolume *mass_world, G4VPhysicalVolume *scoring_world, std::vector< ScoringPlaneConfig > const &planes)
Build the configured planes and place them in scoring_world, the parallel world; after_volume referen...
Definition scoring_planes.hpp:381
ScoringShape
Definition scoring_planes.hpp:72
std::unordered_map< G4LogicalVolume *, ScoringPlaneInfo > ScoringPlaneMap
Definition scoring_planes.hpp:108
SimHit make_base_hit(G4Step const *step, int detector_id)
Definition geant4_sim_core.hpp:36
void validate_scoring_planes(std::vector< ScoringPlaneConfig > const &planes)
Check a set of plane configurations for consistency on their own (no geometry needed); throws std::in...
Definition scoring_planes.hpp:155
constexpr char const * kScoringWorldName
Name of the parallel world holding the scoring planes, and of the G4ParallelWorldPhysics constructor ...
Definition scoring_planes.hpp:70
Definition clhep_bridge.hpp:22
ship::Length length(G4double v)
Definition clhep_bridge.hpp:28
One scoring plane, as configured in the geant4 module's scoring_planes.
Definition scoring_planes.hpp:76
ship::Energy ke_threshold
Record only tracks entering with at least this kinetic energy.
Definition scoring_planes.hpp:95
ship::Length gap
Definition scoring_planes.hpp:93
ship::Length r_max
tube only
Definition scoring_planes.hpp:83
ship::Length half_y
box only
Definition scoring_planes.hpp:81
ship::Length half_x
box only
Definition scoring_planes.hpp:80
std::string after_volume
Definition scoring_planes.hpp:92
ship::Length half_z
both shapes
Definition scoring_planes.hpp:84
ScoringShape shape
Definition scoring_planes.hpp:79
ship::Length r_min
tube only
Definition scoring_planes.hpp:82
std::optional< ship::Vec3< ship::Length > > position
Definition scoring_planes.hpp:91
bool kill
Stop and kill every track that steps into the plane, recorded or not (FairShip's StopTrack()).
Definition scoring_planes.hpp:98
std::string name
Volume name; must not exist in the geometry yet.
Definition scoring_planes.hpp:77
What the sensitive detector needs to know about a placed plane.
Definition scoring_planes.hpp:102
double ke_threshold
Definition scoring_planes.hpp:104
std::int32_t id
Definition scoring_planes.hpp:103
bool kill
Definition scoring_planes.hpp:105
A placed plane's extent in the global frame (Geant4 internal units).
Definition scoring_planes.hpp:279
double half_z
Definition scoring_planes.hpp:282
double half_y
Definition scoring_planes.hpp:282
G4ThreeVector centre
Definition scoring_planes.hpp:281
double r_min
Definition scoring_planes.hpp:282
ScoringShape shape
Definition scoring_planes.hpp:280
double r_max
Definition scoring_planes.hpp:282
double half_x
Definition scoring_planes.hpp:282
A physical volume reached from the world, with the transform taking its local frame to the global one...
Definition scoring_planes.hpp:183
G4VPhysicalVolume * parent
Definition scoring_planes.hpp:185
G4VPhysicalVolume * pv
Definition scoring_planes.hpp:184
G4Transform3D to_global
Definition scoring_planes.hpp:186