Skip to content

Commit f704495

Browse files
authored
Merge pull request #4131 from Shuonli/function_fit
functional fit option for waveform processing
2 parents ab0c1db + 47d0f38 commit f704495

File tree

6 files changed

+382
-2
lines changed

6 files changed

+382
-2
lines changed

offline/packages/CaloReco/CaloTowerBuilder.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ int CaloTowerBuilder::InitRun(PHCompositeNode *topNode)
7575
WaveformProcessing->set_bitFlipRecovery(m_dobitfliprecovery);
7676
}
7777

78+
// Set functional fit parameters
79+
if (_processingtype == CaloWaveformProcessing::FUNCFIT)
80+
{
81+
WaveformProcessing->set_funcfit_type(m_funcfit_type);
82+
WaveformProcessing->set_powerlaw_params(m_powerlaw_power, m_powerlaw_decay);
83+
WaveformProcessing->set_doubleexp_params(m_doubleexp_power, m_doubleexp_peaktime1, m_doubleexp_peaktime2, m_doubleexp_ratio);
84+
}
85+
7886
if (m_dettype == CaloTowerDefs::CEMC)
7987
{
8088
m_detector = "CEMC";
@@ -476,6 +484,7 @@ int CaloTowerBuilder::process_event(PHCompositeNode *topNode)
476484
towerinfo->set_pedestal(processed_waveforms.at(idx).at(2));
477485
towerinfo->set_chi2(processed_waveforms.at(idx).at(3));
478486
bool SZS = isSZS(processed_waveforms.at(idx).at(1), processed_waveforms.at(idx).at(3));
487+
479488
if (processed_waveforms.at(idx).at(4) == 0)
480489
{
481490
towerinfo->set_isRecovered(false);

offline/packages/CaloReco/CaloTowerBuilder.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,26 @@ class CaloTowerBuilder : public SubsysReco
9494
m_dobitfliprecovery = dobitfliprecovery;
9595
}
9696

97+
// Functional fit options: 0 = PowerLawExp, 1 = PowerLawDoubleExp
98+
void set_funcfit_type(int type)
99+
{
100+
m_funcfit_type = type;
101+
}
102+
103+
void set_powerlaw_params(double power, double decay)
104+
{
105+
m_powerlaw_power = power;
106+
m_powerlaw_decay = decay;
107+
}
108+
109+
void set_doubleexp_params(double power, double peaktime1, double peaktime2, double ratio)
110+
{
111+
m_doubleexp_power = power;
112+
m_doubleexp_peaktime1 = peaktime1;
113+
m_doubleexp_peaktime2 = peaktime2;
114+
m_doubleexp_ratio = ratio;
115+
}
116+
97117
void set_tbt_softwarezerosuppression(const std::string &url)
98118
{
99119
m_zsURL = url;
@@ -150,6 +170,15 @@ class CaloTowerBuilder : public SubsysReco
150170
std::string m_directURL;
151171
std::string m_zsURL;
152172
std::string m_zs_fieldname{"zs_threshold"};
173+
174+
// Functional fit parameters
175+
int m_funcfit_type{1}; // 0 = PowerLawExp, 1 = PowerLawDoubleExp
176+
double m_powerlaw_power{4.0};
177+
double m_powerlaw_decay{1.5};
178+
double m_doubleexp_power{2.0};
179+
double m_doubleexp_peaktime1{5.0};
180+
double m_doubleexp_peaktime2{5.0};
181+
double m_doubleexp_ratio{0.3};
153182
};
154183

155184
#endif // CALOTOWERBUILDER_H

offline/packages/CaloReco/CaloWaveformFitting.cc

Lines changed: 252 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <TF1.h>
44
#include <TFile.h>
5+
#include <TH1F.h>
56
#include <TProfile.h>
67
#include <TSpline.h>
78

@@ -21,7 +22,7 @@
2122
#include <limits>
2223
#include <string>
2324

24-
static ROOT::TThreadExecutor *t = new ROOT::TThreadExecutor(1);// NOLINT(misc-use-anonymous-namespace)
25+
static ROOT::TThreadExecutor *t = new ROOT::TThreadExecutor(1); // NOLINT(misc-use-anonymous-namespace)
2526
double CaloWaveformFitting::template_function(double *x, double *par)
2627
{
2728
Double_t v1 = (par[0] * h_template->Interpolate(x[0] - par[1])) + par[2];
@@ -619,3 +620,253 @@ float CaloWaveformFitting::psinc(float time, std::vector<float> &vec_signal_samp
619620

620621
return sum;
621622
}
623+
624+
double CaloWaveformFitting::SignalShape_PowerLawExp(double *x, double *par)
625+
{
626+
// par[0]: Amplitude
627+
// par[1]: Sample Start (t0)
628+
// par[2]: Power
629+
// par[3]: Decay
630+
// par[4]: Pedestal
631+
double pedestal = par[4];
632+
if (x[0] < par[1])
633+
{
634+
return pedestal;
635+
}
636+
double signal = par[0] * pow((x[0] - par[1]), par[2]) * exp(-(x[0] - par[1]) * par[3]);
637+
return pedestal + signal;
638+
}
639+
640+
double CaloWaveformFitting::SignalShape_PowerLawDoubleExp(double *x, double *par)
641+
{
642+
// par[0]: Amplitude
643+
// par[1]: Sample Start (t0)
644+
// par[2]: Power
645+
// par[3]: Peak Time 1
646+
// par[4]: Pedestal
647+
// par[5]: Amplitude ratio
648+
// par[6]: Peak Time 2
649+
double pedestal = par[4];
650+
if (x[0] < par[1])
651+
{
652+
return pedestal;
653+
}
654+
double signal = par[0] * pow((x[0] - par[1]), par[2]) *
655+
(((1.0 - par[5]) / pow(par[3], par[2]) * exp(par[2])) *
656+
exp(-(x[0] - par[1]) * (par[2] / par[3])) +
657+
(par[5] / pow(par[6], par[2]) * exp(par[2])) *
658+
exp(-(x[0] - par[1]) * (par[2] / par[6])));
659+
return pedestal + signal;
660+
}
661+
662+
std::vector<std::vector<float>> CaloWaveformFitting::calo_processing_funcfit(const std::vector<std::vector<float>> &chnlvector)
663+
{
664+
std::vector<std::vector<float>> fit_values;
665+
int nchnls = chnlvector.size();
666+
667+
for (int m = 0; m < nchnls; m++)
668+
{
669+
const std::vector<float> &v = chnlvector.at(m);
670+
int nsamples = v.size();
671+
672+
float amp = 0;
673+
float time = 0;
674+
float ped = 0;
675+
float chi2 = std::numeric_limits<float>::quiet_NaN();
676+
677+
// Handle zero-suppressed samples (2-sample case)
678+
if (nsamples == _nzerosuppresssamples)
679+
{
680+
amp = v.at(1) - v.at(0);
681+
time = std::numeric_limits<float>::quiet_NaN();
682+
ped = v.at(0);
683+
if (v.at(0) != 0 && v.at(1) == 0)
684+
{
685+
chi2 = 1000000;
686+
}
687+
fit_values.push_back({amp, time, ped, chi2, 0});
688+
continue;
689+
}
690+
691+
// Find peak position and estimate pedestal
692+
float maxheight = 0;
693+
int maxbin = 0;
694+
for (int i = 0; i < nsamples; i++)
695+
{
696+
if (v.at(i) > maxheight)
697+
{
698+
maxheight = v.at(i);
699+
maxbin = i;
700+
}
701+
}
702+
703+
float pedestal = 1500;
704+
if (maxbin > 4)
705+
{
706+
pedestal = 0.5 * (v.at(maxbin - 4) + v.at(maxbin - 5));
707+
}
708+
else if (maxbin > 3)
709+
{
710+
pedestal = v.at(maxbin - 4);
711+
}
712+
else
713+
{
714+
pedestal = 0.5 * (v.at(nsamples - 3) + v.at(nsamples - 2));
715+
}
716+
717+
// Software zero suppression check
718+
if ((_bdosoftwarezerosuppression && v.at(6) - v.at(0) < _nsoftwarezerosuppression) ||
719+
(_maxsoftwarezerosuppression && maxheight - pedestal < _nsoftwarezerosuppression))
720+
{
721+
amp = v.at(6) - v.at(0);
722+
time = std::numeric_limits<float>::quiet_NaN();
723+
ped = v.at(0);
724+
if (v.at(0) != 0 && v.at(1) == 0)
725+
{
726+
chi2 = 1000000;
727+
}
728+
fit_values.push_back({amp, time, ped, chi2, 0});
729+
continue;
730+
}
731+
732+
// Create histogram for fitting
733+
TH1F h("h_funcfit", "", nsamples, -0.5, nsamples - 0.5);
734+
int ndata = 0;
735+
for (int i = 0; i < nsamples; ++i)
736+
{
737+
if ((v.at(i) == 16383) && _handleSaturation)
738+
{
739+
continue;
740+
}
741+
h.SetBinContent(i + 1, v.at(i));
742+
h.SetBinError(i + 1, 1);
743+
ndata++;
744+
}
745+
746+
// If too many saturated, use all data
747+
if (ndata < (nsamples - 4))
748+
{
749+
ndata = nsamples;
750+
for (int i = 0; i < nsamples; ++i)
751+
{
752+
h.SetBinContent(i + 1, v.at(i));
753+
h.SetBinError(i + 1, 1);
754+
}
755+
}
756+
757+
double fit_amp = 0;
758+
double fit_time = 0;
759+
double fit_ped = 0;
760+
double chi2val = 0;
761+
int npar = 0;
762+
763+
if (m_funcfit_type == POWERLAWEXP)
764+
{
765+
// Create fit function with 5 parameters
766+
TF1 f("f_powerlaw", SignalShape_PowerLawExp, 0, nsamples, 5);
767+
npar = 5;
768+
769+
// Set initial parameters
770+
double risetime = m_powerlaw_power / m_powerlaw_decay;
771+
double par[5];
772+
par[0] = maxheight - pedestal; // Amplitude
773+
par[1] = maxbin - risetime; // t0
774+
par[1] = std::max<double>(par[1], 0);
775+
par[2] = m_powerlaw_power; // Power
776+
par[3] = m_powerlaw_decay; // Decay
777+
par[4] = pedestal; // Pedestal
778+
779+
f.SetParameters(par);
780+
f.SetParLimits(0, (maxheight - pedestal) * 0.5, (maxheight - pedestal) * 10);
781+
f.SetParLimits(1, 0, nsamples);
782+
f.SetParLimits(2, 0, 10.0);
783+
f.SetParLimits(3, 0, 10.0);
784+
f.SetParLimits(4, pedestal - std::abs(maxheight - pedestal), pedestal + std::abs(maxheight - pedestal));
785+
786+
// Perform fit
787+
h.Fit(&f, "QRN0W", "", 0, nsamples);
788+
789+
// Calculate peak amplitude and time from fit parameters
790+
// Peak height is (p0 * Power(p2/p3, p2)) / exp(p2)
791+
fit_amp = (f.GetParameter(0) * pow(f.GetParameter(2) / f.GetParameter(3), f.GetParameter(2))) / exp(f.GetParameter(2));
792+
// Peak time is t0 + power/decay
793+
fit_time = f.GetParameter(1) + f.GetParameter(2) / f.GetParameter(3);
794+
fit_ped = f.GetParameter(4);
795+
796+
// Calculate chi2
797+
for (int i = 0; i < nsamples; i++)
798+
{
799+
if (h.GetBinContent(i + 1) > 0)
800+
{
801+
double diff = h.GetBinContent(i + 1) - f.Eval(i);
802+
chi2val += diff * diff;
803+
}
804+
}
805+
}
806+
else // POWERLAWDOUBLEEXP
807+
{
808+
// Create fit function with 7 parameters
809+
TF1 f("f_doubleexp", SignalShape_PowerLawDoubleExp, 0, nsamples, 7);
810+
npar = 7;
811+
812+
// Set initial parameters
813+
double risetime = 2.0;
814+
double par[7];
815+
par[0] = (maxheight - pedestal) * 0.7; // Amplitude
816+
par[1] = maxbin - risetime; // t0
817+
par[1] = std::max<double>(par[1], 0);
818+
par[2] = m_doubleexp_power; // Power
819+
par[3] = m_doubleexp_peaktime1; // Peak Time 1
820+
par[4] = pedestal; // Pedestal
821+
par[5] = m_doubleexp_ratio; // Amplitude ratio
822+
par[6] = m_doubleexp_peaktime2; // Peak Time 2
823+
824+
f.SetParameters(par);
825+
f.SetParLimits(0, (maxheight - pedestal) * -1.5, (maxheight - pedestal) * 1.5);
826+
f.SetParLimits(1, maxbin - 3 * risetime, maxbin + risetime);
827+
f.SetParLimits(2, 1, 5.0);
828+
f.SetParLimits(3, risetime * 0.5, risetime * 4);
829+
f.SetParLimits(4, pedestal - std::abs(maxheight - pedestal), pedestal + std::abs(maxheight - pedestal));
830+
f.SetParLimits(5, 0, 1);
831+
f.SetParLimits(6, risetime * 0.5, risetime * 4);
832+
833+
// Perform fit
834+
h.Fit(&f, "QRN0W", "", 0, nsamples);
835+
836+
// Find peak by evaluating the function
837+
double peakpos1 = f.GetParameter(3);
838+
double peakpos2 = f.GetParameter(6);
839+
double max_peakpos = f.GetParameter(1) + (peakpos1 > peakpos2 ? peakpos1 : peakpos2);
840+
max_peakpos = std::min<double>(max_peakpos, nsamples - 1);
841+
842+
fit_time = f.GetMaximumX(f.GetParameter(1), max_peakpos);
843+
fit_amp = f.Eval(fit_time) - f.GetParameter(4);
844+
fit_ped = f.GetParameter(4);
845+
846+
// Calculate chi2
847+
for (int i = 0; i < nsamples; i++)
848+
{
849+
if (h.GetBinContent(i + 1) > 0)
850+
{
851+
double diff = h.GetBinContent(i + 1) - f.Eval(i);
852+
chi2val += diff * diff;
853+
}
854+
}
855+
}
856+
857+
int ndf = ndata - npar;
858+
if (ndf > 0)
859+
{
860+
chi2val /= ndf;
861+
}
862+
else
863+
{
864+
chi2val = std::numeric_limits<double>::quiet_NaN();
865+
}
866+
867+
fit_values.push_back({static_cast<float>(fit_amp), static_cast<float>(fit_time),
868+
static_cast<float>(fit_ped), static_cast<float>(chi2val), 0});
869+
}
870+
871+
return fit_values;
872+
}

0 commit comments

Comments
 (0)