Skip to content

Commit 948e308

Browse files
committed
Add implementations for cutting staves and sensors on nominal radii. Also change kSensorsPerStack to a vector in which order of sensor stack height we will pad the staves.
1 parent 88d69a0 commit 948e308

3 files changed

Lines changed: 169 additions & 73 deletions

File tree

Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
using PositionType = std::pair<double, unsigned>;
2626
using PositionTypes = std::vector<PositionType>;
2727
using PosNegPositionTypes = std::pair<PositionTypes, PositionTypes>;
28+
// define type of the y position range: First pair is (min, max) for positive y
29+
using PositionRangeType = std::pair<std::pair<double, double>, std::pair<double, double>>;
2830
namespace Constants = o2::ft3::ModuleConstants;
2931

3032
class FT3Module
@@ -67,12 +69,13 @@ class FT3Module
6769
double Rout, double overlap, TGeoVolume* motherVolume);
6870

6971
// Helper functions
70-
void fill_stave(PosNegPositionTypes& y_positions, double Rout,
71-
double x_left, unsigned kSensorStack, double tolerance,
72-
std::pair<double, double> y_start);
72+
void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout,
73+
double x_left, unsigned kSensorStack, double tolerance_inner,
74+
double tolerance_outer, PositionRangeType y_range);
7375
void addStaveVolume(
7476
TGeoVolume* motherVolume, std::string volumeName, int direction,
75-
unsigned* volume_count, double staveLength,
77+
unsigned* volume_count, double staveLength, double tolerance_inner,
78+
double tolerance_outer, double Rin, double Rout,
7679
double x_mid, double y_mid, double z_stave_shift_abs);
7780
void addDetectorVolume(
7881
TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count,

Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,19 @@ namespace o2::ft3::ModuleConstants
4646
const double single_sensor_height = 3.2;
4747
const double inactive_width = 0.2;
4848
const double sensor2x1_gap = 0.02;
49+
const double stackGap = sensor2x1_gap; // gap between 2xN module stacks
4950

5051
const double active_width = single_sensor_width - inactive_width;
5152
const double active_height = single_sensor_height;
5253

5354
const double sensor2x1_width = 2 * single_sensor_width;
5455
const double sensor2x1_active_width = 2 * active_width;
5556
const double sensor2x1_height = single_sensor_height;
56-
const unsigned kSensorsPerStack = 1;
57-
const double sensor_stack_height = kSensorsPerStack * sensor2x1_height +
58-
(kSensorsPerStack - 1) * sensor2x1_gap;
57+
const std::vector<unsigned> kSensorsPerStack = {4,2,1};
58+
inline const double getStackHeight(unsigned nSensorsPerStack) {
59+
return nSensorsPerStack * sensor2x1_height +
60+
(nSensorsPerStack - 1) * sensor2x1_gap;
61+
}
5962
/*
6063
* Constants for staves are written for both positive
6164
* and negative x even though they are just mirrored now,

Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx

Lines changed: 156 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,44 @@ double calculate_y_circle(double x, double radius)
9393
return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0;
9494
}
9595

96+
std::pair<double, double> calculate_y_range(
97+
double x_left, double x_right, double Rin, double Rout)
98+
{
99+
double max_y_abs;
100+
double min_y_abs;
101+
/*
102+
* Have 5 cases:
103+
* (1) Stave wholly on the left of inner radius
104+
* (2) Stave wholly on the left, but within inner radius
105+
* (3) Stave crosses the middle x=0
106+
* (4) Stave wholly on the right, but within inner radius
107+
* (5) Stave wholly on the right of inner radius
108+
*/
109+
if (x_right < -Rin) {
110+
// Stave is completely on the left of inner radius
111+
min_y_abs = 0;
112+
max_y_abs = calculate_y_circle(x_left, Rout);
113+
} else if (x_left < -Constants::sensor2x1_width) {
114+
// Stave is completely on the left, but within inner radius
115+
min_y_abs = calculate_y_circle(x_right, Rin);
116+
max_y_abs = calculate_y_circle(x_left, Rout);
117+
} else if (x_left < 0) {
118+
// Stave crosses the middle x=0
119+
min_y_abs = Rin;
120+
// x_right should be > 0, but might have FLP issues, so do abs nonetheless
121+
max_y_abs = calculate_y_circle(std::max(std::abs(x_left), std::abs(x_right)), Rout);
122+
} else if (x_left < Rin) {
123+
// Stave is completely on the right, but within inner radius
124+
min_y_abs = calculate_y_circle(x_left, Rin);
125+
max_y_abs = calculate_y_circle(x_right, Rout);
126+
} else {
127+
// Stave is completely on the right of inner radius
128+
min_y_abs = 0.;
129+
max_y_abs = calculate_y_circle(x_right, Rout);
130+
}
131+
return {min_y_abs, max_y_abs};
132+
}
133+
96134
/*
97135
* This function is a helper function which will pad out the stave with sensors
98136
* until there is no more space available.
@@ -111,54 +149,60 @@ double calculate_y_circle(double x, double radius)
111149
* y_start: the y positions to start placing sensors,
112150
* for positive and negative y respectively
113151
*/
114-
void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout,
115-
double x_left, unsigned kSensorStack, double tolerance,
116-
std::pair<double, double> y_start={0, 0})
152+
void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout,
153+
double x_left, unsigned kSensorStack, double tolerance_inner,
154+
double tolerance_outer, PositionRangeType y_ranges)
117155
{
118156
// start with upper half of the stave, then mirror to the bottom half
119157
double x_right = x_left + Constants::sensor2x1_width;
120-
double y_top = y_start.first;
121-
// either start at given start position, or at the top of the last placed sensors
122-
if (!y_positions.first.empty()) {
123-
y_top = y_positions.first.back().first
124-
+ Constants::sensor2x1_height * y_positions.first.back().second
125-
+ Constants::sensor2x1_gap;
126-
}
127158
// add the height of kSensorStack sensors + the gaps in between them
128-
double sensorTileHeight = Constants::sensor2x1_height * kSensorStack
129-
+ Constants::sensor2x1_gap * (kSensorStack - 1);
159+
double sensorStackHeight = Constants::getStackHeight(kSensorStack);
160+
double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap;
130161

131-
double max_y_abs;
132-
// y_max(x_left) > y_max(x_right) means that the top of the sensor
133-
// will hit the outer radius at x_right first
134-
if (x_left > -Constants::single_sensor_width) {
135-
// tolerance already in maximum y position
136-
max_y_abs = calculate_y_circle(x_right, Rout) - tolerance;
162+
std::pair<double, double> absAllowedYRange =
163+
calculate_y_range(x_left, x_right, Rin, Rout);
164+
165+
// shift allowed range by tolerance. Note that this sum can be negative, but
166+
// that is not a problem, it just means that we can always place sensors over the edge
167+
absAllowedYRange.first += tolerance_inner;
168+
absAllowedYRange.second -= tolerance_outer;
169+
// in case a big tolerance is given, cut on the given range instead
170+
double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second);
171+
172+
double y_top; // top half of the xy grid
173+
// either start at given value (adjusted for tolerance), or at last placed sensors
174+
if (!y_positions.first.empty()) { // sensors already placed
175+
y_top = y_positions.first.back().first + sensorAbsStackYShift;
176+
} else if (y_ranges.first.first < absAllowedYRange.first) {
177+
// given starting y is lower than the minimum allowed y, start at the latter.
178+
y_top = absAllowedYRange.first;
137179
} else {
138-
max_y_abs = calculate_y_circle(x_left, Rout) - tolerance;
180+
// given starting y is acceptable, start there
181+
y_top = y_ranges.first.first;
139182
}
140-
unsigned n_sensors_placed = y_positions.first.size() + y_positions.second.size();
141-
142-
while ( (y_top + sensorTileHeight) <= max_y_abs ) {
183+
while ( (y_top + sensorStackHeight) <= max_sensor_y_abs ) {
143184
y_positions.first.emplace_back(y_top, kSensorStack);
144-
y_top += sensorTileHeight + Constants::sensor2x1_gap;
185+
y_top += sensorAbsStackYShift;
145186
}
146187

147188
// now we do the same for the negative y positions
148189
// they do not have to be exactly mirrored, hence done separately
149-
double y_bottom = y_start.second;
190+
double y_bottom;
150191
if (!y_positions.second.empty()) {
151192
// subtract instead to move further down
152-
y_bottom = y_positions.second.back().first
153-
- Constants::sensor2x1_height * y_positions.second.back().second
154-
- Constants::sensor2x1_gap;
193+
y_bottom = y_positions.second.back().first - sensorAbsStackYShift;
194+
} else if (y_ranges.second.first > -absAllowedYRange.first) {
195+
// given starting y is closer to x-axis than max allowed y, start at the latter.
196+
y_bottom = -absAllowedYRange.first;
197+
} else {
198+
// given starting y is acceptable, start there
199+
y_bottom = y_ranges.second.first;
155200
}
156-
157-
while ( (y_bottom - sensorTileHeight) >= -max_y_abs ) {
201+
// fill in the sensors on negative y
202+
while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) {
158203
y_positions.second.emplace_back(y_bottom, kSensorStack);
159-
y_bottom -= (sensorTileHeight + Constants::sensor2x1_gap);
204+
y_bottom -= sensorAbsStackYShift;
160205
}
161-
unsigned sensors_placed_after = y_positions.first.size() + y_positions.second.size();
162206
}
163207

164208
/*
@@ -167,7 +211,8 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout,
167211
*/
168212
void FT3Module::addStaveVolume(
169213
TGeoVolume* motherVolume, std::string volumeName, int direction,
170-
unsigned* volume_count, double staveLength,
214+
unsigned* volume_count, double staveLength, double tolerance_inner,
215+
double tolerance_outer, double Rin, double Rout,
171216
double x_mid, double y_mid, double z_stave_shift_abs)
172217
{
173218
// Set some constants for readability
@@ -215,20 +260,48 @@ void FT3Module::addStaveVolume(
215260
zv_inner[2] = zv_inner[1];
216261
xv_inner[2] = -xv_inner[1];
217262

218-
// create the extruded volumes from z=0 (later y=0 after rotation) to stave length
219-
// and not from midpoint - staveLength/2 to midpoint + staveLength/2,
220-
// translate after rotation
263+
/*
264+
* create the extruded volumes from z=0 (later y=0 after rotation) to stave length
265+
* and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later
266+
*
267+
* Note also that we first need to check if the length is allowed given the inner
268+
* and outer radius of the layer.
269+
*/
270+
double x_left = x_mid - Constants::single_sensor_width;
271+
double x_right = x_mid + Constants::single_sensor_width;
272+
std::pair<double, double> absAllowedYRange =
273+
calculate_y_range(x_left, x_right, Rin, Rout);
274+
275+
// shift allowed range by tolerance. Note that this sum can be negative, but
276+
// that is not a problem, it just means that we can always place staves over the edge
277+
absAllowedYRange.first += tolerance_inner;
278+
absAllowedYRange.second -= tolerance_outer;
279+
280+
double maxStaveLength = absAllowedYRange.second - absAllowedYRange.first;
281+
double staveLengthToUse = std::min(staveLength, maxStaveLength);
282+
if (staveLengthToUse <= 0) {
283+
LOG(warning) << "Stave " << volumeName << " has non-positive length after applying "
284+
<< "tolerance, skipping stave. Max allowed length: " << maxStaveLength
285+
<< " tolerance inner: " << tolerance_inner
286+
<< " tolerance outer: " << tolerance_outer;
287+
return;
288+
}
289+
double staveLengthDiff = staveLength - staveLengthToUse;
290+
if (staveLengthDiff > 0) { // had to cut it, hence y_mid must move too
291+
y_mid = absAllowedYRange.first + staveLengthToUse / 2;
292+
}
293+
221294
TGeoXtru* staveFull = new TGeoXtru(2);
222295
staveFull->SetName(( volumeName + "_Xtru_outer").c_str());
223296
staveFull->DefinePolygon(3, xv_outer, zv_outer);
224297
staveFull->DefineSection(0, 0);
225-
staveFull->DefineSection(1, staveLength);
298+
staveFull->DefineSection(1, staveLengthToUse);
226299

227300
TGeoXtru* staveInner = new TGeoXtru(2);
228301
staveInner->SetName(( volumeName + "_Xtru_inner").c_str());
229302
staveInner->DefinePolygon(3, xv_inner, zv_inner);
230303
staveInner->DefineSection(0, 0);
231-
staveInner->DefineSection(1, staveLength);
304+
staveInner->DefineSection(1, staveLengthToUse);
232305

233306
TGeoCompositeShape* staveShape = new TGeoCompositeShape(
234307
(volumeName + "_shape").c_str(),
@@ -250,7 +323,7 @@ void FT3Module::addStaveVolume(
250323
*/
251324
double z_shift = (direction == 1) ? z_stave_shift_abs : -z_stave_shift_abs;
252325
TGeoCombiTrans* combiTrans =
253-
new TGeoCombiTrans(x_mid, y_mid - staveLength / 2, z_shift, rot);
326+
new TGeoCombiTrans(x_mid, y_mid - staveLengthToUse / 2, z_shift, rot);
254327
motherVolume->AddNode(staveVolume,
255328
*volume_count,
256329
combiTrans);
@@ -452,14 +525,27 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction
452525
double y_midpoint = 0.;
453526
bool mirrorStaveAroundX = false;
454527
// default positive and negative starting points has a gap around x-axis
455-
std::pair<double, double> y_start{0., -Constants::sensor2x1_gap};
528+
PositionRangeType y_ranges = {{0, Constants::y_lengths[i_stave]},
529+
{-Constants::stackGap, -Constants::y_lengths[i_stave]}};
456530
auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID);
457531
if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) {
458532
// there is a defined midpoint for this stave, use this for starting points
459533
y_midpoint = y_midpoint_it->second.first; // avoid double map lookup
460534
mirrorStaveAroundX = y_midpoint_it->second.second;
461-
double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2;
462-
y_start = {y_start_pos, -y_start_pos};
535+
double y_diff_abs = Constants::y_lengths[i_stave] / 2;
536+
y_ranges.first = {y_midpoint - y_diff_abs, y_midpoint + y_diff_abs};
537+
y_ranges.second = {-y_midpoint + y_diff_abs, -y_midpoint - y_diff_abs};
538+
}
539+
540+
// Define tolerances for cutting staves and placing sensors
541+
double tolerance_inner = -1000; // large negative number to allow given numbers
542+
double tolerance_outer = -1000;
543+
// cut staves on nominal inner radius if specified
544+
if (ft3Params.cutStavesOnNominalRadius_inner) {
545+
tolerance_inner = 0.;
546+
}
547+
if (ft3Params.cutStavesOnNominalRadius_outer) {
548+
tolerance_outer = 0.;
463549
}
464550

465551
// Get whether the stave is shifted backward or not
@@ -470,40 +556,44 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction
470556
"_" + std::to_string(direction);
471557
addStaveVolume(
472558
motherVolume, stave_volume_name, direction, &volume_count,
473-
Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave],
559+
Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer,
560+
Rin, Rout, Constants::x_midpoints[i_stave],
474561
y_midpoint, mZ + z_stave_shift_abs
475562
);
476-
if (y_midpoint > Rin) {
477-
// stave midpoint is beyond nominal inner radius,
478-
// so we can reasonably expect to mirror sensors around the x-axis
563+
/*
564+
* There are three cases in which we want to mirror the stave around the x-axis,
565+
* which correspond to the stave not going fully from + to - Rout in y.
566+
*
567+
* (1) The inner tolerance is 0 (or positive)
568+
* a) AND either x_left or x_right lies within the inner radius
569+
* (2) The inner tolerance is large (allow stave placement as wished)
570+
* a) AND the given stave midpoint is above the inner radius
571+
*/
572+
double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2;
573+
double x_right = x_left + Constants::sensor2x1_width;
574+
bool mirrorStaveAroundXAxis = false;
575+
if (tolerance_inner >= 0) {
576+
double x_innermost = std::min(std::abs(x_left), std::abs(x_right));
577+
mirrorStaveAroundXAxis = (x_innermost < Rin);
578+
} else { // Have negative tolerance, so can place staves as wished
579+
mirrorStaveAroundXAxis = (y_midpoint > Rin);
580+
}
581+
// Now create the mirrored stave
582+
if (mirrorStaveAroundXAxis) {
479583
addStaveVolume(
480584
motherVolume, stave_volume_name + "_mirrored", direction, &volume_count,
481-
Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave],
585+
Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer,
586+
Rin, Rout, Constants::x_midpoints[i_stave],
482587
-y_midpoint, mZ + z_stave_shift_abs
483588
);
484589
}
485590

486-
double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2;
487-
double x_right = x_left + Constants::sensor2x1_width;
488-
double tolerance = -Constants::sensor_stack_height; // allow one sensor placement beyond
489-
// cut staves on nominal inner radius if specified
490-
if (ft3Params.cutStavesOnNominalRadius) {
491-
double min_y_at_x;
492-
if (x_left * x_right < 0) {
493-
// stave crosses y-axis, so we start at y=Rin
494-
min_y_at_x = Rin;
495-
} else if (x_left > 0) {
496-
// stave is on the right side, so minimum y is at x_left
497-
min_y_at_x = calculate_y_circle(x_left, Rin);
498-
} else {
499-
// stave is on the left side, so minimum y is at x_right
500-
min_y_at_x = calculate_y_circle(x_right, Rin);
501-
}
502-
y_start = {min_y_at_x, -min_y_at_x};
503-
tolerance = 0.; // no tolerance in case of cutting at nominal radius
591+
// now add the sensor positions on the stave
592+
for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) {
593+
fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left,
594+
Constants::kSensorsPerStack[i_kSens], tolerance_inner,
595+
tolerance_outer, y_ranges);
504596
}
505-
fill_stave(y_positionsPosNeg.back(), Rout, x_left, Constants::kSensorsPerStack,
506-
tolerance, y_start);
507597
}
508598

509599
for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) {

0 commit comments

Comments
 (0)