ATLAS Offline Software
Loading...
Searching...
No Matches
MuonChannelDesign.h
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
3*/
4
5/***************************************************************************
6 properties of a plane based detector allowing for a stereo angle
7 ----------------------------------------------------------------------
8***************************************************************************/
9
10#ifndef MUONREADOUTGEOMETRY_MUONCHANNELDESIGN_H
11#define MUONREADOUTGEOMETRY_MUONCHANNELDESIGN_H
12
14#include "GaudiKernel/MsgStream.h"
18#include "cmath"
19namespace MuonGM {
20
22 public:
24 enum class ChannelType { etaStrip, phiStrip };
25 enum class DetType { MM, STGC, Other };
28 int nch{-1}; // total #of active strips
29 double inputPitch{0.}; // we use this param to define the pitch for MM
30 double inputWidth{0.};
31 double inputLength{0.};
32 double firstPitch{0.}; // Pitch of 1st strip or number of wires in 1st group
33 double groupWidth{0.}; // Number of Wires per group
34 double nGroups{0.}; // Number of Wire groups
35 double wireCutout{0.};
36 double thickness{0.}; // gas thickness
37 int nMissedTopEta{0}; // #of strips that are not connected to any FE boards (MM)
41 int totalStrips{0}; // total strips per MM module
44 double distanceToReadout(const Amg::Vector2D& pos) const;
45
47 double distanceToChannel(const Amg::Vector2D& pos, int nChannel) const;
48
50 int channelNumber(const Amg::Vector2D& pos) const;
51
53 int wireGroupNumber(const Amg::Vector2D& pos) const;
54
56 int wireNumber(const Amg::Vector2D& pos) const;
57
59 double channelWidth() const;
60
62 void defineTrapezoid(double HalfShortY, double HalfLongY, double HalfHeight);
63 void defineTrapezoid(double HalfShortY, double HalfLongY, double HalfHeight, double sAngle);
64 void defineDiamond(double HalfShortY, double HalfLongY, double HalfHeight, double ycutout);
65
67 double stereoAngle() const { return m_sAngle; }
68
69 double yCutout() const { return m_yCutout; }
70
72 double hasStereoAngle() const {return m_hasStereo;}
73
75 double isDiamondShape() const {return m_isDiamond;}
76
78 const AmgSymMatrix(2)& rotation() const { return m_rotMat; }
79
81 double channelLength(int channel) const;
82
84 double channelHalfLength(int st, bool left) const;
85
87 double gasGapThickness() const { return thickness; }
88
90 bool center(int channel, Amg::Vector2D& pos) const;
92 bool leftEdge(int channel, Amg::Vector2D& pos) const;
94 bool rightEdge(int channel, Amg::Vector2D& pos) const;
96 int numberOfMissingTopStrips() const;
99
100 // radial distance (active area)
101 double xSize() const;
102 // top length (active area)
103 double maxYSize() const;
104 // bottom length (active area)
105 double minYSize() const;
106
112 double passivatedLength(double passivWidth, bool left) const;
114 double passivatedHeight(double passivHeight, bool edge) const;
115
117 void setFirstPos(const double pos);
118
120 double firstPos() const;
121
126 int positionRelativeToStrip(const Amg::Vector2D& lpos, Amg::Vector2D& rel_pos) const;
127
128 private:
130 bool channelPosition(int channel, Amg::Vector2D& pos) const;
132 // - For MM, it accounts for routed stereo strips by also checking the intersection with the trapezoid bases.
133 // - For sTGC, it accounts for the rectangular region of QL3 returning -0.5*m_maxYSize for x > 0.
134 // In case uncapped is set to true these special boundary tests are not applied,
135 // and the intersection of the extended strip line with the side of the trapezoid is returned.
136 bool leftInterSect(int channel, Amg::Vector2D& pos, bool uncapped = false) const;
138 bool rightInterSect(int channel, Amg::Vector2D& pos, bool uncapped = false) const;
140 bool geomCenter(int channel, Amg::Vector2D& pos) const;
141
143 void setStereoAngle(double sAngle);
144
145 bool m_hasStereo{false};
146 bool m_isDiamond{false};
147 double m_sAngle{0.}; // stereo angle
148 double m_yCutout{0.};
154 double m_firstPos{0.};
156 Amg::Vector2D m_topEdge{Amg::Vector2D::Zero()};
158 Amg::Vector2D m_bottomEdge{Amg::Vector2D::Zero()};
160 Amg::Vector2D m_bottomLeft{Amg::Vector2D::Zero()};
162 Amg::Vector2D m_bottomRight{Amg::Vector2D::Zero()};
164 Amg::Vector2D m_topLeft{Amg::Vector2D::Zero()};
166 Amg::Vector2D m_topRight{Amg::Vector2D::Zero()};
167
168 AmgSymMatrix(2) m_rotMat{AmgSymMatrix(2)::Identity()};
169
170 double m_maxHorSize{0.}; // Maximum length of the horizontal edges
171 double m_xSize{0.}; // radial distance (active area)
172 double m_minYSize{0.}; // bottom length (active area)
173 double m_maxYSize{0.}; // top length (active area)
174
175 };
176
177 //============================================================================
178 inline double MuonChannelDesign::distanceToChannel(const Amg::Vector2D& pos, int chNum) const {
179 Amg::Vector2D cen{Amg::Vector2D::Zero()};
180 if (!center(chNum, cen)) return std::numeric_limits<float>::max();
181
182 return (pos - cen).x();
183 }
184
185
186 //============================================================================
187 inline double MuonChannelDesign::distanceToReadout(const Amg::Vector2D& pos) const {
188 throw std::runtime_error("MuonChannelDesign::distanceToReadout() - is not properly defined");
189 return pos.dot(pos);
190 }
191
192
193
194 //============================================================================
195 inline int MuonChannelDesign::channelNumber(const Amg::Vector2D& pos) const {
196
197 int chNum{-1};
199 // ** MM strips: keeping cases outside the active area, but within the envelope,
200 // to avoid warnings from MuonPRDTest. Those channels are removed from digitization.
201 const Amg::Vector2D posInEta = m_rotMat.inverse()*pos;
202 const double xMid = (posInEta - firstPos()*Amg::Vector2D::UnitX()).dot(m_stereoNormal) / m_stereoNormal.x();
203 const int missedBottom = numberOfMissingBottomStrips();
204
205 // first position is always 1/2 pitch above the center of the first active strip
206 chNum = static_cast<int>(std::floor( xMid / inputPitch)) + missedBottom + 2;
207 if (chNum < 1 || chNum > totalStrips) chNum = -1;
208
209 } else if (type == ChannelType::phiStrip && detType == DetType::STGC) {
210
211 // ** sTGC wires: return the wire group
212 chNum = wireGroupNumber(pos);
213
214 } else {
215
216 // ** All other cases
217 chNum = (int)std::floor( (pos.x() - firstPos()) / inputPitch ) + 2;
218 if (chNum < 1 || chNum > nch) chNum = -1;
219 }
220
221 return chNum;
222 }
223
224
225 //============================================================================
227
228 // The wires in the 1st gas volume of QL1, QS1 can not be read for digits
229 if (type != ChannelType::phiStrip || detType != DetType::STGC) return -1;
230
231 // Get the wire number associated to the position
232 int wire_number = wireNumber(pos);
233
234 // Find the wire group associated to the wire number.
235 // For wires, firstPitch is the number of wires in the 1st group.
236 int grNumber{-1};
237 if (wire_number <= firstPitch) {
238 grNumber = 1;
239 } else {
240 grNumber = (wire_number - 1 - firstPitch) / groupWidth + 2; // 20 wires per group,
241 // Case a hit is positionned after the last wire but inside the gas volume.
242 // Especially important for QL3. We still consider the digit active.
243 if (grNumber > nGroups && pos.x() < 0.5 * m_maxYSize) grNumber = nGroups;
244 }
245
246 // If hit is in an inactive wire region of QL1/QS1, return 63 (invalid).
247 // This allows better tracking of hits.
248 if (wireCutout != 0. && pos.y() < 0.5 * m_xSize - wireCutout) return 63;
249 if (grNumber < 1 || grNumber > nGroups) return -1;
250
251 return grNumber;
252 }
253
254
255 //============================================================================
256 inline int MuonChannelDesign::wireNumber(const Amg::Vector2D& pos) const {
257
258 // Only determine wire number for sTGC wire surfaces
259 int wire_number{-1};
261 if ((pos.x() > -0.5 * m_maxYSize) && (pos.x() < firstPos())) { // before first wire
262 wire_number = 1;
263 } else {
264 wire_number = (pos.x() - firstPos()) / inputPitch + 1;
265 if (wire_number < 1 || wire_number > nch) {
266 MsgStream log(Athena::getMessageSvc(), "MuonChannelDesign");
267 if (log.level() <= MSG::DEBUG) {
268 log << MSG::DEBUG << "sTGC wire number out of range: wire number = " << wire_number << " local pos = (" << pos.x() << ", " << pos.y() << ")" << endmsg;
269 }
270 }
271 }
272 }
273
274 return wire_number;
275 }
276
277
278 //============================================================================
280
281 if (type != ChannelType::etaStrip) return -1;
282
283 // if the lpos is out of bounds, express it w.r.t. the nearest active strip.
284 // it's not our job to boundary check.
285 int istrip = channelNumber(lpos);
286 if (istrip < 0) return istrip;
287 istrip = std::max (istrip, numberOfMissingBottomStrips() + 1);
288 istrip = std::min (istrip, numberOfMissingBottomStrips() + nch);
289
290 // get the intersection of the eta and stereo strips in the local reference frame
291 Amg::Vector2D chan_pos{Amg::Vector2D::Zero()};
292 channelPosition(istrip, chan_pos);
293 chan_pos = rotation() * chan_pos;
294
295 // strip edge in the local reference frame (note that the uncapped option is set to true)
296 Amg::Vector2D edge_pos{Amg::Vector2D::Zero()};
297 (lpos.y() > 0) ? rightInterSect(istrip, edge_pos, true) : leftInterSect(istrip, edge_pos, true);
298 edge_pos = rotation() * edge_pos;
299
300 rel_pos[0] = (lpos - chan_pos).x() / channelWidth();
301 rel_pos[1] = (lpos - chan_pos).y() / std::abs( edge_pos.y() - chan_pos.y() );
302
303 return istrip;
304 }
305
306
307 //============================================================================
308 inline bool MuonChannelDesign::channelPosition(int st, Amg::Vector2D& pos) const {
309
311
312 if (st < 1 || st > nch) return false;
313
314 if (detType == DetType::STGC) {
315
317 if (st > nGroups || st == 63) return false; // 63 is defined as an unactive digit
318
319 // calculate the end of the first wire group (accounts for staggering).
320 // for wires, firstPitch is the number of wires in the first group
321 double firstX = firstPos() + (firstPitch - 1) * inputPitch;
322 double locX{0.};
323
324 if (st == 1) {
325 // first group: average the starting and ending x of the group
326 locX = 0.5 * (-0.5 * m_maxYSize + firstX);
327 } else if (st == nGroups) {
328 // last group: average the starting and ending x of the group
329 locX = 0.5 * (0.5 * m_maxYSize + firstX + (nGroups - 2) * groupWidth * inputPitch);
330 } else {
331 // For the R3 geometry converted from Phase II, we are redefining the center of the wiregroup
332 // to be between the 10th and the 11th wire of the group, originally defined on the 10th wire.
333 // Hence, we are defining an offset, wireOffset to be used in the defining the local x of the wiregroup center.
334 double wireOffset{0.};
336 wireOffset = 0.5 * inputPitch;
337 }
338 locX = firstX + wireOffset + groupWidth * inputPitch * (st - 1.5);
339 }
340
341 pos[0] = locX;
342 pos[1] = 0.;
343
344 }
345
346 } else if (detType == DetType::MM) {
347
349 const int nMissedBottom = numberOfMissingBottomStrips();
350 if (st <= nMissedBottom || st > nMissedBottom + nch) return false;
351
352 // firstPos is 1/2 pitch above the center of the first active strip.
353 pos = (firstPos() + inputPitch* (st - nMissedBottom - 1.5)) * Amg::Vector2D::UnitX();
354 } else {
355
357 if (st < 1 || st > nch) return false;
358
359 double x = firstPos() + (st - 1.5)*inputPitch;
360 if (detType == DetType::STGC) {
361 if (firstPitch == inputPitch && st == nch) x -= inputWidth/4.; // case last strip is a half-strip
362 if (firstPitch < inputPitch && st == 1) x += inputWidth/4.; // case first strip is a half-strip
363 }
364
365 pos[0] = x;
366 pos[1] = 0;
367 }
368
369 return true;
370 }
371
372 //============================================================================
373 inline double MuonChannelDesign::channelHalfLength(int st, bool left) const {
374 Amg::Vector2D cen{Amg::Vector2D::Zero()}, side{Amg::Vector2D::Zero()};
375 if (!geomCenter(st,cen) || (left&& !leftInterSect(st, side)) || (!left && !rightInterSect(st, side))) return -0.5;
376 if (type == ChannelType::etaStrip && detType == DetType::MM && (st < 1 || st > totalStrips)) return -0.5;
377 // default case:
378 return (cen - side).mag();
379 }
380
381
382 //============================================================================
383 inline double MuonChannelDesign::channelLength(int st) const {
384 Amg::Vector2D left{Amg::Vector2D::Zero()}, right{Amg::Vector2D::Zero()};
385 if(!leftInterSect(st,left)|| !rightInterSect(st, right)) return -0.5;
386 return (left - right).mag();
387 }
388
389
390 //============================================================================
391 inline double MuonChannelDesign::channelWidth() const {
392
393 if (detType == DetType::STGC) {
394 // eta strips: return the pitch (3.2mm), not the width (2.7mm)
395 // phi wires: return width of full wire group
397 }
398
399 return inputWidth;
400 }
401
402 //============================================================================
403 inline bool MuonChannelDesign::geomCenter(int channel, Amg::Vector2D& pos) const {
404 Amg::Vector2D l_pos{Amg::Vector2D::Zero()}, r_pos{Amg::Vector2D::Zero()};
405 if (!leftInterSect(channel,l_pos) || !rightInterSect(channel,r_pos)) return false;
406 pos = 0.5 * (l_pos + r_pos);
407 return true;
408 }
409
410 //============================================================================
411 inline bool MuonChannelDesign::leftInterSect(int channel, Amg::Vector2D& pos, bool uncapped /*= false*/) const {
413 Amg::Vector2D chanPos{Amg::Vector2D::Zero()};
414 if (!channelPosition(channel, chanPos)) return false;
415
417 if (!uncapped && m_isDiamond && chanPos.x() > 0) {
418 pos = Amg::Vector2D(chanPos.x(), -0.5*m_maxYSize);
419 return true;
420 }
421
422 std::optional<double> lambda = Amg::intersect<2>(chanPos, m_stereoDir, m_bottomLeft, m_bottomEdge);
423 if (!lambda) return false;
426 if (!uncapped && m_hasStereo && ( (*lambda) < 0. || (*lambda) > m_maxHorSize)) {
427 const Amg::Vector2D e_y{0., 1.};
428 const std::optional<double> bottom_line = Amg::intersect<2>(m_stereoDir.x() > 0.? m_bottomLeft: m_bottomRight, e_y, chanPos, m_stereoDir);
429 if (bottom_line) {
430 pos = chanPos + (*bottom_line)* m_stereoDir;
431 return true;
432 }
433 }
434 pos = (m_bottomLeft + (*lambda) * m_bottomEdge);
435 return true;
436 }
437
438 //============================================================================
439 inline bool MuonChannelDesign::rightInterSect(int channel, Amg::Vector2D& pos, bool uncapped /*= false*/) const {
441 Amg::Vector2D chanPos{Amg::Vector2D::Zero()};
442 if (!channelPosition(channel, chanPos)) return false;
443
445 if (!uncapped && m_isDiamond && chanPos.x() > 0) {
446 pos = Amg::Vector2D(chanPos.x(), 0.5*m_maxYSize);
447 return true;
448 }
449
451 const std::optional<double> lambda =Amg::intersect<2>(chanPos, m_stereoDir, m_topRight, m_topEdge);
452 if (!lambda) return false;
455 if (!uncapped && m_hasStereo&& ( (*lambda) < 0 || (*lambda) > m_maxHorSize)) {
456 const Amg::Vector2D e_y{0., 1.};
457 const std::optional<double> top_line =Amg::intersect<2>(m_stereoDir.x() > 0. ? m_topRight: m_topLeft, e_y, chanPos, m_stereoDir);
458 if (top_line) {
459 pos = chanPos + (*top_line) * m_stereoDir;
460 return true;
461 }
462 }
463 pos = (m_topRight + (*lambda) * m_topEdge);
464 return true;
465 }
466
467 //============================================================================
470 inline double MuonChannelDesign::xSize() const {return m_xSize;}
471 inline double MuonChannelDesign::maxYSize() const {return m_maxYSize;}
472 inline double MuonChannelDesign::minYSize() const {return m_minYSize;}
473 inline double MuonChannelDesign::firstPos() const {return m_firstPos;}
474
475 //============================================================================
476 inline bool MuonChannelDesign::center(int channel, Amg::Vector2D& pos) const {
477 if (!geomCenter(channel, pos)) return false;
478 pos = m_rotMat * pos;
479 return true;
480 }
481
482 //============================================================================
483 inline bool MuonChannelDesign::leftEdge(int channel, Amg::Vector2D& pos) const {
484 if (!leftInterSect(channel, pos)) return false;
485 pos = m_rotMat * pos;
486 return true;
487 }
488
489 //============================================================================
490 inline bool MuonChannelDesign::rightEdge(int channel, Amg::Vector2D& pos) const {
491 if (!rightInterSect(channel, pos)) return false;
492 pos = m_rotMat * pos;
493 return true;
494 }
495
496
497 //============================================================================
498 // The passivated length
499 inline double MuonChannelDesign::passivatedLength(double passivWidth, bool left) const{
500 const Amg::Vector2D& edge = (left ? m_bottomEdge : m_topEdge);
501 return passivWidth * std::abs(m_stereoDir.x() *edge.y() - m_stereoDir.y()* edge.x());
502 }
503
504 //============================================================================
505 inline double MuonChannelDesign::passivatedHeight(double passivHeight, bool edge_pcb) const{
506 return passivHeight*std::abs(edge_pcb? m_stereoDir.x() : 1.);
507 }
508
509} // namespace MuonGM
510#endif // MUONREADOUTGEOMETRY_MUONCHANNELDESIGN_H
#define endmsg
#define y
#define x
singleton-like access to IMessageSvc via open function and helper
std::optional< double > intersect(const AmgVector(N)&posA, const AmgVector(N)&dirA, const AmgVector(N)&posB, const AmgVector(N)&dirB)
Calculates the point B' along the line B that's closest to a second line A.
Eigen::Matrix< double, 2, 1 > Vector2D
IMessageSvc * getMessageSvc(bool quiet=false)
Ensure that the Athena extensions are properly loaded.
Definition GeoMuonHits.h:27
Definition dot.py:1
double m_firstPos
Position of the first measurement.
double stereoAngle() const
returns the stereo angle
bool rightInterSect(int channel, Amg::Vector2D &pos, bool uncapped=false) const
Returns the right edge of the strip.
bool center(int channel, Amg::Vector2D &pos) const
STRIPS ONLY: Returns the center on the strip.
void setStereoAngle(double sAngle)
calculate local stereo angle
double channelHalfLength(int st, bool left) const
STRIPS ONLY: calculate channel length on the given side of the x axis (for MM stereo strips)
bool leftEdge(int channel, Amg::Vector2D &pos) const
STRIPS ONLY: Returns the left edge of the strip.
Amg::Vector2D m_topEdge
Vector describing the right edge of the trapzoid.
int numberOfMissingBottomStrips() const
Returns the number of missing bottom strips.
bool rightEdge(int channel, Amg::Vector2D &pos) const
STRIPS ONLY: Returns the right edge of the strip.
Amg::Vector2D m_bottomLeft
Bottom left point of the trapezoid.
void defineDiamond(double HalfShortY, double HalfLongY, double HalfHeight, double ycutout)
Amg::Vector2D m_bottomRight
Bottom right point of the trapezoid.
int wireGroupNumber(const Amg::Vector2D &pos) const
calculate local wire group number, range 1=64 like identifiers. Returns -1 if out of range
double channelWidth() const
calculate local channel width
double hasStereoAngle() const
returns whether the stereo angle is non-zero
Amg::Vector2D m_topLeft
Top right point of the trapezoid.
Amg::Vector2D m_bottomEdge
Vector describing the left edge of the trapezoid.
int wireNumber(const Amg::Vector2D &pos) const
calculate the sTGC wire number. The method can return a value outside the range [1,...
double distanceToReadout(const Amg::Vector2D &pos) const
distance to readout
double firstPos() const
Returns the position of the first strip along the x-axis.
bool channelPosition(int channel, Amg::Vector2D &pos) const
calculate local channel position for a given channel number
void defineTrapezoid(double HalfShortY, double HalfLongY, double HalfHeight)
set the trapezoid dimensions
int positionRelativeToStrip(const Amg::Vector2D &lpos, Amg::Vector2D &rel_pos) const
STRIPS ONLY.
double passivatedLength(double passivWidth, bool left) const
top edge -> left edge bottom edge -> right edge a rectangle with height H parallel to the inclined ed...
void setFirstPos(const double pos)
Set the position of the first strip along the x-axis.
int numberOfMissingTopStrips() const
Returns the number of missing top strips.
Amg::Vector2D m_topRight
Bottom right point of the trapezoid.
double channelLength(int channel) const
STRIPS ONLY: calculate channel length for a given strip number.
Amg::Vector2D m_stereoNormal
Direction pointing to the next strips.
double passivatedHeight(double passivHeight, bool edge) const
Passivation is applied parallel to.
double gasGapThickness() const
thickness of gas gap
double distanceToChannel(const Amg::Vector2D &pos, int nChannel) const
distance to channel - residual
const AmgSymMatrix(2) &rotation() const
returns the rotation matrix between eta <-> phi layer
bool geomCenter(int channel, Amg::Vector2D &pos) const
Returns the geometrical strip center.
bool leftInterSect(int channel, Amg::Vector2D &pos, bool uncapped=false) const
Returns the intersection of the strip with the left edge of the trapezoid. Special cases:
int channelNumber(const Amg::Vector2D &pos) const
calculate local channel number, range 1=nstrips like identifiers. Returns -1 if out of range
Amg::Vector2D m_stereoDir
Direction of the strips.
double isDiamondShape() const
returns whether it's a diamond shape (sTGC QL3)