Belle II Software prerelease-11-00-00a
TOPBackSplashTimingModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <Math/Vector3D.h>
10#include <framework/dataobjects/EventMetaData.h>
11#include <framework/datastore/StoreObjPtr.h>
12#include <framework/logging/Logger.h>
13#include <framework/utilities/IOIntercept.h>
14#include <math.h>
15#include <string>
16#include <top/modules/TOPBackSplashTiming/TOPBackSplashTimingModule.h>
17#include <top/dataobjects/TOPLikelihood.h>
18#include <tracking/dataobjects/ExtHit.h>
19
20#include <RooAddPdf.h>
21#include <RooArgList.h>
22#include <RooArgSet.h>
23#include <RooExponential.h>
24#include <RooFormulaVar.h>
25#include <RooGenericPdf.h>
26#include <RooJohnson.h>
27#include <RooPolynomial.h>
28#include <RooPlot.h>
29#include <RooProdPdf.h>
30#include <RooMsgService.h>
31#include <TCanvas.h>
32#include <TF1.h>
33#include <TLine.h>
34#include <TPaveText.h>
35
36using namespace Belle2;
37
38REG_MODULE(TOPBackSplashTiming);
39
41 : Module(),
42 m_eclClusters("ECLClusters"), // DataStore name
43 m_digits("TOPDigits"),
44 m_tracks("Tracks"),
45 m_fitparams{{{ -0.6, 4, 52, -0.145075, 0.885072, 0.386489, -1.361947, 0.662144, 7.134312, 0.449013, 27.397423},
46 { -0.5, 4, 51, -0.082732, 0.795969, 0.674888, -1.281492, 0.232786, 6.784821, 0.452656, 26.333126},
47 { -0.4, 5, 50, -0.071727, 0.828312, 0.657696, -1.228757, 0.297860, 7.371104, 0.514466, 24.651866},
48 { -0.3, 5, 49, -0.071360, 0.833236, 0.628764, -1.167383, 0.418553, 7.939829, 0.421581, 23.109511},
49 { -0.2, 6, 49, -0.075286, 0.850655, 0.631255, -1.187544, 0.526128, 8.469763, 0.345813, 21.632224},
50 { -0.1, 6, 48, -0.084704, 0.888931, 0.598693, -1.350362, 0.595812, 9.081677, 0.433728, 20.394818},
51 { 0.0, 7, 48, -0.091469, 0.859441, 0.587823, -1.244311, 0.641963, 9.767411, 0.364095, 18.956789},
52 { 0.1, 7, 48, -0.100383, 0.941895, 0.548498, -1.434151, 0.806756, 10.400005, 0.382099, 17.762602},
53 { 0.2, 9, 47, -0.108262, 0.917920, 0.531122, -1.417409, 0.875909, 11.205481, 0.355874, 16.347529},
54 { 0.3, 9, 48, -0.125444, 1.024868, 0.493756, -1.684685, 1.010871, 11.895900, 0.330729, 15.227423},
55 { 0.4, 10, 48, -0.140058, 1.022069, 0.458708, -1.517339, 1.246522, 13.112702, 0.366439, 13.597788},
56 { 0.5, 12, 47, -0.144628, 1.201504, 0.450720, -2.059850, 1.280475, 13.949971, 0.278829, 12.030733},
57 { 0.6, 13, 48, -0.156869, 1.095534, 0.451224, -1.872655, 1.412490, 15.791843, 0.292740, 9.729090},
58 { 0.7, 15, 50, -0.167605, 2.117748, 0.352186, -1.999952, 3.009412, 17.997337, 0.332440, 6.500688},
59 { 0.8, 19, 51, -0.232060, 0.726019, 0.581841, -0.142827, 1.205148, 24.876122, 0.457538, 1.328250}}}
60// Fit params for combined pdf (See initialise for definition)
61// Params: 0-cosTheta, 1-fit start, 2-fit end, 3-coeff, 4-delta, 5-frac, 6-gamma, 7-lambda, 8-mu, 9-w, 10-shift-mu
62{
63 setDescription(R"DOC(A module to save timing of neutral cluster events derived from nearby TOP interactions)DOC");
64
65 addParam("saveFits", m_saveFits,
66 "Set to true to save RooPlot of fit to TOP signal from which the timing is extracted, for debug.",
67 false);
68 addParam("minClusterE", m_minClusterE,
69 "Minimum (incl.) cluster energy to be considered in adding relation to TOP time extraction [GeV]",
70 0.5);
71 addParam("minNphotons", m_minNphotons,
72 "Minimum (incl.) no. of Cherenkov photons measured in TOP slot for which time extraction will take place",
73 50);
74 addParam("minClusterNHits", m_minClusterNHits,
75 "Minimum (incl.) no. of crystals in cluster required for adding relation to TOP time extraction",
76 10.0);
77 addParam("includeSlotsWithTracks", m_includeSlotsWithTracks,
78 "Flag to allow fitting of neutral cluster TOP signal even if there are charged tracks matched to the same slot",
79 false);
80 addParam("saveMoreFitParams", m_saveMoreFitParams,
81 "Flag to save additonal parameters from TOP timing fit (e.g. RooFit errors) to output, for debug.",
82 false);
83}
84
86{
87 RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING);
88
89 // Container for RooWorkspaces
90 m_wss.clear();
91
92 for (auto row : m_fitparams) {
93 std::string xname = "x_" + std::to_string(row[0]);
94 RooRealVar x("x", xname.c_str(), -10, 80); // row[1], row[2]);
95 RooRealVar mu("mu", "mu", row[8], -9, 80);//row[1], row[2]);
96 RooRealVar gamma("gamma", "gamma", row[6], row[6], row[6]);
97 RooRealVar lambda("lambda", "lambda", row[7], row[7], row[7]);
98 RooRealVar delta("delta", "delta", row[4], row[4], row[4]);
99
100 // Forward pulse timing model
101 RooJohnson john("john", "john", x, mu, lambda, gamma, delta, 0);
102
103 RooRealVar shift_minus_mu("shift_minus_mu", "shift_minus_mu", row[10], 0., 30.);
104 RooRealVar coeff("coeff", "coeff", row[3], row[3], row[3]);
105 RooRealVar w("w", "w", row[9], row[9], row[9]);
106 RooRealVar frac("frac", "frac", row[5], 0.1, 1.);
107
108 // Constants of fit
109 gamma.setConstant(true);
110 lambda.setConstant(true);
111 delta.setConstant(true);
112 shift_minus_mu.setConstant(true);
113 coeff.setConstant(true);
114 w.setConstant(true);
115
116 RooFormulaVar shift("shift", "@0+@1", RooArgList(mu, shift_minus_mu));
117 RooFormulaVar xshifted("xshifted", "@0-@1", RooArgList(x, shift));
118 RooExponential exp("exp", "exp", xshifted, coeff);
119 RooGenericPdf smooth_step("smooth_step", "1.0/(1.0 + exp((-@0)/@1))", RooArgList(xshifted, w));
120
121 // Secondary pulse timing model
122 RooProdPdf smoothedexp("smoothedexp", "exp * smooth_step", RooArgList(exp, smooth_step));
123
124 // Signal pulse model
125 RooAddPdf sgnModel("sgnModel", "signal model", john, smoothedexp, frac);
126
127 RooRealVar slope("slope", "slope", 0);
128 slope.setConstant(true);
129 RooPolynomial bkgModel("bkgModel", "bkgModel", x, RooArgList(slope));
130
131 RooRealVar sigPhotons("sigPhotons", "sigPhotons", 50, 0, 500);
132 RooRealVar bkgPhotons("bkgPhotons", "bkgPhotons", 50, 0, 500);
133
134 // Combined signal (dual pulse) with flat background noise
135 std::string modelname = "model_" + std::to_string(row[0]);
136 RooAddPdf model("model", modelname.c_str(), RooArgList(sgnModel, bkgModel), RooArgList(sigPhotons, bkgPhotons));
137
138 std::string wsname = "ws_" + std::to_string(row[0]);
139 RooWorkspace ws("ws", wsname.c_str());
140
141 ws.import(model);
142
143 m_wss.push_back(ws);
144 }
145}
146
148{
149 // Derive slot no. from azimuthal angle
150 if (phi < 0) {
151 phi += 2 * M_PI;
152 }
153 return int(phi / (M_PI / 8) + 1);
154}
155
156void TOPBackSplashTimingModule::makePlot(double cosTheta, double clusterE, int moduleID, RooAbsPdf* model,
157 RooRealVar* x, RooDataSet data, RooFitResult* res, int nTracksPerSlot)
158{
159 // For debugging
160 int cosThetaClusterIndex = int(std::round(cosTheta * 10)); // integer bin in [-6, 8]
161 double nearestClusterCosTheta = cosThetaClusterIndex / 10.0;
162 cosThetaClusterIndex += 6; // shift to [0, 14]
163 int binning = (m_fitparams[cosThetaClusterIndex][2] - m_fitparams[cosThetaClusterIndex][1]);
164
165 // Create RooPlot
166 TCanvas* c = new TCanvas("c", "Johnson SU fit", 900, 900);
167 StoreObjPtr<EventMetaData> eventMetaData;
168 std::string rooPlotTitle = "evtNo: " + std::to_string(eventMetaData->getEvent()) + " runNo.: " + std::to_string(
169 eventMetaData->getRun()) + " clusterE: " + std::to_string(
170 clusterE) + " slot: " + std::to_string(moduleID) + " fitted cosTheta: " + std::to_string(nearestClusterCosTheta) +
171 " no. of tracks in slot: " + std::to_string(nTracksPerSlot);
172
173 RooPlot* frame = x->frame(RooFit::Title(rooPlotTitle.c_str()));
174 data.plotOn(frame, RooFit::Binning(binning));
175 model->plotOn(frame);
176
177 model->plotOn(frame,
178 RooFit::MoveToBack(),
179 RooFit::Components("john"),
180 RooFit::LineColor(kCyan));
181
182 model->plotOn(frame,
183 RooFit::MoveToBack(),
184 RooFit::Components("smoothedexp"),
185 RooFit::LineColor(kSpring));
186
187 // reduced chi-square, 1 fit param
188 double redchisq = frame->chiSquare(res->floatParsFinal().getSize());
189
190 // Canvas
191 frame->Draw();
192
193 // Stat box
194 TPaveText* box = new TPaveText(0.7, 0.65, 0.98, 0.92, "NDC");
195 box->SetFillColor(0);
196 box->SetBorderSize(1);
197
198 // Header
199 int nPhotons = data.numEntries();
200 box->AddText(Form("nPhotons=%d", nPhotons));
201
202 // Parameters
203 double xpeak1 = 0;
204 for (int i = 0; i < res->floatParsFinal().getSize(); ++i) {
205 RooRealVar* p = (RooRealVar*)res->floatParsFinal().at(i);
206 box->AddText(Form("%s = %.3f ± %.3f",
207 p->GetName(),
208 p->getVal(),
209 p->getError()));
210 if ((std::string)p->GetName() == "mu") {
211 xpeak1 = p->getVal();
212 }
213 }
214 // Johnson mu (used as extracted time)
215 TLine* xpeak1line = new TLine(xpeak1, 0, xpeak1, 1e6);
216 xpeak1line->SetLineStyle(2);
217 xpeak1line->SetLineColor(kRed);
218 xpeak1line->SetLineWidth(5);
219 xpeak1line->Draw();
220
221 // Extra info
222 box->AddText("Results");
223 box->AddText(Form("forward peak: %.2f ns", xpeak1));
224 box->AddText(Form("redchisq = %.4f", redchisq));
225
226 box->SetTextSizePixels(50);
227 box->Draw();
228
229 // Save canvas
230 std::string roofitname = "TOPBackSplashFit_evtNo_" + std::to_string(eventMetaData->getEvent()) + "_runNo_" + std::to_string(
231 eventMetaData->getRun()) + "_clusterE_" + std::to_string(
232 clusterE) + "_slot_" + std::to_string(moduleID) + "_cosTheta_" + std::to_string(
233 nearestClusterCosTheta) + ".png";
234 c->SaveAs(roofitname.c_str());
235}
236
238 std::vector<const TOPDigit*>& digitsPerSlot, double clusterE, double clusterCosTheta, int nTracksPerSlot)
239{
240
241 // Intercept RooFit messages
243 initLogCapture.setIndent(" ");
244 initLogCapture.start();
245
246 // Get pdf according to cosTheta
247 int cosThetaIndex = int(std::round(clusterCosTheta * 10)); // integer bin in [-6, 8]
248 cosThetaIndex += 6; // shift to [0, 14]
249
250 // Note edge case: 0.85 < cosTheta < 0.8572 (TOP acceptance) rounds to 0.9
251 // covers ~31 to 31.79 degrees, omit these cases
252 if (cosThetaIndex >= int(m_wss.size())) {
253 return nullptr;
254 }
255
256 RooRealVar* x = m_wss[cosThetaIndex].var("x");
257
258 RooDataSet data("data", "unbinned", RooArgSet(*x));
259 for (auto digit : digitsPerSlot) {
260 double v = digit->getTime();
261 x->setVal(v);
262 data.add(RooArgSet(*x));
263 }
264 // Second check no. of photons (if enough digits in fit boundaries)
265 if (data.numEntries() < m_minNphotons) {
266 return nullptr;
267 }
268
269 double clusterSinTheta = TMath::Sqrt(1 - clusterCosTheta * clusterCosTheta);
270 double zeta = 119 * (clusterCosTheta / clusterSinTheta) + 83.;
271
272 //Range for the main peak position.
273 // minTim calculated with beta = 1 and direct propagation in the bar (no reflections)
274 // maxTime calculate for a p=200 MeV neutron with reflections in the bar
275 double minTime = 120 / (30 * clusterSinTheta) + zeta / 20.;
276 double maxTime = 120 / (3 * clusterSinTheta) + 1.4 * zeta / 20.;
277 double shift = 2 * (270 - zeta) / 20;
278
279 m_wss[cosThetaIndex].var("sigPhotons")->setVal(data.numEntries() * 0.8);
280 m_wss[cosThetaIndex].var("sigPhotons")->setMin(data.numEntries() * 0.2);
281 m_wss[cosThetaIndex].var("sigPhotons")->setMax(data.numEntries() * 1.1);
282
283 m_wss[cosThetaIndex].var("bkgPhotons")->setVal(data.numEntries() * 0.2);
284 m_wss[cosThetaIndex].var("bkgPhotons")->setMin(data.numEntries() * 0.);
285 m_wss[cosThetaIndex].var("bkgPhotons")->setMax(data.numEntries() * 1.1);
286
287 m_wss[cosThetaIndex].var("shift_minus_mu")->setVal(shift);
288 m_wss[cosThetaIndex].var("shift_minus_mu")->setMin(0.);
289 m_wss[cosThetaIndex].var("shift_minus_mu")->setMax(shift * 1.3);
290 m_wss[cosThetaIndex].var("shift_minus_mu")->setConstant(false);
291 m_wss[cosThetaIndex].var("mu")->setVal(0.5 * (minTime + maxTime));
292 m_wss[cosThetaIndex].var("mu")->setMin(minTime);
293 m_wss[cosThetaIndex].var("mu")->setMax(maxTime);
294
295 RooAbsPdf* model = m_wss[cosThetaIndex].pdf("model");
296
297 RooFitResult* res = model->fitTo(data, RooFit::Save(), RooFit::PrintLevel(-1), RooFit::Strategy(0), RooFit::Extended());
298
299 if (res->status() > 0) {
300 return nullptr;
301 }
302
303 double res_time = m_wss[cosThetaIndex].var("mu")->getValV();
304 double res_timeErr = m_wss[cosThetaIndex].var("mu")->getError();
305 double res_deltaT = m_wss[cosThetaIndex].var("shift_minus_mu")->getValV();
306 double res_deltaTErr = m_wss[cosThetaIndex].var("shift_minus_mu")->getError();
307 double res_frac = m_wss[cosThetaIndex].var("frac")->getValV();
308 double res_fracErr = m_wss[cosThetaIndex].var("frac")->getError();
309 double res_signalPhotons = m_wss[cosThetaIndex].var("sigPhotons")->getValV();
310 double res_signalPhotonsErr = m_wss[cosThetaIndex].var("sigPhotons")->getError();
311
312
313 // Deriving chi2/dof: Need to bin but not Draw
314 int binning = (m_fitparams[cosThetaIndex][2] - m_fitparams[cosThetaIndex][1]);
315 RooPlot* frame = x->frame();
316 data.plotOn(frame, RooFit::Binning(binning));
317 model->plotOn(frame);
318 int nfloatParsFinal = res->floatParsFinal().getSize();
319 double redchisq = frame->chiSquare(nfloatParsFinal);
320
321 if (m_saveFits == true) {
322 makePlot(clusterCosTheta, clusterE, moduleID, model, x, data, res, nTracksPerSlot);
323 }
324
325 initLogCapture.finish();
326
327 // Saving results & plotting (time, chi2/dof, no. photons)
329 TOPBackSplashFitResult* fitresult = m_fitresult.appendNew(res_time, res_timeErr, res_deltaT, res_deltaTErr, res_frac, res_fracErr,
330 res_signalPhotons, res_signalPhotonsErr, redchisq, data.sumEntries(), nTracksPerSlot);
331 return fitresult;
332 } else {
333 TOPBackSplashFitResult* fitresult = m_fitresult.appendNew(res_time, redchisq, data.sumEntries(), nTracksPerSlot);
334 return fitresult;
335 }
336
337}
338
339
341{
342 B2INFO("TOPBackSplashTimingModule initialized");
343 m_eclClusters.isRequired();
344 m_digits.isRequired();
345 m_tracks.isRequired();
346 m_fitresult.registerInDataStore();
347 m_fitresult.registerRelationTo(m_eclClusters);
349}
350
352{
353 // Step 1: See which tracks can be matched to slots, ignore these slots
354 // Even if only neutral clusters are being considered, the same slot
355 // might be adjacent to an additional charged cluster, which will make be bkg
356 // to anti-neutron TOP signal
357 int nTracksPerSlots[16] = {0};
358 for (const auto& track : m_tracks) {
359 const auto* tl = track.getRelated<TOPLikelihood>();
360 if (not tl) continue;
361 const auto* te = tl->getRelated<ExtHit>();
362 if (not te) continue;
363 int slotID = te->getCopyID();
364 // i.e. count how many charged track in slot
365 nTracksPerSlots[slotID - 1]++;
366 }
367
368 // Step 2: Clean digitis, assign to slot
369 std::array<std::vector<const TOPDigit*>, 16> digitsPerSlots;
370 for (const auto& digi : m_digits) {
371 if (digi.getHitQuality() != TOPDigit::c_Good) {
372 continue;
373 }
374 if (digi.getTime() > 0 && digi.getTime() < 80) {
375 digitsPerSlots[ int(digi.getModuleID()) - 1].push_back(&digi);
376 }
377 }
378 // Step 3: Iterate over clusters with cleaning
379 for (const auto& cluster : m_eclClusters) {
380
381 // Clusters in barrel and within TOP acceptance,
382 // Must have neutral hadron treatment of clusters only
383 // No tracks matched to clusters
384 // Min cluster energy, min clusterNHits (values passed to module)
385 if (cluster.getTheta() > 31.0 * M_PI / 180.0 && cluster.getTheta() < 128.0 * M_PI / 180.0 &&
386 cluster.getDetectorRegion() == 2 &&
387 cluster.hasHypothesis(ECLCluster::EHypothesisBit::c_neutralHadron) &&
388 cluster.isTrack() == false &&
390 cluster.getNumberOfCrystals() >= m_minClusterNHits) {
391
392 // Derive module in front of cluster
393 double phi = cluster.getPhi();
395 if (m_includeSlotsWithTracks == false && nTracksPerSlots[moduleID - 1] > 0) {
396 continue;
397 }
398 // minNphotons check: are there enough photons to fit?
399 if (int(digitsPerSlots[moduleID - 1].size()) < m_minNphotons) {
400 continue;
401 }
402
403 // For labelling plot file
404 double clusterE = cluster.getEnergy(ECLCluster::EHypothesisBit::c_neutralHadron);
405
406 // Step 4: Perform fit on digits nearest to cluster
407 auto* fitresult = fitTimingDigits(moduleID, digitsPerSlots[moduleID - 1],
408 clusterE, std::cos(cluster.getTheta()), nTracksPerSlots[moduleID - 1]);
409 // Step 5: Setting up ECL relation to fit
410 if (fitresult) {
411 fitresult->addRelationTo(&cluster);
412 }
413 }
414 }
415}
@ c_neutralHadron
CR is reconstructed as a neutral hadron (N2)
Definition ECLCluster.h:43
Store one Ext hit as a ROOT object.
Definition ExtHit.h:31
bool start()
Start intercepting the output.
Capture stdout and stderr and convert into log messages.
void setIndent(const std::string &indent)
Set the indent for each line of the output, default is the supplied name + ": "
bool finish()
Finish the capture and emit the message if output has appeard on stdout or stderr.
@ c_Info
Info: for informational messages, e.g.
Definition LogConfig.h:27
Module()
Constructor.
Definition Module.cc:30
T * getRelated(const std::string &name="", const std::string &namedRelation="") const
Get the object to or from which this object has a relation.
Type-safe access to single objects in the data store.
Definition StoreObjPtr.h:96
Class to store the quantities determined in the TOPBackSplashTiming module and relate to correspondin...
void makePlot(double, double, int, RooAbsPdf *, RooRealVar *, RooDataSet, RooFitResult *, int)
Creates RooPlots of fitted TOPtiming and save as png.
void initialize() override
Initialize the Module.
bool m_saveMoreFitParams
saves additional fit parameters (e.g.
void event() override
Event processor.
std::array< std::array< double, 11 >, 15 > m_fitparams
container of 11 TOP timing fit params per cosTheta
bool m_saveFits
plot and save fits of TOP timing
bool m_includeSlotsWithTracks
whether to fit neutral cluster TOP signal in slots with bkg tracks
void prepareFitModels()
Constructs RooFit objects for fitting.
std::vector< RooWorkspace > m_wss
container of RooWorkSpaces, containing fit funcs per cosTheta
int getModuleFromPhi(double)
Maps azimuthal angle to corresponding TOP slot no.
StoreArray< Track > m_tracks
Store Array of Track.
TOPBackSplashFitResult * fitTimingDigits(int, std::vector< const TOPDigit * > &, double, double, int)
Perform fitting of TOP timing in nearby slot.
StoreArray< TOPBackSplashFitResult > m_fitresult
StoreArray of TOPBackSplashFitResult.
StoreArray< ECLCluster > m_eclClusters
StoreArray of ECLCluster.
StoreArray< TOPDigit > m_digits
StoreArray of TOPDigit.
double m_minClusterE
minimum energy of ECL clusters to consider [GeV]
Class to store TOP log likelihoods (output of TOPReconstructor).
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition Module.h:649
Abstract base class for different kinds of events.