CMS 3D CMS Logo

Pixel3DDigitizerAlgorithm.cc
Go to the documentation of this file.
1 #include <cmath>
2 #include <vector>
3 #include <algorithm>
4 
11 
12 using namespace sipixelobjects;
13 
14 namespace {
15  // Analogously to CMSUnits (no um defined)
16  constexpr double operator""_um(long double length) { return length * 1e-4; }
17  constexpr double operator""_um_inv(long double length) { return length * 1e4; }
18 } // namespace
19 
21  : PixelDigitizerAlgorithm(conf, iC),
22  np_column_radius_(
23  (conf.getParameter<edm::ParameterSet>("Pixel3DDigitizerAlgorithm").getParameter<double>("NPColumnRadius")) *
24  1.0_um),
25  ohm_column_radius_(
26  (conf.getParameter<edm::ParameterSet>("Pixel3DDigitizerAlgorithm").getParameter<double>("OhmicColumnRadius")) *
27  1.0_um),
28  np_column_gap_(
29  (conf.getParameter<edm::ParameterSet>("Pixel3DDigitizerAlgorithm").getParameter<double>("NPColumnGap")) *
30  1.0_um) {
31  // XXX - NEEDED?
32  pixelFlag_ = true;
33 
34  edm::LogInfo("Pixel3DDigitizerAlgorithm")
35  << "Algorithm constructed \n"
36  << "Configuration parameters:\n"
37  << "\n*** Threshold"
38  << "\n Endcap = " << theThresholdInE_Endcap_ << " electrons"
39  << "\n Barrel = " << theThresholdInE_Barrel_ << " electrons"
40  << "\n*** Gain"
41  << "\n Electrons per ADC:" << theElectronPerADC_ << "\n ADC Full Scale: " << theAdcFullScale_
42  << "\n*** The delta cut-off is set to " << tMax_ << "\n*** Pixel-inefficiency: " << addPixelInefficiency_;
43 }
44 
46 
47 //
48 // -- Select the Hit for Digitization
49 //
50 bool Pixel3DDigitizerAlgorithm::select_hit(const PSimHit& hit, double tCorr, double& sigScale) const {
51  double time = hit.tof() - tCorr;
52  return (time >= theTofLowerCut_ && time < theTofUpperCut_);
53 }
54 
55 const bool Pixel3DDigitizerAlgorithm::is_inside_n_column_(const LocalPoint& p, const float& sensor_thickness) const {
56  // The insensitive volume of the column: sensor thickness - column gap distance
57  return (p.perp() <= np_column_radius_ && p.z() <= (sensor_thickness - np_column_gap_));
58 }
59 
61  const std::pair<float, float>& half_pitch) const {
62  // The four corners of the cell
63  return ((p - LocalVector(half_pitch.first, half_pitch.second, 0)).perp() <= ohm_column_radius_) ||
64  ((p - LocalVector(-half_pitch.first, half_pitch.second, 0)).perp() <= ohm_column_radius_) ||
65  ((p - LocalVector(half_pitch.first, -half_pitch.second, 0)).perp() <= ohm_column_radius_) ||
66  ((p - LocalVector(-half_pitch.first, -half_pitch.second, 0)).perp() <= ohm_column_radius_);
67 }
68 
69 // Diffusion algorithm: Probably not needed,
70 // Assuming the position point is given in the reference system of the proxy
71 // cell, centered at the n-column.
72 // The algorithm assumes only 1-axis could produce the charge migration, this assumption
73 // could be enough given that the p-columns (5 um radius) are in the corners of the cell
74 // (no producing charge in there)
75 // The output is vector of newly created charge in the neighbour pixel i+1 or i-1,
76 // defined by its position higher than abs(half_pitch) and the the sign providing
77 // the addition or subtraction in the pixel (i+-1)
78 std::vector<digitizerUtility::EnergyDepositUnit> Pixel3DDigitizerAlgorithm::diffusion(
79  const LocalPoint& pos,
80  const float& ncarriers,
81  const std::function<LocalVector(float, float)>& u_drift,
82  const std::pair<float, float> hpitches,
83  const float& thickness) const {
84  // FIXME -- DM : Note that with a 0.3 will be enough (if using current sigma formulae)
85  // With the current sigma, this value is dependent of the thickness,
86  // Note that this formulae is coming from planar sensors, a similar
87  // study with data will be needed to extract the sigma for 3D
88  const float max_migration_radius = 0.4_um;
89  // Need to know which axis is the relevant one
90  int displ_ind = -1;
91  float pitch = 0.0;
92 
93  // Check the group is near the edge of the pixel, so diffusion will
94  // be relevant in order to migrate between pixel cells
95  if (hpitches.first - std::abs(pos.x()) < max_migration_radius) {
96  displ_ind = 0;
97  pitch = hpitches.first;
98  } else if (hpitches.second - std::abs(pos.y()) < max_migration_radius) {
99  displ_ind = 1;
100  pitch = hpitches.second;
101  } else {
102  // Nothing to do, too far away
103  return std::vector<digitizerUtility::EnergyDepositUnit>();
104  }
105 
106  // The new EnergyDeposits in the neighbour pixels
107  // (defined by +1 to the right (first axis) and +1 to the up (second axis)) <-- XXX
108  std::vector<digitizerUtility::EnergyDepositUnit> migrated_charge;
109 
110  // FIXME -- DM
111  const float diffusion_step = 0.1_um;
112 
113  // The position while drifting
114  std::vector<float> pos_moving({pos.x(), pos.y(), pos.z()});
115  // The drifting: drift field and steps
116  std::function<std::vector<float>(int)> do_step =
117  [&pos_moving, &u_drift, diffusion_step](int i) -> std::vector<float> {
118  auto dd = u_drift(pos_moving[0], pos_moving[1]);
119  return std::vector<float>({i * diffusion_step * dd.x(), i * diffusion_step * dd.y(), i * diffusion_step * dd.z()});
120  };
121 
122  LogDebug("Pixel3DDigitizerAlgorithm::diffusion")
123  << "\nMax. radius from the pixel edge to migrate charge: " << max_migration_radius * 1.0_um_inv << " [um]"
124  << "\nMigration axis: " << displ_ind
125  << "\n(super-)Charge distance to the pixel edge: " << (pitch - pos_moving[displ_ind]) * 1.0_um_inv << " [um]";
126 
127  // How many sigmas (probably a configurable, to be decided not now)
128  const float N_SIGMA = 3.0;
129 
130  // Start the drift and check every step
131  // Some variables needed
132  float current_carriers = ncarriers;
133  std::vector<float> newpos({pos_moving[0], pos_moving[1], pos_moving[2]});
134  float distance_edge = 0.0_um;
135  // Current diffusion value
136  const float sigma = 0.4_um;
137  for (int i = 1;; ++i) {
138  std::transform(pos_moving.begin(), pos_moving.end(), do_step(i).begin(), pos_moving.begin(), std::plus<float>());
139  distance_edge = pitch - std::abs(pos_moving[displ_ind]);
140  // Get the amount of charge on the neighbor pixel: note the
141  // transformation to a Normal
142  float migrated_e = current_carriers * 0.5 * (1.0 - std::erf(distance_edge / (sigma * std::sqrt(2.0))));
143 
144  LogDebug("(super-)charge diffusion") << "step-" << i << ", Current carriers Ne= " << current_carriers << ","
145  << "r=(" << pos_moving[0] * 1.0_um_inv << ", " << pos_moving[1] * 1.0_um_inv
146  << ", " << pos_moving[2] * 1.0_um_inv << ") [um], "
147  << "Migrated charge: " << migrated_e;
148 
149  // Move the migrated charge
150  current_carriers -= migrated_e;
151 
152  // Either far away from the edge or almost half of the carriers already migrated
153  if (std::abs(distance_edge) >= max_migration_radius || current_carriers <= 0.5 * ncarriers) {
154  break;
155  }
156 
157  // Create the ionization point:
158  // First update the newpos vector: the new charge position at the neighbouring pixel
159  // is created in the same position as its "parent carriers"
160  // except the direction of migration
161  std::vector<float> newpos(pos_moving);
162  // Let's create the new charge carriers around 3 sigmas away
163  newpos[displ_ind] += std::copysign(N_SIGMA * sigma, newpos[displ_ind]);
164  migrated_charge.push_back(digitizerUtility::EnergyDepositUnit(migrated_e, newpos[0], newpos[1], newpos[2]));
165  }
166  return migrated_charge;
167 }
168 
169 // ======================================================================
170 //
171 // Drift the charge segments to the column (collection surface)
172 // Include the effect of E-field and B-field
173 //
174 // =====================================================================
175 std::vector<digitizerUtility::SignalPoint> Pixel3DDigitizerAlgorithm::drift(
176  const PSimHit& hit,
177  const Phase2TrackerGeomDetUnit* pixdet,
178  const GlobalVector& bfield,
179  const std::vector<digitizerUtility::EnergyDepositUnit>& ionization_points) const {
180  return drift(hit, pixdet, bfield, ionization_points, true);
181 }
182 std::vector<digitizerUtility::SignalPoint> Pixel3DDigitizerAlgorithm::drift(
183  const PSimHit& hit,
184  const Phase2TrackerGeomDetUnit* pixdet,
185  const GlobalVector& bfield,
186  const std::vector<digitizerUtility::EnergyDepositUnit>& ionization_points,
187  bool diffusion_activated) const {
188  // -- Current reference system is placed in the center of the module
189  // -- The natural reference frame should be discribed taking advantatge of
190  // -- the cylindrical nature of the pixel geometry -->
191  // -- the new reference frame should be placed in the center of the n-column, and in the
192  // -- surface of the ROC using cylindrical coordinates
193 
194  // Get ROC pitch, half_pitch and sensor thickness to be used to create the
195  // proxy pixel cell reference frame
196  const auto pitch = pixdet->specificTopology().pitch();
197  const auto half_pitch = std::make_pair<float, float>(pitch.first * 0.5, pitch.second * 0.5);
198  const float thickness = pixdet->specificSurface().bounds().thickness();
199  const int nrows = pixdet->specificTopology().nrows();
200  const int ncolumns = pixdet->specificTopology().ncolumns();
201  const float pix_rounding = 0.99;
202 
203  // The maximum radial distance is going to be used to evaluate radiation damage XXX?
204  const float max_radial_distance =
205  std::sqrt(half_pitch.first * half_pitch.first + half_pitch.second * half_pitch.second);
206 
207  // All pixels are going to be translated to a proxy pixel cell (all pixels should behave
208  // equally no matter their position w.r.t. the module) and describe the movements there
209  // Define the center of the pixel local reference frame: the current cartesian local reference
210  // frame is centered at half_width_x,half_width_y,half_thickness
211  // XXX -- This info could be obtained at init/construction time?
212  LocalPoint center_proxy_cell(half_pitch.first, half_pitch.second, -0.5 * thickness);
213 
214  LogDebug("Pixel3DDigitizerAlgorithm::drift")
215  << "Pixel pitch:" << pitch.first * 1.0_um_inv << ", " << pitch.second * 1.0_um_inv << " [um]";
216 
217  // And the drift direction (assumed same for all the sensor)
218  // XXX call the function which will return a functional
219  std::function<LocalVector(float, float)> drift_direction = [](float x, float y) -> LocalVector {
220  const float theta = std::atan2(y, x);
221  return LocalVector(-std::cos(theta), -std::sin(theta), 0.0);
222  };
223  // The output
224  std::vector<digitizerUtility::SignalPoint> collection_points;
225  //collection_points.resize(ionization_points.size());
226  collection_points.reserve(ionization_points.size());
227 
228  // Radiation damage limit of application
229  // (XXX: No sense for 3D, let this be until decided what algorithm to use)
230  const float RAD_DAMAGE = 0.001;
231 
232  for (const auto& super_charge : ionization_points) {
233  // Extract the pixel cell
234  auto current_pixel = pixdet->specificTopology().pixel(LocalPoint(super_charge.x(), super_charge.y()));
235  // `pixel` function does not check to be in the ROC bounds,
236  // so check it here and fix potential rounding problems.
237  // Careful, this is assuming a rounding problem (1 unit), more than 1 pixel
238  // away is probably showing some backward problem worth it to track.
239  // This is also correcting out of bounds migrated charge from diffusion.
240  // The charge will be moved to the edge of the row/column.
241  current_pixel.first = std::clamp(current_pixel.first, float(0.0), (nrows - 1) + pix_rounding);
242  current_pixel.second = std::clamp(current_pixel.second, float(0.0), (ncolumns - 1) + pix_rounding);
243 
244  const auto current_pixel_int = std::make_pair(std::floor(current_pixel.first), std::floor(current_pixel.second));
245 
246  // Convert to the 1x1 proxy pixel cell (pc), where all calculations are going to be
247  // performed. The pixel is scaled to the actual pitch
248  const auto relative_position_at_pc =
249  std::make_pair((current_pixel.first - current_pixel_int.first) * pitch.first,
250  (current_pixel.second - current_pixel_int.second) * pitch.second);
251 
252  // Changing the reference frame to the proxy pixel cell
253  LocalPoint position_at_pc(relative_position_at_pc.first - center_proxy_cell.x(),
254  relative_position_at_pc.second - center_proxy_cell.y(),
255  super_charge.z() - center_proxy_cell.z());
256 
257  LogDebug("Pixel3DDigitizerAlgorithm::drift")
258  << "(super-)Charge\nlocal position: (" << super_charge.x() * 1.0_um_inv << ", " << super_charge.y() * 1.0_um_inv
259  << ", " << super_charge.z() * 1.0_um_inv << ") [um]"
260  << "\nMeasurement Point (row,column) (" << current_pixel.first << ", " << current_pixel.second << ")"
261  << "\nProxy pixel-cell frame (centered at left-back corner): (" << relative_position_at_pc.first * 1.0_um_inv
262  << ", " << relative_position_at_pc.second * 1.0_um_inv << ") [um]"
263  << "\nProxy pixel-cell frame (centered at n-column): (" << position_at_pc.x() * 1.0_um_inv << ", "
264  << position_at_pc.y() * 1.0_um_inv << ") [um] "
265  << "\nNe=" << super_charge.energy() << " electrons";
266 
267  // Check if the point is inside any of the column --> no charge was actually created then
268  if (is_inside_n_column_(position_at_pc, thickness) || is_inside_ohmic_column_(position_at_pc, half_pitch)) {
269  LogDebug("Pixel3DDigitizerAlgorithm::drift") << "Remove charge, inside the n-column or p-column!!";
270  continue;
271  }
272 
273  float nelectrons = super_charge.energy();
274  // XXX -- Diffusion: using the center frame
275  if (diffusion_activated) {
276  auto migrated_charges = diffusion(position_at_pc, super_charge.energy(), drift_direction, half_pitch, thickness);
277  for (auto& mc : migrated_charges) {
278  // Remove the migrated charges
279  nelectrons -= mc.energy();
280  // and convert back to the pixel ref. system
281  // Low-left origin/pitch -> relative within the pixel (a)
282  // Adding the pixel
283  const float pixel_x = current_pixel_int.first + (mc.x() + center_proxy_cell.x()) / pitch.first;
284  const float pixel_y = current_pixel_int.second + (mc.y() + center_proxy_cell.y()) / pitch.second;
285  const auto lp = pixdet->specificTopology().localPosition(MeasurementPoint(pixel_x, pixel_y));
286  // Remember: the drift function will move the reference system to the top. We need to subtract
287  // (center_proxy_cell.z() is a constant negative value) what we previously added in order to
288  // avoid a double translation when calling the drift function below the drift function
289  // initially considers the reference system centered in the module at half thickness)
290  mc.migrate_position(LocalPoint(lp.x(), lp.y(), mc.z() + center_proxy_cell.z()));
291  }
292  if (!migrated_charges.empty()) {
293  LogDebug("Pixel3DDigitizerAlgorithm::drift") << "****************"
294  << "MIGRATING (super-)charges"
295  << "****************";
296  // Drift this charges on the other pixel
297  auto mig_colpoints = drift(hit, pixdet, bfield, migrated_charges, false);
298  collection_points.insert(std::end(collection_points), mig_colpoints.begin(), mig_colpoints.end());
299  LogDebug("Pixel3DDigitizerAlgorithm::drift") << "*****************"
300  << "DOME MIGRATION"
301  << "****************";
302  }
303  }
304 
305  // Perform the drift, and check a potential lost of carriers because
306  // they reach the pasivation region (-z < thickness/2)
307  // XXX: not doing nothing, the carriers reach the electrode surface,
308  const float drift_distance = position_at_pc.perp() - np_column_radius_;
309 
310  // Insert a charge loss due to Rad Damage here
311  // XXX: ??
312  float energyOnCollector = nelectrons;
313  // FIXME: is this correct? Not for 3D...
314 
315  if (pseudoRadDamage_ >= RAD_DAMAGE) {
316  const float module_radius = pixdet->surface().position().perp();
317  if (module_radius <= pseudoRadDamageRadius_) {
318  const float kValue = pseudoRadDamage_ / (module_radius * module_radius);
319  energyOnCollector = energyOnCollector * std::exp(-1.0 * kValue * drift_distance / max_radial_distance);
320  }
321  }
322  LogDebug("Pixel3DDigitizerAlgorithm::drift")
323  << "Drift distance = " << drift_distance * 1.0_um_inv << " [um], "
324  << "Initial electrons = " << super_charge.energy()
325  << " [electrons], Electrons after loss/diff= " << energyOnCollector << " [electrons] ";
326  // Load the Charge distribution parameters
327  // XXX -- probably makes no sense the SignalPoint anymore...
328  collection_points.push_back(digitizerUtility::SignalPoint(
329  current_pixel_int.first, current_pixel_int.second, 0.0, 0.0, hit.tof(), energyOnCollector));
330  }
331 
332  return collection_points;
333 }
334 
335 // ====================================================================
336 // Signal is already "induced" (actually electrons transported to the
337 // n-column) at the electrode. Just collecting and adding-up all pixel
338 // signal and linking it to the simulated energy deposit (hit)
339 void Pixel3DDigitizerAlgorithm::induce_signal(std::vector<PSimHit>::const_iterator inputBegin,
340  const PSimHit& hit,
341  const size_t hitIndex,
342  const size_t firstHitIndex,
343  const uint32_t tofBin,
344  const Phase2TrackerGeomDetUnit* pixdet,
345  const std::vector<digitizerUtility::SignalPoint>& collection_points) {
346  // X - Rows, Left-Right
347  // Y - Columns, Down-Up
348  const uint32_t detId = pixdet->geographicalId().rawId();
349  // Accumulated signal at each channel of this detector
350  signal_map_type& the_signal = _signal[detId];
351 
352  // Choose the proper pixel-to-channel converter
353  std::function<int(int, int)> pixelToChannel =
355  : static_cast<std::function<int(int, int)> >(Phase2TrackerDigi::pixelToChannel);
356 
357  // Iterate over collection points on the collection plane
358  for (const auto& pt : collection_points) {
359  // Extract corresponding channel (position is already given in pixel indices)
360  const int channel = pixelToChannel(pt.position().x(), pt.position().y());
361 
362  float corr_time = hit.tof() - pixdet->surface().toGlobal(hit.localPosition()).mag() * c_inv;
363  if (makeDigiSimLinks_) {
364  the_signal[channel] +=
365  digitizerUtility::Ph2Amplitude(pt.amplitude(), &hit, pt.amplitude(), corr_time, hitIndex, tofBin);
366  } else {
367  the_signal[channel] += digitizerUtility::Ph2Amplitude(pt.amplitude(), nullptr, pt.amplitude());
368  }
369 
370  LogDebug("Pixel3DDigitizerAlgorithm")
371  << " Induce charge at row,col:" << pt.position() << " N_electrons:" << pt.amplitude() << " [Channel:" << channel
372  << "]\n [Accumulated signal in this channel:" << the_signal[channel].ampl() << "] "
373  << " Global index linked PSimHit:" << hitIndex;
374  }
375 }
Pixel3DDigitizerAlgorithm(const edm::ParameterSet &conf, edm::ConsumesCollector iC)
Local3DVector LocalVector
Definition: LocalVector.h:12
T perp() const
Definition: PV3DBase.h:69
Point3DBase< Scalar, LocalTag > LocalPoint
Definition: Definitions.h:30
virtual std::pair< float, float > pixel(const LocalPoint &p) const =0
virtual LocalPoint localPosition(const MeasurementPoint &) const =0
virtual int ncolumns() const =0
T z() const
Definition: PV3DBase.h:61
std::vector< digitizerUtility::EnergyDepositUnit > diffusion(const LocalPoint &pos, const float &ncarriers, const std::function< LocalVector(float, float)> &u_drift, const std::pair< float, float > pitches, const float &thickness) const
Sin< T >::type sin(const T &t)
Definition: Sin.h:22
virtual int nrows() const =0
std::map< int, digitizerUtility::Ph2Amplitude, std::less< int > > signal_map_type
string dd
Definition: createTree.py:154
std::vector< digitizerUtility::SignalPoint > drift(const PSimHit &hit, const Phase2TrackerGeomDetUnit *pixdet, const GlobalVector &bfield, const std::vector< digitizerUtility::EnergyDepositUnit > &ionization_points) const override
static int pixelToChannel(int row, int col)
Definition: PixelDigi.h:75
virtual float thickness() const =0
T x() const
Definition: PV3DBase.h:59
T y() const
Definition: PV3DBase.h:60
Measurement2DPoint MeasurementPoint
Measurement points are two-dimensional by default.
T sqrt(T t)
Definition: SSEVec.h:19
static PackedDigiType pixelToChannel(unsigned int row, unsigned int col)
Cos< T >::type cos(const T &t)
Definition: Cos.h:22
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
DetId geographicalId() const
The label of this GeomDet.
Definition: GeomDet.h:64
bool select_hit(const PSimHit &hit, double tCorr, double &sigScale) const override
constexpr double c_inv
const bool is_inside_n_column_(const LocalPoint &p, const float &sensor_thickness) const
GlobalPoint toGlobal(const Point2DBase< Scalar, LocalTag > lp) const
Definition: Surface.h:79
Log< level::Info, false > LogInfo
const Plane & surface() const
The nominal surface of the GeomDet.
Definition: GeomDet.h:37
T mag() const
The vector magnitude. Equivalent to sqrt(vec.mag2())
const PositionType & position() const
constexpr uint32_t rawId() const
get the raw id
Definition: DetId.h:57
const bool is_inside_ohmic_column_(const LocalPoint &p, const std::pair< float, float > &pitch) const
static const double N_SIGMA
HLT enums.
virtual const PixelTopology & specificTopology() const
Returns a reference to the pixel proxy topology.
virtual std::pair< float, float > pitch() const =0
const Plane & specificSurface() const
Same as surface(), kept for backward compatibility.
Definition: GeomDet.h:40
constexpr uint32_t pixelToChannel(int row, int col)
Geom::Theta< T > theta() const
#define LogDebug(id)
void induce_signal(std::vector< PSimHit >::const_iterator inputBegin, const PSimHit &hit, const size_t hitIndex, const size_t firstHitIndex, const uint32_t tofBin, const Phase2TrackerGeomDetUnit *pixdet, const std::vector< digitizerUtility::SignalPoint > &collection_points) override
const Bounds & bounds() const
Definition: Surface.h:87
unsigned transform(const HcalDetId &id, unsigned transformCode)