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->localToGlobalTransform(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::SimSiStripHit>* 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;
360 <<
"Local to global\n"
361 << hit_surface->localToGlobalTransform(geometryContext()).rotation()
363 << hit_surface->localToGlobalTransform(geometryContext()).translation();
368 Acts::Vector3 dummy_momentum;
369 Acts::Vector2 local_pos_2d;
372 constexpr double surface_thickness = 0.320 * Acts::UnitConstants::mm;
379 local_pos_2d = hit_surface
380 ->globalToLocal(geometryContext(), global_pos,
381 dummy_momentum, surface_thickness)
383 }
catch (
const std::exception& e) {
384 ldmx_log(warn) <<
"hit not on surface... Skipping.";
389 measurement.
setTruthU(
static_cast<float>(local_pos_2d[0]));
394 if (use_charge_digitization_) {
396 const auto* placement = hit_surface->surfacePlacement();
398 ldmx_log(warn) <<
"No detector element for layer_id=" << layer_id
399 <<
" — skipping hit";
402 const double thickness =
405 strip_digitizer_->setThickness(thickness);
408 const Acts::Transform3 surf_transform =
409 hit_surface->localToGlobalTransform(geometryContext());
413 const Acts::Vector3 local_pos_3d = surf_transform.inverse() * global_pos;
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();
423 Acts::Vector3 local_dir_3d;
426 surf_transform.rotation().transpose() * (global_mom / mom_mag);
429 local_dir_3d = Acts::Vector3(0.0, 0.0, 1.0);
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;
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;
452 auto strip_charges = strip_digitizer_->computeStripCharges(
453 sim_hit.getEdep(), local_pos_3d, local_dir_3d, path_length);
455 ldmx_log(trace) <<
"Charge digi: " << strip_charges.size()
456 <<
" strips from computeStripCharges (pre-noise)";
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()});
480 measurements.push_back(measurement);
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_;
493 static_cast<float>(sigma_u_ * sigma_u_),
494 static_cast<float>(tracking::digitization::SIGMA_V_MM *
495 tracking::digitization::SIGMA_V_MM));
497 auto transf_global_pos{hit_surface->localToGlobal(
498 geometryContext(), local_pos_2d, dummy_momentum)};
500 transf_global_pos(1),
501 transf_global_pos(2));
505 measurements.push_back(measurement);
511 if (raw_hits && pulse_shape_) {
512 const int adc_max = (1 << tracking::digitization::ADC_BITS) - 1;
514 for (
auto& [lyr_id, strip_contribs_map] : layer_strip_contribs) {
516 std::map<int, double> total_charges;
517 for (
const auto& [strip_idx, contribs] : strip_contribs_map) {
519 for (
const auto& c : contribs) total += c.charge_electrons_;
520 total_charges[strip_idx] = total;
524 strip_digitizer_->applyNoiseAndThreshold(total_charges);
525 if (total_charges.empty())
continue;
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());
531 int track_id_out = -1;
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);
539 const auto& contribs = contrib_it->second;
542 const StripContrib* dom = &contribs.front();
543 for (
const auto& c : contribs)
544 if (c.charge_electrons_ > dom->charge_electrons_) dom = &c;
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_;
553 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
555 const double t_samp =
556 tracking::digitization::T0_OFFSET_NS +
557 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
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));
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_;
581 const double peak_adc =
582 final_charge / tracking::digitization::ADC_ELECTRONS_PER_COUNT;
583 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
585 const double t_samp =
586 tracking::digitization::T0_OFFSET_NS +
587 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
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));
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);