Belle II Software development
eclee5x5Algorithm.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/* Own header. */
10#include <ecl/calibration/eclee5x5Algorithm.h>
11
12/* ECL headers. */
13#include <ecl/dataobjects/ECLElementNumbers.h>
14#include <ecl/dbobjects/ECLCrystalCalib.h>
15
16/* ROOT headers. */
17#include <TDecompLU.h>
18#include <TH2F.h>
19#include <TF1.h>
20#include <TFile.h>
21#include <TMatrixD.h>
22#include <TMatrixDSym.h>
23#include <TROOT.h>
24
25using namespace std;
26using namespace Belle2;
27using namespace ECL;
28
31{
33 "Perform energy calibration of ecl crystals by analyzing energy in 25-crystal sums from Bhabha events"
34 );
35}
36
38{
41 TH1F* dummy;
42 dummy = (TH1F*)gROOT->FindObject("AverageExpECrys");
43 if (dummy) {delete dummy;}
44 dummy = (TH1F*)gROOT->FindObject("AverageElecCalib");
45 if (dummy) {delete dummy;}
46 dummy = (TH1F*)gROOT->FindObject("AverageInitCalib");
47 if (dummy) {delete dummy;}
48 dummy = (TH1F*)gROOT->FindObject("meanEnvsCrysID");
49 if (dummy) {delete dummy;}
50
52 gROOT->SetBatch();
53
56 B2INFO("eclee5x5Algorithm parameters:");
57 B2INFO("outputName = " << m_outputName);
58 B2INFO("minEntries = " << m_minEntries);
59 B2INFO("payloadName = " << m_payloadName);
60 B2INFO("storeConst = " << m_storeConst);
61 if (m_payloadName == "ECLeedPhiData" or m_payloadName == "ECLeedPhiMC" or m_payloadName == "None") {
62 B2INFO("fracLo = " << m_fracLo);
63 B2INFO("fracHiSym = " << m_fracHiSym);
64 B2INFO("fracHiASym = " << m_fracHiASym);
65 B2INFO("nsigLo = " << m_nsigLo);
66 B2INFO("nsigHiSym = " << m_nsigHiSym);
67 B2INFO("nsigHiASym = " << m_nsigHiASym);
68 }
69
72 auto EnVsCrysID = getObjectPtr<TH2F>("EnVsCrysID");
73 auto RvsCrysID = getObjectPtr<TH1F>("RvsCrysID");
74 auto NRvsCrysID = getObjectPtr<TH1F>("NRvsCrysID");
75 auto Qmatrix = getObjectPtr<TH2F>("Qmatrix");
76 auto ElecCalibvsCrys = getObjectPtr<TH1F>("ElecCalibvsCrys");
77 auto ExpEvsCrys = getObjectPtr<TH1F>("ExpEvsCrys");
78 auto InitialCalibvsCrys = getObjectPtr<TH1F>("InitialCalibvsCrys");
79 auto CalibEntriesvsCrys = getObjectPtr<TH1F>("CalibEntriesvsCrys");
80 auto EntriesvsCrys = getObjectPtr<TH1F>("EntriesvsCrys");
81 auto dPhivsThetaID = getObjectPtr<TH2F>("dPhivsThetaID");
82
86 TH1F* AverageExpECrys = new TH1F("AverageExpECrys", "Average expected E per crys from collector;Crystal ID;Energy (GeV)",
89 TH1F* AverageElecCalib = new TH1F("AverageElecCalib", "Average electronics calib const vs crystal;Crystal ID;Calibration constant",
91 TH1F* AverageInitCalib = new TH1F("AverageInitCalib", "Average initial calib const vs crystal;Crystal ID;Calibration constant",
93 TH1F* meanEnvsCrysID = new TH1F("meanEnvsCrysID", "Mean normalized energy vs crystal;CrystalID;E/Eexp",
95
96 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
97 double TotEntries = CalibEntriesvsCrys->GetBinContent(cellID);
98 if (TotEntries > 0.) {
99 AverageElecCalib->SetBinContent(cellID, ElecCalibvsCrys->GetBinContent(cellID) / TotEntries);
100 AverageExpECrys->SetBinContent(cellID, ExpEvsCrys->GetBinContent(cellID) / TotEntries);
101 AverageInitCalib->SetBinContent(cellID, InitialCalibvsCrys->GetBinContent(cellID) / TotEntries);
102 }
103
104 TH1D* En = EnVsCrysID->ProjectionY("En", cellID, cellID);
105 meanEnvsCrysID->SetBinContent(cellID, En->GetMean());
106 meanEnvsCrysID->SetBinError(cellID, En->GetStdDev());
107 }
108
111 TString fName = m_outputName;
112 TFile* histfile = new TFile(fName, "recreate");
113 EnVsCrysID->Write();
114 RvsCrysID->Write();
115 NRvsCrysID->Write();
116 Qmatrix->Write();
117 AverageElecCalib->Write();
118 AverageExpECrys->Write();
119 AverageInitCalib->Write();
120 EntriesvsCrys->Write();
121 dPhivsThetaID->Write();
122 meanEnvsCrysID->Write();
123
126 if (m_payloadName == "None") {
127 B2INFO("eclee5x5Algorithm has not been asked to find constants; copying input histograms and quitting");
128 histfile->Close();
129 return c_NotEnoughData;
130 }
131
134 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
135
137 if (AverageInitCalib->GetBinContent(cellID) > 0.) {
138 if (EntriesvsCrys->GetBinContent(cellID) < m_minEntries) {
139 histfile->Close();
140 B2INFO("eclee5x5Algorithm: insufficient data for cellID = " << cellID << " " << EntriesvsCrys->GetBinContent(cellID) << " entries");
141 return c_NotEnoughData;
142 }
143 }
144 }
145
146
150
151
154 bool foundConst = false;
155 TH1F* gVsCrysID = new TH1F("gVsCrysID", "Ratio of new to old calibration vs crystal ID;crystal ID;vector g",
157 TH1F* CalibVsCrysID = new TH1F("CalibVsCrysID", "Calibration constant vs crystal ID;crystal ID;counts per GeV",
159 TH1F* ExpEnergyperCrys = new TH1F("ExpEnergyperCrys", "Expected energy per crystal;Crystal ID;Energy in 25 crystal sum (GeV)",
162 TString title = "dPhi cut per crystal " + m_payloadName + ";Crystal ID;dPhi requirement (deg)";
163 TH1F* dPhiperCrys = new TH1F("dPhiperCrys", title, ECLElementNumbers::c_NCrystals, 0, ECLElementNumbers::c_NCrystals);
164
167 if (m_payloadName == "ECLExpee5x5E") {
168 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
169 float mean = meanEnvsCrysID->GetBinContent(cellID);
170 float stdDev = meanEnvsCrysID->GetBinError(cellID);
171 float inputE = AverageExpECrys->GetBinContent(cellID);
172 if (mean > 0. and stdDev > 0.) {
173 ExpEnergyperCrys->SetBinContent(cellID, mean * abs(inputE));
174 ExpEnergyperCrys->SetBinError(cellID, stdDev * abs(inputE));
175 } else {
176 ExpEnergyperCrys->SetBinContent(cellID, inputE);
177 ExpEnergyperCrys->SetBinError(cellID, 0.05 * inputE);
178 }
179 }
180
181 //..Generate the payload, if requested
182 foundConst = true;
183 if (m_storeConst) {
184 std::vector<float> tempCalib;
185 std::vector<float> tempCalibStdDev;
186
187 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
188 tempCalib.push_back(ExpEnergyperCrys->GetBinContent(cellID));
189 tempCalibStdDev.push_back(ExpEnergyperCrys->GetBinError(cellID));
190 }
191 ECLCrystalCalib* ExpectedE = new ECLCrystalCalib();
192 ExpectedE->setCalibVector(tempCalib, tempCalibStdDev);
193 saveCalibration(ExpectedE, "ECLExpee5x5E");
194 B2INFO("eclee5x5Algorithm: successfully stored expected energies ECLExpee5x5E");
195 }
196
199 } else if (m_payloadName == "ECLCrystalEnergy5x5") {
200
201 //..Create the Q matrix and the R vector
202 TMatrixDSym matrixQ(ECLElementNumbers::c_NCrystals);
203 TVectorD vectorR(ECLElementNumbers::c_NCrystals);
204 for (int ix = 1; ix <= ECLElementNumbers::c_NCrystals; ix++) {
205 vectorR[ix - 1] = RvsCrysID->GetBinContent(ix);
206 for (int iy = 1; iy <= ECLElementNumbers::c_NCrystals; iy++) {
207 matrixQ[ix - 1][iy - 1] = Qmatrix->GetBinContent(ix, iy);
208 }
209 }
210
211 //..Crystals that are not being calibrated have no entries in NR. Adjust R and Q to get g=-1
212 int nNotCalibrated = 0;
213 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
214 if (NRvsCrysID->GetBinContent(cellID) < 0.5) {
215 for (int othercell = 1; othercell <= ECLElementNumbers::c_NCrystals; othercell++) {
216 matrixQ[cellID - 1][othercell - 1] = 0.;
217 matrixQ[othercell - 1][cellID - 1] = 0.;
218 }
219 matrixQ[cellID - 1][cellID - 1] = 1.;
220 vectorR[cellID - 1] = -1.;
221 nNotCalibrated++;
222 }
223 }
224 B2INFO("eclee5x5Algorithm: " << nNotCalibrated << " crystals will not be calibrated. ");
225
226 //..Invert to solve Q g = R
227 TDecompLU lu(matrixQ);
228 bool solved;
229 TVectorD vectorg = lu.Solve(vectorR, solved);
230
231 //..Fill histograms and check that there are no unexpected negative output values
232 if (solved) {
233 foundConst = true;
234 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
235 gVsCrysID->SetBinContent(cellID, vectorg[cellID - 1]);
236 gVsCrysID->SetBinError(cellID, 0.);
237 float newCalib = vectorg[cellID - 1] * abs(AverageInitCalib->GetBinContent(cellID));
238 CalibVsCrysID->SetBinContent(cellID, newCalib);
239 CalibVsCrysID->SetBinError(cellID, 0.);
240
241 if (vectorg[cellID - 1] < 0. and NRvsCrysID->GetBinContent(cellID) > 0.) {foundConst = false;}
242 }
243 }
244
245 //..Generate the payload if requested, and if the matrix inversion worked
246 if (m_storeConst and foundConst) {
247 std::vector<float> tempCalib;
248 std::vector<float> tempCalibStdDev;
249
250 for (int cellID = 1; cellID <= ECLElementNumbers::c_NCrystals; cellID++) {
251 tempCalib.push_back(CalibVsCrysID->GetBinContent(cellID));
252 tempCalibStdDev.push_back(CalibVsCrysID->GetBinError(cellID));
253 }
254 ECLCrystalCalib* e5x5ECalib = new ECLCrystalCalib();
255 e5x5ECalib->setCalibVector(tempCalib, tempCalibStdDev);
256 saveCalibration(e5x5ECalib, "ECLCrystalEnergyee5x5");
257 B2INFO("eclee5x5Algorithm: successfully stored calibration ECLCrystalEnergyee5x5");
258 }
259
262 } else if (m_payloadName == "ECLeedPhiData" or m_payloadName == "ECLeedPhiMC") {
263
264 //..Find mean and sigma of Gaussian fit to ThetaID projections with sufficient statistics
265 float dPhiCenter[69] = {};
266 float dPhiHalfWidth[69] = {};
267 int firstID = 0;
268 int lastID = 0;
269 const int nbins = dPhivsThetaID->GetNbinsX();
270 for (int ib = 1; ib <= nbins; ib++) {
271 TH1D* proj = dPhivsThetaID->ProjectionY("proj", ib, ib);
272 if (proj->Integral() < 100) {continue;}
273 int thetaID = (int)dPhivsThetaID->GetXaxis()->GetBinCenter(ib);
274 if (firstID == 0) { firstID = thetaID; }
275 lastID = thetaID;
276
277 //..Find the fit limits
278 double fracHi = m_fracHiSym;
279 double nsigHi = m_nsigHiSym;
280 if (thetaID <= m_lastLoThetaID) {
281 fracHi = m_fracHiASym;
282 nsigHi = m_nsigHiASym;
283 }
284
285 //..Fit range includes all bins with entries>m_fracLo*peak on the low side of the
286 // peak, and entries>m_fracHi*peak on the high side.
287 // i.e. low edge of bin on low side, high edge on high side = low edge of bin+1
288 double peak = proj->GetMaximum();
289 int nPhiBins = proj->GetNbinsX();
290 int binLo = 1;
291 do {
292 binLo++;
293 } while (proj->GetBinContent(binLo) < m_fracLo * peak and binLo < nPhiBins);
294 double xfitLo = proj->GetBinLowEdge(binLo);
295
296 //..Start search for the upper edge of the fit range at 175 deg to avoid gamma gamma
297 // and e gamma peaks
298 int binHi = proj->GetXaxis()->FindBin(175.01);
299 do {
300 binHi--;
301 } while (proj->GetBinContent(binHi)<fracHi* peak and binHi>1);
302 double xfitHi = proj->GetBinLowEdge(binHi + 1);
303
304 //..Check that the fit region is sensible
305 int peakBin = proj->GetMaximumBin();
306 if (binLo >= peakBin or binHi <= peakBin) {
307 B2ERROR("Flawed dPhi fit range for thetaID = " << thetaID << " peakBin = " << peakBin << " binLo = " << binLo << "binHi = " <<
308 binHi);
309 }
310
311 //..Now fit a Gaussian to the selected region
312 proj->Fit("gaus", "", "", xfitLo, xfitHi);
313
314 //..Find mean, sigma, and selection range. Record center and half-width of range.
315 TF1* fitGaus = proj->GetFunction("gaus");
316 float mean = fitGaus->GetParameter(1);
317 float sigma = fitGaus->GetParameter(2);
318 float dPhiLo = mean - m_nsigLo * sigma;
319 float dPhiHi = mean + nsigHi * sigma;
320 dPhiCenter[thetaID] = 0.5 * (dPhiLo + dPhiHi);
321 dPhiHalfWidth[thetaID] = 0.5 * (dPhiHi - dPhiLo);
322 }
323
324 //..Pad the thetaID's without fits with the first or last thetaID values
325 for (int thetaID = 0; thetaID < firstID; thetaID++) {
326 dPhiCenter[thetaID] = dPhiCenter[firstID];
327 dPhiHalfWidth[thetaID] = dPhiHalfWidth[firstID];
328 }
329 for (int thetaID = lastID + 1; thetaID < 69; thetaID++) {
330 dPhiCenter[thetaID] = dPhiCenter[lastID];
331 dPhiHalfWidth[thetaID] = dPhiHalfWidth[lastID];
332 }
333
334 //..Now copy these to each crystal to generate the payload and fill the output histogram.
335 // We will use ECLNeighours to get the number of crystals in each theta ring
337 std::vector<float> tempCalib;
338 std::vector<float> tempCalibWidth;
339 tempCalib.resize(ECLElementNumbers::c_NCrystals);
340 tempCalibWidth.resize(ECLElementNumbers::c_NCrystals);
341 int crysID = 0;
342 for (int thetaID = 0; thetaID < 69; thetaID++) {
343 for (int ic = 0; ic < m_eclNeighbours5x5->getCrystalsPerRing(thetaID); ic++) {
344 tempCalib.at(crysID) = dPhiCenter[thetaID];
345 tempCalibWidth.at(crysID) = dPhiHalfWidth[thetaID];
346 dPhiperCrys->SetBinContent(crysID + 1, dPhiCenter[thetaID]);
347 dPhiperCrys->SetBinError(crysID + 1, dPhiHalfWidth[thetaID]);
348 crysID++;
349 }
350 }
351
352 //..Store the payload, if requested
353 foundConst = true;
354 if (m_storeConst) {
355 ECLCrystalCalib* eedPhi = new ECLCrystalCalib();
356 eedPhi->setCalibVector(tempCalib, tempCalibWidth);
358 B2INFO("eclee5x5Algorithm: successfully stored calibration " << m_payloadName);
359 }
360
363 } else {
364 B2ERROR("eclee5x5Algorithm: invalid payload name: m_payloadName = " << m_payloadName);
365 }
366
369 histfile->cd();
370 if (m_payloadName == "ECLExpee5x5E") {
371 ExpEnergyperCrys->Write();
372 } else if (m_payloadName == "ECLCrystalEnergy5x5") {
373 gVsCrysID->Write();
374 CalibVsCrysID->Write();
375 } else if (m_payloadName == "ECLeedPhiData" or m_payloadName == "ECLeedPhiMC") {
376 dPhiperCrys->Write();
377 }
378 histfile->Close();
379
380 dummy = (TH1F*)gROOT->FindObject("gVsCrysID"); delete dummy;
381 dummy = (TH1F*)gROOT->FindObject("CalibVsCrysID"); delete dummy;
382 dummy = (TH1F*)gROOT->FindObject("ExpEnergyperCrys"); delete dummy;
383 dummy = (TH1F*)gROOT->FindObject("dPhiperCrys"); delete dummy;
384
387 if (!m_storeConst) {
388 if (foundConst) {
389 B2INFO("eclee5x5Algorithm successfully found constants but was not asked to store them");
390 } else {
391 B2INFO("eclee5x5Algorithm was not asked to store constants, and did not succeed in finding them");
392 }
393 return c_Failure;
394 } else if (!foundConst) {
395 if (m_payloadName == "ECLExpee5x5E") {
396 B2INFO("eclee5x5Algorithm: failed to store expected values");
397 } else if (m_payloadName == "ECLCrystalEnergy5x5") {
398 B2INFO("eclee5x5Algorithm: failed to store calibration constants");
399 } else if (m_payloadName == "ECLeedPhiData" or m_payloadName == "ECLeedPhiMC") {
400 B2INFO("eclee5x5Algorithm: failed to find dPhi* selection criteria");
401 }
402 return c_Failure;
403 }
404 return c_OK;
405}
Base class for calibration algorithms.
void saveCalibration(TClonesArray *data, const std::string &name)
Store DBArray payload with given name with default IOV.
void setDescription(const std::string &description)
Set algorithm description (in constructor)
EResult
The result of calibration.
@ c_OK
Finished successfuly =0 in Python.
@ c_NotEnoughData
Needs more data =2 in Python.
@ c_Failure
Failed =3 in Python.
General DB object to store one calibration number per ECL crystal.
void setCalibVector(const std::vector< float > &CalibConst, const std::vector< float > &CalibConstUnc)
Set vector of constants with uncertainties.
Class to get the neighbours for a given cell id.
Definition: ECLNeighbours.h:25
short int getCrystalsPerRing(const short int thetaid) const
return number of crystals in a given theta ring
Definition: ECLNeighbours.h:39
int m_minEntries
all crystals to be calibrated must have this many entries
double m_fracHiASym
or fracHiASym*peak, at low values of thetaID
double m_fracLo
start dPhi fit where data is > fraclo*peak
double m_nsigHiASym
or mean+nsigHiASym*sigma at low thetaID
double m_nsigLo
dPhi region is mean - nsigLo*sigma
std::string m_payloadName
Name of the payload to be stored.
ECL::ECLNeighbours * m_eclNeighbours5x5
Neighbours, used to get nCrys per ring.
std::string m_outputName
..Parameters to control job to find energy calibration using Bhabhas
bool m_storeConst
write payload to localdb if true
int m_lastLoThetaID
use asymmetric dPhi range for thetaID<= this value
virtual EResult calibrate() override
..Run algorithm on events
double m_fracHiSym
end dPhi fit where data is > fracHiSym*peak
double m_nsigHiSym
to mean + nsigHiSym*sigma
const int c_NCrystals
Number of crystals.
Abstract base class for different kinds of events.
STL namespace.