LDMX Software
tracking::reco::DigitizationProcessor Class Reference

Digitization processor for the silicon strip tracker. More...

#include <DigitizationProcessor.h>

Public Member Functions

 DigitizationProcessor (const std::string &name, framework::Process &process)
 
void onProcessStart () override
 Callback for the EventProcessor to take any necessary action when the processing of events starts, such as creating histograms.
 
void configure (framework::config::Parameters &parameters) override
 Callback for the EventProcessor to configure itself from the given set of parameters.
 
void onNewRun (const ldmx::RunHeader &header) override
 Before the run starts (but after the conditions are configured) set up the random seeds for this run.
 
void produce (framework::Event &event) override
 Process the event and put new data products into it.
 
std::vector< ldmx::MeasurementdigitizeHits (const std::vector< ldmx::SimTrackerHit > &sim_hits, std::vector< ldmx::SimSiStripHit > *raw_hits=nullptr)
 Digitize a collection of SimTrackerHits into Measurements.
 
bool mergeSimHits (const std::vector< ldmx::SimTrackerHit > &sim_hits, std::vector< ldmx::SimTrackerHit > &merged_hits)
 
bool mergeHits (const std::vector< ldmx::SimTrackerHit > &sihits, std::vector< ldmx::SimTrackerHit > &mergedHits)
 
- Public Member Functions inherited from tracking::reco::TrackingGeometryUser
 TrackingGeometryUser (const std::string &name, framework::Process &p)
 
- Public Member Functions inherited from framework::Producer
 Producer (const std::string &name, Process &process)
 Class constructor.
 
virtual void process (Event &event) final
 Processing an event for a Producer is calling produce.
 
- Public Member Functions inherited from framework::EventProcessor
 DECLARE_FACTORY (EventProcessor, EventProcessor *, const std::string &, Process &)
 declare that we have a factory for this class
 
 EventProcessor (const std::string &name, Process &process)
 Class constructor.
 
virtual ~EventProcessor ()=default
 Class destructor.
 
virtual void beforeNewRun (ldmx::RunHeader &run_header)
 Callback for Producers to add parameters to the run header before conditions are initialized.
 
virtual void onFileOpen (EventFile &event_file)
 Callback for the EventProcessor to take any necessary action when a new event input ROOT file is opened.
 
virtual void onFileClose (EventFile &event_file)
 Callback for the EventProcessor to take any necessary action when a event input ROOT file is closed.
 
virtual void onProcessEnd ()
 Callback for the EventProcessor to take any necessary action when the processing of events finishes, such as calculating job-summary quantities.
 
template<class T >
const T & getCondition (const std::string &condition_name)
 Access a conditions object for the current event.
 
TDirectory * getHistoDirectory ()
 Access/create a directory in the histogram file for this event processor to create histograms and analysis tuples.
 
void setStorageHint (framework::StorageControl::Hint hint)
 Mark the current event as having the given storage control hint from this module_.
 
void setStorageHint (framework::StorageControl::Hint hint, const std::string &purposeString)
 Mark the current event as having the given storage control hint from this module and the given purpose string.
 
int getLogFrequency () const
 Get the current logging frequency from the process.
 
int getRunNumber () const
 Get the run number from the process.
 
std::string getName () const
 Get the processor name.
 
void createHistograms (const std::vector< framework::config::Parameters > &histos)
 Internal function which is used to create histograms passed from the python configuration @parma histos vector of Parameters that configure histograms to create.
 

Private Member Functions

void buildLorentzCache ()
 

Private Attributes

std::string hit_collection_
 Input hit collection to digitize.
 
std::string out_collection_
 Output measurement collection name.
 
double min_e_dep_
 Minimum energy deposition cut [MeV].
 
int track_id_
 Select a particular track ID (-1 = accept all).
 
bool merge_hits_ {false}
 Merge sim hits on the same sensor before digitizing.
 
bool do_smearing_ {true}
 Flag to enable/disable smearing in Mode 0.
 
double sigma_u_ {0}
 u-direction smearing sigma [mm].
 
double sigma_v_ {0}
 v-direction smearing sigma [mm].
 
bool use_charge_digitization_ {false}
 If true, use the full SiStripDigitizer instead of simple smearing.
 
tracking::digitization::SiStripDigitizer::SensorParams sensor_params_
 Parameters forwarded to SiStripDigitizer.
 
std::unique_ptr< tracking::digitization::SiStripDigitizerstrip_digitizer_
 The charge digitizer (constructed in onProcessStart).
 
std::string out_raw_collection_ {""}
 Output raw hit collection name (empty = don't save raw hits).
 
std::unique_ptr< tracking::digitization::PulseShapepulse_shape_
 The constructed pulse shape (created in onProcessStart).
 
bool use_lorentz_ {true}
 If false, skip Lorentz angle calculation and drift carriers straight (equivalent to zero magnetic field perpendicular to the sensor normal).
 
std::string field_map_ {""}
 Path to the magnetic field map file.
 
std::unordered_map< unsigned int, std::pair< double, double > > lorentz_tan_cache_
 Per-layer cached Lorentz tangents: layer_id → {tan_electron, tan_hole}.
 
std::string tracker_hit_passname_
 Input collection pass name.
 
std::string dump_geo_csv_ {""}
 If non-empty, write a CSV of all ACTS surface transforms to this path.
 
std::default_random_engine generator_
 
std::shared_ptr< std::normal_distribution< float > > normal_
 

Additional Inherited Members

- Protected Member Functions inherited from tracking::reco::TrackingGeometryUser
const Acts::GeometryContext & geometryContext ()
 
const Acts::MagneticFieldContext & magneticFieldContext ()
 
const Acts::CalibrationContext & calibrationContext ()
 
const geo::TrackersTrackingGeometrygeometry ()
 
void loadBField (const std::string &path, const std::vector< double > &map_offset={0., 0., 0.})
 Load the interpolated B-field map from path and cache it.
 
void loadBField (const std::vector< double > &map_offset={0., 0., 0.})
 Load B-field from the path recorded in the detector GDML.
 
std::shared_ptr< Acts::MagneticFieldProvider > bField () const
 Return the loaded B-field provider.
 
- Protected Member Functions inherited from framework::EventProcessor
void abortEvent ()
 Abort the event immediately.
 
- Protected Attributes inherited from framework::EventProcessor
HistogramPool histograms_
 helper object for making and filling histograms
 
NtupleManagerntuple_ {NtupleManager::getInstance()}
 Manager for any ntuples.
 
logging::logger the_log_
 The logger for this EventProcessor.
 

Detailed Description

Digitization processor for the silicon strip tracker.

Two modes are available, selected by the use_charge_digitization parameter:

Mode 0 (default, use_charge_digitization = false): Simple Gaussian smearing of the local coordinates. Fast and sufficient for most studies. Resolution set by sigma_u / sigma_v.

Mode 1 (use_charge_digitization = true): Realistic charge digitization. The track segment through the sensor is divided into sub-segments; each creates electron-hole pairs (N = Edep / E_pair). Thermal diffusion during drift spreads the charge as a Gaussian across strips. Electronic noise is added and a threshold is applied; the surviving strip cluster is clustered by a charge-weighted centroid to produce the local U measurement.

Definition at line 50 of file DigitizationProcessor.h.

Constructor & Destructor Documentation

◆ DigitizationProcessor()

tracking::reco::DigitizationProcessor::DigitizationProcessor ( const std::string & name,
framework::Process & process )

Definition at line 12 of file DigitizationProcessor.cxx.

14 : TrackingGeometryUser(name, process) {}
virtual void process(Event &event) final
Processing an event for a Producer is calling produce.

Member Function Documentation

◆ buildLorentzCache()

void tracking::reco::DigitizationProcessor::buildLorentzCache ( )
private

Definition at line 138 of file DigitizationProcessor.cxx.

138 {
139 if (!use_charge_digitization_) return;
140
141 if (field_map_.empty())
142 loadBField();
143 else
145
146 // Low-field (Hall) mobility from the Canali model [cm²/(V·s)] → [m²/(V·s)]
147 const double t = sensor_params_.temperature;
148 auto carrier_e = tracking::digitization::getCarrier(-1);
149 auto carrier_h = tracking::digitization::getCarrier(1);
150 const double mu_e = carrier_e.mu0(t) * 1.0e-4; // m²/(V·s)
151 const double mu_h = carrier_h.mu0(t) * 1.0e-4;
152
153 auto bfield_cache = bField()->makeCache(magneticFieldContext());
154
155 for (const auto& [layer_id, surface] : geometry().layer_surface_map_) {
156 const Acts::Vector3 center_mm = surface->center(geometryContext());
157 const auto b_result = bField()->getField(center_mm, bfield_cache);
158 if (!b_result.ok()) continue;
159
160 // B in Tesla (ACTS field providers return values in Acts internal units)
161 const Acts::Vector3 b_t = b_result.value() / Acts::UnitConstants::T;
162
163 // Sensor W-normal = 3rd column of the rotation matrix
164 const Acts::Vector3 w_hat =
165 surface->localToGlobalTransform(geometryContext()).rotation().col(2);
166
167 const double bw = b_t.dot(w_hat); // [T]
168
169 // tan(θ_L) = charge_sign · μ · Bw
170 // electrons: charge = −1, holes: charge = +1
171 const double tan_e = -mu_e * bw;
172 const double tan_h = +mu_h * bw;
173
174 lorentz_tan_cache_[layer_id] = {tan_e, tan_h};
175
176 ldmx_log(debug) << "Lorentz cache: layer=" << layer_id << " Bw=" << bw
177 << " T" << " tan_e=" << tan_e << " tan_h=" << tan_h;
178 }
179
180 ldmx_log(info) << "Lorentz tangents computed for "
181 << lorentz_tan_cache_.size() << " layers from field map "
182 << (field_map_.empty() ? geometry().fieldMapFile()
183 : field_map_);
184}
tracking::digitization::SiStripDigitizer::SensorParams sensor_params_
Parameters forwarded to SiStripDigitizer.
std::unordered_map< unsigned int, std::pair< double, double > > lorentz_tan_cache_
Per-layer cached Lorentz tangents: layer_id → {tan_electron, tan_hole}.
bool use_charge_digitization_
If true, use the full SiStripDigitizer instead of simple smearing.
std::string field_map_
Path to the magnetic field map file.
std::shared_ptr< Acts::MagneticFieldProvider > bField() const
Return the loaded B-field provider.
void loadBField(const std::string &path, const std::vector< double > &map_offset={0., 0., 0.})
Load the interpolated B-field map from path and cache it.

◆ configure()

void tracking::reco::DigitizationProcessor::configure ( framework::config::Parameters & parameters)
overridevirtual

Callback for the EventProcessor to configure itself from the given set of parameters.

The parameters a processor has access to are the member variables of the python class in the sequence that has class_name equal to the EventProcessor class name.

For an example, look at MyProcessor.

Parameters
parametersParameters for configuration.

Reimplemented from framework::EventProcessor.

Definition at line 86 of file DigitizationProcessor.cxx.

87 {
89 parameters.get<std::string>("hit_collection", "TaggerSimHits");
90
91 tracker_hit_passname_ = parameters.get<std::string>("tracker_hit_passname");
93 parameters.get<std::string>("out_collection", "OutputMeasuements");
94 min_e_dep_ = parameters.get<double>("min_e_dep", 0.05);
95 track_id_ = parameters.get<int>("track_id", -1);
96 do_smearing_ = parameters.get<bool>("do_smearing", true);
97 sigma_u_ = parameters.get<double>("sigma_u", 0.01);
98 sigma_v_ = parameters.get<double>("sigma_v", 0.);
99 merge_hits_ = parameters.get<bool>("merge_hits", false);
100
101 // Mode 1: charge digitization parameters
103 parameters.get<bool>("use_charge_digitization", false);
104
106 sensor_params_.bias_voltage = parameters.get<double>("bias_voltage", 200.0);
108 parameters.get<double>("depletion_voltage", 70.0);
109 sensor_params_.temperature = parameters.get<double>("temperature", 300.0);
111 parameters.get<double>("noise_electrons", 1000.0);
113 parameters.get<double>("threshold_electrons", 3000.0);
114 // Fixed sensor properties — not user-configurable.
115 // LDMX (and HPS) use n-type bulk with hole-side readout.
119 use_lorentz_ = parameters.get<bool>("use_lorentz", true);
121 parameters.get<double>("electron_lorentz_tangent", 0.0);
123 parameters.get<double>("hole_lorentz_tangent", 0.0);
124 sensor_params_.trapping = parameters.get<double>("trapping", 0.0);
126 parameters.get<double>("deposition_granularity", 0.10);
127 sensor_params_.n_segments_min = parameters.get<int>("n_segments_min", 5);
128 // n_readout_strips is fixed by the sensor geometry constant
129 // N_READOUT_STRIPS.
130
131 out_raw_collection_ = parameters.get<std::string>("out_raw_collection", "");
132 field_map_ = parameters.get<std::string>("field_map", "");
133 }
134
135 dump_geo_csv_ = parameters.get<std::string>("dump_geo_csv", "");
136}
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
std::string out_collection_
Output measurement collection name.
std::string hit_collection_
Input hit collection to digitize.
bool do_smearing_
Flag to enable/disable smearing in Mode 0.
std::string dump_geo_csv_
If non-empty, write a CSV of all ACTS surface transforms to this path.
bool use_lorentz_
If false, skip Lorentz angle calculation and drift carriers straight (equivalent to zero magnetic fie...
std::string tracker_hit_passname_
Input collection pass name.
double min_e_dep_
Minimum energy deposition cut [MeV].
int track_id_
Select a particular track ID (-1 = accept all).
double sigma_u_
u-direction smearing sigma [mm].
bool merge_hits_
Merge sim hits on the same sensor before digitizing.
double sigma_v_
v-direction smearing sigma [mm].
std::string out_raw_collection_
Output raw hit collection name (empty = don't save raw hits).
double electron_lorentz_tangent
tan(θ_Lorentz) for electrons. Sign encodes U-shift direction.
double threshold_electrons
Readout threshold [electrons]. Strips below this are suppressed.
double noise_electrons
Electronic noise sigma [electrons ENC].
double bias_voltage
Applied reverse-bias voltage [V].
double deposition_granularity
Adaptive segmentation granularity: max U-step as fraction of sense_pitch.
double trapping
Charge-trapping fraction lost per 100 µm of drift.
bool hole_side_readout
Simulate and read out the hole-collection side (p-strips / backplane).
int n_segments_min
Minimum number of track sub-segments (used when the track is close to normal incidence so the adaptiv...
bool electron_side_readout
Simulate and read out the electron-collection side (n-strips).
bool is_n_type
true = n-type bulk; false = p-type bulk. LDMX (and HPS) use n-type bulk.

References framework::config::Parameters::get().

◆ digitizeHits()

std::vector< ldmx::Measurement > tracking::reco::DigitizationProcessor::digitizeHits ( const std::vector< ldmx::SimTrackerHit > & sim_hits,
std::vector< ldmx::SimSiStripHit > * raw_hits = nullptr )

Digitize a collection of SimTrackerHits into Measurements.

Operates in either smearing mode or full charge-digitization mode depending on the use_charge_digitization configuration flag.

Parameters
sim_hitsThe collection of SimTrackerHits to digitize.
raw_hitsIf non-null and charge digitization is active, filled with one SimSiStripHit per above-threshold readout strip.

Definition at line 324 of file DigitizationProcessor.cxx.

326 {
327 ldmx_log(debug) << "Found: " << sim_hits.size() << " sim hits in '"
328 << hit_collection_ << "' with passname '"
329 << tracker_hit_passname_ << "'";
330
331 std::vector<ldmx::Measurement> measurements;
332
333 struct StripContrib {
334 double charge_electrons_;
335 double hit_time_ns_;
336 int track_id_;
337 int pdg_id_;
338 int sim_hit_id_;
339 float edep_;
340 };
341 // layer_id -> strip_idx -> per-hit contributions (populated in Phase 1,
342 // consumed in Phase 2 after the loop to apply noise once per strip)
343 std::map<int, std::map<int, std::vector<StripContrib>>> layer_strip_contribs;
344
345 for (auto& sim_hit : sim_hits) {
346 // Energy deposition cut
347 if (sim_hit.getEdep() <= min_e_dep_) continue;
348 if (track_id_ > 0 && sim_hit.getTrackID() != track_id_) continue;
349
350 ldmx::Measurement measurement(sim_hit);
351
352 // Sensor identification
353 auto layer_id = tracking::sim::utils::getSensorID(sim_hit);
354 measurement.setLayerID(layer_id);
355
356 auto hit_surface{geometry().getSurface(layer_id)};
357 if (!hit_surface) continue;
358
359 ldmx_log(trace)
360 << "Local to global\n"
361 << hit_surface->localToGlobalTransform(geometryContext()).rotation()
362 << "\n"
363 << hit_surface->localToGlobalTransform(geometryContext()).translation();
364
365 // -----------------------------------------------------------------------
366 // Project global hit position onto the surface (2D local coords)
367 // -----------------------------------------------------------------------
368 Acts::Vector3 dummy_momentum;
369 Acts::Vector2 local_pos_2d;
370
371 // TODO: clarify / derive the 0.320 mm surface tolerance from the geometry
372 constexpr double surface_thickness = 0.320 * Acts::UnitConstants::mm;
373
374 Acts::Vector3 global_pos(measurement.getGlobalPosition()[0],
375 measurement.getGlobalPosition()[1],
376 measurement.getGlobalPosition()[2]);
377
378 try {
379 local_pos_2d = hit_surface
380 ->globalToLocal(geometryContext(), global_pos,
381 dummy_momentum, surface_thickness)
382 .value();
383 } catch (const std::exception& e) {
384 ldmx_log(warn) << "hit not on surface... Skipping.";
385 continue;
386 }
387
388 // Store the projected truth U before any smearing or charge digitization.
389 measurement.setTruthU(static_cast<float>(local_pos_2d[0]));
390
391 // -----------------------------------------------------------------------
392 // Mode 1: realistic charge digitization
393 // -----------------------------------------------------------------------
395 // Read sensor thickness from the geometry.
396 const auto* placement = hit_surface->surfacePlacement();
397 if (!placement) {
398 ldmx_log(warn) << "No detector element for layer_id=" << layer_id
399 << " — skipping hit";
400 continue;
401 }
402 const double thickness =
403 static_cast<const tracking::geo::DetectorElement*>(placement)
404 ->thickness();
405 strip_digitizer_->setThickness(thickness);
406
407 // Build the full 3D local position and direction for charge simulation.
408 const Acts::Transform3 surf_transform =
409 hit_surface->localToGlobalTransform(geometryContext());
410
411 // 3D local position: apply the inverse surface transform to the global
412 // hit position so that we know the depth (W) coordinate.
413 const Acts::Vector3 local_pos_3d = surf_transform.inverse() * global_pos;
414
415 // 3D local direction: rotate the global unit momentum into local frame.
416 // Apply the same LDMX→ACTS frame permutation as Measurement.cxx:
417 // ACTS-X = LDMX-z [2], ACTS-Y = LDMX-x [0], ACTS-Z = LDMX-y [1].
418 Acts::Vector3 global_mom(sim_hit.getMomentum()[2],
419 sim_hit.getMomentum()[0],
420 sim_hit.getMomentum()[1]);
421 const double mom_mag = global_mom.norm();
422
423 Acts::Vector3 local_dir_3d;
424 if (mom_mag > 0.0) {
425 local_dir_3d =
426 surf_transform.rotation().transpose() * (global_mom / mom_mag);
427 } else {
428 // Degenerate case: treat as normal incidence
429 local_dir_3d = Acts::Vector3(0.0, 0.0, 1.0);
430 }
431
432 // Path length through the sensor; fall back to thickness / |cos θ|
433 // if the stored value is not set.
434 double path_length = sim_hit.getPathLength();
435 if (path_length <= 0.0) {
436 const double cos_theta = std::abs(local_dir_3d[2]);
437 path_length = (cos_theta > 1e-3) ? thickness / cos_theta : thickness;
438 }
439
440 // Apply per-layer Lorentz tangents from the B-field cache (if available).
441 if (use_lorentz_) {
442 auto lorentz_it = lorentz_tan_cache_.find(layer_id);
443 if (lorentz_it != lorentz_tan_cache_.end()) {
444 strip_digitizer_->mutableParams().electron_lorentz_tangent =
445 lorentz_it->second.first;
446 strip_digitizer_->mutableParams().hole_lorentz_tangent =
447 lorentz_it->second.second;
448 }
449 }
450
451 // Compute charge deposited on each strip
452 auto strip_charges = strip_digitizer_->computeStripCharges(
453 sim_hit.getEdep(), local_pos_3d, local_dir_3d, path_length);
454
455 ldmx_log(trace) << "Charge digi: " << strip_charges.size()
456 << " strips from computeStripCharges (pre-noise)";
457
458 // Phase 1: accumulate this hit's strip charges into the per-layer map.
459 // Noise is applied once per strip in Phase 2 (after the sim-hit loop)
460 // so that overlapping contributions from different SimParticles are
461 // summed before threshold is applied.
462 if (raw_hits && pulse_shape_) {
463 const double hit_time_ns = sim_hit.getTime();
464 for (const auto& [strip_idx, charge] : strip_charges) {
465 layer_strip_contribs[layer_id][strip_idx].push_back(StripContrib{
466 charge, hit_time_ns, sim_hit.getTrackID(), sim_hit.getPdgID(),
467 sim_hit.getID(), sim_hit.getEdep()});
468 }
469 }
470
471 // Measurements are produced downstream by StripFitProcessor +
472 // StripClusterProcessor for the reconstructed position, but we still
473 // emit a truth-position Measurement here so that DigiDQM can build a
474 // per-layer truth-U lookup for the sim_cluster_du residual.
475 // Global position, time, edep, ID, and track ID are already populated
476 // by the Measurement(sim_hit) constructor above; set local coords and
477 // zero the covariance (this is a truth hit, not a smeared measurement).
478 measurement.setLocalPosition(local_pos_2d(0), local_pos_2d(1));
479 measurement.setLocalCovariance(0., 0.);
480 measurements.push_back(measurement);
481
482 // -----------------------------------------------------------------------
483 // Mode 0: simple Gaussian smearing
484 // -----------------------------------------------------------------------
485 } else {
486 if (do_smearing_) {
487 float smear_factor{(*normal_)(generator_)};
488 local_pos_2d[0] += smear_factor * sigma_u_;
489 smear_factor = (*normal_)(generator_);
490 local_pos_2d[1] += smear_factor * sigma_v_;
491
492 measurement.setLocalCovariance(
493 static_cast<float>(sigma_u_ * sigma_u_),
494 static_cast<float>(tracking::digitization::SIGMA_V_MM *
495 tracking::digitization::SIGMA_V_MM));
496
497 auto transf_global_pos{hit_surface->localToGlobal(
498 geometryContext(), local_pos_2d, dummy_momentum)};
499 measurement.setGlobalPosition(measurement.getGlobalPosition()[0],
500 transf_global_pos(1),
501 transf_global_pos(2));
502 }
503
504 measurement.setLocalPosition(local_pos_2d(0), local_pos_2d(1));
505 measurements.push_back(measurement);
506 }
507 } // loop over sim hits
508
509 // Phase 2: apply noise once per strip across all sim-hit contributions,
510 // then build SimSiStripHits with correctly superimposed pulse shapes.
511 if (raw_hits && pulse_shape_) {
512 const int adc_max = (1 << tracking::digitization::ADC_BITS) - 1;
513
514 for (auto& [lyr_id, strip_contribs_map] : layer_strip_contribs) {
515 // Sum all contributions to get the total pre-noise charge per strip.
516 std::map<int, double> total_charges;
517 for (const auto& [strip_idx, contribs] : strip_contribs_map) {
518 double total = 0.0;
519 for (const auto& c : contribs) total += c.charge_electrons_;
520 total_charges[strip_idx] = total;
521 }
522
523 // Add noise to every strip (and its ±1 neighbours) then apply threshold.
524 strip_digitizer_->applyNoiseAndThreshold(total_charges);
525 if (total_charges.empty()) continue;
526
527 for (const auto& [strip_idx, final_charge] : total_charges) {
528 const auto contrib_it = strip_contribs_map.find(strip_idx);
529 const bool has_signal = (contrib_it != strip_contribs_map.end());
530
531 int track_id_out = -1;
532 int pdg_id_out = 0;
533 int sim_hit_id_out = -1;
534 float edep_out = 0.f;
535 double ref_time_ns = 0.0;
536 std::vector<short> samples(tracking::digitization::N_SAMPLES);
537
538 if (has_signal) {
539 const auto& contribs = contrib_it->second;
540
541 // Dominant contributor = strip's largest single charge deposit.
542 const StripContrib* dom = &contribs.front();
543 for (const auto& c : contribs)
544 if (c.charge_electrons_ > dom->charge_electrons_) dom = &c;
545
546 ref_time_ns = dom->hit_time_ns_;
547 track_id_out = dom->track_id_;
548 pdg_id_out = dom->pdg_id_;
549 sim_hit_id_out = dom->sim_hit_id_;
550 for (const auto& c : contribs) edep_out += c.edep_;
551
552 // ADC = pedestal + superposition of each contributor's shaped pulse.
553 for (int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
554 ++isamp) {
555 const double t_samp =
556 tracking::digitization::T0_OFFSET_NS +
557 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
558 double val =
559 static_cast<double>(tracking::digitization::ADC_PEDESTAL);
560 for (const auto& c : contribs)
561 val += (c.charge_electrons_ /
562 tracking::digitization::ADC_ELECTRONS_PER_COUNT) *
563 pulse_shape_->eval(t_samp - c.hit_time_ns_);
564 samples[isamp] = static_cast<short>(
565 std::clamp(static_cast<int>(std::round(val)), 0, adc_max));
566 }
567 } else {
568 // Noise-only strip: added as a ±1 neighbour by
569 // applyNoiseAndThreshold. Borrow the nearest signal strip's dominant
570 // hit time for pulse shaping.
571 for (int delta : {-1, +1}) {
572 const auto nb = strip_contribs_map.find(strip_idx + delta);
573 if (nb != strip_contribs_map.end() && !nb->second.empty()) {
574 const StripContrib* dom = &nb->second.front();
575 for (const auto& c : nb->second)
576 if (c.charge_electrons_ > dom->charge_electrons_) dom = &c;
577 ref_time_ns = dom->hit_time_ns_;
578 break;
579 }
580 }
581 const double peak_adc =
582 final_charge / tracking::digitization::ADC_ELECTRONS_PER_COUNT;
583 for (int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
584 ++isamp) {
585 const double t_samp =
586 tracking::digitization::T0_OFFSET_NS +
587 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
588 const double val =
589 static_cast<double>(tracking::digitization::ADC_PEDESTAL) +
590 peak_adc * pulse_shape_->eval(t_samp - ref_time_ns);
591 samples[isamp] = static_cast<short>(
592 std::clamp(static_cast<int>(std::round(val)), 0, adc_max));
593 }
594 }
595
596 raw_hits->emplace_back(lyr_id, strip_idx, std::move(samples),
597 static_cast<long>(ref_time_ns), track_id_out,
598 pdg_id_out, sim_hit_id_out, edep_out);
599 }
600 }
601 } // Phase 2
602
603 return measurements;
604} // digitizeHits
std::unique_ptr< tracking::digitization::PulseShape > pulse_shape_
The constructed pulse shape (created in onProcessStart).
std::unique_ptr< tracking::digitization::SiStripDigitizer > strip_digitizer_
The charge digitizer (constructed in onProcessStart).

References ldmx::Measurement::getGlobalPosition(), ldmx::Measurement::setGlobalPosition(), ldmx::Measurement::setLayerID(), ldmx::Measurement::setLocalCovariance(), ldmx::Measurement::setLocalPosition(), and ldmx::Measurement::setTruthU().

◆ mergeHits()

bool tracking::reco::DigitizationProcessor::mergeHits ( const std::vector< ldmx::SimTrackerHit > & sihits,
std::vector< ldmx::SimTrackerHit > & mergedHits )

Definition at line 225 of file DigitizationProcessor.cxx.

227 {
228 if (sihits.size() < 1) return false;
229
230 if (sihits.size() == 1) {
231 mergedHits.push_back(sihits[0]);
232 return true;
233 }
234
235 ldmx::SimTrackerHit merged_hit;
236 merged_hit.setLayerID(sihits[0].getLayerID());
237 merged_hit.setModuleID(sihits[0].getModuleID());
238 merged_hit.setID(sihits[0].getID());
239 merged_hit.setTrackID(sihits[0].getTrackID());
240
241 double x{0}, y{0}, z{0}, px{0}, py{0}, pz{0};
242 double t{0}, e{0}, edep{0}, path{0};
243 int pdg_id = sihits[0].getPdgID();
244
245 for (auto hit : sihits) {
246 double edep_hit = hit.getEdep();
247 edep += edep_hit;
248 e += hit.getEnergy();
249 t += edep_hit * hit.getTime();
250 x += edep_hit * hit.getPosition()[0];
251 y += edep_hit * hit.getPosition()[1];
252 z += edep_hit * hit.getPosition()[2];
253 px += edep_hit * hit.getMomentum()[0];
254 py += edep_hit * hit.getMomentum()[1];
255 pz += edep_hit * hit.getMomentum()[2];
256 path += edep_hit * hit.getPathLength();
257
258 if (hit.getPdgID() != pdg_id) {
259 ldmx_log(error)
260 << "ERROR:: Found hits with compatible sensorID and track_id "
261 "but different PDGID";
262 ldmx_log(error) << "TRACKID ==" << hit.getTrackID() << " vs "
263 << sihits[0].getTrackID();
264 ldmx_log(error) << "PDGID== " << hit.getPdgID() << " vs " << pdg_id;
265 return false;
266 }
267 }
268
269 merged_hit.setTime(t / edep);
270 merged_hit.setPosition(x / edep, y / edep, z / edep);
271 merged_hit.setMomentum(px / edep, py / edep, pz / edep);
272 merged_hit.setPathLength(path / edep);
273 merged_hit.setEnergy(e);
274 merged_hit.setEdep(edep);
275 merged_hit.setPdgID(pdg_id);
276
277 mergedHits.push_back(merged_hit);
278 return true;
279}
Represents a simulated tracker hit in the simulation.
void setEdep(const float edep)
Set the energy deposited on the hit [MeV].
void setModuleID(const int moduleID)
Set the module ID associated with a hit.
void setPosition(const float x_, const float y_, const float z_)
Set the position of the hit [mm].
void setTime(const float time)
Set the global time of the hit [ns].
void setID(const long id)
Set the detector ID of the hit.
void setLayerID(const int layerID)
Set the geometric layer ID of the hit.
void setPathLength(const float pathLength)
Set the path length of the hit [mm].
void setEnergy(const float energy)
Set the energy of the hit.
void setPdgID(const int simPdgID)
Set the Sim particle track ID of the hit.
void setTrackID(const int simTrackID)
Set the Sim particle track ID of the hit.
void setMomentum(const float px, const float py, const float pz)
Set the momentum of the particle at the position at which the hit took place [GeV].

◆ mergeSimHits()

bool tracking::reco::DigitizationProcessor::mergeSimHits ( const std::vector< ldmx::SimTrackerHit > & sim_hits,
std::vector< ldmx::SimTrackerHit > & merged_hits )

Definition at line 281 of file DigitizationProcessor.cxx.

283 {
284 // Key: [sensor_id][track_id] → list of hits to merge
285 std::map<int, std::map<int, std::vector<ldmx::SimTrackerHit>>> hitmap;
286
287 for (const auto& hit : sim_hits) {
288 unsigned int index = tracking::sim::utils::getSensorID(hit);
289 unsigned int trackid = hit.getTrackID();
290 hitmap[index][trackid].push_back(hit);
291
292 ldmx_log(trace) << "hitmap being filled, size::[" << index << "]["
293 << trackid << "] size " << hitmap[index][trackid].size();
294 }
295
296 typedef std::map<int,
297 std::map<int, std::vector<ldmx::SimTrackerHit>>>::iterator
298 hitmap_it1;
299 typedef std::map<int, std::vector<ldmx::SimTrackerHit>>::iterator hitmap_it2;
300
301 for (hitmap_it1 it = hitmap.begin(); it != hitmap.end(); it++) {
302 for (hitmap_it2 it2 = it->second.begin(); it2 != it->second.end(); it2++) {
303 mergeHits(it2->second, merged_hits);
304 }
305 }
306
307 ldmx_log(debug) << "Sim_hits Size = " << sim_hits.size()
308 << " Merged_hits Size = " << merged_hits.size();
309
310 for (const auto& hit : sim_hits) {
311 ldmx_log(trace) << hit;
312 }
313 for (const auto& mhit : merged_hits) {
314 ldmx_log(trace) << mhit;
315 }
316
317 return true;
318}

◆ onNewRun()

void tracking::reco::DigitizationProcessor::onNewRun ( const ldmx::RunHeader & header)
overridevirtual

Before the run starts (but after the conditions are configured) set up the random seeds for this run.

Parameters
[in]headerRunHeader for this run, unused

Reimplemented from framework::EventProcessor.

Definition at line 186 of file DigitizationProcessor.cxx.

186 {
189 const uint64_t seed = rseed.getSeed("Tracking::DigitizationProcessor");
190 generator_.seed(seed);
191 if (strip_digitizer_) strip_digitizer_->seed(seed);
192}
const T & getCondition(const std::string &condition_name)
Access a conditions object for the current event.
static const std::string CONDITIONS_OBJECT_NAME
Conditions object name.

References framework::RandomNumberSeedService::CONDITIONS_OBJECT_NAME.

◆ onProcessStart()

void tracking::reco::DigitizationProcessor::onProcessStart ( )
overridevirtual

Callback for the EventProcessor to take any necessary action when the processing of events starts, such as creating histograms.

Reimplemented from framework::EventProcessor.

Definition at line 16 of file DigitizationProcessor.cxx.

16 {
17 normal_ = std::make_shared<std::normal_distribution<float>>(0., 1.);
18
21 std::make_unique<tracking::digitization::SiStripDigitizer>(
23 ldmx_log(info) << "Charge digitization enabled."
24 << " thickness=from geometry"
25 << " sense_pitch=" << tracking::digitization::SENSE_PITCH_MM
26 << " mm" << " readout_pitch="
27 << tracking::digitization::READOUT_PITCH_MM << " mm"
28 << " Vbias=" << sensor_params_.bias_voltage << " V"
29 << " Vdep=" << sensor_params_.depletion_voltage << " V"
30 << " bulk=" << (sensor_params_.is_n_type ? "n" : "p")
31 << "-type" << " e_lorentz_tan="
33 << " h_lorentz_tan=" << sensor_params_.hole_lorentz_tangent
34 << " trapping=" << sensor_params_.trapping
35 << " noise=" << sensor_params_.noise_electrons << " e-"
36 << " threshold=" << sensor_params_.threshold_electrons
37 << " e-"
38 << " n_segments_min=" << sensor_params_.n_segments_min
39 << " granularity=" << sensor_params_.deposition_granularity;
40
42 std::string(tracking::digitization::PULSE_SHAPE_NAME),
43 tracking::digitization::PEAKING_TIME_NS,
44 tracking::digitization::SECOND_TIME_CONST_NS);
45 ldmx_log(info) << "Pulse shaping: shape="
46 << tracking::digitization::PULSE_SHAPE_NAME
47 << " tp=" << tracking::digitization::PEAKING_TIME_NS
48 << " ns"
49 << " n_samples=" << tracking::digitization::N_SAMPLES
50 << " sampling_interval="
51 << tracking::digitization::SAMPLING_INTERVAL_NS << " ns"
52 << " t0_offset=" << tracking::digitization::T0_OFFSET_NS
53 << " ns";
54
55 if (field_map_.empty()) {
56 ldmx_log(debug) << "field_map not set; will auto-load from GDML";
57 }
58 if (use_lorentz_)
59 buildLorentzCache();
60 else
61 ldmx_log(info)
62 << "Lorentz angle correction disabled (use_lorentz=false).";
63 }
64
65 // Dump all ACTS surfaces to CSV for geometry verification.
66 if (!dump_geo_csv_.empty()) {
67 std::ofstream csv(dump_geo_csv_);
68 csv << "layer_id,cx,cy,cz,Ux,Uy,Uz,Vx,Vy,Vz,Wx,Wy,Wz\n";
69 for (const auto& [layer_id, surface] : geometry().layer_surface_map_) {
70 const auto& xf = surface->localToGlobalTransform(geometryContext());
71 const auto ctr = xf.translation(); // centre [mm in Acts units]
72 const auto r = xf.rotation();
73 const auto u = r.col(0);
74 const auto v = r.col(1);
75 const auto w = r.col(2);
76 csv << layer_id << "," << ctr.x() << "," << ctr.y() << "," << ctr.z()
77 << "," << u.x() << "," << u.y() << "," << u.z() << "," << v.x() << ","
78 << v.y() << "," << v.z() << "," << w.x() << "," << w.y() << ","
79 << w.z() << "\n";
80 }
81 ldmx_log(info) << "Surface geometry written to " << dump_geo_csv_ << " ("
82 << geometry().layer_surface_map_.size() << " surfaces)";
83 }
84}
static std::unique_ptr< PulseShape > make(const std::string &name, double tp, double tp2=0.0)
Factory: construct a pulse shape by name.

References tracking::digitization::PulseShape::make().

◆ produce()

void tracking::reco::DigitizationProcessor::produce ( framework::Event & event)
overridevirtual

Process the event and put new data products into it.

Parameters
eventThe Event to process.

Implements framework::Producer.

Definition at line 194 of file DigitizationProcessor.cxx.

194 {
195 ldmx_log(trace) << " Getting the tracking geometry:" << geometry().getTG();
196
197 const auto& sim_hits = event.getCollection<ldmx::SimTrackerHit>(
199
200 std::vector<ldmx::SimTrackerHit> merged_hits;
201 std::vector<ldmx::Measurement> measurements;
202 std::vector<ldmx::SimSiStripHit> raw_hits;
203
204 const bool save_raw =
206 auto* raw_ptr = save_raw ? &raw_hits : nullptr;
207
208 if (merge_hits_) {
209 mergeSimHits(sim_hits, merged_hits);
210 measurements = digitizeHits(merged_hits, raw_ptr);
211 } else {
212 measurements = digitizeHits(sim_hits, raw_ptr);
213 }
214
215 event.add(out_collection_, measurements);
216 if (save_raw) {
217 event.add(out_raw_collection_, raw_hits);
218 }
219}
std::vector< ldmx::Measurement > digitizeHits(const std::vector< ldmx::SimTrackerHit > &sim_hits, std::vector< ldmx::SimSiStripHit > *raw_hits=nullptr)
Digitize a collection of SimTrackerHits into Measurements.

Member Data Documentation

◆ do_smearing_

bool tracking::reco::DigitizationProcessor::do_smearing_ {true}
private

Flag to enable/disable smearing in Mode 0.

Definition at line 106 of file DigitizationProcessor.h.

106{true};

◆ dump_geo_csv_

std::string tracking::reco::DigitizationProcessor::dump_geo_csv_ {""}
private

If non-empty, write a CSV of all ACTS surface transforms to this path.

Definition at line 150 of file DigitizationProcessor.h.

150{""};

◆ field_map_

std::string tracking::reco::DigitizationProcessor::field_map_ {""}
private

Path to the magnetic field map file.

Empty = use fixed configured tangents.

Definition at line 136 of file DigitizationProcessor.h.

136{""};

◆ generator_

std::default_random_engine tracking::reco::DigitizationProcessor::generator_
private

Definition at line 152 of file DigitizationProcessor.h.

◆ hit_collection_

std::string tracking::reco::DigitizationProcessor::hit_collection_
private

Input hit collection to digitize.

Definition at line 91 of file DigitizationProcessor.h.

◆ lorentz_tan_cache_

std::unordered_map<unsigned int, std::pair<double, double> > tracking::reco::DigitizationProcessor::lorentz_tan_cache_
private

Per-layer cached Lorentz tangents: layer_id → {tan_electron, tan_hole}.

Definition at line 139 of file DigitizationProcessor.h.

◆ merge_hits_

bool tracking::reco::DigitizationProcessor::merge_hits_ {false}
private

Merge sim hits on the same sensor before digitizing.

Definition at line 100 of file DigitizationProcessor.h.

100{false};

◆ min_e_dep_

double tracking::reco::DigitizationProcessor::min_e_dep_
private

Minimum energy deposition cut [MeV].

Definition at line 96 of file DigitizationProcessor.h.

◆ normal_

std::shared_ptr<std::normal_distribution<float> > tracking::reco::DigitizationProcessor::normal_
private

Definition at line 153 of file DigitizationProcessor.h.

◆ out_collection_

std::string tracking::reco::DigitizationProcessor::out_collection_
private

Output measurement collection name.

Definition at line 93 of file DigitizationProcessor.h.

◆ out_raw_collection_

std::string tracking::reco::DigitizationProcessor::out_raw_collection_ {""}
private

Output raw hit collection name (empty = don't save raw hits).

Definition at line 124 of file DigitizationProcessor.h.

124{""};

◆ pulse_shape_

std::unique_ptr<tracking::digitization::PulseShape> tracking::reco::DigitizationProcessor::pulse_shape_
private

The constructed pulse shape (created in onProcessStart).

Definition at line 126 of file DigitizationProcessor.h.

◆ sensor_params_

tracking::digitization::SiStripDigitizer::SensorParams tracking::reco::DigitizationProcessor::sensor_params_
private

Parameters forwarded to SiStripDigitizer.

Definition at line 118 of file DigitizationProcessor.h.

◆ sigma_u_

double tracking::reco::DigitizationProcessor::sigma_u_ {0}
private

u-direction smearing sigma [mm].

Definition at line 108 of file DigitizationProcessor.h.

108{0};

◆ sigma_v_

double tracking::reco::DigitizationProcessor::sigma_v_ {0}
private

v-direction smearing sigma [mm].

Definition at line 110 of file DigitizationProcessor.h.

110{0};

◆ strip_digitizer_

std::unique_ptr<tracking::digitization::SiStripDigitizer> tracking::reco::DigitizationProcessor::strip_digitizer_
private

The charge digitizer (constructed in onProcessStart).

Definition at line 120 of file DigitizationProcessor.h.

◆ track_id_

int tracking::reco::DigitizationProcessor::track_id_
private

Select a particular track ID (-1 = accept all).

Definition at line 98 of file DigitizationProcessor.h.

◆ tracker_hit_passname_

std::string tracking::reco::DigitizationProcessor::tracker_hit_passname_
private

Input collection pass name.

Definition at line 147 of file DigitizationProcessor.h.

◆ use_charge_digitization_

bool tracking::reco::DigitizationProcessor::use_charge_digitization_ {false}
private

If true, use the full SiStripDigitizer instead of simple smearing.

Definition at line 116 of file DigitizationProcessor.h.

116{false};

◆ use_lorentz_

bool tracking::reco::DigitizationProcessor::use_lorentz_ {true}
private

If false, skip Lorentz angle calculation and drift carriers straight (equivalent to zero magnetic field perpendicular to the sensor normal).

Definition at line 133 of file DigitizationProcessor.h.

133{true};

The documentation for this class was generated from the following files: