16void DigitizationProcessor::onProcessStart() {
17 normal_ = std::make_shared<std::normal_distribution<float>>(0., 1.);
19 if (use_charge_digitization_) {
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="
32 << sensor_params_.electron_lorentz_tangent
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
38 <<
" n_segments_min=" << sensor_params_.n_segments_min
39 <<
" granularity=" << sensor_params_.deposition_granularity;
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
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
55 if (field_map_.empty()) {
56 ldmx_log(debug) <<
"field_map not set; will auto-load from GDML";
62 <<
"Lorentz angle correction disabled (use_lorentz=false).";
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->transform(geometryContext());
71 const auto ctr = xf.translation();
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() <<
","
81 ldmx_log(info) <<
"Surface geometry written to " << dump_geo_csv_ <<
" ("
82 << geometry().layer_surface_map_.size() <<
" surfaces)";
324std::vector<ldmx::Measurement> DigitizationProcessor::digitizeHits(
325 const std::vector<ldmx::SimTrackerHit>& sim_hits,
326 std::vector<ldmx::RawSiStripHit>* raw_hits) {
327 ldmx_log(debug) <<
"Found: " << sim_hits.size() <<
" sim hits in '"
328 << hit_collection_ <<
"' with passname '"
329 << tracker_hit_passname_ <<
"'";
331 std::vector<ldmx::Measurement> measurements;
333 struct StripContrib {
334 double charge_electrons;
343 std::map<int, std::map<int, std::vector<StripContrib>>> layer_strip_contribs;
345 for (
auto& sim_hit : sim_hits) {
347 if (sim_hit.getEdep() <= min_e_dep_)
continue;
348 if (track_id_ > 0 && sim_hit.getTrackID() != track_id_)
continue;
353 auto layer_id = tracking::sim::utils::getSensorID(sim_hit);
356 auto hit_surface{geometry().getSurface(layer_id)};
357 if (!hit_surface)
continue;
359 ldmx_log(trace) <<
"Local to global\n"
360 << hit_surface->transform(geometryContext()).rotation()
362 << hit_surface->transform(geometryContext()).translation();
367 Acts::Vector3 dummy_momentum;
368 Acts::Vector2 local_pos_2d;
371 constexpr double surface_thickness = 0.320 * Acts::UnitConstants::mm;
378 local_pos_2d = hit_surface
379 ->globalToLocal(geometryContext(), global_pos,
380 dummy_momentum, surface_thickness)
382 }
catch (
const std::exception& e) {
383 ldmx_log(warn) <<
"hit not on surface... Skipping.";
388 measurement.
setTruthU(
static_cast<float>(local_pos_2d[0]));
393 if (use_charge_digitization_) {
395 const auto* det_el = hit_surface->associatedDetectorElement();
397 ldmx_log(warn) <<
"No detector element for layer_id=" << layer_id
398 <<
" — skipping hit";
401 const double thickness = det_el->thickness();
402 strip_digitizer_->setThickness(thickness);
405 const Acts::Transform3 surf_transform =
406 hit_surface->transform(geometryContext());
410 const Acts::Vector3 local_pos_3d = surf_transform.inverse() * global_pos;
415 Acts::Vector3 global_mom(sim_hit.getMomentum()[2],
416 sim_hit.getMomentum()[0],
417 sim_hit.getMomentum()[1]);
418 const double mom_mag = global_mom.norm();
420 Acts::Vector3 local_dir_3d;
423 surf_transform.rotation().transpose() * (global_mom / mom_mag);
426 local_dir_3d = Acts::Vector3(0.0, 0.0, 1.0);
431 double path_length = sim_hit.getPathLength();
432 if (path_length <= 0.0) {
433 const double cos_theta = std::abs(local_dir_3d[2]);
434 path_length = (cos_theta > 1e-3) ? thickness / cos_theta : thickness;
439 auto lorentz_it = lorentz_tan_cache_.find(layer_id);
440 if (lorentz_it != lorentz_tan_cache_.end()) {
441 strip_digitizer_->mutableParams().electron_lorentz_tangent =
442 lorentz_it->second.first;
443 strip_digitizer_->mutableParams().hole_lorentz_tangent =
444 lorentz_it->second.second;
449 auto strip_charges = strip_digitizer_->computeStripCharges(
450 sim_hit.getEdep(), local_pos_3d, local_dir_3d, path_length);
452 ldmx_log(trace) <<
"Charge digi: " << strip_charges.size()
453 <<
" strips from computeStripCharges (pre-noise)";
459 if (raw_hits && pulse_shape_) {
460 const double hit_time_ns = sim_hit.getTime();
461 for (
const auto& [strip_idx, charge] : strip_charges) {
462 layer_strip_contribs[layer_id][strip_idx].push_back(StripContrib{
463 charge, hit_time_ns, sim_hit.getTrackID(), sim_hit.getPdgID(),
464 sim_hit.getID(), sim_hit.getEdep()});
477 measurements.push_back(measurement);
484 float smear_factor{(*normal_)(generator_)};
485 local_pos_2d[0] += smear_factor * sigma_u_;
486 smear_factor = (*normal_)(generator_);
487 local_pos_2d[1] += smear_factor * sigma_v_;
490 static_cast<float>(sigma_u_ * sigma_u_),
491 static_cast<float>(tracking::digitization::SIGMA_V_MM *
492 tracking::digitization::SIGMA_V_MM));
494 auto transf_global_pos{hit_surface->localToGlobal(
495 geometryContext(), local_pos_2d, dummy_momentum)};
497 transf_global_pos(1),
498 transf_global_pos(2));
502 measurements.push_back(measurement);
508 if (raw_hits && pulse_shape_) {
509 const int adc_max = (1 << tracking::digitization::ADC_BITS) - 1;
511 for (
auto& [lyr_id, strip_contribs_map] : layer_strip_contribs) {
513 std::map<int, double> total_charges;
514 for (
const auto& [strip_idx, contribs] : strip_contribs_map) {
516 for (
const auto& c : contribs) total += c.charge_electrons;
517 total_charges[strip_idx] = total;
521 strip_digitizer_->applyNoiseAndThreshold(total_charges);
522 if (total_charges.empty())
continue;
524 for (
const auto& [strip_idx, final_charge] : total_charges) {
525 const auto contrib_it = strip_contribs_map.find(strip_idx);
526 const bool has_signal = (contrib_it != strip_contribs_map.end());
528 int track_id_out = -1;
530 int sim_hit_id_out = -1;
531 float edep_out = 0.f;
532 double ref_time_ns = 0.0;
533 std::vector<short> samples(tracking::digitization::N_SAMPLES);
536 const auto& contribs = contrib_it->second;
539 const StripContrib* dom = &contribs.front();
540 for (
const auto& c : contribs)
541 if (c.charge_electrons > dom->charge_electrons) dom = &c;
543 ref_time_ns = dom->hit_time_ns;
544 track_id_out = dom->track_id;
545 pdg_id_out = dom->pdg_id;
546 sim_hit_id_out = dom->sim_hit_id;
547 for (
const auto& c : contribs) edep_out += c.edep;
550 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
552 const double t_samp =
553 tracking::digitization::T0_OFFSET_NS +
554 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
556 static_cast<double>(tracking::digitization::ADC_PEDESTAL);
557 for (
const auto& c : contribs)
558 val += (c.charge_electrons /
559 tracking::digitization::ADC_ELECTRONS_PER_COUNT) *
560 pulse_shape_->eval(t_samp - c.hit_time_ns);
561 samples[isamp] =
static_cast<short>(
562 std::clamp(
static_cast<int>(std::round(val)), 0, adc_max));
568 for (
int delta : {-1, +1}) {
569 const auto nb = strip_contribs_map.find(strip_idx + delta);
570 if (nb != strip_contribs_map.end() && !nb->second.empty()) {
571 const StripContrib* dom = &nb->second.front();
572 for (
const auto& c : nb->second)
573 if (c.charge_electrons > dom->charge_electrons) dom = &c;
574 ref_time_ns = dom->hit_time_ns;
578 const double peak_adc =
579 final_charge / tracking::digitization::ADC_ELECTRONS_PER_COUNT;
580 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
582 const double t_samp =
583 tracking::digitization::T0_OFFSET_NS +
584 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
586 static_cast<double>(tracking::digitization::ADC_PEDESTAL) +
587 peak_adc * pulse_shape_->eval(t_samp - ref_time_ns);
588 samples[isamp] =
static_cast<short>(
589 std::clamp(
static_cast<int>(std::round(val)), 0, adc_max));
593 raw_hits->emplace_back(lyr_id, strip_idx, std::move(samples),
594 static_cast<long>(ref_time_ns), track_id_out,
595 pdg_id_out, sim_hit_id_out, edep_out);