SHiP Geometry
SHiP experiment geometry implementation using GeoModel.
Loading...
Searching...
No Matches
SBTEnvelope.h
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (C) CERN for the benefit of the SHiP Collaboration
3
4#pragma once
5
7
8#include <algorithm>
9#include <array>
10#include <cstddef>
11#include <span>
12
13class GeoMaterial;
14class GeoPhysVol;
15
16namespace SHiPGeometry::SBT {
17
54 double z_lo_mm = 0.0;
55 double z_hi_mm = 0.0;
56 double dx_lo_mm = 0.0;
57 double dx_hi_mm = 0.0;
58 double dy_lo_mm = 0.0;
59 double dy_hi_mm = 0.0;
60};
61
62namespace detail {
63
64// Index of the sub-frustum containing z (clamped at the exit face).
65//
66// The cast truncates towards zero rather than flooring, which lands on the
67// same index for every f: the two agree for f >= 0, and a negative f clamps
68// to 0 whichever way it was rounded. Truncating also keeps this header clear
69// of C++23's constexpr <cmath>, which not every standard library ships yet.
70constexpr int subFrustumAt(double z_mm, const SBTParams& params = kSBT) {
71 const double f = (z_mm - params.zEntrance) / subLength(params);
72 return std::clamp(static_cast<int>(f), 0, params.nSubFrustum - 1);
73}
74
75// The half-width the SIDE containers track at z. Inside the flat piece of a
76// sub-frustum they are frozen at that sub-frustum's entrance half-width (see
77// SBTSensorBuilder::placeSideContainer), which is what makes the X envelope a
78// sawtooth rather than a straight line.
79constexpr double sideTrackedXHalf(double z_mm, const SBTParams& params = kSBT) {
80 const int s = subFrustumAt(z_mm, params);
81 const double zLo = zSubLo(s, params);
82 if (z_mm <= zLo + zSplitOffset(params)) {
83 return xHalfAt(zLo, params);
84 }
85 return xHalfAt(z_mm, params);
86}
87
88} // namespace detail
89
97constexpr double innerFreeHalfX(double z_mm, const SBTParams& params = kSBT) {
98 // Side scintillator containers. (Columns and corner beams are outboard of
99 // these by construction: their inner face is at x_half - flange_width/2,
100 // a full container_thickness further out.)
101 return sideSensorInnerX(detail::sideTrackedXHalf(z_mm, params), params);
102}
103
111constexpr double innerFreeHalfY(double z_mm, const SBTParams& params = kSBT) {
112 const double yHalf = yHalfAt(z_mm, params);
113 // Top/bottom scintillator containers ...
114 const double sensor = topBottomSensorInnerY(yHalf, params);
115 // ... and the longitudinal beams' inner flange, which hangs below them.
116 const double beam = longBeamInnerY(yHalf, params);
117 // (Cross-beams sit a full beam-height above y_half and never reach in.)
118 //
119 // KNOWN LIMITATION (conservative): the longitudinal beams do not run the
120 // full sub-frustum length — they stand off by ~0.5*flange_width from each
121 // boundary (SBTStructureBuilder). In those ~142 mm end bands the true Y
122 // bound is the shallower scintillator, but we cap at the deeper beam line
123 // regardless, so the helium is up to ~13.5 mm short of the material there.
124 // This never overlaps (it only makes the helium smaller), but it does leave
125 // the helium slightly inside the "no unphysical margin" ideal over ~2.8 m of
126 // length — worth ~0.18 m^3, i.e. 0.04% of the fiducial volume. Reclaiming it
127 // means subdividing the Y envelope at the beam ends, which has to account
128 // for the flange box's z-projection overshooting its nominal end; deferred
129 // to a dedicated change rather than risking that interaction here.
130 return std::min(sensor, beam);
131}
132
133namespace detail {
134
146constexpr double knotAt(int i, const SBTParams& params = kSBT) {
147 if (i >= 2 * params.nSubFrustum) {
148 return zExit(params);
149 }
150 const int s = i / 2;
151 return (i % 2 == 0) ? zSubLo(s, params) : zSubLo(s, params) + zSplitOffset(params);
152}
153
154// The X envelope STEPS at each zSplitOffset knot: the flat piece's inner face
155// sits at x_half(zLo)-..., and the tracking piece that follows begins at
156// x_half(zSplit)-..., xGrowth*zSplitOffset further out. Sampling the outboard
157// branch at the knot would put the helium's leading edge in the same plane as
158// the flat piece's inner face — no overlap in the strict sense, but a
159// coincident surface, which Geant4's navigator will not thank us for, and
160// which silently eats the clearance besides.
161//
162// So evaluate the envelope as a *closed* set: at a knot, take the smaller of
163// the two one-sided limits. The helium is then continuous, strictly inscribed,
164// and keeps its full clearance everywhere.
165constexpr double envelopeAtKnot(double z_mm, bool isX, const SBTParams& params = kSBT) {
166 constexpr double kEps = 1e-6;
167 const double lo = std::max(z_mm - kEps, params.zEntrance);
168 const double hi = std::min(z_mm + kEps, zExit(params));
169 return isX ? std::min(innerFreeHalfX(lo, params), innerFreeHalfX(hi, params))
170 : std::min(innerFreeHalfY(lo, params), innerFreeHalfY(hi, params));
171}
172
173} // namespace detail
174
185constexpr HeliumPiece heliumPieceAt(int i, const SBTParams& params = kSBT) {
186 const double zLo = detail::knotAt(i, params);
187 const double zHi = detail::knotAt(i + 1, params);
188 return HeliumPiece{
189 .z_lo_mm = zLo,
190 .z_hi_mm = zHi,
191 .dx_lo_mm = detail::envelopeAtKnot(zLo, /*isX=*/true, params) - params.heliumClearance,
192 .dx_hi_mm = detail::envelopeAtKnot(zHi, /*isX=*/true, params) - params.heliumClearance,
193 .dy_lo_mm = detail::envelopeAtKnot(zLo, /*isX=*/false, params) - params.heliumClearance,
194 .dy_hi_mm = detail::envelopeAtKnot(zHi, /*isX=*/false, params) - params.heliumClearance,
195 };
196}
197
220constexpr bool leavesDecayRegion(const SBTParams& params = kSBT) {
221 if (!isWellFormed(params)) {
222 return false;
223 }
224 for (int i = 0; i < 2 * params.nSubFrustum; ++i) {
225 const HeliumPiece piece = heliumPieceAt(i, params);
226 if (piece.z_hi_mm <= piece.z_lo_mm) {
227 return false;
228 }
229 if (piece.dx_lo_mm <= 0.0 || piece.dx_hi_mm <= 0.0 || piece.dy_lo_mm <= 0.0 ||
230 piece.dy_hi_mm <= 0.0) {
231 return false;
232 }
233 }
234 return true;
235}
236
244inline constexpr auto kHeliumPieces = [] {
245 std::array<HeliumPiece, 2 * kSBT.nSubFrustum> pieces{};
246 for (int i = 0; i < 2 * kSBT.nSubFrustum; ++i) {
247 pieces[static_cast<std::size_t>(i)] = heliumPieceAt(i, kSBT);
248 }
249 return pieces;
250}();
251
252// An SBT whose containers, beams or clearances swallow the decay region fails
253// the build.
254static_assert(leavesDecayRegion(kSBT),
255 "the configured SBT leaves no decay region — check containerThickness, "
256 "hbeamHeight, nSubFrustum and heliumClearance");
257
258// ── Arbitrary parameters, for the tests ─────────────────────────────────
259//
260// std::array's extent is part of its type, and test_decayvolume sweeps
261// nSubFrustum between 5 and 20, so a swept stack needs one type that covers
262// them all. This is a testing affordance, not a geometry parameter: the
263// shipped stack is kHeliumPieces above and is exactly sized.
264
265inline constexpr int kMaxSubFrustum = 64;
266static_assert(kSBT.nSubFrustum <= kMaxSubFrustum);
267
269 std::array<HeliumPiece, 2u * kMaxSubFrustum> pieces{};
270 std::size_t count = 0;
272 [[nodiscard]] constexpr std::span<const HeliumPiece> view() const {
273 return {pieces.data(), count};
274 }
275};
276
280constexpr HeliumSlabs heliumSlabs(const SBTParams& params = kSBT) {
281 HeliumSlabs slabs;
282 if (params.nSubFrustum <= 0 || params.nSubFrustum > kMaxSubFrustum) {
283 return slabs;
284 }
285 slabs.count = 2u * static_cast<std::size_t>(params.nSubFrustum);
286 for (std::size_t i = 0; i < slabs.count; ++i) {
287 slabs.pieces[i] = heliumPieceAt(static_cast<int>(i), params);
288 }
289 return slabs;
290}
291
306void buildHelium(GeoPhysVol* container, const GeoMaterial* helium,
307 std::span<const HeliumPiece> pieces = kHeliumPieces);
308
309} // namespace SHiPGeometry::SBT
constexpr double knotAt(int i, const SBTParams &params=kSBT)
Z of envelope knot i, for 0 <= i <= 2*nSubFrustum.
Definition SBTEnvelope.h:146
constexpr int subFrustumAt(double z_mm, const SBTParams &params=kSBT)
Definition SBTEnvelope.h:70
constexpr double sideTrackedXHalf(double z_mm, const SBTParams &params=kSBT)
Definition SBTEnvelope.h:79
constexpr double envelopeAtKnot(double z_mm, bool isX, const SBTParams &params=kSBT)
Definition SBTEnvelope.h:165
Compile-time parameters of the Surround Background Tagger (SBT).
Definition SBTConstants.h:18
constexpr double zSubLo(int s, const SBTParams &params=kSBT)
Z of the start of sub-frustum s (mm).
Definition SBTConstants.h:137
constexpr auto kHeliumPieces
The helium slabs of the shipped SBT.
Definition SBTEnvelope.h:244
constexpr bool leavesDecayRegion(const SBTParams &params=kSBT)
Does params leave a decay region for the helium to fill?
Definition SBTEnvelope.h:220
constexpr HeliumPiece heliumPieceAt(int i, const SBTParams &params=kSBT)
Helium slab i, for 0 <= i < 2*nSubFrustum.
Definition SBTEnvelope.h:185
constexpr HeliumSlabs heliumSlabs(const SBTParams &params=kSBT)
The helium stack for arbitrary params.
Definition SBTEnvelope.h:280
constexpr SBTParams kSBT
The shipped SBT.
Definition SBTConstants.h:75
void buildHelium(GeoPhysVol *container, const GeoMaterial *helium, std::span< const HeliumPiece > pieces=kHeliumPieces)
Place helium slabs into container.
Definition SBTEnvelope.cpp:19
constexpr double yHalfAt(double z_mm, const SBTParams &params=kSBT)
Half-extent of the frustum envelope in Y at a given Z (mm).
Definition SBTConstants.h:133
constexpr double subLength(const SBTParams &params=kSBT)
Length of one sub-frustum along Z (mm).
Definition SBTConstants.h:103
constexpr double topBottomSensorInnerY(double y_half_mm, const SBTParams &params=kSBT)
|Y| of a top/bottom container's innermost face (mm).
Definition SBTConstants.h:192
constexpr double innerFreeHalfX(double z_mm, const SBTParams &params=kSBT)
|X| of the innermost SBT material at z_mm (mm).
Definition SBTEnvelope.h:97
constexpr int kMaxSubFrustum
Definition SBTEnvelope.h:265
constexpr double xHalfAt(double z_mm, const SBTParams &params=kSBT)
Half-extent of the frustum envelope in X at a given Z (mm).
Definition SBTConstants.h:129
constexpr bool isWellFormed(const SBTParams &params=kSBT)
Are params self-consistent, before asking whether they leave a decay region? Every dimension positive...
Definition SBTConstants.h:276
constexpr double zExit(const SBTParams &params=kSBT)
Exit-face Z in the DecayVolume local frame (mm).
Definition SBTConstants.h:117
constexpr double innerFreeHalfY(double z_mm, const SBTParams &params=kSBT)
|Y| of the innermost SBT material at z_mm (mm).
Definition SBTEnvelope.h:111
constexpr double longBeamInnerY(double y_half_mm, const SBTParams &params=kSBT)
|Y| reached by a longitudinal beam's inner flange surface (mm).
Definition SBTConstants.h:261
constexpr double sideSensorInnerX(double x_half_mm, const SBTParams &params=kSBT)
|X| of a side container's innermost face (mm).
Definition SBTConstants.h:178
constexpr double zSplitOffset(const SBTParams &params=kSBT)
Z offset, from the start of a sub-frustum, of the column front-flange outer edge.
Definition SBTConstants.h:164
The innermost free region of the SBT, and the helium that fills it.
Definition SBTEnvelope.h:53
double dx_hi_mm
half-width in X at z_hi
Definition SBTEnvelope.h:57
double z_lo_mm
Definition SBTEnvelope.h:54
double dx_lo_mm
half-width in X at z_lo
Definition SBTEnvelope.h:56
double dy_lo_mm
half-height in Y at z_lo
Definition SBTEnvelope.h:58
double z_hi_mm
Definition SBTEnvelope.h:55
double dy_hi_mm
half-height in Y at z_hi
Definition SBTEnvelope.h:59
Definition SBTEnvelope.h:268
constexpr std::span< const HeliumPiece > view() const
A view of the filled prefix. Borrows from *this — keep it alive.
Definition SBTEnvelope.h:272
std::size_t count
Definition SBTEnvelope.h:270
std::array< HeliumPiece, 2u *kMaxSubFrustum > pieces
Definition SBTEnvelope.h:269
Every dimension the SBT geometry is parameterised by (mm).
Definition SBTConstants.h:33
int nSubFrustum
Definition SBTConstants.h:40