22void DigitizationProcessor::onProcessStart() {
23 normal_ = std::make_shared<std::normal_distribution<float>>(0., 1.);
25 if (use_charge_digitization_) {
27 std::make_unique<tracking::digitization::SiStripDigitizer>(
29 ldmx_log(info) <<
"Charge digitization enabled."
30 <<
" thickness=from geometry"
31 <<
" sense_pitch=" << tracking::digitization::SENSE_PITCH_MM
32 <<
" mm" <<
" readout_pitch="
33 << tracking::digitization::READOUT_PITCH_MM <<
" mm"
34 <<
" Vbias=" << sensor_params_.bias_voltage <<
" V"
35 <<
" Vdep=" << sensor_params_.depletion_voltage <<
" V"
36 <<
" bulk=" << (sensor_params_.is_n_type ?
"n" :
"p")
37 <<
"-type" <<
" e_lorentz_tan="
38 << sensor_params_.electron_lorentz_tangent
39 <<
" h_lorentz_tan=" << sensor_params_.hole_lorentz_tangent
40 <<
" trapping=" << sensor_params_.trapping
41 <<
" noise=" << sensor_params_.noise_electrons <<
" e-"
42 <<
" threshold=" << sensor_params_.threshold_electrons
44 <<
" n_segments_min=" << sensor_params_.n_segments_min
45 <<
" granularity=" << sensor_params_.deposition_granularity;
48 std::string(tracking::digitization::PULSE_SHAPE_NAME),
49 tracking::digitization::PEAKING_TIME_NS,
50 tracking::digitization::SECOND_TIME_CONST_NS);
51 ldmx_log(info) <<
"Pulse shaping: shape="
52 << tracking::digitization::PULSE_SHAPE_NAME
53 <<
" tp=" << tracking::digitization::PEAKING_TIME_NS
55 <<
" n_samples=" << tracking::digitization::N_SAMPLES
56 <<
" sampling_interval="
57 << tracking::digitization::SAMPLING_INTERVAL_NS <<
" ns"
58 <<
" t0_offset=" << tracking::digitization::T0_OFFSET_NS
61 if (field_map_.empty()) {
62 ldmx_log(debug) <<
"field_map not set; will auto-load from GDML";
68 <<
"Lorentz angle correction disabled (use_lorentz=false).";
72 if (!dump_geo_csv_.empty()) {
73 std::ofstream csv(dump_geo_csv_);
74 csv <<
"layer_id,cx,cy,cz,Ux,Uy,Uz,Vx,Vy,Vz,Wx,Wy,Wz\n";
75 for (
const auto& [layer_id, surface] : geometry().layer_surface_map_) {
76 const auto& xf = surface->localToGlobalTransform(geometryContext());
77 const auto ctr = xf.translation();
78 const auto r = xf.rotation();
79 const auto u = r.col(0);
80 const auto v = r.col(1);
81 const auto w = r.col(2);
82 csv << layer_id <<
"," << ctr.x() <<
"," << ctr.y() <<
"," << ctr.z()
83 <<
"," << u.x() <<
"," << u.y() <<
"," << u.z() <<
"," << v.x() <<
","
84 << v.y() <<
"," << v.z() <<
"," << w.x() <<
"," << w.y() <<
","
87 ldmx_log(info) <<
"Surface geometry written to " << dump_geo_csv_ <<
" ("
88 << geometry().layer_surface_map_.size() <<
" surfaces)";
330std::vector<ldmx::Measurement> DigitizationProcessor::digitizeHits(
331 const std::vector<ldmx::SimTrackerHit>& sim_hits,
332 std::vector<ldmx::SimSiStripHit>* raw_hits) {
333 ldmx_log(debug) <<
"Found: " << sim_hits.size() <<
" sim hits in '"
334 << hit_collection_ <<
"' with passname '"
335 << tracker_hit_passname_ <<
"'";
337 std::vector<ldmx::Measurement> measurements;
339 struct StripContrib {
340 double charge_electrons_;
349 std::map<int, std::map<int, std::vector<StripContrib>>> layer_strip_contribs;
351 for (
auto& sim_hit : sim_hits) {
353 if (sim_hit.getEdep() <= min_e_dep_)
continue;
354 if (track_id_ > 0 && sim_hit.getTrackID() != track_id_)
continue;
359 auto layer_id = tracking::sim::utils::getSensorID(sim_hit);
362 auto hit_surface{geometry().getSurface(layer_id)};
363 if (!hit_surface)
continue;
366 <<
"Local to global\n"
367 << hit_surface->localToGlobalTransform(geometryContext()).rotation()
369 << hit_surface->localToGlobalTransform(geometryContext()).translation();
374 Acts::Vector3 dummy_momentum;
375 Acts::Vector2 local_pos_2d;
378 constexpr double surface_thickness = 0.320 * Acts::UnitConstants::mm;
385 local_pos_2d = hit_surface
386 ->globalToLocal(geometryContext(), global_pos,
387 dummy_momentum, surface_thickness)
389 }
catch (
const std::exception& e) {
390 ldmx_log(warn) <<
"hit not on surface... Skipping.";
395 measurement.
setTruthU(
static_cast<float>(local_pos_2d[0]));
400 if (use_charge_digitization_) {
402 const auto* placement = hit_surface->surfacePlacement();
404 ldmx_log(warn) <<
"No detector element for layer_id=" << layer_id
405 <<
" — skipping hit";
408 const double thickness =
411 strip_digitizer_->setThickness(thickness);
414 const Acts::Transform3 surf_transform =
415 hit_surface->localToGlobalTransform(geometryContext());
419 const Acts::Vector3 local_pos_3d = surf_transform.inverse() * global_pos;
424 Acts::Vector3 global_mom(sim_hit.getMomentum()[2],
425 sim_hit.getMomentum()[0],
426 sim_hit.getMomentum()[1]);
427 const double mom_mag = global_mom.norm();
429 Acts::Vector3 local_dir_3d;
432 surf_transform.rotation().transpose() * (global_mom / mom_mag);
435 local_dir_3d = Acts::Vector3(0.0, 0.0, 1.0);
440 double path_length = sim_hit.getPathLength();
441 if (path_length <= 0.0) {
442 const double cos_theta = std::abs(local_dir_3d[2]);
443 path_length = (cos_theta > 1e-3) ? thickness / cos_theta : thickness;
448 auto lorentz_it = lorentz_tan_cache_.find(layer_id);
449 if (lorentz_it != lorentz_tan_cache_.end()) {
450 strip_digitizer_->mutableParams().electron_lorentz_tangent =
451 lorentz_it->second.first;
452 strip_digitizer_->mutableParams().hole_lorentz_tangent =
453 lorentz_it->second.second;
458 auto strip_charges = strip_digitizer_->computeStripCharges(
459 sim_hit.getEdep(), local_pos_3d, local_dir_3d, path_length);
461 ldmx_log(trace) <<
"Charge digi: " << strip_charges.size()
462 <<
" strips from computeStripCharges (pre-noise)";
468 if (raw_hits && pulse_shape_) {
469 const double hit_time_ns = sim_hit.getTime();
470 for (
const auto& [strip_idx, charge] : strip_charges) {
471 layer_strip_contribs[layer_id][strip_idx].push_back(StripContrib{
472 charge, hit_time_ns, sim_hit.getTrackID(), sim_hit.getPdgID(),
473 sim_hit.getID(), sim_hit.getEdep()});
486 measurements.push_back(measurement);
493 float smear_factor{(*normal_)(generator_)};
494 local_pos_2d[0] += smear_factor * sigma_u_;
495 smear_factor = (*normal_)(generator_);
496 local_pos_2d[1] += smear_factor * sigma_v_;
499 static_cast<float>(sigma_u_ * sigma_u_),
500 static_cast<float>(tracking::digitization::SIGMA_V_MM *
501 tracking::digitization::SIGMA_V_MM));
503 auto transf_global_pos{hit_surface->localToGlobal(
504 geometryContext(), local_pos_2d, dummy_momentum)};
506 transf_global_pos(1),
507 transf_global_pos(2));
511 measurements.push_back(measurement);
517 if (raw_hits && pulse_shape_) {
518 const int adc_max = (1 << tracking::digitization::ADC_BITS) - 1;
520 for (
auto& [lyr_id, strip_contribs_map] : layer_strip_contribs) {
522 std::map<int, double> total_charges;
523 for (
const auto& [strip_idx, contribs] : strip_contribs_map) {
525 for (
const auto& c : contribs) total += c.charge_electrons_;
526 total_charges[strip_idx] = total;
530 strip_digitizer_->applyNoiseAndThreshold(total_charges);
531 if (total_charges.empty())
continue;
533 for (
const auto& [strip_idx, final_charge] : total_charges) {
534 const auto contrib_it = strip_contribs_map.find(strip_idx);
535 const bool has_signal = (contrib_it != strip_contribs_map.end());
537 int track_id_out = -1;
539 int sim_hit_id_out = -1;
540 float edep_out = 0.f;
541 double ref_time_ns = 0.0;
542 std::vector<short> samples(tracking::digitization::N_SAMPLES);
545 const auto& contribs = contrib_it->second;
548 const StripContrib* dom = &contribs.front();
549 for (
const auto& c : contribs)
550 if (c.charge_electrons_ > dom->charge_electrons_) dom = &c;
552 ref_time_ns = dom->hit_time_ns_;
553 track_id_out = dom->track_id_;
554 pdg_id_out = dom->pdg_id_;
555 sim_hit_id_out = dom->sim_hit_id_;
556 for (
const auto& c : contribs) edep_out += c.edep_;
559 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
561 const double t_samp =
562 tracking::digitization::T0_OFFSET_NS +
563 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
565 static_cast<double>(tracking::digitization::ADC_PEDESTAL);
566 for (
const auto& c : contribs)
567 val += (c.charge_electrons_ /
568 tracking::digitization::ADC_ELECTRONS_PER_COUNT) *
569 pulse_shape_->eval(t_samp - c.hit_time_ns_);
570 samples[isamp] =
static_cast<short>(
571 std::clamp(
static_cast<int>(std::round(val)), 0, adc_max));
577 for (
int delta : {-1, +1}) {
578 const auto nb = strip_contribs_map.find(strip_idx + delta);
579 if (nb != strip_contribs_map.end() && !nb->second.empty()) {
580 const StripContrib* dom = &nb->second.front();
581 for (
const auto& c : nb->second)
582 if (c.charge_electrons_ > dom->charge_electrons_) dom = &c;
583 ref_time_ns = dom->hit_time_ns_;
587 const double peak_adc =
588 final_charge / tracking::digitization::ADC_ELECTRONS_PER_COUNT;
589 for (
int isamp = 0; isamp < tracking::digitization::N_SAMPLES;
591 const double t_samp =
592 tracking::digitization::T0_OFFSET_NS +
593 isamp * tracking::digitization::SAMPLING_INTERVAL_NS;
595 static_cast<double>(tracking::digitization::ADC_PEDESTAL) +
596 peak_adc * pulse_shape_->eval(t_samp - ref_time_ns);
597 samples[isamp] =
static_cast<short>(
598 std::clamp(
static_cast<int>(std::round(val)), 0, adc_max));
602 raw_hits->emplace_back(lyr_id, strip_idx, std::move(samples),
603 static_cast<long>(ref_time_ns), track_id_out,
604 pdg_id_out, sim_hit_id_out, edep_out);