RayleighHist.cxx
1 #include "RayleighHist.h"
2 #include "TMath.h"
3 #include "TF1.h"
4 #include "TList.h"
5 #include "FourierBuffer.h"
6 
7 #include "TMinuit.h"
8 #include "TPad.h"
9 
10 ClassImp(Acclaim::RayleighHist);
11 
12 #define NUM_BINS 10
13 
14 
15 Acclaim::RayleighHist::RayleighHist(FourierBuffer* fb,
16  const char* name, const char* title) :
17  TH1D(name, title, 10, 0, 1), amplitudes(fb ? fb->bufferSize : 1000), fNx(10), fNumEvents(0), fNumNonEmptyBins(0),
18  fNumFitParams(1),
19  theFitParams(std::vector<double>(fNumFitParams, 0)),
20  theFitParamsSteps(std::vector<double>(fNumFitParams, 1e-3)),
21  fChiSquaredFunc(this, &Acclaim::RayleighHist::getRayleighChiSquare, fNumFitParams),
22  fBinWidth(1), fFitEveryNAdds(10), fNumAddsMod10(fFitEveryNAdds), fRayleighNorm(0){
23 
24  binValues.resize(fNx, 0);
25  squaredBinErrors.resize(fNx, 0),
26 
27  fParent = fb;
28  freqMHz = -9999;
29  // fracOfEventsWanted = (1 - 1e-8);
30  fracOfEventsWanted = 0.999;
31 
32  fitMethod = kAdaptive;
33  // chiSquareErrorMethod = kPearson;
34  chiSquareErrorMethod = kPoisson;
35  // fitMethod = Scan;
36  // fitMethod = JustEvalGuess;
37 
38 
39  fRay = fParent ? (TF1*) fParent->fRay->Clone(TString::Format("%s_fit", name)) : NULL;
40  fParamsTF1.resize(2, 0);
41 
42 
43  grLastAddedAmp = new TGraph(2);
44  grLastAddedAmp->SetName("grLastAddedAmplitude");
45  grLastAddedAmp->SetTitle("Last added amplitude");
46 
47  fMinimizer = ROOT::Math::Factory::CreateMinimizer("Minuit2", "");
48  // set tolerance , etc...
49  fMinimizer->SetMaxFunctionCalls(1000); // for Minuit/Minuit2
50  fMinimizer->SetMaxIterations(1000); // for GSL
51  fMinimizer->SetTolerance(0.001);
52  fMinimizer->SetPrintLevel(0);
53 
54  // create funciton wrapper for minmizer
55  // a IMultiGenFunction type
56 
57  fMinimizer->SetFunction(fChiSquaredFunc);
58 }
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 Acclaim::RayleighHist::~RayleighHist(){
69 
70  if(fRay){
71  delete fRay;
72  fRay = NULL;
73  }
74 
75  if(fMinimizer){
76  delete fMinimizer;
77  fMinimizer = NULL;
78  }
79 
80  if(grLastAddedAmp){
81  delete grLastAddedAmp;
82  grLastAddedAmp = NULL;
83  }
84 }
85 
86 
87 void Acclaim::RayleighHist::guessMaxBinLimitAndSigmaFromMean(double meanAmp, double& upperLimit, double& sigmaGuess, double fracOfEventsInsideMaxAmp){
88  upperLimit = meanAmp*TMath::Sqrt(-(4./TMath::Pi())*TMath::Log(1.0 - fracOfEventsInsideMaxAmp));
89  sigmaGuess = TMath::Sqrt(2./TMath::Pi())*meanAmp;
90 }
91 
92 
94 
95  double mean = amplitudes.getMean();
96  double maxAmp, sigmaGuess;
97  guessMaxBinLimitAndSigmaFromMean(mean, maxAmp, sigmaGuess, fracOfEventsWanted);
98 
99  // let's say, within 10% is OK?
100  // double xup = GetBinLowEdge(NUM_BINS+1);
101  double xup = fBinWidth*(NUM_BINS+1);
102  if(xup/maxAmp > 0.9 && xup/maxAmp < 1.1){
103  return true;
104  }
105  else{
106  return false;
107  }
108 }
109 
110 
112 
113  const double desiredMaxAmp = meanAmp*TMath::Sqrt(-(4./TMath::Pi())*TMath::Log(1.0 - fracOfEventsWanted));
114 
115  // // get 4 bins from the rising edge to the peak
116  // const Int_t risingEdgeBins = 4;
117  // fBinWidth = sigmaGuess/risingEdgeBins;
118 
119  fBinWidth = desiredMaxAmp/NUM_BINS;
120  // NUM_BINS = TMath::Nint(desiredMaxAmp/fBinWidth);
121  // NUM_BINS = TMath::Nint(desiredMaxAmp/fBinWidth);
122 
123  binCentres.resize(NUM_BINS, 0);
124  squaredBinCentres.resize(NUM_BINS, 0);
125  binValues.resize(NUM_BINS, 0);
126  squaredBinErrors.resize(NUM_BINS, 0);
127 
128  SetBins(NUM_BINS, 0, desiredMaxAmp);
129 
130  // empty bins inc. overflow and underflow
131  for(int bx = 0; bx <= NUM_BINS + 1; bx++){
132  SetBinContent(bx, 0);
133  }
134 
135  for(int bin=0; bin < NUM_BINS; bin++){
136  binValues[bin] = 0;
137  }
138 
139  // set bin content by hand...
140  for(RingBuffer::iterator it=amplitudes.begin(); it != amplitudes.end(); ++it){
141  double amp = (*it);
142  if(amp < desiredMaxAmp){
143  int bin = floor(amp/fBinWidth);
144  binValues[bin]++;
145  }
146  }
147 
148  // // set bin content by hand...
149  // for(RingBuffer::iterator it=amplitudes.begin(); it != amplitudes.end(); ++it){
150  // double amp = (*it);
151  // int bx = TH1D::FindBin(amp);
152  // SetBinContent(bx, GetBinContent(bx)+1);
153  // }
154 
155  // ... set errors once at the end
156  fNumNonEmptyBins = 0;
157  for(int bx=1; bx <= NUM_BINS; bx++){
158  // double y = GetBinContent(bx);
159  double y = binValues[bx-1];
160 
161  SetBinContent(bx, y);
162  SetBinError(bx, TMath::Sqrt(y));
163 
164  // update cached values
165  binCentres[bx-1] = fBinWidth*0.5 + (bx-1)*fBinWidth;
166  squaredBinCentres[bx-1] = binCentres[bx-1]*binCentres[bx-1];
167  binValues[bx-1] = y;
168  squaredBinErrors[bx-1] = binValues[bx-1];
169  fNumNonEmptyBins += y > 0 ? 1 : 0;
170 
171  }
172  fRayleighNorm = amplitudes.size()*fBinWidth;
173 
174 }
175 
176 
177 int Acclaim::RayleighHist::Fill(double amp, double sign){
178 
179  // Here I force poisson errors for bin content *even if removing events*
180  // this allows this histogram to be used for a rolling average
181 
182  sign = sign >= 0 ? 1 : -1;
183  int bx = TH1D::Fill(amp, sign);
184  double n = GetBinContent(bx);
185 
186  // if it currently equals 1 and we just filled it,
187  // then it must previously have been empty
188  fNumNonEmptyBins += n == 1 ? 1 : 0;
189 
190  SetBinError(bx, TMath::Sqrt(n));
191  if(bx > 0 && bx <= NUM_BINS){
192  binValues[bx-1] = n;
193  squaredBinErrors[bx-1] = binValues[bx-1];
194  }
195  fNumEvents += sign;
196  return bx;
197 }
198 
199 
200 bool Acclaim::RayleighHist::add(double newAmp){
201 
202  // first we remove the old value, should be zero if unused
203 
204  // first we remove the old value, should be zero if unused
205 
206  bool needRemoveOld = amplitudes.numElements() == amplitudes.size();
207  double oldAmp = amplitudes.insert(newAmp);
208  bool removedOld = false;
209  if(needRemoveOld){
210  Fill(oldAmp, -1); // remove old event from hist
211  removedOld = true;
212  }
213  Fill(newAmp);
214 
215  // fNumEvents = amplitudes.numElements();
216 
217  double mean = amplitudes.getMean();
218  fRayleighAmpGuess = TMath::Sqrt(2./TMath::Pi())*mean;
219  if(!axisRangeOK()){
220  rebinAndRefill(mean);
221  }
222  fRayleighNorm = fBinWidth*fNumEvents; // fNumEvents is updated in Fill()
223 
224  bool updatedFit = false;
225  if(fNumAddsMod10 == fFitEveryNAdds){
226  fitRayleigh(false);
227  fNumAddsMod10 = 0;
228  updatedFit = true;
229  }
230  fNumAddsMod10++;
231 
232  grLastAddedAmp->SetPoint(0, newAmp, 0);
233  grLastAddedAmp->SetPoint(1, newAmp, 100000000);
234 
235 
236  return updatedFit;
237 }
238 
239 
240 
241 
242 void Acclaim::RayleighHist::getRayleighFitParams(double& rayAmp, double& chiSquare, int& ndf){
243  rayAmp = fRayleighAmplitude;
244  chiSquare = fChiSquare;
245  ndf = fNDF;
246 }
247 
248 
249 
250 double Acclaim::RayleighHist::getRayleighChiSquare(const double* params){
251 
252  double amplitude = params[0];
253 
254  double chiSquare=0;
255  if(amplitude <= 0){ // does this fuck the fitter?
256  chiSquare = 9999 + fabs(amplitude);
257  }
258  else{
259  double invAmpSquare = 1./(amplitude*amplitude);
260  double minusHalfInvAmpSquare = -0.5*invAmpSquare;
261  double exponentPreFactorPart = fRayleighNorm*invAmpSquare;
262  // std::cout << amplitude << "\t" << invAmpSquare << "\t" << exponentPreFactorPart << "\t" << fRayleighNorm << "\t" << fBinWidth << "\t" << fNumEvents << std::endl;
263  for(int i=0; i < NUM_BINS; i++){
264  if(squaredBinErrors[i] > 0){
265  double yTheoretical = exponentPreFactorPart*binCentres[i]*exp(minusHalfInvAmpSquare*squaredBinCentres[i]);
266  double yMeasured = binValues[i];
267  double dy = yTheoretical - yMeasured;
268  if(chiSquareErrorMethod==kPoisson){
269  chiSquare += dy*dy/squaredBinErrors[i];
270  }
271  else if(chiSquareErrorMethod==kPearson){
272  chiSquare += dy*dy/yTheoretical;
273  }
274  else{
275  std::cerr << "Error in " << __PRETTY_FUNCTION__
276  << ". Unknown chiSquareErrorMethod" << std::endl;
277  }
278  }
279  }
280  }
281  return chiSquare;
282 }
283 
284 
285 
286 
287 void Acclaim::RayleighHist::fitRayleighAdaptive(bool forGuiUpdateTF1){
288  // prepareRayleighFitCache();
289  fitRayleighJustEvalGuess(forGuiUpdateTF1);
290 
291  if(fChiSquare / fNDF >= 2){
292  fitRayleighScan(forGuiUpdateTF1);
293  }
294  else{
295  if(forGuiUpdateTF1){
296  updateParamsOfTF1();
297  std::cout << __PRETTY_FUNCTION__ << "\t" << fChiSquare << "\t" << fNDF << "\t" << fNumNonEmptyBins << "\t" << std::endl;
298  }
299  }
300 }
301 
302 
303 
304 void Acclaim::RayleighHist::fitRayleighScan(bool forGuiUpdateTF1){
305  fNDF = fNumNonEmptyBins - 1; // -1 since we are varying the amplitude parameter
306 
307  // const int nStep = 2*rayleighAmplitude/theFitParamsSteps.at(0);
308  const int nStep = 100; //2*rayleighAmplitude/theFitParamsSteps.at(0);
309  double ds = 2*fRayleighAmpGuess/nStep;
310  double bestAmp = 0;
311  double minChiSquare = 1e9;
312  for(int s=1; s < nStep; s++){
313  double amp = s*ds;
314  double cs = getRayleighChiSquare(&amp);
315  // std::cout << s << "\t" << amp << "\t" << cs << "\t" << nStep << "\t" << ds << std::endl;
316 
317  if(cs < minChiSquare){
318  bestAmp = amp;
319  minChiSquare = cs;
320  }
321  }
322  fRayleighAmplitude = bestAmp;
323  fChiSquare = minChiSquare;
324 
325  if(forGuiUpdateTF1){
326  updateParamsOfTF1();
327  std::cout << __PRETTY_FUNCTION__ << "\t" << fChiSquare << "\t" << fNDF << "\t" << fNumNonEmptyBins << "\t" << std::endl;
328  }
329 }
330 
331 
332 void Acclaim::RayleighHist::fitRayleighJustEvalGuess(bool forGuiUpdateTF1){
333  // prepareRayleighFitCache();
334  fNDF = fNumNonEmptyBins; // no varying parameters
335  fChiSquare = getRayleighChiSquare(&fRayleighAmplitude);
336  if(forGuiUpdateTF1){
337  updateParamsOfTF1();
338  std::cout << __PRETTY_FUNCTION__ << "\t" << fChiSquare << "\t" << fNDF << "\t" << fNumNonEmptyBins << "\t" << std::endl;
339  }
340 }
341 
342 
343 
344 void Acclaim::RayleighHist::fitRayleighTF1(){
345  fRay->FixParameter(0, fRayleighNorm);
346  fRay->SetParameter(1, fRayleighAmpGuess);
347  fRay->SetParLimits(1, 0, fRayleighAmpGuess*2);
348  this->Fit(fRay, "Q0");
349  fChiSquare = fRay->GetChisquare();
350  fNDF = fRay->GetNDF();
351  fRayleighAmplitude = fRay->GetParameter(1);
352  std::cout << __PRETTY_FUNCTION__ << "\t" << fChiSquare << "\t" << fNDF << "\t" << fNumNonEmptyBins << "\t" << std::endl;
353 }
354 
355 
356 
357 void Acclaim::RayleighHist::fitRayleighMinuit(bool forGuiUpdateTF1){
358  theFitParams.at(0) = fRayleighAmpGuess;
359  fMinimizer->SetVariable(0, "amplitude", fRayleighAmpGuess, theFitParamsSteps.at(0));
360  bool successfulMinimization = fMinimizer->Minimize();
361  if(!successfulMinimization){
362  std::cerr << fTitle << " failed the minimization, amplitude = " << theFitParams.at(0)
363  << "\t" << ", intial guess was " << fRayleighAmpGuess << std::endl << std::endl;
364  }
365  fRayleighAmplitude = theFitParams.at(0); // this isn't quite right, but I'm not going to fix it now...
366  // std::cout << fRayleighAmplitude << "\t" << fMinimizer->MinValue() << std::endl;
367  fChiSquare = getRayleighChiSquare(&fRayleighAmplitude);
368  fNDF = fNumNonEmptyBins - 1;
369  if(forGuiUpdateTF1){
370  updateParamsOfTF1();
371  std::cout << __PRETTY_FUNCTION__ << "\t" << fChiSquare << "\t" << fNDF << "\t" << fNumNonEmptyBins << "\t" << std::endl;
372  }
373 }
374 
375 
376 
377 void Acclaim::RayleighHist::fitRayleigh(bool forGuiUpdateTF1){
378 
379  // the internals that need to be updated by the fit
380  fRayleighAmplitude = fRayleighAmpGuess; // sensible initial guess calculated elsewhere
381  fNDF = 0;
382  fChiSquare = 0;
383 
384  if(fitMethod==kAdaptive){
385  fitRayleighAdaptive(forGuiUpdateTF1);
386  }
387  else if(fitMethod==kScan){
388  fitRayleighScan(forGuiUpdateTF1);
389  }
390  else if(fitMethod==kJustEvalGuess){
391  fitRayleighJustEvalGuess(forGuiUpdateTF1);
392  }
393  else if(fitMethod==kTF1){
394  fitRayleighTF1();
395  }
396  else if(fitMethod==kMinuit){
397  fitRayleighMinuit(forGuiUpdateTF1);
398  }
399  else{
400  std::cerr << "Warning in " << __PRETTY_FUNCTION__
401  << ", you asked for an unknown fit method so I'm not doing anything." << std::endl;
402  }
403 }
404 
405 
406 void Acclaim::RayleighHist::updateParamsOfTF1(){
407  if(fRay){
408  if(fParamsTF1.at(0) != fRayleighNorm){
409  fParamsTF1.at(0) = fRayleighNorm;
410  fRay->SetParameter(0, fParamsTF1.at(0));
411  }
412  if(fParamsTF1.at(1) != fRayleighAmplitude){
413  fParamsTF1.at(1) = fRayleighAmplitude;
414  fRay->SetParameter(1, fParamsTF1.at(1));
415  }
416  }
417 }
418 
419 
420 void Acclaim::RayleighHist::Draw(Option_t* opt){
421 
422  TH1D::Draw(opt);
423 
424  if(fRay){
425  updateParamsOfTF1();
426  fRay->Draw("lsame");
427  }
428  if(grLastAddedAmp){
429  grLastAddedAmp->SetLineColor(kMagenta);
430  grLastAddedAmp->Draw("lsame");
431  // std::cout << grLastAddedAmp->GetX()[0] << "\t"
432  // << fRayleighAmplitude << "\t"
433  // << getCDF(grLastAddedAmp->GetX()[0]) << std::endl;
434  // for(int i=0; i <= 10; i++){
435  // std::cout << getCDF(double(i)*fRayleighAmplitude) << std::endl;
436  // }
437  }
438 
439 }
440 
void getRayleighFitParams(double &rayAmp, double &chiSquare, int &ndf)
Output Rayleigh distribution parameters.
virtual bool add(double newAmp)
Input amplitudes events.
STL namespace.
Namespace which wraps everything in the library.
virtual int Fill(double amp, double sign=1)
Fill the histogram, this is called by add(double)
void rebinAndRefill(double meanAmp)
Dynamically rebin and refill histogram with contents of RingBuffer of amplitudes. ...
bool axisRangeOK() const
Checks current axis range is reasonable.