Commit e7db1c12bd3f4221a337eb9d9bd34092eea7927a

Authored by Jürgen Knödlseder
1 parent c5b2ebf7

Implement a new method to compute the butterfly diagram (#1498).

According to a study by Stephan Fegan done in the context of Fermi/LAT analysis, the best way to determine a butterfly diagram is to draw the envelope of the error ellipse of the model parameters. I implemented this method now in ctbutterfly. I also changed the output file, having now two columns giving as function of energy th minimum and maximum intensities.
doc/source/reference_manual/ctbutterfly.rst
@@ -9,8 +9,15 @@ Computes butterfly. @@ -9,8 +9,15 @@ Computes butterfly.
9 Synopsis 9 Synopsis
10 -------- 10 --------
11 11
12 -Calculates the confidence band of a spectral model, taking into account the  
13 -covariance matrix of a likelihood fit. 12 +Calculates the confidence band of a power law model, taking into account the
  13 +covariance matrix of a likelihood fit. The tool derives the envelope of
  14 +power law models with parameters that lie within the error ellipse. It
  15 +outputs an ASCII file in column separated value (CSV) format containing
  16 +the results. Each row in the result file corresponds to an energy. The
  17 +columns give the energy in MeV, the fitted intensity in ph/cm2/s/MeV,
  18 +and the minimum and maximum intensity that envelope the power law model
  19 +with a given confidence (by default a confidence level of 68% is used, but
  20 +the level can be adjusted using the ``confidence`` parameter).
14 21
15 22
16 General parameters 23 General parameters
@@ -45,7 +52,13 @@ General parameters @@ -45,7 +52,13 @@ General parameters
45 52
46 ``outfile [file]`` 53 ``outfile [file]``
47 Output butterfly ASCII file name. 54 Output butterfly ASCII file name.
  55 +
  56 +``(refit = no) [boolean]``
  57 + Performs refitting of solution ignoring any provided covariance matrix.
48 58
  59 +``(confidence = 0.68) [real]``
  60 + Confidence level for error computation.
  61 +
49 ``(matrix = "NONE") [file]`` 62 ``(matrix = "NONE") [file]``
50 Input covariance matrix file (not used) 63 Input covariance matrix file (not used)
51 64
@@ -89,11 +102,12 @@ Standard parameters @@ -89,11 +102,12 @@ Standard parameters
89 ``(mode = ql) [string]`` 102 ``(mode = ql) [string]``
90 Mode of automatic parameters (default is "ql", i.e. "query and learn"). 103 Mode of automatic parameters (default is "ql", i.e. "query and learn").
91 104
92 -``(logfile = ctskymap.log) [string]`` 105 +``(logfile = ctbutterfly.log) [string]``
93 Name of log file. 106 Name of log file.
94 107
95 108
96 Related tools 109 Related tools
97 ------------- 110 -------------
98 111
99 -None 112 +:ref:`ctulimit`
  113 +:ref:`cterror`
doc/source/reference_manual/cterror.rst
@@ -89,4 +89,5 @@ Standard parameters @@ -89,4 +89,5 @@ Standard parameters
89 Related tools 89 Related tools
90 ------------- 90 -------------
91 91
92 -:ref:`ctulimit`  
93 \ No newline at end of file 92 \ No newline at end of file
  93 +:ref:`ctulimit`
  94 +:ref:`ctbutterfly`
doc/source/reference_manual/ctlike.rst
@@ -12,11 +12,10 @@ Synopsis @@ -12,11 +12,10 @@ Synopsis
12 Determines source model parameters, such as flux, spectral index, source 12 Determines source model parameters, such as flux, spectral index, source
13 position, and source extent from a maximum likelihood analysis of CTA data. 13 position, and source extent from a maximum likelihood analysis of CTA data.
14 The analysis can be done in a binned or an unbinned formulation of the 14 The analysis can be done in a binned or an unbinned formulation of the
15 -log-likelihood function.  
16 -For binned analysis, a counts cube produced by :doc:`ctbin` is required.  
17 -For unbinned analysis, an event list processed by :doc:`ctselect` is required.  
18 -Based on the input file format, ctlike automatically selects between binned  
19 -and unbinned maximum likelihood analysis. 15 +log-likelihood function. For binned analysis, a counts cube produced by
  16 +:doc:`ctbin` is required. For unbinned analysis, an event list processed by
  17 +:doc:`ctselect` is required. Based on the input file format, ctlike
  18 +automatically selects between binned and unbinned maximum likelihood analysis.
20 19
21 20
22 General parameters 21 General parameters
examples/show_butterfly.py
@@ -18,7 +18,6 @@ @@ -18,7 +18,6 @@
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # 19 #
20 # ========================================================================== 20 # ==========================================================================
21 -  
22 import gammalib 21 import gammalib
23 import sys 22 import sys
24 try: 23 try:
@@ -26,32 +25,34 @@ try: @@ -26,32 +25,34 @@ try:
26 except: 25 except:
27 sys.exit("This script needs matplotlib") 26 sys.exit("This script needs matplotlib")
28 27
29 -# =============== #  
30 -# Script entry #  
31 -# =============== #  
32 -if __name__ == "__main__":  
33 28
34 - # check for given butterfly file 29 +# ============ #
  30 +# Script entry #
  31 +# ============ #
  32 +if __name__ == "__main__":
  33 + """
  34 + """
  35 + # Check for given butterfly file
35 if not len(sys.argv) == 2: 36 if not len(sys.argv) == 2:
36 sys.exit("Usage: show_butterfly.py butterfly.txt") 37 sys.exit("Usage: show_butterfly.py butterfly.txt")
37 38
38 # Read given butterfly file 39 # Read given butterfly file
39 filename = sys.argv[1] 40 filename = sys.argv[1]
40 - csv = gammalib.GCsv(filename) 41 + csv = gammalib.GCsv(filename)
41 42
42 - # initialise arrays to be filled 43 + # Initialise arrays to be filled
43 butterfly_x = [] 44 butterfly_x = []
44 butterfly_y = [] 45 butterfly_y = []
  46 + line_x = []
  47 + line_y = []
45 48
46 - line_x = []  
47 - line_y = []  
48 -  
49 - # loop over rows of the file 49 + # Loop over rows of the file
50 nrows = csv.nrows() 50 nrows = csv.nrows()
51 for row in range(nrows): 51 for row in range(nrows):
  52 +
52 # Compute upper edge of confidence band 53 # Compute upper edge of confidence band
53 butterfly_x.append(csv.real(row,0)) 54 butterfly_x.append(csv.real(row,0))
54 - butterfly_y.append(csv.real(row,1)+csv.real(row,2)) 55 + butterfly_y.append(csv.real(row,2))
55 56
56 # Set line values 57 # Set line values
57 line_x.append(csv.real(row,0)) 58 line_x.append(csv.real(row,0))
@@ -61,21 +62,19 @@ if __name__ == &quot;__main__&quot;: @@ -61,21 +62,19 @@ if __name__ == &quot;__main__&quot;:
61 # of the confidence band 62 # of the confidence band
62 for row in range(nrows): 63 for row in range(nrows):
63 index = nrows - 1 - row 64 index = nrows - 1 - row
64 -  
65 butterfly_x.append(csv.real(index,0)) 65 butterfly_x.append(csv.real(index,0))
66 - low_error = csv.real(index,1)-csv.real(index,2) 66 + low_error = csv.real(index,3)
67 if low_error < 1e-26: 67 if low_error < 1e-26:
68 low_error = 1e-26 68 low_error = 1e-26
69 butterfly_y.append(low_error) 69 butterfly_y.append(low_error)
70 70
71 - # plot the butterfly and spectral line 71 + # Plot the butterfly and spectral line
72 plt.figure() 72 plt.figure()
73 plt.ylim([1e-26,1e-14]) 73 plt.ylim([1e-26,1e-14])
74 plt.loglog() 74 plt.loglog()
75 plt.grid() 75 plt.grid()
76 plt.plot(line_x,line_y,color='black',ls='-') 76 plt.plot(line_x,line_y,color='black',ls='-')
77 plt.fill(butterfly_x,butterfly_y,color='green',alpha=0.5) 77 plt.fill(butterfly_x,butterfly_y,color='green',alpha=0.5)
78 -  
79 - plt.xlabel("Energy [MeV]")  
80 - plt.ylabel(r"dN/dE [MeV$^{-1}$ s$^{-1}$ cm$^{-2}$]") 78 + plt.xlabel("Energy (MeV)")
  79 + plt.ylabel(r"dN/dE (MeV$^{-1}$ s$^{-1}$ cm$^{-2}$)")
81 plt.show() 80 plt.show()
src/ctbutterfly/ctbutterfly.cpp
@@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
36 /* __ Method name definitions ____________________________________________ */ 36 /* __ Method name definitions ____________________________________________ */
37 #define G_GET_PARAMETERS "ctbutterfly::get_parameters()" 37 #define G_GET_PARAMETERS "ctbutterfly::get_parameters()"
38 #define G_SAVE "ctbutterfly::save()" 38 #define G_SAVE "ctbutterfly::save()"
39 -#define G_GET_MODEL_PARAMETER "ctbutterfly::get_model_parameter()" 39 +#define G_CHECK_MODEL "ctbutterfly::check_model()"
40 40
41 /* __ Debug definitions __________________________________________________ */ 41 /* __ Debug definitions __________________________________________________ */
42 42
@@ -241,11 +241,40 @@ void ctbutterfly::run(void) @@ -241,11 +241,40 @@ void ctbutterfly::run(void)
241 log << m_obs << std::endl; 241 log << m_obs << std::endl;
242 } 242 }
243 243
  244 + // If refit is selected then do maximum likelihood fit
  245 + if (m_refit) {
  246 +
  247 + // Write header
  248 + if (logTerse()) {
  249 + log << std::endl;
  250 + log.header1("Compute best-fit likelihood");
  251 + }
  252 +
  253 + // Optimize and compute errors
  254 + m_obs.optimize(m_opt);
  255 + m_obs.errors(m_opt);
  256 +
  257 + // Get covariance matrix by inverting the curvature matrix. Note that
  258 + // the curvature matrix is for the unscaled parameters, hence we have
  259 + // to scale them before we can proceed.
  260 + GObservations::likelihood likelihood = m_obs.function();
  261 + m_covariance = likelihood.curvature()->invert();
  262 +
  263 + // Write optimised model and covariance matrix into logger
  264 + if (logTerse()) {
  265 + log << m_opt << std::endl;
  266 + log << m_obs.models() << std::endl;
  267 + log << m_covariance << std::endl;
  268 + }
  269 +
  270 + } // endif: refit selected
  271 +
244 // Get model instance for further computations 272 // Get model instance for further computations
245 GModels models = m_obs.models(); 273 GModels models = m_obs.models();
246 274
247 - // Compute covariance matrix if necessary  
248 - if (m_covariance.size() == 0) { 275 + // If no refit is selected and no covariance matrix has been specified
  276 + // then compute now the covariance matrix
  277 + if (!m_refit && m_covariance.size() == 0) {
249 278
250 // Write header 279 // Write header
251 if (logTerse()) { 280 if (logTerse()) {
@@ -258,101 +287,195 @@ void ctbutterfly::run(void) @@ -258,101 +287,195 @@ void ctbutterfly::run(void)
258 GObservations::likelihood likelihood = m_obs.function(); 287 GObservations::likelihood likelihood = m_obs.function();
259 likelihood.eval(pars); 288 likelihood.eval(pars);
260 289
261 - // Get covariance matrix by inverting the curvature matrix 290 + // Get covariance matrix by inverting the curvature matrix. Note that
  291 + // the curvature matrix is for the unscaled parameters, hence we have
  292 + // to scale them before we can proceed.
262 m_covariance = likelihood.curvature()->invert(); 293 m_covariance = likelihood.curvature()->invert();
263 294
264 - } 295 + // Log covariance matrix
  296 + if (logTerse()) {
  297 + log << m_covariance << std::endl;
  298 + }
  299 +
  300 + } // endif: covariance computation needed
265 301
266 // Write header 302 // Write header
267 if (logTerse()) { 303 if (logTerse()) {
268 log << std::endl; 304 log << std::endl;
269 - log.header1("Generate butterfly"); 305 + log.header1("Prepare butterfly computation");
  306 + }
  307 +
  308 + // Find parameter indices. We do this by the memory location of the
  309 + // model parameters.
  310 + GModelSky* skymodel = dynamic_cast<GModelSky*>(models[m_srcname]);
  311 + GModelPar* par_prefactor = &(*skymodel)["Prefactor"];
  312 + GModelPar* par_index = &(*skymodel)["Index"];
  313 + int inx_prefactor = -1;
  314 + int inx_index = -1;
  315 + for (int i = 0; i < models.npars(); ++i) {
  316 + if (models.pars()[i] == par_prefactor) {
  317 + inx_prefactor = i;
  318 + }
  319 + if (models.pars()[i] == par_index) {
  320 + inx_index = i;
  321 + }
270 } 322 }
271 323
272 - // Initialise dummy time to evaluate spectral model  
273 - GTime time = GTime(); 324 + // Check indices
274 325
275 - // Initialise vector of gradients  
276 - GVector grad = GVector(m_obs.models().npars()); 326 + // Store model values and scales
  327 + double prefactor_mean = (*skymodel)["Prefactor"].value();
  328 + double index_mean = (*skymodel)["Index"].value();
  329 + double pivot_mean = (*skymodel)["PivotEnergy"].value();
  330 + double prefactor_scale = (*skymodel)["Prefactor"].scale();
  331 + double index_scale = (*skymodel)["Index"].scale();
  332 + GEnergy pivot(pivot_mean, "MeV");
277 333
278 - // Loop over energy bins positions  
279 - for (int i = 0; i < m_ebounds.size(); ++i) { 334 + // Show parameter indices
  335 + if (logTerse()) {
  336 + log << gammalib::parformat("Prefactor");
  337 + log << prefactor_mean << " ph/cm2/s/MeV (";
  338 + log << inx_prefactor << ")" << std::endl;
  339 + log << gammalib::parformat("Index");
  340 + log << index_mean << " (" << inx_index << ")" << std::endl;
  341 + log << gammalib::parformat("Pivot energy");
  342 + log << pivot << std::endl;
  343 + }
280 344
281 - // Initialise number of gradients  
282 - int num_gradient = 0; 345 + // Extract covariance elements
  346 + double cov_pp = m_covariance(inx_prefactor,inx_prefactor);
  347 + double cov_pg = m_covariance(inx_prefactor,inx_index);
  348 + double cov_gg = m_covariance(inx_index,inx_index);
283 349
284 - // Get the energy of current bin  
285 - GEnergy energy = m_ebounds.elogmean(i); 350 + // Initialise min and max intensities
  351 + m_min_intensities.assign(m_ebounds.size(), 1e30);
  352 + m_max_intensities.assign(m_ebounds.size(), 0.0);
286 353
287 - // Header for verbose logging  
288 - if (logExplicit()) {  
289 - std::string msg = "Computing butterfly for bin number "+  
290 - gammalib::str(i)+" at "+energy.print();  
291 - log << std::endl;  
292 - log.header2(msg);  
293 - } 354 + // Compute eigenvectors and eigenvalues
  355 + double lambda1;
  356 + double lambda2;
  357 + GVector vector1(2);
  358 + GVector vector2(2);
  359 + eigenvectors(cov_pp, cov_pg, cov_pg, cov_gg,
  360 + &lambda1, &lambda2, &vector1, &vector2);
294 361
295 - // Initialise flux value  
296 - double model_flux = 0.0; 362 + // Show eigenvectors and eigenvalues
  363 + if (logTerse()) {
  364 + log << gammalib::parformat("Eigenvalue 1") << lambda1 << std::endl;
  365 + log << gammalib::parformat("Eigenvalue 2") << lambda2 << std::endl;
  366 + log << gammalib::parformat("Eigenvector 1") << vector1 << std::endl;
  367 + log << gammalib::parformat("Eigenvector 2") << vector2 << std::endl;
  368 + }
297 369
298 - // Loop over models  
299 - for (int j = 0; j < models.size(); ++j) { 370 + // Confidence scaling
  371 + double sigma = gammalib::erfinv(m_confidence) * gammalib::sqrt_two;
  372 + double scale = (sigma*sigma);
300 373
301 - // If source name is found, retrieve the nominal flux  
302 - if (models[j]->name() == m_srcname) { 374 + // Show confidence stuff
  375 + if (logTerse()) {
  376 + log << gammalib::parformat("Confidence level");
  377 + log << m_confidence << std::endl;
  378 + log << gammalib::parformat("Corresponding scaling");
  379 + log << scale << std::endl;
  380 + }
303 381
304 - // Note that we have to compute the gradients here  
305 - // repetively to update the gradients in the vector  
306 - // No check for valid pointers necessary here as this  
307 - // was covered by get_model_parameter()  
308 - GModelSky* skymodel = dynamic_cast<GModelSky*>(models[j]);  
309 - GModelSpectral* spectral = skymodel->spectral();  
310 - model_flux = spectral->eval_gradients(energy, time); 382 + // Compute minor and major axes of error ellipse
  383 + double major = scale*std::sqrt(lambda1);
  384 + double minor = scale*std::sqrt(lambda2);
311 385
312 - } 386 + // Write header
  387 + if (logTerse()) {
  388 + log << std::endl;
  389 + log.header1("Generate butterfly");
  390 + }
313 391
314 - // Loop over model parameters  
315 - for (int i = 0; i < models[j]->size(); ++i) { 392 + // Walk around the error ellipse
  393 + int nt = 360;
  394 + double dt = gammalib::twopi/double(nt);
  395 + double t = 0.0;
  396 + for (int i = 0; i < nt; ++i, t += dt) {
  397 +
  398 + // Pre-compute sin and cosine
  399 + double sin_t = std::sin(t);
  400 + double cos_t = std::cos(t);
  401 +
  402 + // Compute prefactor and index
  403 + double prefactor = (major * cos_t * vector1[0] +
  404 + minor * sin_t * vector2[0]) * prefactor_scale +
  405 + prefactor_mean;
  406 + double index = (major * cos_t * vector1[1] +
  407 + minor * sin_t * vector2[1]) * index_scale +
  408 + index_mean;
  409 +
  410 + // Show prefactor and index
  411 + if (logTerse()) {
  412 + log << gammalib::parformat("Angle "+gammalib::str(t*gammalib::rad2deg));
  413 + log << prefactor << " ph/cm2/s/MeV; ";
  414 + log << index << std::endl;
  415 + }
316 416
317 - // Get model parameter  
318 - GModelPar parameter = models[j]->at(i); 417 + // Setup power law model
  418 + GModelSpectralPlaw plaw(prefactor, index, pivot);
319 419
320 - // Set gradient value to the vector  
321 - grad[num_gradient] = parameter.gradient(); 420 + // Evaluate power law at each energy and extract minimum and
  421 + // maximum intensity
  422 + for (int k = 0; k < m_ebounds.size(); ++k) {
322 423
323 - // increment vector index  
324 - num_gradient++; 424 + // Get the energy of current bin
  425 + GTime time;
  426 + GEnergy energy = m_ebounds.elogmean(k);
325 427
326 - } // endfor: Looped over model parameters 428 + // Evaluate power law
  429 + double intensity = plaw.eval(energy, time);
  430 +
  431 + // Get extremes
  432 + if (intensity < m_min_intensities[k]) {
  433 + m_min_intensities[k] = intensity;
  434 + }
  435 + if (intensity > m_max_intensities[k]) {
  436 + m_max_intensities[k] = intensity;
  437 + }
  438 +
  439 + } // endfor: looped over all energies
  440 +
  441 + } // endfor: looped over all angles
  442 +
  443 + // Write header
  444 + if (logTerse()) {
  445 + log << std::endl;
  446 + log.header1("Results");
  447 + }
327 448
328 - } // endfor: Looped over models 449 + // Setup power law model
  450 + GModelSpectralPlaw plaw(prefactor_mean, index_mean, pivot);
329 451
330 - // Multiply covariance to the gradient vector  
331 - GVector vector = m_covariance * grad; 452 + // Show results
  453 + for (int k = 0; k < m_ebounds.size(); ++k) {
332 454
333 - // Get the error from the scalar product  
334 - double error = std::sqrt(grad * vector); 455 + // Get the energy of current bin
  456 + GTime time;
  457 + GEnergy energy = m_ebounds.elogmean(k);
335 458
336 - // Multiply in scale of spectral parameter  
337 - error *= m_par_scale; 459 + // Evaluate power law
  460 + double intensity = plaw.eval(energy, time);
338 461
339 - // Store flux, value and energy for saving  
340 - m_fluxes.push_back(model_flux); 462 + // Store results
341 m_energies.push_back(energy.MeV()); 463 m_energies.push_back(energy.MeV());
342 - m_errors.push_back(error); 464 + m_intensities.push_back(intensity);
343 465
344 // Log information 466 // Log information
345 - if (logExplicit()) {  
346 - log << " Flux at ";  
347 - log << energy.print();  
348 - log << ": ";  
349 - log << model_flux;  
350 - log << " +- ";  
351 - log << error;  
352 - log << " ph/cm2/s/MeV"; 467 + if (logTerse()) {
  468 + log << gammalib::parformat("Intensity at "+energy.print());
  469 + log << intensity;
  470 + log << " (";
  471 + log << m_min_intensities[k];
  472 + log << ", ";
  473 + log << m_max_intensities[k];
  474 + log << ") ph/cm2/s/MeV";
  475 + log << std::endl;
353 } 476 }
354 477
355 - } //endfor: loop over energy bin 478 + } // endfor
356 479
357 // Restore energy dispersion flag for all CTA observations 480 // Restore energy dispersion flag for all CTA observations
358 for (int i = 0; i < m_obs.size(); ++i) { 481 for (int i = 0; i < m_obs.size(); ++i) {
@@ -384,14 +507,15 @@ void ctbutterfly::save(void) @@ -384,14 +507,15 @@ void ctbutterfly::save(void)
384 // Get output filename 507 // Get output filename
385 m_outfile = (*this)["outfile"].filename(); 508 m_outfile = (*this)["outfile"].filename();
386 509
387 - // Create CSV table with 3 columns  
388 - GCsv table(m_fluxes.size(), 3); 510 + // Create CSV table with 4 columns
  511 + GCsv table(m_energies.size(), 4);
389 512
390 // Fill CSV table 513 // Fill CSV table
391 - for (int i = 0; i < m_fluxes.size(); ++i) { 514 + for (int i = 0; i < m_energies.size(); ++i) {
392 table.real(i, 0, m_energies[i]); 515 table.real(i, 0, m_energies[i]);
393 - table.real(i, 1, m_fluxes[i]);  
394 - table.real(i, 2, m_errors[i]); 516 + table.real(i, 1, m_intensities[i]);
  517 + table.real(i, 2, m_min_intensities[i]);
  518 + table.real(i, 3, m_max_intensities[i]);
395 } 519 }
396 520
397 // Save CSV table 521 // Save CSV table
@@ -415,18 +539,25 @@ void ctbutterfly::init_members(void) @@ -415,18 +539,25 @@ void ctbutterfly::init_members(void)
415 { 539 {
416 // Initialise members 540 // Initialise members
417 m_srcname.clear(); 541 m_srcname.clear();
418 - m_outfile.clear();  
419 - m_ebounds.clear(); 542 + m_confidence = 0.68;
  543 + m_max_iter = 50;
420 m_apply_edisp = false; 544 m_apply_edisp = false;
  545 + m_refit = false;
  546 + m_ebounds.clear();
  547 + m_outfile.clear();
421 548
422 // Initialise protected members 549 // Initialise protected members
423 - m_skymodel = NULL;  
424 - m_par_scale = 0.0; 550 + m_opt.clear();
425 m_obs.clear(); 551 m_obs.clear();
426 m_covariance.clear(); 552 m_covariance.clear();
427 m_energies.clear(); 553 m_energies.clear();
428 - m_fluxes.clear();  
429 - m_errors.clear(); 554 + m_intensities.clear();
  555 + m_min_intensities.clear();
  556 + m_max_intensities.clear();
  557 +
  558 + // Set optimizer parameters
  559 + m_opt.max_iter(m_max_iter);
  560 + m_opt.max_stalls(10);
430 561
431 // Return 562 // Return
432 return; 563 return;
@@ -441,20 +572,22 @@ void ctbutterfly::init_members(void) @@ -441,20 +572,22 @@ void ctbutterfly::init_members(void)
441 void ctbutterfly::copy_members(const ctbutterfly& app) 572 void ctbutterfly::copy_members(const ctbutterfly& app)
442 { 573 {
443 // Copy attributes 574 // Copy attributes
444 - m_srcname = app.m_srcname;  
445 - m_outfile = app.m_outfile;  
446 - m_ebounds = app.m_ebounds; 575 + m_srcname = app.m_srcname;
  576 + m_confidence = app.m_confidence;
  577 + m_max_iter = app.m_max_iter;
447 m_apply_edisp = app.m_apply_edisp; 578 m_apply_edisp = app.m_apply_edisp;
448 - 579 + m_refit = app.m_refit;
  580 + m_ebounds = app.m_ebounds;
  581 + m_outfile = app.m_outfile;
  582 +
449 // Copy protected members 583 // Copy protected members
450 - m_obs = app.m_obs;  
451 - m_covariance = app.m_covariance;  
452 - m_energies = app.m_energies;  
453 - m_fluxes = app.m_fluxes;  
454 - m_errors = app.m_errors;  
455 -  
456 - // Extract model parameter  
457 - get_model_parameter(); 584 + m_obs = app.m_obs;
  585 + m_opt = app.m_opt;
  586 + m_covariance = app.m_covariance;
  587 + m_energies = app.m_energies;
  588 + m_intensities = app.m_intensities;
  589 + m_min_intensities = app.m_min_intensities;
  590 + m_max_intensities = app.m_max_intensities;
458 591
459 // Return 592 // Return
460 return; 593 return;
@@ -513,15 +646,9 @@ void ctbutterfly::get_parameters(void) @@ -513,15 +646,9 @@ void ctbutterfly::get_parameters(void)
513 646
514 // Get name of test source and check container for this name 647 // Get name of test source and check container for this name
515 m_srcname = (*this)["srcname"].string(); 648 m_srcname = (*this)["srcname"].string();
516 - if (!m_obs.models().contains(m_srcname)) {  
517 - std::string msg = "Source \""+m_srcname+"\" not found in model "  
518 - "container. Please add a source with that name "  
519 - "or check for possible typos.";  
520 - throw GException::invalid_value(G_GET_PARAMETERS, msg);  
521 - }  
522 649
523 - // Get relevant model and parameter for model computation  
524 - get_model_parameter(); 650 + // Check model name and type
  651 + check_model();
525 652
526 // Create energy boundaries from user parameters 653 // Create energy boundaries from user parameters
527 m_ebounds = create_ebounds(); 654 m_ebounds = create_ebounds();
@@ -539,6 +666,10 @@ void ctbutterfly::get_parameters(void) @@ -539,6 +666,10 @@ void ctbutterfly::get_parameters(void)
539 // Read energy dispersion flag 666 // Read energy dispersion flag
540 m_apply_edisp = (*this)["edisp"].boolean(); 667 m_apply_edisp = (*this)["edisp"].boolean();
541 668
  669 + // Read other parameters
  670 + m_confidence = (*this)["confidence"].real();
  671 + m_refit = (*this)["refit"].boolean();
  672 +
542 // Optionally read ahead parameters so that they get correctly 673 // Optionally read ahead parameters so that they get correctly
543 // dumped into the log file 674 // dumped into the log file
544 if (read_ahead()) { 675 if (read_ahead()) {
@@ -551,62 +682,114 @@ void ctbutterfly::get_parameters(void) @@ -551,62 +682,114 @@ void ctbutterfly::get_parameters(void)
551 682
552 683
553 /***********************************************************************//** 684 /***********************************************************************//**
554 - * @brief Get application parameters 685 + * @brief Check if sky model is valid
555 * 686 *
556 * @exception GException::invalid_value 687 * @exception GException::invalid_value
557 - * Did not find a valid model parameter 688 + * Did not find a valid model
558 * 689 *
559 - * Extracts a pointer to the sky model (m_skymodel) and a pointer to the  
560 - * relevant model parameter (m_model_par) from the model container. 690 + * Checks whether the specified sky model exists and whether it is a power
  691 + * law.
561 ***************************************************************************/ 692 ***************************************************************************/
562 -void ctbutterfly::get_model_parameter(void) 693 +void ctbutterfly::check_model(void)
563 { 694 {
564 // Continue only if source model exists 695 // Continue only if source model exists
565 if (m_obs.models().contains(m_srcname)) { 696 if (m_obs.models().contains(m_srcname)) {
566 697
567 // Get relevant model and parameter for upper limit computation. 698 // Get relevant model and parameter for upper limit computation.
568 - GModels& models = const_cast<GModels&>(m_obs.models());  
569 - m_skymodel = dynamic_cast<GModelSky*>(models[m_srcname]);  
570 - if (m_skymodel == NULL) { 699 + GModels& models = const_cast<GModels&>(m_obs.models());
  700 + GModelSky* model = dynamic_cast<GModelSky*>(models[m_srcname]);
  701 + if (model == NULL) {
571 std::string msg = "Source \""+m_srcname+"\" is not a sky model. " 702 std::string msg = "Source \""+m_srcname+"\" is not a sky model. "
572 "Please specify the name of a sky model for " 703 "Please specify the name of a sky model for "
573 "butterfly computation."; 704 "butterfly computation.";
574 - throw GException::invalid_value(G_GET_MODEL_PARAMETER, msg); 705 + throw GException::invalid_value(G_CHECK_MODEL, msg);
575 } 706 }
576 707
577 - GModelPar* modelpar;  
578 -  
579 - if (m_skymodel->spectral()->type() == "NodeFunction") {  
580 - std::string msg = "\"NodeFunction\" cannot be used as spectral "  
581 - "model for an butterfly computation. "  
582 - "Please specify another spectral model.";  
583 - throw GException::invalid_value(G_GET_MODEL_PARAMETER, msg);  
584 - }  
585 - if (m_skymodel->spectral()->has_par("Normalization")) {  
586 - modelpar = &(m_skymodel->spectral()->operator[]("Normalization"));  
587 - }  
588 - else if (m_skymodel->spectral()->has_par("Prefactor")) {  
589 - modelpar = &(m_skymodel->spectral()->operator[]("Prefactor"));  
590 - }  
591 - else if (m_skymodel->spectral()->has_par("Integral")) {  
592 - modelpar = &(m_skymodel->spectral()->operator[]("Integral"));  
593 - }  
594 - else if (m_skymodel->spectral()->has_par("Value")) {  
595 - modelpar = &(m_skymodel->spectral()->operator[]("Value"));  
596 - }  
597 - else {  
598 - std::string msg = "Require spectral parameter \"Normalization\", "  
599 - "\"Prefactor\", \"Integral\" or \"Value\" for "  
600 - "butterfly computation. The specified source "  
601 - "\""+m_srcname+"\" does not have such a parameter.";  
602 - throw GException::invalid_value(G_GET_MODEL_PARAMETER, msg); 708 + // Check that the spectral model is a power law
  709 + if (model->spectral()->type() != "PowerLaw") {
  710 + std::string msg = "\""+model->spectral()->type()+"\" cannot be "
  711 + "used as spectral model for an butterfly "
  712 + "computation. Please specify a power law model.";
  713 + throw GException::invalid_value(G_CHECK_MODEL, msg);
603 } 714 }
604 715
605 - // Set scale value from parameter  
606 - m_par_scale = modelpar->scale();  
607 -  
608 } // endif: source model existed 716 } // endif: source model existed
609 717
  718 + // Otherwise throw an exception
  719 + else {
  720 + std::string msg = "Source \""+m_srcname+"\" not found in model "
  721 + "container. Please add a source with that name "
  722 + "or check for possible typos.";
  723 + throw GException::invalid_value(G_CHECK_MODEL, msg);
  724 + }
  725 +
  726 + // Return
  727 + return;
  728 +}
  729 +
  730 +
  731 +/***********************************************************************//**
  732 + * @brief Compute normalized eigenvectors and eigenvalues
  733 + *
  734 + * @param[in] a Covariance matrix element (0,0)
  735 + * @param[in] b Covariance matrix element (0,1)
  736 + * @param[in] c Covariance matrix element (1,0)
  737 + * @param[in] d Covariance matrix element (1,1)
  738 + * @param[out] lambda1 First eigenavlue
  739 + * @param[out] lambda2 Second eigenavlue
  740 + * @param[out] vector1 First eigenvector (normalized)
  741 + * @param[out] vector2 Second eigenvector (normalized)
  742 + *
  743 + * Computes the eigenvalues and eigenvectors for a 2x2 matrix. See
  744 + * http://www.math.harvard.edu/archive/21b_fall_04/exhibits/2dmatrices/index.html
  745 + ***************************************************************************/
  746 +void ctbutterfly::eigenvectors(const double& a,
  747 + const double& b,
  748 + const double& c,
  749 + const double& d,
  750 + double* lambda1,
  751 + double* lambda2,
  752 + GVector* vector1,
  753 + GVector* vector2)
  754 +{
  755 + // Compute trace and determinant
  756 + double trace = a + d;
  757 + double det = a*d - b*c;
  758 +
  759 + // Compute eigenvalues
  760 + double arg1 = 0.5*trace;
  761 + double arg2 = std::sqrt(0.25*trace*trace - det);
  762 + *lambda1 = arg1 + arg2;
  763 + *lambda2 = arg1 - arg2;
  764 +
  765 + // Compute eigenvectors
  766 + double vector1_1;
  767 + double vector1_2;
  768 + double vector2_1;
  769 + double vector2_2;
  770 + if (c != 0.0) {
  771 + (*vector1)[0] = *lambda1 - det;
  772 + (*vector1)[1] = c;
  773 + (*vector2)[0] = *lambda2 - det;
  774 + (*vector2)[1] = c;
  775 + }
  776 + else if (b != 0.0) {
  777 + (*vector1)[0] = b;
  778 + (*vector1)[1] = *lambda1 - a;
  779 + (*vector2)[0] = b;
  780 + (*vector2)[1] = *lambda2 - a;
  781 + }
  782 + else {
  783 + (*vector1)[0] = 1.0;
  784 + (*vector1)[1] = 0.0;
  785 + (*vector2)[0] = 0.0;
  786 + (*vector2)[1] = 1.0;
  787 + }
  788 +
  789 + // Normalize eigenvectors
  790 + *vector1 /= norm(*vector1);
  791 + *vector2 /= norm(*vector2);
  792 +
610 // Return 793 // Return
611 return; 794 return;
612 } 795 }
src/ctbutterfly/ctbutterfly.hpp
@@ -78,22 +78,33 @@ protected: @@ -78,22 +78,33 @@ protected:
78 void copy_members(const ctbutterfly& app); 78 void copy_members(const ctbutterfly& app);
79 void free_members(void); 79 void free_members(void);
80 void get_parameters(void); 80 void get_parameters(void);
81 - void get_model_parameter(void); 81 + void check_model(void);
  82 + void eigenvectors(const double& a,
  83 + const double& b,
  84 + const double& c,
  85 + const double& d,
  86 + double* lambda1,
  87 + double* lambda2,
  88 + GVector* vector1,
  89 + GVector* vector2);
82 90
83 // User parameters 91 // User parameters
84 - std::string m_srcname; //!< Name of source to compute butterfly  
85 - std::string m_outfile; //!< Output ascii file  
86 - GEbounds m_ebounds; //!< Energy binning definition  
87 - bool m_apply_edisp; //!< Apply energy dispersion? 92 + std::string m_srcname; //!< Name of source to compute butterfly
  93 + double m_confidence; //!< Confidence level
  94 + int m_max_iter; //!< Maximum number of iterations
  95 + bool m_apply_edisp; //!< Apply energy dispersion?
  96 + bool m_refit; //!< Do refit?
  97 + GEbounds m_ebounds; //!< Energy binning definition
  98 + std::string m_outfile; //!< Output ascii file
88 99
89 // Protected members 100 // Protected members
90 - GObservations m_obs; //!< Observation container  
91 - GMatrixSparse m_covariance; //!< Covariance matrix  
92 - GModelSky* m_skymodel; //!< Pointer to sky model  
93 - double m_par_scale; //!< Scale of normalising parameter  
94 - std::vector<double> m_energies; //!< Energy values for storage  
95 - std::vector<double> m_fluxes; //!< Flux values per energy bin  
96 - std::vector<double> m_errors; //!< Flux errors per energy bin 101 + GOptimizerLM m_opt; //!< Optimizer
  102 + GObservations m_obs; //!< Observation container
  103 + GMatrixSparse m_covariance; //!< Covariance matrix
  104 + std::vector<double> m_energies; //!< Energy values
  105 + std::vector<double> m_intensities; //!< Power law intensity
  106 + std::vector<double> m_min_intensities; //!< Minimum intensities
  107 + std::vector<double> m_max_intensities; //!< Maximum intensities
97 }; 108 };
98 109
99 /***********************************************************************//** 110 /***********************************************************************//**
src/ctbutterfly/ctbutterfly.par
@@ -17,17 +17,19 @@ @@ -17,17 +17,19 @@
17 # 17 #
18 # General parameters 18 # General parameters
19 #=================== 19 #===================
20 -inobs, f, a, "events.fits",,,"Input event list, counts cube or observation definition file"  
21 -inmodel, f, a, "$CTOOLS/share/models/crab.xml",,, "Source model"  
22 -srcname, s, a, "Crab",,,"Source of interest"  
23 -expcube, f, a, "NONE",,, "Exposure cube file (only needed for stacked analysis)"  
24 -psfcube, f, a, "NONE",,, "PSF cube file (only needed for stacked analysis)"  
25 -bkgcube, f, a, "NONE",,, "Background cube file (only needed for stacked analysis)"  
26 -caldb, s, a, "prod2",,, "Calibration database"  
27 -irf, s, a, "South_50h",,, "Instrument response function"  
28 -edisp, b, h, no,,, "Apply energy dispersion?"  
29 -outfile, f, a, "butterfly.txt",,, "Output ascii file"  
30 -matrix, f, h, "NONE",,, "Input covariance Matrix FITS file" 20 +inobs, f, a, "events.fits",,,"Input event list, counts cube or observation definition file"
  21 +inmodel, f, a, "$CTOOLS/share/models/crab.xml",,, "Source model"
  22 +srcname, s, a, "Crab",,,"Source of interest"
  23 +expcube, f, a, "NONE",,, "Exposure cube file (only needed for stacked analysis)"
  24 +psfcube, f, a, "NONE",,, "PSF cube file (only needed for stacked analysis)"
  25 +bkgcube, f, a, "NONE",,, "Background cube file (only needed for stacked analysis)"
  26 +caldb, s, a, "prod2",,, "Calibration database"
  27 +irf, s, a, "South_50h",,, "Instrument response function"
  28 +edisp, b, h, no,,, "Apply energy dispersion?"
  29 +outfile, f, a, "butterfly.txt",,, "Output ascii file"
  30 +refit, b, h, no,,, "Do refitting?"
  31 +confidence, r, h, 0.68,0.0,1.0, "Confidence level"
  32 +matrix, f, h, "NONE",,, "Input covariance Matrix FITS file"
31 33
32 # 34 #
33 # Energy binning parameters 35 # Energy binning parameters
src/cterror/cterror.par
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 # ------------------------------------------------------------------------- # 6 # ------------------------------------------------------------------------- #
7 # # 7 # #
8 # File: cterror.par # 8 # File: cterror.par #
9 -# Version: 01-00-00 # 9 +# Version: 1.0.0 #
10 # # 10 # #
11 # Author: Florent Forest # 11 # Author: Florent Forest #
12 # florent.forest@isae.fr # 12 # florent.forest@isae.fr #