GausFitCache_test.cc
Go to the documentation of this file.
1 /**
2  * @file GausFitCache_test.cc
3  * @brief Test for classes in GausFitCache,h
4  * @author Gianluca Petrillo (petrillo@fnal.gov)
5  * @date March 27th, 2015
6  * @see GausFitCache,h
7  */
8 
9 // define the following symbol to produce plots of what's going on
10 // #define GAUSFITCACHE_TEST_DEBUG 1
11 
12 // C/C++ standard libraries
13 #include <cassert>
14 #include <cmath>
15 #include <array>
16 #include <limits> // std::numeric_limits<>
17 
18 // boost test libraries
19 #define BOOST_TEST_MODULE ( HitAnaAlg_test )
20 #include "boost/test/unit_test.hpp"
21 #include "cetlib/pow.h"
22 
23 // ROOT libraries
24 #include "TH1D.h"
25 #include "TFitResultPtr.h"
26 #include "TFitResult.h"
27 #ifdef GAUSFITCACHE_TEST_DEBUG
28 # include "TCanvas.h"
29 #endif // GAUSFITCACHE_TEST_DEBUG
30 
31 // LArSoft libraries
33 
35 using cet::square;
36 using tolerance_t = decltype(0.001% tolerance());
37 
38 namespace {
39  double gaus(double x, double mean, double sigma, double amplitude) {
40  double const z = (x - mean) / sigma;
41  return amplitude * std::exp(-0.5*square(z));
42  } // gaus()
43 }
44 
45 
46 //******************************************************************************
47 BOOST_AUTO_TEST_SUITE( GausFitCacheSuite )
48 
49 
50 //******************************************************************************
51 // test that the test function behaves like a gaussian
52 BOOST_AUTO_TEST_CASE(TestGaussianTest)
53 {
54  // static function to be tested:
55  const auto gaus_func = ::gaus;
56 
57  const double exp1sigma = std::exp(-0.5*square(1.));
58  const double exp2sigma = std::exp(-0.5*square(2.));
59 
60  // here there should be some more believable test...
61  const double amplitude = 16.;
62  for (double sigma = 0.5; sigma < 2.25; ++sigma) {
63  for (double mean = -5; mean < 5.5; ++mean) {
64 
65  BOOST_TEST(gaus_func(-6.5 * sigma + mean, mean, sigma, amplitude) > 0.);
66  BOOST_TEST(gaus_func(-5.5 * sigma + mean, mean, sigma, amplitude) > 0.);
67  BOOST_TEST(gaus_func(-4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
68 
69  auto const tol = 0.001% tolerance();
70  BOOST_TEST(gaus_func(-2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
71  BOOST_TEST(gaus_func(-1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
72  BOOST_TEST(gaus_func( 0.0 * sigma + mean, mean, sigma, amplitude) == amplitude, tol);
73  BOOST_TEST(gaus_func( 1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
74  BOOST_TEST(gaus_func( 2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
75 
76  BOOST_TEST(gaus_func( 4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
77  BOOST_TEST(gaus_func( 5.5 * sigma + mean, mean, sigma, amplitude) > 0.);
78  BOOST_TEST(gaus_func( 6.5 * sigma + mean, mean, sigma, amplitude) > 0.);
79 
80  } // for mean
81  } // for sigma
82 
83 } // BOOST_AUTO_TEST_CASE(TestGaussianTest)
84 
85 
86 //******************************************************************************
87 // test that the library functions behaves like a gaussians
88 
90  using Func_t = Double_t (*) (Double_t const*, Double_t const*);
91 
93 
94  Double_t operator()
95  (Double_t const x, Double_t mean, Double_t sigma, Double_t amplitude) const
96  {
97  // BUG the double brace syntax is required to work around clang bug 21629
98  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
99  std::array<Double_t, 3> params = {{ amplitude, mean, sigma }};
100  return func(&x, params.data());
101  } // operator()
102 
104 }; // RootGausFuncWrapper()
105 
106 
107 BOOST_AUTO_TEST_CASE(GaussianTest)
108 {
109  // static function to be tested:
110  const auto gaus_func = RootGausFuncWrapper
112 
113  const double exp1sigma = std::exp(-0.5*square(1.));
114  const double exp2sigma = std::exp(-0.5*square(2.));
115 
116  const double amplitude = 16.;
117  for (double sigma = 0.5; sigma < 2.25; ++sigma) {
118  for (double mean = -5; mean < 5.5; ++mean) {
119 
120  BOOST_TEST(gaus_func(-6.5 * sigma + mean, mean, sigma, amplitude) > 0.);
121  BOOST_TEST(gaus_func(-5.5 * sigma + mean, mean, sigma, amplitude) > 0.);
122  BOOST_TEST(gaus_func(-4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
123 
124  auto const tol = 0.001% tolerance();
125  BOOST_TEST(gaus_func(-2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
126  BOOST_TEST(gaus_func(-1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
127  BOOST_TEST(gaus_func( 0.0 * sigma + mean, mean, sigma, amplitude) == amplitude, tol);
128  BOOST_TEST(gaus_func(+1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
129  BOOST_TEST(gaus_func(+2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
130 
131  BOOST_TEST(gaus_func(+4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
132  BOOST_TEST(gaus_func(+5.5 * sigma + mean, mean, sigma, amplitude) > 0.);
133  BOOST_TEST(gaus_func(+6.5 * sigma + mean, mean, sigma, amplitude) > 0.);
134 
135  } // for mean
136  } // for sigma
137 
138 } // BOOST_AUTO_TEST_CASE(GaussianTest)
139 
140 
141 BOOST_AUTO_TEST_CASE(GaussianTrunc5Test)
142 {
143  // static function to be tested:
144  const auto gaus_func = RootGausFuncWrapper
145  (hit::details::CompiledGausFitCacheBaseStruct::gaus_trunc<5>);
146 
147  const double exp1sigma = std::exp(-0.5*square(1.));
148  const double exp2sigma = std::exp(-0.5*square(2.));
149 
150  const double amplitude = 16.;
151  for (double sigma = 0.5; sigma < 2.25; ++sigma) {
152  for (double mean = -5; mean < 5.5; ++mean) {
153 
154  BOOST_TEST(gaus_func(-6.5 * sigma + mean, mean, sigma, amplitude) == 0.);
155  BOOST_TEST(gaus_func(-5.5 * sigma + mean, mean, sigma, amplitude) == 0.);
156  BOOST_TEST(gaus_func(-4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
157 
158  auto const tol = 0.001% tolerance();
159  BOOST_TEST(gaus_func(-2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
160  BOOST_TEST(gaus_func(-1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
161  BOOST_TEST(gaus_func( 0.0 * sigma + mean, mean, sigma, amplitude) == amplitude, tol);
162  BOOST_TEST(gaus_func( 1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
163  BOOST_TEST(gaus_func( 2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
164 
165  BOOST_TEST(gaus_func( 4.5 * sigma + mean, mean, sigma, amplitude) > 0.);
166  BOOST_TEST(gaus_func( 5.5 * sigma + mean, mean, sigma, amplitude) == 0.);
167  BOOST_TEST(gaus_func( 6.5 * sigma + mean, mean, sigma, amplitude) == 0.);
168 
169  } // for mean
170  } // for sigma
171 
172 } // BOOST_AUTO_TEST_CASE(GaussianTrunc5Test)
173 
174 
175 BOOST_AUTO_TEST_CASE(GaussianTrunc4Test)
176 {
177  // static function to be tested:
178  const auto gaus_func = RootGausFuncWrapper
179  (hit::details::CompiledGausFitCacheBaseStruct::gaus_trunc<4>);
180 
181  const double exp1sigma = std::exp(-0.5*square(1.));
182  const double exp2sigma = std::exp(-0.5*square(2.));
183 
184  const double amplitude = 16.;
185  for (double sigma = 0.5; sigma < 2.25; ++sigma) {
186  for (double mean = -5; mean < 5.5; ++mean) {
187 
188  BOOST_TEST(gaus_func(-6.5 * sigma + mean, mean, sigma, amplitude) == 0.);
189  BOOST_TEST(gaus_func(-5.5 * sigma + mean, mean, sigma, amplitude) == 0.);
190  BOOST_TEST(gaus_func(-4.5 * sigma + mean, mean, sigma, amplitude) == 0.);
191 
192  auto const tol = 0.001% tolerance();
193  BOOST_TEST(gaus_func(-2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
194  BOOST_TEST(gaus_func(-1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
195  BOOST_TEST(gaus_func( 0.0 * sigma + mean, mean, sigma, amplitude) == amplitude, tol);
196  BOOST_TEST(gaus_func( 1.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp1sigma, tol);
197  BOOST_TEST(gaus_func( 2.0 * sigma + mean, mean, sigma, amplitude) == amplitude * exp2sigma, tol);
198 
199  BOOST_TEST(gaus_func( 4.5 * sigma + mean, mean, sigma, amplitude) == 0.);
200  BOOST_TEST(gaus_func( 5.5 * sigma + mean, mean, sigma, amplitude) == 0.);
201  BOOST_TEST(gaus_func( 6.5 * sigma + mean, mean, sigma, amplitude) == 0.);
202 
203  } // for mean
204  } // for sigma
205 
206 } // BOOST_AUTO_TEST_CASE(GaussianTrunc4Test)
207 
208 
209 //******************************************************************************
210 // test the multi-gaussian functions
211 
212 /// Expect for each Gaussian ROOT-like parameters: amplitude, mean, sigma
213 Double_t multi_gaus
214  (Double_t x, const unsigned int nGaus, Double_t const* params)
215 {
216 
217  Double_t res = 0.;
218  for (unsigned int iGaus = 0; iGaus < nGaus; ++iGaus) {
219  // our gaus() takes the parameters as: mean, sigma, amplitude
220  res += gaus(x, params[1], params[2], params[0]);
221  params += 3;
222  } // for
223  return res;
224 } // multi_gaus()
225 
226 
227 // Returns the parameters in pFunc sorted to follow the expected ones in Params
228 std::vector<Double_t> SortGaussianResults
229  (TF1 const* pFunc, Double_t const* Params)
230 {
231  assert(pFunc->GetNpar() % 3 == 0); // Sorting non-Gaussian function!
232 
233  const size_t nGaus = pFunc->GetNpar() / 3;
234 
235  std::vector<size_t> BestMatch(nGaus, nGaus); // initialize with invalid number
236  for (size_t iGaus = 0; iGaus < nGaus; ++iGaus) {
237  double best_match_quality = std::numeric_limits<double>::max();
238  for (size_t iFitGaus = 0; iFitGaus < nGaus; ++iFitGaus) {
239  // so far, we compare all the parameters with the same weight
240  const double match_quality
241  = square(Params[iGaus*3 + 0] - pFunc->GetParameter(iFitGaus*3 + 0))
242  + square(Params[iGaus*3 + 1] - pFunc->GetParameter(iFitGaus*3 + 1))
243  + square(Params[iGaus*3 + 2] - pFunc->GetParameter(iFitGaus*3 + 2))
244  ;
245  if (match_quality < best_match_quality) {
246  best_match_quality = match_quality;
247  BestMatch[iGaus] = iFitGaus;
248  } // if this is the best so far
249  } // for fit
250 
251  assert(BestMatch[iGaus] < nGaus); // No reasonable solution matched!
252 
253  for (size_t i = 0; i < iGaus; ++i) {
254  // Same fit Gaussian solution matches multiple solutions
255  assert(BestMatch[i] != BestMatch[iGaus]);
256  } // for i
257 
258  } // for solution
259 
260  std::vector<Double_t> sorted_params(nGaus * 3);
261  for (size_t iGaus = 0; iGaus < nGaus; ++iGaus) {
262  const size_t iBestFit = BestMatch[iGaus];
263  for (size_t i = 0; i < 3; ++i)
264  sorted_params[iGaus*3 + i] = pFunc->GetParameter(iBestFit*3 + i);
265  }
266  return sorted_params;
267 } // SortGaussianResults()
268 
269 
270 // Test a fit with a three-Gaussian function from the compiled cache
272  tolerance_t tol) {
273  std::string name = GausCache.GetName();
274 
275  constexpr size_t nGaus = 3; // we test three gaussians
276 
277  // sorted by increasing mean
278  const Double_t Params[nGaus * 3] = { // amplitude, mean, sigma
279  4.0, -2.0, 1.5,
280  5.0, 0.1, 0.3,
281  2.0, 1.0, 0.5
282  }; // Params[]
283 
284  // fit test
285  // - fill the input histogram
286  TH1D Hist
287  ("H3Gaus", ("Three-Gaussian test - " + name).c_str(), 200, -10., 10.);
288  for (Int_t iBin = 1; iBin <= Hist.GetNbinsX(); ++iBin)
289  Hist.SetBinContent(iBin, multi_gaus(Hist.GetBinCenter(iBin), nGaus, Params));
290 
291 #ifdef GAUSFITCACHE_TEST_DEBUG
292  Hist.Draw();
293 #endif // GAUSFITCACHE_TEST_DEBUG
294 
295  // - get the 3-Gaussian fitting function
296  TF1* pFunc = GausCache.Get(nGaus);
297 
298  // - prepare the function with reasonable initial values
299  const double MeanOffset = -((double(nGaus) - 1.) / 2.);
300  const double MeanStep = 1.;
301  for (size_t iGaus = 0; iGaus < nGaus; ++iGaus) {
302  size_t BaseIndex = iGaus * 3;
303  pFunc->SetParameter(BaseIndex + 0, 1.); // amplitude
304  pFunc->SetParameter(BaseIndex + 1, MeanOffset + iGaus * MeanStep); // mean
305  pFunc->SetParameter(BaseIndex + 2, 1.); // sigma
306  } // for
307 
308  // function range is ignored in the fit, but used when drawing
309  pFunc->SetRange(Hist.GetBinCenter(1), Hist.GetBinCenter(Hist.GetNbinsX()));
310 
311 #ifdef GAUSFITCACHE_TEST_DEBUG
312  TF1* pFCopy = pFunc->DrawCopy("L SAME");
313  pFCopy->SetLineStyle(kDashed);
314  pFCopy->SetLineColor(kCyan);
315 #endif // GAUSFITCACHE_TEST_DEBUG
316 
317  // - fit
318  TFitResultPtr fit_res = Hist.Fit(pFunc, "WQ0NS");
319  BOOST_TEST(int(fit_res) == 0);
320 
321 #ifdef GAUSFITCACHE_TEST_DEBUG
322  pFCopy = pFunc->DrawCopy("L SAME");
323  pFCopy->SetLineWidth(1);
324  pFCopy->SetLineColor(kRed);
325 
326  gPad->SaveAs(("3GausTest_" + name + ".eps").c_str());
327 #endif // GAUSFITCACHE_TEST_DEBUG
328 
329  // - sort the results according to the expected ones
330  std::vector<Double_t> results = SortGaussianResults(pFunc, Params);
331 
332 
333  // - check them
334  for (size_t iGaus = 0; iGaus < nGaus; ++iGaus) {
335  const size_t iParam = iGaus * 3;
336 
337  // - check amplitude
338  BOOST_TEST(results[iParam + 0] == Params[iParam + 0], tol);
339  // - check mean
340  BOOST_TEST(results[iParam + 1] == Params[iParam + 1], tol);
341  // - check sigma
342  BOOST_TEST(results[iParam + 2] == Params[iParam + 2], tol);
343 
344  } // for iGaus
345 
346  BOOST_WARN(pFunc->GetChisquare() < pFunc->GetNDF() / 2.);
347 
348 #ifdef GAUSFITCACHE_TEST_DEBUG
349  pFunc->Print();
350  // always print the fit result
351 #else // !GAUSFITCACHE_TEST_DEBUG
352  // in case of trouble, print the fit result
353  if ((int(fit_res) != 0) || (pFunc->GetChisquare() >= pFunc->GetNDF() / 2.))
354 #endif // GAUSFITCACHE_TEST_DEBUG
355  {
356  fit_res->Print();
357  }
358 
359 } // ThreeGaussianFitTest()
360 
361 
362 
363 // Test that the three-Gaussian function behaves as expected
364 BOOST_AUTO_TEST_CASE(ThreeGaussianTest)
365 {
366 
367  const Double_t Params[] = {
368  4.0, -2.0, 1.5,
369  5.0, 0.1, 0.3,
370  2.0, 1.0, 0.5
371  }; // Params[]
372 
373  // rounding errors on x on the look do not really matter
374  for (double x = -10.; x <= +10.; x += 0.1) {
375  const Double_t expected = multi_gaus(x, 3, Params);
376 
377  const Double_t tested
378  = hit::details::CompiledGausFitCacheBaseStruct::ngaus<3>(&x, Params);
379 
380  BOOST_TEST(tested == expected, 0.001% tolerance());
381  } // for x
382 
383 } // BOOST_AUTO_TEST_CASE(ThreeGaussianTest)
384 
385 
386 // Test a fit with a three-Gaussian function from the run-time generated cache
387 BOOST_AUTO_TEST_CASE(RunTimeThreeGaussianFitTest)
388 {
389  // max 20 Gaussians
390  hit::GausFitCache GausCache("RunTimeGaussians");
391  ThreeGaussianFitTest(GausCache, 0.02% tolerance());
392 } // BOOST_AUTO_TEST_CASE(RunTimeThreeGaussianFitTest)
393 
394 
395 // Test a fit with a three-Gaussian function from the compiled cache
396 BOOST_AUTO_TEST_CASE(CompiledThreeGaussianFitTest)
397 {
398  // max 20 Gaussians
399  hit::CompiledGausFitCache<20> GausCache("CompiledGaussians");
400  ThreeGaussianFitTest(GausCache, 0.02% tolerance());
401 } // BOOST_AUTO_TEST_CASE(CompiledThreeGaussianFitTest)
402 
403 
404 // Test a fit with a three-Gaussian function (each truncated at 5 sigma)
405 // from the compiled cache
406 BOOST_AUTO_TEST_CASE(CompiledTruncated5ThreeGaussianFitTest)
407 {
408  // max 20 Gaussians; truncate at 5 sigma
410  ("CompiledTruncated5Gaussians");
411  ThreeGaussianFitTest(GausCache, 0.1% tolerance()); // 0.1% tolerance
412 } // BOOST_AUTO_TEST_CASE(CompiledTruncated5ThreeGaussianFitTest)
413 
414 
415 // Test a fit with a three-Gaussian function (each truncated at 4 sigma)
416 // from the compiled cache
417 BOOST_AUTO_TEST_CASE(CompiledTruncated4ThreeGaussianFitTest)
418 {
419  // max 20 Gaussians; truncate at 4 sigma
421  ("CompiledTruncated4Gaussians");
422  ThreeGaussianFitTest(GausCache, 0.1% tolerance());
423 } // BOOST_AUTO_TEST_CASE(CompiledTruncated4ThreeGaussianFitTest)
424 
425 // Test a fit with a three-Gaussian function (each truncated at 3 sigma)
426 // from the compiled cache
427 BOOST_AUTO_TEST_CASE(CompiledTruncated3ThreeGaussianFitTest)
428 {
429  // max 20 Gaussians; truncate at 3 sigma
431  ("CompiledTruncated3Gaussians");
432  ThreeGaussianFitTest(GausCache, 10.% tolerance()); // (seriously, it's that bad)
433 } // BOOST_AUTO_TEST_CASE(CompiledTruncated3ThreeGaussianFitTest)
434 
435 
436 BOOST_AUTO_TEST_SUITE_END()
static QCString name
Definition: declinfo.cpp:673
static Double_t gaus(Double_t const *x, Double_t const *params)
Single Gaussian function.
const char expected[]
Definition: Exception_t.cc:22
std::string string
Definition: nybbler.cc:12
auto const tol
Definition: SurfXYZTest.cc:16
auto const tolerance
void ThreeGaussianFitTest(hit::GausFitCache &GausCache, tolerance_t tol)
std::vector< Double_t > SortGaussianResults(TF1 const *pFunc, Double_t const *Params)
constexpr T square(T x)
Definition: pow.h:21
decltype(0.001%tolerance()) tolerance_t
A set of TF1 linear sum of base functions (Gaussians)
Definition: GausFitCache.h:46
A set of TF1 linear sum of Gaussians.
Definition: GausFitCache.h:277
A set of TF1 linear sum of truncated Gaussians.
Definition: GausFitCache.h:310
Double_t(*)(Double_t const *, Double_t const *) Func_t
T gaus(T x, T amplitude, T mean, T sigma)
Tests GausssianFit object with a known input.
virtual TF1 * Get(size_t nFunc)
Returns a function sum of nFunc base functions.
static int max(int a, int b)
Double_t multi_gaus(Double_t x, const unsigned int nGaus, Double_t const *params)
Expect for each Gaussian ROOT-like parameters: amplitude, mean, sigma.
Provide caches for TF1 functions to be used with ROOT fitters.
list x
Definition: train.py:276
std::string GetName() const
Return the name of this cache.
Definition: GausFitCache.h:55
double mean(sqlite3 *db, std::string const &table_name, std::string const &column_name)
Definition: statistics.cc:16
BOOST_AUTO_TEST_CASE(TestGaussianTest)