LDMX Software
StripPulseFitter.cxx
1#include "Tracking/Digitization/StripPulseFitter.h"
2
3#include <cmath>
4#include <limits>
5
6namespace tracking::digitization {
7
8StripPulseFitter::StripPulseFitter(const PulseShape& shape, double t0_offset_ns,
9 double sampling_interval_ns,
10 double pedestal_adc, double noise_sigma_adc,
11 double t_scan_min_ns, double t_scan_max_ns,
12 double t_scan_step_ns)
13 : shape_(&shape),
14 t0_offset_ns_(t0_offset_ns),
15 sampling_interval_ns_(sampling_interval_ns),
16 pedestal_adc_(pedestal_adc),
17 inv_sigma2_(1.0 / (noise_sigma_adc * noise_sigma_adc)),
18 t_scan_min_ns_(t_scan_min_ns),
19 t_scan_max_ns_(t_scan_max_ns),
20 t_scan_step_ns_(t_scan_step_ns) {}
21
22// ---------------------------------------------------------------------------
23// Private helper: chi2 and amplitude for a fixed hit time T
24// ---------------------------------------------------------------------------
25
26std::pair<double, double> StripPulseFitter::evalAtT(
27 const std::vector<short>& samples, double T) const {
28 const int n = static_cast<int>(samples.size());
29
30 // Evaluate pulse shape at each sample time.
31 // t_i = t0_offset + i * dt → argument to shape = t_i - T
32 double sum_sf = 0.0; // Σ (S_i - ped) * f_i
33 double sum_f2 = 0.0; // Σ f_i²
34
35 for (int i = 0; i < n; ++i) {
36 const double t_arg = t0_offset_ns_ + i * sampling_interval_ns_ - T;
37 const double fi = shape_->eval(t_arg);
38 const double si = static_cast<double>(samples[i]) - pedestal_adc_;
39 sum_sf += si * fi;
40 sum_f2 += fi * fi;
41 }
42
43 // Degenerate: pulse shape is zero everywhere in the window.
44 if (sum_f2 < 1.0e-12) {
45 return {std::numeric_limits<double>::max(), 0.0};
46 }
47
48 const double amplitude = sum_sf / sum_f2;
49
50 // Compute chi2 for this (T, amplitude) pair.
51 double chi2 = 0.0;
52 for (int i = 0; i < n; ++i) {
53 const double t_arg = t0_offset_ns_ + i * sampling_interval_ns_ - T;
54 const double fi = shape_->eval(t_arg);
55 const double si = static_cast<double>(samples[i]) - pedestal_adc_;
56 const double resid = si - amplitude * fi;
57 chi2 += resid * resid * inv_sigma2_;
58 }
59
60 return {chi2, amplitude};
61}
62
63// ---------------------------------------------------------------------------
64// Public: fit
65// ---------------------------------------------------------------------------
66
68 const std::vector<short>& samples) const {
69 FitResult result;
70 result.ndf = static_cast<int>(samples.size()) - 2;
71
72 // Quick sanity: check that at least one sample is above pedestal.
73 bool any_signal = false;
74 for (auto s : samples) {
75 if (static_cast<double>(s) > pedestal_adc_) {
76 any_signal = true;
77 break;
78 }
79 }
80 if (!any_signal) {
81 return result; // converged = false
82 }
83
84 // -------------------------------------------------------------------
85 // Coarse scan over T to find the approximate minimum.
86 // -------------------------------------------------------------------
87 double best_chi2 = std::numeric_limits<double>::max();
88 double best_T = t_scan_min_ns_;
89 double best_amp = 0.0;
90
91 // Keep track of the three-point neighbourhood for parabolic refinement.
92 double T_lo = t_scan_min_ns_, chi2_lo = std::numeric_limits<double>::max();
93 double T_hi = t_scan_min_ns_, chi2_hi = std::numeric_limits<double>::max();
94
95 double T_prev = t_scan_min_ns_;
96 double chi2_prev = std::numeric_limits<double>::max();
97
98 for (double T = t_scan_min_ns_; T <= t_scan_max_ns_; T += t_scan_step_ns_) {
99 auto [chi2, amp] = evalAtT(samples, T);
100
101 if (chi2 < best_chi2) {
102 // Update neighbourhood: the previous point becomes T_lo.
103 T_lo = T_prev;
104 chi2_lo = chi2_prev;
105 best_chi2 = chi2;
106 best_T = T;
107 best_amp = amp;
108 } else if (T > best_T && chi2_hi > best_chi2) {
109 // First point to the right of the minimum.
110 T_hi = T;
111 chi2_hi = chi2;
112 }
113
114 T_prev = T;
115 chi2_prev = chi2;
116 }
117
118 // -------------------------------------------------------------------
119 // Parabolic refinement around the minimum if neighbours are available.
120 // -------------------------------------------------------------------
121 if (T_lo < best_T && T_hi > best_T && chi2_lo > best_chi2 &&
122 chi2_hi > best_chi2) {
123 // Fit a parabola through (T_lo, chi2_lo), (best_T, best_chi2), (T_hi,
124 // chi2_hi).
125 const double d1 = best_T - T_lo;
126 const double d2 = T_hi - best_T;
127 const double dc1 = best_chi2 - chi2_lo;
128 const double dc2 = chi2_hi - best_chi2;
129 // Vertex of parabola: ΔT = (d1²·dc2 - d2²·dc1) / (2·(d1·dc2 + d2·dc1))
130 const double denom = 2.0 * (d1 * dc2 + d2 * dc1);
131 if (std::abs(denom) > 1.0e-12) {
132 const double delta = (d1 * d1 * dc2 - d2 * d2 * dc1) / denom;
133 // Only accept the refinement if it stays within the bracket.
134 if (std::abs(delta) < std::max(d1, d2)) {
135 const double T_refined = best_T + delta;
136 auto [chi2_ref, amp_ref] = evalAtT(samples, T_refined);
137 if (chi2_ref < best_chi2) {
138 best_T = T_refined;
139 best_amp = amp_ref;
140 best_chi2 = chi2_ref;
141 }
142 }
143 }
144 }
145
146 result.amplitude = best_amp;
147 result.t0 = best_T;
148 result.chi2 = best_chi2;
149 result.converged = true;
150 return result;
151}
152
153} // namespace tracking::digitization
Abstract base class for silicon-strip readout pulse shapes.
Definition PulseShape.h:30
virtual double eval(double t) const =0
Evaluate the peak-normalised pulse amplitude at time t [ns] after charge arrival.
std::pair< double, double > evalAtT(const std::vector< short > &samples, double T) const
Evaluate χ²(T) and the corresponding best-fit amplitude.
StripPulseFitter(const PulseShape &shape, double t0_offset_ns, double sampling_interval_ns, double pedestal_adc, double noise_sigma_adc, double t_scan_min_ns=-50.0, double t_scan_max_ns=150.0, double t_scan_step_ns=1.0)
double inv_sigma2_
1/σ², pre-computed.
FitResult fit(const std::vector< short > &samples) const
Fit the pulse to the given ADC sample vector.
int ndf
Degrees of freedom = n_samples − 2.
double amplitude
Fitted peak amplitude [ADC counts], ped-subtracted.
bool converged
False if no above-pedestal samples found.
double chi2
Chi-squared value at the minimum.
double t0
Fitted hit arrival time T [ns].