Project
Loading...
Searching...
No Matches
FT3Module.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
17#include <TGeoManager.h>
18#include <TGeoMaterial.h>
19#include <TGeoMedium.h>
20#include <TGeoBBox.h>
21#include <TGeoXtru.h>
22#include <TGeoMatrix.h>
23#include <TGeoCompositeShape.h>
24#include <Framework/Logger.h>
25#include <cmath>
26#include <iostream>
27#include <vector>
28#include <set>
29#include <algorithm>
30#include <utility>
31
32TGeoMaterial* FT3Module::siliconMat = nullptr;
33TGeoMedium* FT3Module::siliconMed = nullptr;
34
35TGeoMaterial* FT3Module::copperMat = nullptr;
36TGeoMedium* FT3Module::copperMed = nullptr;
37
38TGeoMaterial* FT3Module::kaptonMat = nullptr;
39TGeoMedium* FT3Module::kaptonMed = nullptr;
40
41TGeoMaterial* FT3Module::epoxyMat = nullptr;
42TGeoMedium* FT3Module::epoxyMed = nullptr;
43
44TGeoMaterial* FT3Module::AluminumMat = nullptr;
45TGeoMedium* FT3Module::AluminumMed = nullptr;
46
47TGeoMaterial* FT3Module::carbonFiberMat = nullptr;
48TGeoMedium* FT3Module::carbonFiberMed = nullptr;
49
51{
52 LOG(debug) << "FT3Module: initialize_materials";
53 if (siliconMat) {
54 return;
55 }
56
57 TGeoManager* geoManager = gGeoManager;
58
59 auto* itsH = new TGeoElement("FT3_H", "Hydrogen", 1, 1.00794);
60 auto* itsC = new TGeoElement("FT3_C", "Carbon", 6, 12.0107);
61 auto* itsO = new TGeoElement("FT3_O", "Oxygen", 8, 15.994);
62
63 siliconMat = new TGeoMaterial("FT3_Silicon", 28.0855, 14, 2.33);
64 siliconMed = new TGeoMedium("FT3_Silicon", 1, siliconMat);
65
66 copperMat = new TGeoMaterial("FT3_Copper", 63.546, 29, 8.96);
67 copperMed = new TGeoMedium("FT3_Copper", 2, copperMat);
68
69 kaptonMat = new TGeoMaterial("FT3_Kapton", 13.84, 6.88, 1.346);
70 kaptonMed = new TGeoMedium("FT3_Kapton", 3, kaptonMat);
71
72 // TODO: Check with Rene the exact type of carbon fiber
73 carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8);
74 carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat);
75
76 // Epoxy: C18 H19 O3
77 auto* itsEpoxy = new TGeoMixture("FT3_Epoxy", 3);
78 itsEpoxy->AddElement(itsC, 18);
79 itsEpoxy->AddElement(itsH, 19);
80 itsEpoxy->AddElement(itsO, 3);
81 itsEpoxy->SetDensity(2.186);
82
83 epoxyMed = new TGeoMedium("FT3_Epoxy", 4, itsEpoxy);
84 epoxyMat = epoxyMed->GetMaterial();
85
86 AluminumMat = new TGeoMaterial("Aluminum", 26.98, 13, 2.7);
87 AluminumMed = new TGeoMedium("Aluminum", 5, AluminumMat);
88 LOG(debug) << "FT3Module: done initialize_materials";
89}
90
91double calculate_y_circle(double x, double radius)
92{
93 return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0;
94}
95
96std::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
134/*
135 * This function is a helper function which will pad out the stave with sensors
136 * until there is no more space available.
137 *
138 * Arguments:
139 * y_positions: a pair of vectors, where each vector contains pairs of
140 * y position and stack height for the positive and negative y positions respectively.
141 * This argument will be appended with the new sensor positions and stack heights.
142 * Rout: the outer radius of the layer
143 * Rin: the inner radius of the layer
144 * x_left: the x position of the left edge of the sensor to be placed
145 * kSensorStack: the number of sensors to be stacked on top of each other
146 * tolerance: the tolerance to be subtracted from the maximum y position to avoid
147 * placing sensors too close to the edge. If this is negative, it effectively
148 * means that you can place sensors beyond the nominal disc edge
149 * y_start: the y positions to start placing sensors,
150 * for positive and negative y respectively
151 */
152void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout,
153 double x_left, unsigned kSensorStack, PositionRangeType y_ranges,
154 std::pair<double, double>& absAllowedYRange)
155{
156 // start with upper half of the stave, then mirror to the bottom half
157 // add the height of kSensorStack sensors + the gaps in between them
158 double sensorStackHeight = Constants::getStackHeight(kSensorStack);
159 double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap;
160
161 // in case a big tolerance is given, cut on the given range instead
162 double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second);
163
164 double y_top; // top half of the xy grid, y>0
165 // either start at given value (adjusted for tolerance), or at last placed sensors
166 if (!y_positions.first.empty()) { // sensors already placed
167 double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second);
168 y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap;
169 } else if (absAllowedYRange.first > 0) {
170 // there is a minimum inner value --> start at the max of the two
171 y_top = std::max(absAllowedYRange.first, y_ranges.first.first);
172 } else {
173 // No inner minimum value, start at given value
174 y_top = y_ranges.first.first;
175 }
176 // fill positive y sensor positions
177 while ((y_top + sensorStackHeight) <= max_sensor_y_abs) {
178 y_positions.first.emplace_back(y_top, kSensorStack);
179 y_top += sensorAbsStackYShift;
180 }
181
182 // now we do the same for the negative y positions
183 // they do not have to be exactly mirrored, hence done separately
184 double y_bottom;
185 if (!y_positions.second.empty()) {
186 // subtract instead to move further down
187 double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second);
188 y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap;
189 } else if (absAllowedYRange.first > 0) {
190 // there is a minimum inner value --> start at the min of the two
191 y_bottom = std::min(-absAllowedYRange.first, y_ranges.second.first);
192 } else {
193 // No inner minimum value, start at given value
194 y_bottom = y_ranges.second.first;
195 }
196 // fill in the sensors on negative y
197 while ((y_bottom - sensorStackHeight) >= -max_sensor_y_abs) {
198 y_positions.second.emplace_back(y_bottom, kSensorStack);
199 y_bottom -= sensorAbsStackYShift;
200 }
201}
202
203/*
204 * Create the vertices of the triangles that make up the stave cross section
205 *
206 * Each array of 3 corresponds to x or z values of the 3 triangle vertices,
207 * and the outer array corresponds to which triangle:
208 *
209 * [x_outer, z_outer, x_inner, z_inner], each of which has three values
210 */
211std::array<std::array<double, 3>, 4> buildStaveTriangle(int direction)
212{
213 // Set some constants for readability
216 /*
217 * Inner and outer vertices of the stave cross section triangle
218 * all vertices are at y_mid, we simply extend the triangle into y dir.
219 * We work in the local coordinate system of the stave, but still
220 * call the coordinates x and z for readability.
221 *
222 * 1. Get all local coordinates of the two triangle vertices
223 * 2. Extrude a volume from the subtracted triangle cross section area
224 * 3. Rotate the volume around the x-axis since it is by default in xy,
225 * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y)
226 * 4. Translate the volume to the given position (arguments)
227 *
228 */
229 std::array<double, 3> xv_inner, xv_outer, zv_inner, zv_outer;
230 // calculate the coordinates of the triangle vertices
231 // Top/bottom vertex (apex)
232 xv_outer[0] = 0;
233 zv_outer[0] = (direction == 1) ? -H
234 : H;
235 ;
236 // right
238 zv_outer[1] = 0;
239 // left
240 xv_outer[2] = -xv_outer[1];
241 zv_outer[2] = 0;
242
243 // now get inner vertices, shifted inwards by effective carbon thickness
244 xv_inner[0] = xv_outer[0];
245 double z_shift_inner = d / Constants::sinTheta;
246 zv_inner[0] = (direction == 1) ? zv_outer[0] + z_shift_inner
247 : zv_outer[0] - z_shift_inner;
248 // face vertices, first right
249 zv_inner[1] = (direction == 1) ? zv_outer[1] - d
250 : zv_outer[1] + d;
251 double x_shift_abs = d / TMath::Tan(Constants::alpha / 2);
252 xv_inner[1] = xv_outer[1] - x_shift_abs;
253 // left
254 zv_inner[2] = zv_inner[1];
255 xv_inner[2] = -xv_inner[1];
256
257 return {xv_outer, zv_outer, xv_inner, zv_inner};
258}
259
260/*
261 * This function creates a carbon fibre volume for the stave,
262 * onto which the sensor and its support will be glued.
263 */
264void FT3Module::addStaveVolume(
265 TGeoVolume* motherVolume, std::string volumeName, int direction,
266 unsigned* volume_count, double staveLength,
267 std::array<std::array<double, 3>, 4> staveTriangles,
268 std::pair<double, double>& absAllowedYRange,
269 double x_mid, double y_mid, double z_stave_shift_forward)
270{
271 // The allowed y range is assumed to be non-negative.
272 if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 ||
273 absAllowedYRange.first >= absAllowedYRange.second) {
274 LOG(error) << "Invalid allowed y range in addStaveVolume(): ("
275 << absAllowedYRange.first << ", " << absAllowedYRange.second
276 << "). Both values must be non-negative and the first "
277 << "value must be less than the second value.";
278 return;
279 }
280 // Set the lower and upper y values of the stave:
281 double y_lower = y_mid - staveLength / 2;
282 double y_upper = y_mid + staveLength / 2;
283 bool splitStave = false;
284 if (y_lower > 0) { // This stave is fully above x-axis
285 y_lower = std::max(y_lower, absAllowedYRange.first);
286 y_upper = std::min(y_upper, absAllowedYRange.second);
287 } else if (y_upper < 0) { // stave entirely below x-axis
288 y_lower = std::max(y_lower, -absAllowedYRange.second);
289 y_upper = std::min(y_upper, -absAllowedYRange.first);
290 } else { // Full range stave that goes across x-axis
291 // Here we might have to cut the stave up into two pieces
292 if (absAllowedYRange.first > 0) {
293 // There is a minimum inner value --> Split stave
294 splitStave = true;
295 y_lower = absAllowedYRange.first;
296 } else {
297 // regular stave, use full length, but don't forget outer cut
298 y_lower = std::max(y_lower, -absAllowedYRange.second);
299 }
300 y_upper = std::min(y_upper, absAllowedYRange.second);
301 }
302 double staveLengthToUse = y_upper - y_lower;
303 /*
304 * create the extruded volumes from z=0 (later y=0 after rotation) to stave length
305 * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later
306 *
307 * Note also that we first need to check if the length is allowed given the inner
308 * and outer radius of the layer.
309 */
310 TGeoXtru* staveFull = new TGeoXtru(2);
311 staveFull->SetName((volumeName + "_Xtru_outer").c_str());
312 staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data());
313 staveFull->DefineSection(0, 0);
314 staveFull->DefineSection(1, staveLengthToUse);
315
316 TGeoXtru* staveInner = new TGeoXtru(2);
317 staveInner->SetName((volumeName + "_Xtru_inner").c_str());
318 staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data());
319 staveInner->DefineSection(0, 0);
320 staveInner->DefineSection(1, staveLengthToUse);
321
322 TGeoCompositeShape* staveShape = new TGeoCompositeShape(
323 (volumeName + "_shape").c_str(),
324 Form("%s - %s", staveFull->GetName(), staveInner->GetName()));
325 TGeoVolume* staveVolume = new TGeoVolume(
326 (volumeName).c_str(),
327 staveShape,
329 staveVolume->SetLineColor(Constants::carbonFiberColor);
330 staveVolume->SetFillColorAlpha(Constants::carbonFiberColor, 0.4);
331
332 TGeoRotation* rot = new TGeoRotation();
333 rot->RotateX(-90); // lift from xy plane into xz plane
334 /*
335 * After rotations the face of the stave lies in the xy-plane,
336 * facing downwards for direction == 1 and upwards for direction == 0.
337 * We still need to shift it in z to get the right staggered layout.
338 * This means moving the staves that must be shifted in the opposite
339 * direction they are facing: up for direction 1, and down for direction 0.
340 *
341 * Unlike a regular node placement, we have to put the stave at its
342 * starting point in y, not the midpoint. Hence, if we have the mirror,
343 * the starting point is the upper y value, since that is the bottom
344 * of the mirrored stave -- by the outer radius
345 */
346 double z_shift = (direction == 1) ? z_stave_shift_forward : -z_stave_shift_forward;
347 TGeoCombiTrans* combiTrans =
348 new TGeoCombiTrans(x_mid, y_lower, z_shift, rot);
349 motherVolume->AddNode(staveVolume,
350 *volume_count,
351 combiTrans);
352 (*volume_count)++;
353
354 // if the stave needs to be split, reuse the same volume on opposite side
355 if (splitStave) {
356 TGeoCombiTrans* combiTransSplit =
357 new TGeoCombiTrans(x_mid, -y_upper, z_shift, rot);
358 motherVolume->AddNode(staveVolume,
359 *volume_count,
360 combiTransSplit);
361 (*volume_count)++;
362 }
363}
364
365/*
366 * Generic helper function that adds a box at the given position with
367 * the given dimensions to the given mother volume, with the given color and name.
368 */
369
370void FT3Module::addDetectorVolume(
371 TGeoVolume* motherVolume, std::string volumeName, int color,
372 unsigned* volume_count, double x_mid, double y_mid, double z_mid,
373 double x_half_length, double y_half_length, double z_half_length)
374{
375 TGeoManager* geoManager = gGeoManager;
376 TGeoVolume* volume = geoManager->MakeBox(volumeName.c_str(), siliconMed, x_half_length,
377 y_half_length, z_half_length);
378 volume->SetLineColor(color);
379 volume->SetFillColorAlpha(color, 0.4);
380 motherVolume->AddNode(
381 volume,
382 *volume_count,
383 new TGeoTranslation( // midpoint of box to add
384 x_mid,
385 y_mid,
386 z_mid) // TGeoTranslation
387 ); // addNode
388 (*volume_count)++;
389}
390
391/*
392 * This function adds a glue volume between two element layers,
393 * immediately for a whole 2x1 layout, under both the active and inactive region.
394 */
395void FT3Module::add2x1GlueVolume(
396 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
397 unsigned* volume_count, double x_mid, double y_mid, double z_mid,
398 std::string element_glued_to)
399{
400 std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count);
401 addDetectorVolume(
402 motherVolume, glue_name, Constants::glueColor, volume_count,
403 x_mid, y_mid, z_mid,
405}
406
407/*
408 * This function adds a copper volume onto which the silicon sensor is glued.
409 * As with the glue, this is a whole 2x1 layout volume.
410 */
411void FT3Module::add2x1CopperVolume(
412 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
413 unsigned* volume_count, double x_mid, double y_mid, double z_mid)
414{
415 std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count);
416 addDetectorVolume(
417 motherVolume, copper_name, Constants::CuColor, volume_count,
418 x_mid, y_mid, z_mid,
420}
421
422/*
423 * This function adds a kapton volume behind the copper, which represents the ???
424 * As with copper and glue, this is a whole 2x1 layout volume.
425 */
426void FT3Module::add2x1KaptonVolume(
427 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
428 unsigned* volume_count, double x_mid, double y_mid, double z_mid)
429{
430 std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count);
431 addDetectorVolume(
432 motherVolume, kapton_name, Constants::kaptonColor, volume_count,
433 x_mid, y_mid, z_mid,
435}
436
437/*
438 * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume
439 * at the given (x,y,z) position of the module.
440 *
441 * Because the sensor has an inactive region of 0.2mm on one side, we also add a
442 * separate volume for the inactive region, which will be either on the left or
443 * or right dependent on the if the sensor is on the left or right in a 2x1 layout.
444 * See FT3Module.h for more details on the layout.
445 *
446 * Arguments:
447 * motherVolume: the volume to which the sensor volume will be added
448 * layerNumber: the layer number of the sensor, used for naming
449 * direction: the direction of the sensor (forward or backward eta), used for naming
450 * x_mid: the x position of the center of the sensor volume
451 * y_mid: the y position of the center of the sensor volume
452 * z_mid: the z position of the center of the sensor volume
453 * isLeft: whether the sensor is on the left or right in the 2x1 layout
454 */
455void FT3Module::addSingleSensorVolume(
456 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
457 unsigned* volume_count, double active_x_mid, double y_mid, double z_mid,
458 bool isLeft)
459{
460 TGeoVolume* sensor;
461 TGeoManager* geoManager = gGeoManager;
462 // ACTIVE AREA
463 std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count);
464 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2,
466 sensor->SetLineColor(Constants::SiColor);
467 sensor->SetFillColorAlpha(Constants::SiColor, 0.4);
468 motherVolume->AddNode(
469 sensor,
470 *volume_count,
471 new TGeoTranslation( // midpoint of box to add
472 active_x_mid,
473 y_mid,
474 z_mid) // TGeoTranslation
475 ); // addNode
476 (*volume_count)++;
477 // INACTIVE STRIP ON LEFT OR RIGHT
478 double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2)
479 : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2);
480 std::string sensor_inactive_name =
481 "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count);
482 sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2,
484 sensor->SetLineColor(Constants::SiInactiveColor);
485 sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4);
486 motherVolume->AddNode(
487 sensor,
488 *volume_count,
489 new TGeoTranslation( // midpoint of box to add
490 inactive_x_mid,
491 y_mid,
492 z_mid) // TGeoTranslation
493 ); // addNode
494 (*volume_count)++;
495}
496
497void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction,
498 double Rin, double Rout, double z_offset_local,
499 const Constants::StaveConfig& staveConfig,
500 TGeoVolume* motherVolume)
501{
502 LOG(debug) << "FT3Module: create_layout_staveGeo - Layer "
503 << layerNumber << ", Direction " << direction;
504
506 auto& ft3Params = o2::ft3::FT3BaseParam::Instance();
507
508 // First let's define some constants used throughout
509 /*
510 * we build the volume from the outside in, starting with the silicon,
511 * then glue & materials towards the stave. Depending on direction,
512 * the distance from the center will be mirrored.
513 *
514 * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE |
515 * ----------------------------------------------------------------> z
516 *
517 * Naturally, this will be mirrored for layers in the backwards direction,
518 * such that the face of the sensors always face the interaction region.
519 *
520 * Currently, we stipulate that the default stave face is at local z=0,
521 * that is then shifted by the half air thickness encapsulating the layer
522 * to avoid overlaps with the air and services. All offsets are
523 * calculated for backward direction (since that is a positive shift),
524 * and then flipped for forward. At that point, the innermost/frontmost
525 * stave face is at the edge of the air volume, so we shift it back a little
526 * to make space for the sensor materials and a slight margin.
527 */
528 double totalSensorMaterialThickness =
531 double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1;
532 double z_offset_to_glue_Ka =
533 z_offset_to_carbon_face + Constants::epoxyThickness / 2;
534 double z_offset_to_kapton =
535 z_offset_to_carbon_face + Constants::epoxyThickness +
537 double z_offset_to_copper =
538 z_offset_to_carbon_face + Constants::epoxyThickness +
540 double z_offset_to_glue_Si =
541 z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness +
543 double z_offset_to_silicon =
544 z_offset_to_carbon_face + Constants::epoxyThickness +
547
548 // initialise all y_positions, vector over all staves/columns
549 std::vector<PosNegPositionTypes> y_positionsPosNeg;
550 unsigned volume_count = 0; // give each subvolume a unique ID
551 // stave triangle cross sections are the same for every stave (direction based)
552 std::array<std::array<double, 3>, 4> staveTriangles = buildStaveTriangle(direction);
553 // Create the stave volumes and fill the y positions where to put sensors on the stave
554 for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) {
555 y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}});
556 const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size());
557
558 double y_midpoint = 0.;
559 bool mirrorStaveAroundX = false;
560 // default positive and negative starting points has a gap around x-axis for symmetry
561 double stave_half_length = staveConfig.y_lengths[i_stave] / 2;
562 PositionRangeType y_ranges;
563 if (ft3Params.placeSensorInMiddleOfStave) {
564 /*
565 * We want a sensor to cross over the x-axis for coverage at y=0
566 * N.B. not necessarily exactly mirrored, only if stack gap is the same
567 * as the gap between sensors in a stack.
568 */
569 y_ranges = {{-Constants::sensor2x1_height / 2,
570 stave_half_length},
572 -stave_half_length}};
573 } else {
574 /*
575 * Otherwise have a gap around y=0, so sensors are not placed there.
576 * This means the stave is perfectly mirrored around the x-axis.
577 */
578 y_ranges = {{Constants::stackGap / 2, stave_half_length},
579 {-Constants::stackGap / 2, -stave_half_length}};
580 }
581 auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID);
582 if (y_midpoint_it != staveConfig.staveID_to_y_midpoint.end()) {
583 // there is a defined midpoint for this stave, use this for starting points
584 y_midpoint = y_midpoint_it->second.first; // avoid double map lookup
585 mirrorStaveAroundX = y_midpoint_it->second.second;
586 y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length};
587 y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length};
588 }
589
590 // Define tolerances for cutting staves and placing sensors
591 double tolerance_inner = -1000; // large negative number to allow given numbers
592 double tolerance_outer = -1000;
593 // cut staves on nominal inner radius if specified
594 if (ft3Params.cutStavesOnNominalRadius_inner) {
595 tolerance_inner = 0.;
596 }
597 if (ft3Params.cutStavesOnNominalRadius_outer) {
598 tolerance_outer = 0.;
599 }
600
601 /*
602 * There are three cases in which we want to mirror the stave around the x-axis,
603 * which correspond to the stave not going fully from + to - Rout in y.
604 *
605 * (1) The inner tolerance is 0 (or positive)
606 * a) AND either x_left or x_right lies within the inner radius
607 * (2) The inner tolerance is large (allow stave placement as wished)
608 * a) AND the given stave midpoint is above the inner radius
609 */
610 double x_left = staveConfig.x_midpoints[i_stave] - Constants::sensor2x1_width / 2;
611 double x_right = x_left + Constants::sensor2x1_width;
612 std::pair<double, double> absAllowedYRange =
613 calculate_y_range(x_left, x_right, Rin, Rout);
614
615 /*
616 * Shift allowed range by tolerance. Note that both values in the range must
617 * be non-negative, and if the inner is not, then set it to 0. This just means
618 * that there is no lower limit. The upper limit must however be larger than 0,
619 * if it is not, then skip this stave and give a warning.
620 */
621 absAllowedYRange.first += tolerance_inner;
622 absAllowedYRange.second -= tolerance_outer;
623
624 if (absAllowedYRange.first < 0) {
625 absAllowedYRange.first = 0;
626 }
627 if (absAllowedYRange.second <= 0) {
628 LOG(warning) << "For stave " << i_stave << " in layer " << layerNumber
629 << " with direction " << direction << ": no space to place sensors after applying tolerances, skipping stave.";
630 continue;
631 }
632
633 // Get whether the stave is shifted backward or not before creating
634 double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing);
635 double z_stave_shift_forward = // move staves more inward to fit in layer volume
636 -z_offset_to_carbon_face + z_stave_shift_abs;
637 std::string stave_volume_name =
638 "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) +
639 "_" + std::to_string(direction);
640 addStaveVolume(
641 motherVolume, stave_volume_name, direction, &volume_count,
642 staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange,
643 staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward);
644 // Now create the mirrored stave
645 if (mirrorStaveAroundX) {
646 addStaveVolume(
647 motherVolume, stave_volume_name + "_mirrored", direction, &volume_count,
648 staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange,
649 staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward);
650 }
651
652 // now add the sensor positions on the stave
653 for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) {
654 fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left,
655 Constants::kSensorsPerStack[i_kSens], y_ranges,
656 absAllowedYRange);
657 }
658 }
659
660 // Create volumes for the sensors and the support materials on top of the stave
661 for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) {
662 double x_mid = staveConfig.x_midpoints[i_stave];
663 int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size());
664 /*
665 * Declare an offset multiplier for the z offsets, used for distinguishing
666 * sensors facing either forward or backward.
667 *
668 * In the stave layout, all sensors face inward, and isFront
669 * refers to whether a stave is shifted backwards or not. Thus,
670 * we decide the offset multiplier only with direction, to
671 * keep the face facing inwards.
672 */
673 bool isFront;
674 if (direction == 1) { // direction = 1 is forward
675 isFront = staveConfig.staveOnFront[i_stave];
676 } else {
677 isFront = !(staveConfig.staveOnFront[i_stave]);
678 }
679 int z_offset_multiplier = (direction == 1) ? -1 : 1;
680
681 // Get whether the stave is shifted for staggering or not
682 double z_stave_shift = 0;
683 if (!staveConfig.staveOnFront[i_stave]) {
684 // in forward direction, shifting backwards means +z shift
685 z_stave_shift = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing)
687 }
688
689 for (int y_sign = -1; y_sign < 2; y_sign += 2) {
690 // place sensors at positive and negative y
691 const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first
692 : y_positionsPosNeg[i_stave].second;
693 // define starting midpoint: y = y_start +- distance to middle of sensor
694 for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) {
695 double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2;
696 for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) {
697 TGeoVolume* sensor;
698 // ------------ (1) Silicon sensor ------------
699 // left single sensor of the 2x1
700 double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift;
701 addSingleSensorVolume(
702 motherVolume, layerNumber, direction, i_stave, &volume_count,
703 x_mid - Constants::active_width / 2, y_mid, z_mid, true);
704 // right single sensor of the 2x1
705 addSingleSensorVolume(
706 motherVolume, layerNumber, direction, i_stave, &volume_count,
707 x_mid + Constants::active_width / 2, y_mid, z_mid, false);
708 // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------
709 z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift;
710 add2x1GlueVolume(
711 motherVolume, layerNumber, direction, i_stave, &volume_count,
712 x_mid, y_mid, z_mid, "SiCu");
713 // ------------ (3) Copper layer (FPC) ------------
714 z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift;
715 add2x1CopperVolume(
716 motherVolume, layerNumber, direction, i_stave, &volume_count,
717 x_mid, y_mid, z_mid);
718 // ------------ (4) Kapton layer (FPC) ------------
719 z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift;
720 add2x1KaptonVolume(
721 motherVolume, layerNumber, direction, i_stave, &volume_count,
722 x_mid, y_mid, z_mid);
723 // ------------ (5) Epoxy glue layer between stave and Kapton ------------
724 z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift;
725 add2x1GlueVolume(
726 motherVolume, layerNumber, direction, i_stave, &volume_count,
727 x_mid, y_mid, z_mid, "CarbonKapton");
728 // increment to next sensor: (height + gap of one sensor)
730 } // sensors in stack
731 } // for y_sign (writing of positive or negative y positions)
732 } // i_y_pos
733 } // i_stave
734}
735
736void FT3Module::create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume)
737{
738
739 LOG(debug) << "FT3Module: create_layout - Layer " << layerNumber << ", Direction " << direction << ", Face " << face;
740 TGeoManager* geoManager = gGeoManager;
741
743
744 // double sensor_width = 2.5;
745 // double sensor_height = 9.6;
746 // double active_width = 2.3;
747 // double active_height = 9.6;
748
749 double sensor_width = 5.0;
750 double sensor_height = 9.6;
751 double inactive_width = 0.2; // per side
752 double active_width = 4.6;
753 double active_height = 9.6;
754
755 double silicon_thickness = 0.01;
756 double copper_thickness = 0.006;
757 double kapton_thickness = 0.03;
758 double epoxy_thickness = 0.0012;
759
760 double carbonFiberThickness = 0.01;
761
762 double foamSpacingThickness = 1.0;
763
764 int dist_offset = 0;
765
766 double x_offset;
767 double y_offset;
768
769 double z_offset = (face == "front") ? -foamSpacingThickness / 2.0 - carbonFiberThickness : foamSpacingThickness / 2.0 + carbonFiberThickness;
770
771 // offset correction
772 if (sensor_height == 3.2 && sensor_width == 2.5) {
773 x_offset = 0.8;
774 y_offset = 1.5;
775 } else if (sensor_height == 19.2 && sensor_width == 5) {
776 x_offset = 0.7;
777 y_offset = 9;
778 } else {
779 x_offset = sensor_width / 2;
780 y_offset = sensor_height / 2;
781 }
782
783 double x_condition_min = 0;
784 double x_condition_max = 0;
785 double offset_Rin_lower = 0;
786 double offset_Rin_upper = 0;
787 bool adjust_bottom_y_pos = false;
788 bool adjust_bottom_y_neg = false;
789 double x_adjust_bottom_y_pos = 0;
790 double bottom_y_pos_value = 0;
791 double bottom_y_neg_value = 0;
792
793 double Rin_offset = (sensor_height == 19.2) ? 1 : 0;
794 double Rout_offset = (sensor_height == 19.2) ? 1 : 0;
795
796 if (Rin == 7 && sensor_height == 9.6 && sensor_width == 5) {
797 x_condition_min = -Rin - 2;
798 x_condition_max = Rin;
799 dist_offset = 2;
800 adjust_bottom_y_pos = true;
801 adjust_bottom_y_neg = true;
802 x_adjust_bottom_y_pos = 3.5;
803 bottom_y_pos_value = 3.5;
804 bottom_y_neg_value = -3.5;
805 } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 5) {
806 x_condition_min = -Rin - 6;
807 x_condition_max = Rin;
808 adjust_bottom_y_pos = true;
809 adjust_bottom_y_neg = true;
810 x_adjust_bottom_y_pos = 3.5;
811 bottom_y_pos_value = 3.5;
812 bottom_y_neg_value = -3.5;
813 } else if ((Rin == 5 || Rin == 7) && sensor_height == 19.2) {
814 x_condition_min = -Rin - 3;
815 x_condition_max = Rin - 0.2;
816 dist_offset = 2;
817 adjust_bottom_y_pos = false;
818 adjust_bottom_y_neg = false;
819 } else if (Rin == 5 && sensor_height == 3.2) {
820 x_condition_min = -(Rin + 2.6);
821 x_condition_max = Rin + 1.5;
822 adjust_bottom_y_pos = true;
823 adjust_bottom_y_neg = true;
824 x_adjust_bottom_y_pos = 3.5;
825 bottom_y_pos_value = 3.5;
826 bottom_y_neg_value = -3.5;
827 } else if (Rin == 7 && sensor_height == 3.2) {
828 x_condition_min = -Rin - 1;
829 x_condition_max = Rin - 0.2;
830 adjust_bottom_y_pos = true;
831 adjust_bottom_y_neg = true;
832 x_adjust_bottom_y_pos = 3.5;
833 bottom_y_pos_value = 3.5;
834 bottom_y_neg_value = -3.5;
835 } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 2.5) {
836 x_condition_min = -(Rin + 2.6);
837 x_condition_max = Rin;
838 adjust_bottom_y_pos = true;
839 adjust_bottom_y_neg = true;
840 x_adjust_bottom_y_pos = 3.5;
841 bottom_y_pos_value = 3.5;
842 bottom_y_neg_value = -3.5;
843 } else if (Rin == 7 && sensor_height == 9.6 && sensor_width == 2.5) {
844 x_condition_min = -Rin - 2.6;
845 x_condition_max = Rin + 1;
846 dist_offset = 2;
847 adjust_bottom_y_pos = true;
848 adjust_bottom_y_neg = true;
849 x_adjust_bottom_y_pos = 5.5;
850 bottom_y_pos_value = 3.5;
851 bottom_y_neg_value = -3.5;
852 } else if (Rin == 10 && sensor_height == 9.6 && sensor_width == 5.0) {
853 x_condition_min = -Rin - 4;
854 x_condition_max = Rin;
855 dist_offset = 2;
856 adjust_bottom_y_pos = false;
857 adjust_bottom_y_neg = false;
858 x_adjust_bottom_y_pos = 3.5;
859 bottom_y_pos_value = 3.5;
860 bottom_y_neg_value = -3.5;
861 } else if (Rin == 20 && sensor_height == 9.6 && sensor_width == 5.0) {
862 x_condition_min = -Rin - 4;
863 x_condition_max = Rin;
864 dist_offset = 2;
865 adjust_bottom_y_pos = false;
866 adjust_bottom_y_neg = false;
867 x_adjust_bottom_y_pos = 3.5;
868 bottom_y_pos_value = 3.5;
869 bottom_y_neg_value = -3.5;
870 } else {
871 LOG(warning) << "Different config - to determine offsets needed for " << "Rin = " << Rin << " ; sensor_height = " << sensor_height << " ; sensor_width = " << sensor_width << " layer " << layerNumber;
872 x_condition_min = -Rin - sensor_width;
873 x_condition_max = Rin;
874 adjust_bottom_y_pos = false;
875 adjust_bottom_y_neg = false;
876 }
877
878 offset_Rin_lower = Rin - Rin_offset;
879 offset_Rin_upper = Rout + Rout_offset;
880
881 std::set<std::pair<double, double>> placed_sensors;
882 int sensor_count = 0;
883
884 int placementCounter = 0;
885 bool justSkipped = false;
886
887 std::vector<double> X_positions;
888 std::vector<int> justSkipped1;
889
890 if (sensor_width == 2.5) {
891 // logic for placement - x positions with complete overlap
892 if (face == "front") {
893 X_positions = {-63.4, -60.9, -54.2, -51.7, -45.0, -42.5, -35.8, -33.3, -26.6, -24.1, -17.4, -14.9,
894 -8.2, -5.7, 1.0, 3.5, 10.2, 12.7, 19.4, 21.9, 28.6, 31.1, 37.8, 40.3, 47.0, 49.5,
895 56.2, 58.7, 65.4};
896 justSkipped1 = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
897 } else if (face == "back") {
898 X_positions = {-65.5, -58.8, -56.3, -49.6, -47.1, -40.4, -37.9, -31.2, -28.7, -22.0, -19.5, -12.8,
899 -10.3, -3.6, -1.1, 5.6, 8.1, 14.8, 17.3, 24.0, 26.5, 33.2, 35.7, 42.4, 44.9,
900 51.6, 54.1, 60.8, 63.3};
901 justSkipped1 = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
902 }
903 } else {
904 if (Rin == 10 || Rin == 20) { // v3 paving, rough attempt
905 float overlap = 0.3;
906 // NB: these are left edges
907 float X_start = -2.0 - 13.5 * (sensor_width - overlap);
908 float X_start_pos = 2.0 - 0.5 * (sensor_width - overlap);
909 if (face == "back") {
910 X_start += (sensor_width - overlap);
911 X_start_pos += (sensor_width - overlap);
912 }
913 while (X_start < -2) {
914 X_positions.push_back(X_start);
915 justSkipped1.push_back(1);
916 X_start += 2 * (sensor_width - overlap);
917 }
918 while (X_start_pos < Rout + x_offset - sensor_width) {
919 X_positions.push_back(X_start_pos);
920 justSkipped1.push_back(1);
921 X_start_pos += 2 * (sensor_width - overlap);
922 }
923 } else {
924 // filling for sensors with 2x width, each row skipped
925 if (face == "front") {
926 X_positions = {-63.4, -54.2, -45, -35.8, -26.6, -17.4, -8.2, 1., 10.2, 19.4, 28.6, 37.8, 47., 56.2, 65.4};
927 justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
928 } else if (face == "back") {
929 X_positions = {-58.8, -49.6, -40.4, -31.2, -22, -12.8, -3.6, 5.6, 14.8, 24, 33.2, 42.4, 51.6, 60.8};
930 justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
931 }
932 }
933 }
934
935 if (layout_type == "rectangular") {
936
937 double x_start = -Rout;
938 double x_end = Rout;
939
940 std::vector<double> x_positions;
941 for (double x = x_start; x <= x_end; x += sensor_width) {
942 x_positions.push_back(x);
943 }
944
945 int rowCounter = 0;
946 const int rowsToAlternate = 2;
947
948 for (size_t i = 0; i < X_positions.size(); ++i) {
949
950 double x = X_positions[i];
951 bool justSkippedValue = justSkipped1[i];
952
953 std::vector<double> y_positions_positive;
954 std::vector<double> y_positions_negative;
955
956 for (double y = -Rout - Rin_offset; y <= Rout + Rin_offset; y += sensor_height) {
957 std::vector<std::pair<double, double>> corners = {
958 {x, y},
959 {x + sensor_width, y},
960 {x, y + sensor_height},
961 {x + sensor_width, y + sensor_height}};
962
963 bool within_bounds = std::all_of(corners.begin(), corners.end(), [&](const std::pair<double, double>& corner) {
964 double cx = corner.first;
965 double cy = corner.second;
966 return (offset_Rin_lower <= std::sqrt(cx * cx + cy * cy) && std::sqrt(cx * cx + cy * cy) <= offset_Rin_upper);
967 });
968
969 if (within_bounds) {
970 if (y >= 0) {
971 y_positions_positive.push_back(y);
972 } else {
973 y_positions_negative.push_back(y);
974 }
975 }
976 }
977
978 // adjust y positions near inner circle for positive y
979 if (x_condition_min <= x && x <= x_condition_max && !y_positions_positive.empty()) {
980 double first_y_pos = y_positions_positive.front();
981 double last_y_pos = y_positions_positive.back() - sensor_height;
982 double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
983 double bottom_y_pos = std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin));
984 double top_distance_pos = top_y_pos - last_y_pos;
985
986 if (adjust_bottom_y_pos && x > x_adjust_bottom_y_pos) {
987 bottom_y_pos = bottom_y_pos_value;
988 }
989
990 double bottom_distance_pos = first_y_pos - bottom_y_pos;
991
992 if (std::abs(top_distance_pos + bottom_distance_pos) >= sensor_height) {
993 for (auto& y : y_positions_positive) {
994 y -= bottom_distance_pos - 0.2;
995 }
996 y_positions_positive.push_back(y_positions_positive.back() + sensor_height);
997 }
998 }
999
1000 // adjust y positions near inner circle for negative y
1001 if (x_condition_min <= x && x <= x_condition_max && !y_positions_negative.empty()) {
1002 double first_y_neg = y_positions_negative.front();
1003 double last_y_neg = y_positions_negative.back() + sensor_height;
1004 double top_y_neg = -std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1005 double bottom_y_neg = -std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin));
1006 double top_distance_neg = -(top_y_neg - first_y_neg);
1007
1008 if (adjust_bottom_y_neg && x > x_adjust_bottom_y_pos) {
1009 bottom_y_neg = bottom_y_neg_value;
1010 }
1011
1012 double bottom_distance_neg = -(last_y_neg - bottom_y_neg);
1013
1014 top_distance_neg = std::abs(top_distance_neg);
1015 bottom_distance_neg = std::abs(bottom_distance_neg);
1016 std::sort(y_positions_negative.begin(), y_positions_negative.end());
1017
1018 if (std::abs(top_distance_neg + bottom_distance_neg) >= sensor_height) {
1019 if (sensor_height == 19.2) {
1020 for (auto& y : y_positions_negative) {
1021 y -= bottom_distance_neg;
1022 }
1023 } else {
1024 for (auto& y : y_positions_negative) {
1025 y += bottom_distance_neg - 0.2;
1026 }
1027 }
1028 y_positions_negative.push_back(y_positions_negative.front() - sensor_height);
1029 }
1030 }
1031
1032 // adjust positions for the rest of the disk
1033 if ((x < x_condition_min || x > x_condition_max) && !y_positions_negative.empty() && !y_positions_positive.empty()) {
1034 double first_y_neg = y_positions_negative.front();
1035 double last_y_pos = y_positions_positive.back() + sensor_height;
1036 double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1037 double bottom_y_pos = -top_y_pos;
1038
1039 double top_distance_pos = std::abs(top_y_pos - last_y_pos);
1040 double bottom_distance_pos = std::abs(first_y_neg - bottom_y_pos);
1041
1042 if (top_distance_pos + bottom_distance_pos >= sensor_height) {
1043 for (auto& y : y_positions_positive) {
1044 y += top_distance_pos - 0.2;
1045 }
1046 for (auto& y : y_positions_negative) {
1047 y += top_distance_pos - 0.2;
1048 }
1049 double new_y = y_positions_negative.front() - sensor_height;
1050
1051 if (static_cast<int>(new_y) > static_cast<int>(bottom_y_pos)) {
1052 y_positions_negative.push_back(new_y);
1053 }
1054 }
1055
1056 // Make symmetric adjustments
1057 std::sort(y_positions_negative.begin(), y_positions_negative.end());
1058 std::sort(y_positions_positive.begin(), y_positions_positive.end());
1059
1060 double first_y_pos = y_positions_negative.front();
1061
1062 last_y_pos = y_positions_positive.back() + sensor_height;
1063
1064 top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1065 bottom_y_pos = -top_y_pos;
1066 top_distance_pos = std::abs(top_y_pos - last_y_pos);
1067 bottom_distance_pos = std::abs(first_y_pos - bottom_y_pos);
1068
1069 double Lb = (bottom_distance_pos + top_distance_pos) / 2;
1070
1071 if (top_distance_pos < Lb) {
1072 double shift = Lb - top_distance_pos;
1073 for (auto& y : y_positions_negative) {
1074 y -= shift;
1075 }
1076 for (auto& y : y_positions_positive) {
1077 y -= shift;
1078 }
1079 } else if (top_distance_pos > Lb) {
1080 double shift = top_distance_pos - Lb;
1081 for (auto& y : y_positions_negative) {
1082 y += shift;
1083 }
1084 for (auto& y : y_positions_positive) {
1085 y += shift;
1086 }
1087 }
1088 }
1089
1090 std::vector<double> y_positions = y_positions_positive;
1091 y_positions.insert(y_positions.end(), y_positions_negative.begin(), y_positions_negative.end());
1092
1093 for (double y : y_positions) {
1094
1095 int SiColor;
1096 double R_material_threshold = 0;
1097
1098 if (placed_sensors.find({x, y}) == placed_sensors.end()) {
1099 placed_sensors.insert({x, y});
1100 TGeoVolume* sensor;
1101
1102 double inactive_width = (sensor_width - active_width) / 2;
1103 double left_inactive_x_shift;
1104 double right_inactive_x_shift;
1105 double active_x_shift_sensor;
1106
1107 if (face == "front") {
1108
1109 double active_x_shift, inactive_x_shift;
1110
1111 if (justSkippedValue) {
1112 active_x_shift = x + inactive_width / 2;
1113 active_x_shift_sensor = active_x_shift + inactive_width;
1114
1115 inactive_x_shift = x - active_width / 2 + inactive_width / 2;
1116 } else {
1117 active_x_shift = x - inactive_width / 2;
1118 active_x_shift_sensor = active_x_shift - inactive_width;
1119
1120 inactive_x_shift = x + active_width / 2 - inactive_width / 2;
1121 }
1122
1123 double inactive_x_shift_left, inactive_x_shift_right;
1124
1125 if (sensor_width == 5.0) {
1126
1127 inactive_x_shift_left = x - sensor_width / 2 + inactive_width;
1128 inactive_x_shift_right = x + sensor_width / 2;
1129 }
1130
1131 std::vector<std::pair<double, double>> corners_shifted = {
1132 {x, y},
1133 {x + sensor_width, y},
1134 {x, y + sensor_height},
1135 {x + sensor_width, y + sensor_height}};
1136
1137 bool within_bounds = true;
1138 for (const auto& corner : corners_shifted) {
1139 double cx = corner.first;
1140 double cy = corner.second;
1141 double dist = std::sqrt(cx * cx + cy * cy);
1142
1143 if (Rin > dist || dist >= Rout) {
1144 within_bounds = false;
1145 break;
1146 }
1147 }
1148
1149 if (within_bounds) {
1150
1151 double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset);
1152
1153 if (r_squared < R_material_threshold * R_material_threshold) {
1154 silicon_thickness = 0.005;
1155 copper_thickness = 0.00475;
1156 kapton_thickness = 0.03;
1157 epoxy_thickness = 0.0012;
1158
1159 SiColor = kOrange;
1160 } else {
1161 silicon_thickness = 0.01;
1162 copper_thickness = 0.006;
1163 kapton_thickness = 0.03;
1164 epoxy_thickness = 0.0012;
1165
1166 SiColor = kGreen;
1167 }
1168
1169 if (sensor_width == 2.5) {
1170 // silicon
1171 std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1172 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2);
1173 sensor->SetLineColor(SiColor);
1174 sensor->SetFillColorAlpha(SiColor, 0.4);
1175 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1176
1177 std::string inactive_name = "FT3inactive_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1178 sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2);
1179 sensor->SetLineColor(kRed);
1180 sensor->SetFillColorAlpha(kRed, 1.0);
1181 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1182
1183 } else {
1184
1185 std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1186 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2);
1187 sensor->SetLineColor(SiColor);
1188 sensor->SetFillColorAlpha(SiColor, 0.4);
1189 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x + inactive_width / 2, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1190
1191 std::string inactive_name_left = "FT3inactive_left_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1192 sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1193 sensor->SetLineColor(kRed);
1194 sensor->SetFillColorAlpha(kRed, 1.0);
1195 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1196
1197 std::string inactive_name_right = "FT3inactive_right_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1198 sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1199 sensor->SetLineColor(kRed);
1200 sensor->SetFillColorAlpha(kRed, 1.0);
1201 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1202 }
1203
1204 // silicon-to-FPC epoxy glue
1205 std::string glue_up_name = "FT3glue_up_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1206 sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1207 sensor->SetLineColor(kBlue);
1208 sensor->SetFillColorAlpha(kBlue, 1.0);
1209 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness / 2));
1210
1211 if (r_squared < R_material_threshold * R_material_threshold) {
1212 std::string alu_name = "FT3aluminum_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1213 sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1214 sensor->SetLineColor(kBlack);
1215 sensor->SetFillColorAlpha(kBlack, 0.4);
1216 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2));
1217
1218 } else {
1219 std::string copper_name = "FT3copper_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1220 sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1221 sensor->SetLineColor(kBlack);
1222 sensor->SetFillColorAlpha(kBlack, 0.4);
1223 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2));
1224 }
1225
1226 // kapton
1227 std::string fpc_name = "FT3fpc_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1228 sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2);
1229 sensor->SetLineColor(kGreen);
1230 sensor->SetFillColorAlpha(kGreen, 0.4);
1231 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness / 2));
1232
1233 // FPC-to-support epoxy glue
1234 std::string glue_down_name = "FT3glue_down_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1235 sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1236 sensor->SetLineColor(kBlue);
1237 sensor->SetFillColorAlpha(kBlue, 1.0);
1238 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness / 2));
1239 }
1240 } else {
1241 double x_shifted = x;
1242 double inactive_x_shift, active_x_shift;
1243 double active_x_shift_sensor;
1244
1245 if (justSkippedValue) {
1246 active_x_shift = x + inactive_width / 2;
1247 active_x_shift_sensor = active_x_shift + inactive_width;
1248
1249 inactive_x_shift = x - active_width / 2 + inactive_width / 2;
1250 } else {
1251 active_x_shift = x - inactive_width / 2;
1252 active_x_shift_sensor = active_x_shift - inactive_width;
1253
1254 inactive_x_shift = x + active_width / 2 - inactive_width / 2;
1255 }
1256
1257 double inactive_x_shift_left, inactive_x_shift_right;
1258
1259 if (sensor_width == 5.0) {
1260
1261 inactive_x_shift_left = x - sensor_width / 2 + inactive_width;
1262 inactive_x_shift_right = x + sensor_width / 2;
1263 }
1264
1265 std::vector<std::pair<double, double>> corners_shifted = {
1266 {x_shifted, y},
1267 {x_shifted + sensor_width, y},
1268 {x_shifted, y + sensor_height},
1269 {x_shifted + sensor_width, y + sensor_height}};
1270
1271 bool within_bounds = true;
1272 for (const auto& corner : corners_shifted) {
1273 double cx = corner.first;
1274 double cy = corner.second;
1275 double dist = std::sqrt(cx * cx + cy * cy);
1276
1277 if (Rin > dist + dist_offset || dist >= Rout) {
1278 within_bounds = false;
1279 break;
1280 }
1281 }
1282
1283 if (within_bounds) {
1284
1285 double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset);
1286
1287 if (r_squared < R_material_threshold * R_material_threshold) {
1288 silicon_thickness = 0.005;
1289 copper_thickness = 0.00475; // thinner -> + replaced by alu
1290 kapton_thickness = 0.03;
1291 epoxy_thickness = 0.0006;
1292
1293 SiColor = kOrange;
1294 } else {
1295 silicon_thickness = 0.01;
1296 copper_thickness = 0.006;
1297 kapton_thickness = 0.03;
1298 epoxy_thickness = 0.0012;
1299
1300 SiColor = kGreen;
1301 }
1302
1303 // FPC-to-support epoxy glue
1304 std::string glue_down_name = "FT3glue_down_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1305 sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1306 sensor->SetLineColor(kBlue);
1307 sensor->SetFillColorAlpha(kBlue, 1.0);
1308 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness / 2));
1309
1310 // Kapton
1311 std::string fpc_name = "FT3fpc_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1312 sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2);
1313 sensor->SetLineColor(kGreen);
1314 sensor->SetFillColorAlpha(kGreen, 0.4);
1315 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness / 2));
1316
1317 if (r_squared < R_material_threshold * R_material_threshold) {
1318 // replace copper with alu
1319 std::string alu_name = "FT3aluminum_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1320 sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1321 sensor->SetLineColor(kBlack);
1322 sensor->SetFillColorAlpha(kBlack, 0.4);
1323 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2));
1324
1325 } else {
1326 std::string copper_name = "FT3copper_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1327 sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1328 sensor->SetLineColor(kBlack);
1329 sensor->SetFillColorAlpha(kBlack, 0.4);
1330 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2));
1331 }
1332
1333 // silicon-to-FPC epoxy glue
1334 std::string glue_up_name = "FT3glue_up_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1335 sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1336 sensor->SetLineColor(kBlue);
1337 sensor->SetFillColorAlpha(kBlue, 1.0);
1338 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness / 2));
1339
1340 if (sensor_width == 2.5) {
1341
1342 std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1343 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2);
1344 sensor->SetLineColor(SiColor);
1345 sensor->SetFillColorAlpha(SiColor, 0.4);
1346 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1347
1348 std::string inactive_name = "FT3inactive_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1349 sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2);
1350 sensor->SetLineColor(kRed);
1351 sensor->SetFillColorAlpha(kRed, 1.0);
1352 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1353
1354 } else {
1355 // active (4.6 cm centered)
1356 std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1357 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2);
1358 sensor->SetLineColor(SiColor);
1359 sensor->SetFillColorAlpha(SiColor, 0.4);
1360 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x_shifted + inactive_width / 2, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1361
1362 // left inactive strip
1363 std::string inactive_name_left = "FT3inactive_left_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1364 sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1365 sensor->SetLineColor(kRed);
1366 sensor->SetFillColorAlpha(kRed, 1.0);
1367 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1368
1369 // right inactive strip
1370 std::string inactive_name_right = "FT3inactive_right_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1371 sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1372 sensor->SetLineColor(kRed);
1373 sensor->SetFillColorAlpha(kRed, 1.0);
1374 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1375 }
1376 }
1377 }
1378 }
1379 }
1380
1381 rowCounter++;
1382 }
1383 }
1384 LOG(debug) << "FT3Module: done create_layout";
1385}
1386
1387void FT3Module::createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume)
1388{
1389
1390 LOG(debug) << "FT3Module: createModule - Layer " << layerNumber << ", Direction " << direction << ", Face " << face;
1391 create_layout(mZ, layerNumber, direction, Rin, Rout, overlap, face, layout_type, motherVolume);
1392 LOG(debug) << "FT3Module: done createModule";
1393}
1394
1395void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction,
1396 double Rin, double Rout, double z_offset_local,
1397 const Constants::StaveConfig& staveConfig,
1398 TGeoVolume* motherVolume)
1399{
1400 LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber
1401 << " at z=" << mZ << ", Direction " << direction;
1402 create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout,
1403 z_offset_local, staveConfig, motherVolume);
1404 LOG(debug) << "FT3Module: done createModule_staveGeo";
1405}
std::ostringstream debug
std::pair< double, double > calculate_y_range(double x_left, double x_right, double Rin, double Rout)
Definition FT3Module.cxx:96
double calculate_y_circle(double x, double radius)
Definition FT3Module.cxx:91
std::array< std::array< double, 3 >, 4 > buildStaveTriangle(int direction)
Definition of the FT3Module class.
std::pair< std::pair< double, double >, std::pair< double, double > > PositionRangeType
Definition FT3Module.h:29
std::vector< PositionType > PositionTypes
Definition FT3Module.h:26
std::pair< PositionTypes, PositionTypes > PosNegPositionTypes
Definition FT3Module.h:27
int32_t i
void createModule_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double z_offset_local, const Constants::StaveConfig &staveConfig, TGeoVolume *motherVolume)
static TGeoMedium * carbonFiberMed
Definition FT3Module.h:48
static TGeoMedium * epoxyMed
Definition FT3Module.h:44
static TGeoMaterial * epoxyMat
Definition FT3Module.h:43
static TGeoMaterial * carbonFiberMat
Definition FT3Module.h:47
static TGeoMedium * siliconMed
Definition FT3Module.h:38
static void createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string &face, const std::string &layout_type, TGeoVolume *motherVolume)
static TGeoMedium * copperMed
Definition FT3Module.h:40
static TGeoMaterial * kaptonMat
Definition FT3Module.h:41
static TGeoMaterial * copperMat
Definition FT3Module.h:39
static TGeoMaterial * siliconMat
Definition FT3Module.h:37
static TGeoMedium * AluminumMed
Definition FT3Module.h:46
static TGeoMedium * kaptonMed
Definition FT3Module.h:42
static TGeoMaterial * AluminumMat
Definition FT3Module.h:45
static void initialize_materials()
Definition FT3Module.cxx:50
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint color
Definition glcorearb.h:1272
GLenum GLuint GLint GLenum face
Definition glcorearb.h:3184
GLboolean * data
Definition glcorearb.h:298
const double effectiveCarbonThickness_Stave
const double z_offsetStave(double x_midpoint_spacing)
const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc)
const double getStackHeight(unsigned nSensorsPerStack)
const std::vector< unsigned > kSensorsPerStack
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
const std::vector< double > & x_midpoints
const std::vector< double > & y_lengths
const std::map< int, std::pair< double, bool > > & staveID_to_y_midpoint
const std::vector< bool > & staveOnFront
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"