1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Module for the handling of histograms, including Monte-Carlo error per bin
18 and scale/PDF uncertainties."""
19
20 from __future__ import division
21
22 import array
23 import copy
24 import fractions
25 import itertools
26 import logging
27 import math
28 import os
29 import re
30 import sys
31
32 root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0]
33 sys.path.append(os.path.join(root_path))
34 sys.path.append(os.path.join(root_path,os.pardir))
35 try:
36
37 import madgraph.various.misc as misc
38 from madgraph import MadGraph5Error
39 logger = logging.getLogger("madgraph.various.histograms")
40
41 except ImportError, error:
42
43 import internal.misc as misc
44 from internal import MadGraph5Error
45 logger = logging.getLogger("internal.histograms")
55 """A class to store lists of physics object."""
56
58 """Exception raised if an error occurs in the definition
59 or execution of a physics object list."""
60 pass
61
63 """Creates a new particle list object. If a list of physics
64 object is given, add them."""
65
66 list.__init__(self)
67
68 if init_list is not None:
69 for object in init_list:
70 self.append(object)
71
73 """Appends an element, but test if valid before."""
74
75 assert self.is_valid_element(object), \
76 "Object %s is not a valid object for the current list" % repr(object)
77
78 list.append(self, object)
79
80
82 """Test if object obj is a valid element for the list."""
83 return True
84
86 """String representation of the physics object list object.
87 Outputs valid Python with improved format."""
88
89 mystr = '['
90
91 for obj in self:
92 mystr = mystr + str(obj) + ',\n'
93
94 mystr = mystr.rstrip(',\n')
95
96 return mystr + ']'
97
98
99 -class Bin(object):
100 """A class to store Bin related features and function.
101 """
102
103 - def __init__(self, boundaries=(0.0,0.0), wgts=None, n_entries = 0):
104 """ Initializes an empty bin, necessarily with boundaries. """
105
106 self.boundaries = boundaries
107 self.n_entries = n_entries
108 if not wgts:
109 self.wgts = {'central':0.0}
110 else:
111 self.wgts = wgts
112
114 if name=='boundaries':
115 if not isinstance(value, tuple):
116 raise MadGraph5Error, "Argument '%s' for bin property "+\
117 "'boundaries' must be a tuple."%str(value)
118 else:
119 for coordinate in value:
120 if isinstance(coordinate, tuple):
121 for dim in coordinate:
122 if not isinstance(dim, float):
123 raise MadGraph5Error, "Coordinate '%s' of the bin"+\
124 " boundary '%s' must be a float."%str(dim,value)
125 elif not isinstance(coordinate, float):
126 raise MadGraph5Error, "Element '%s' of the bin boundaries"+\
127 " specified must be a float."%str(bound)
128 elif name=='wgts':
129 if not isinstance(value, dict):
130 raise MadGraph5Error, "Argument '%s' for bin uncertainty "+\
131 "'wgts' must be a dictionary."%str(value)
132 if not 'central' in value.keys():
133 raise MadGraph5Error, "The keys of the dictionary specifying "+\
134 "the weights of the bin must include the keyword 'central'."
135 for val in value.values():
136 if not isinstance(val,float):
137 raise MadGraph5Error, "The bin weight value '%s' is not a "+\
138 "float."%str(val)
139
140 super(Bin, self).__setattr__(name,value)
141
143 """ Accesses a specific weight from this bin."""
144 try:
145 return self.wgts[key]
146 except KeyError:
147 raise MadGraph5Error, "Weight with ID '%s' is not defined for"+\
148 " this bin"%str(key)
149
151 """ Accesses a specific weight from this bin."""
152
153
154
155 assert(isinstance(wgt, float))
156
157 try:
158 self.wgts[key] = wgt
159 except KeyError:
160 raise MadGraph5Error, "Weight with ID '%s' is not defined for"+\
161 " this bin"%str(key)
162
164 """ Add an event to this bin. """
165
166
167 if isinstance(weights, float):
168 weights = {'central': weights}
169
170 for key in weights:
171 if key == 'stat_error':
172 continue
173 try:
174 self.wgts[key] += weights[key]
175 except KeyError:
176 raise MadGraph5Error('The event added defines the weight '+
177 '%s which was not '%key+'registered in this histogram.')
178
179 self.n_entries += 1
180
181 if 'stat_error' not in weights:
182 self.wgts['stat_error'] = self.wgts['central']/math.sqrt(float(self.n_entries))
183 else:
184 self.wgts['stat_error'] = math.sqrt( self.wgts['stat_error']**2 +
185 weights['stat_error']**2 )
186
188 """ Nice representation of this Bin.
189 One can order the weight according to the argument if provided."""
190
191 res = ["Bin boundaries : %s"%str(self.boundaries)]
192 if not short:
193 res.append("Bin weights :")
194 if order is None:
195 label_list = self.wgts.keys()
196 else:
197 label_list = order
198
199 for label in label_list:
200 try:
201 res.append(" -> '%s' : %4.3e"%(str(label),self.wgts[label]))
202 except KeyError:
203 pass
204 else:
205 res.append("Central weight : %4.3e"%self.get_weight())
206
207 return '\n'.join(res)
208
210 """ Apply a given function to all bin weights."""
211 self.wgts = func(self.wgts)
212
213 @classmethod
214 - def combine(cls, binA, binB, func):
215 """ Function to combine two bins. The 'func' is such that it takes
216 two weight dictionaries and merge them into one."""
217
218 res_bin = cls()
219 if binA.boundaries != binB.boundaries:
220 raise MadGraph5Error, 'The two bins to combine have'+\
221 ' different boundaries, %s!=%s.'%(str(binA.boundaries),str(binB.boundaries))
222 res_bin.boundaries = binA.boundaries
223
224 try:
225 res_bin.wgts = func(binA.wgts, binB.wgts)
226 except Exception as e:
227 raise MadGraph5Error, "When combining two bins, the provided"+\
228 " function '%s' triggered the following error:\n\"%s\"\n"%\
229 (func.__name__,str(e))+" when combining the following two bins:\n"+\
230 binA.nice_string(short=False)+"\n and \n"+binB.nice_string(short=False)
231
232 return res_bin
233
234 -class BinList(histograms_PhysicsObjectList):
235 """ A class implementing features related to a list of Bins. """
236
237 - def __init__(self, list = [], bin_range = None,
238 weight_labels = None):
239 """ Initialize a list of Bins. It is possible to define the range
240 as a list of three floats: [min_x, max_x, bin_width]"""
241
242 self.weight_labels = weight_labels
243 if bin_range:
244
245 if not self.weight_labels:
246 self.weight_labels = ['central', 'stat_error']
247 if len(bin_range)!=3 or any(not isinstance(f, float) for f in bin_range):
248 raise MadGraph5Error, "The range argument to build a BinList"+\
249 " must be a list of exactly three floats."
250 current = bin_range[0]
251 while current < bin_range[1]:
252 self.append(Bin(boundaries =
253 (current, min(current+bin_range[2],bin_range[1])),
254 wgts = dict((wgt,0.0) for wgt in weight_labels)))
255 current += bin_range[2]
256 else:
257 super(BinList, self).__init__(list)
258
260 """Test whether specified object is of the right type for this list."""
261
262 return isinstance(obj, Bin)
263
265 if name=='weight_labels':
266 if not value is None and not isinstance(value, list):
267 raise MadGraph5Error, "Argument '%s' for BinList property '%s'"\
268 %(str(value),name)+' must be a list.'
269 elif not value is None:
270 for label in value:
271 if all((not isinstance(label,cls)) for cls in [str, int, tuple]):
272 raise MadGraph5Error, "Argument '%s' for BinList property '%s'"\
273 %(str(value),name)+' must be a string, an '+\
274 'integer or a tuple of float.'
275 if isinstance(label, tuple):
276 for elem in label:
277 if not isinstance(elem, float):
278 raise MadGraph5Error, "Argument "+\
279 "'%s' for BinList property '%s'"%(str(value),name)+\
280 ' can be a tuple, but it must be filled with floats.'
281
282
283 super(BinList, self).__setattr__(name, value)
284
286 """Appends an element, but test if valid before."""
287
288 super(BinList,self).append(object)
289
290 if len(self)==1 and self.weight_labels is None:
291 self.weight_labels = object.wgts.keys()
292
294 """ Nice representation of this BinList."""
295
296 res = ["Number of bin in the list : %d"%len(self)]
297 res.append("Registered weight labels : [%s]"%(', '.join([
298 str(label) for label in self.weight_labels])))
299 if not short:
300 for i, bin in enumerate(self):
301 res.append('Bin number %d :'%i)
302 res.append(bin.nice_string(order=self.weight_labels, short=short))
303
304 return '\n'.join(res)
305
307 """A mother class for all specific implementations of Histogram conventions
308 """
309
310 allowed_dimensions = None
311 allowed_types = []
312 allowed_axis_modes = ['LOG','LIN']
313
314 - def __init__(self, title = "NoName", n_dimensions = 2, type=None,
315 x_axis_mode = 'LIN', y_axis_mode = 'LOG', bins=None):
316 """ Initializes an empty histogram, possibly specifying
317 > a title
318 > a number of dimensions
319 > a bin content
320 """
321
322 self.title = title
323 self.dimension = n_dimensions
324 if not bins:
325 self.bins = BinList([])
326 else:
327 self.bins = bins
328 self.type = type
329 self.x_axis_mode = x_axis_mode
330 self.y_axis_mode = y_axis_mode
331
333 if name=='title':
334 if not isinstance(value, str):
335 raise MadGraph5Error, "Argument '%s' for the histogram property "+\
336 "'title' must be a string."%str(value)
337 elif name=='dimension':
338 if not isinstance(value, int):
339 raise MadGraph5Error, "Argument '%s' for histogram property "+\
340 "'dimension' must be an integer."%str(value)
341 if self.allowed_dimensions and value not in self.allowed_dimensions:
342 raise MadGraph5Error, "%i-Dimensional histograms not supported "\
343 %value+"by class '%s'. Supported dimensions are '%s'."\
344 %(self.__class__.__name__,self.allowed_dimensions)
345 elif name=='bins':
346 if not isinstance(value, BinList):
347 raise MadGraph5Error, "Argument '%s' for histogram property "+\
348 "'bins' must be a BinList."%str(value)
349 else:
350 for bin in value:
351 if not isinstance(bin, Bin):
352 raise MadGraph5Error, "Element '%s' of the "%str(bin)+\
353 " histogram bin list specified must be a bin."
354 elif name=='type':
355 if not (value is None or value in self.allowed_types or
356 self.allowed_types==[]):
357 raise MadGraph5Error, "Argument '%s' for histogram"%str(value)+\
358 " property 'type' must be a string in %s or None."\
359 %([str(t) for t in self.allowed_types])
360 elif name in ['x_axis_mode','y_axis_mode']:
361 if not value in self.allowed_axis_modes:
362 raise MadGraph5Error, "Attribute '%s' of the histogram"%str(name)+\
363 " must be in [%s], ('%s' given)"%(str(self.allowed_axis_modes),
364 str(value))
365
366 super(Histogram, self).__setattr__(name,value)
367
369 """ Nice representation of this histogram. """
370
371 res = ['<%s> histogram:'%self.__class__.__name__]
372 res.append(' -> title : "%s"'%self.title)
373 res.append(' -> dimensions : %d'%self.dimension)
374 if not self.type is None:
375 res.append(' -> type : %s'%self.type)
376 else:
377 res.append(' -> type : None')
378 res.append(' -> (x, y)_axis : ( %s, %s)'%\
379 (tuple([('Linear' if mode=='LIN' else 'Logarithmic') for mode in \
380 [self.x_axis_mode, self.y_axis_mode]])))
381 if short:
382 res.append(' -> n_bins : %s'%len(self.bins))
383 res.append(' -> weight types : [ %s ]'%
384 (', '.join([str(label) for label in self.bins.weight_labels]) \
385 if (not self.bins.weight_labels is None) else 'None'))
386
387 else:
388 res.append(' -> Bins content :')
389 res.append(self.bins.nice_string(short))
390
391 return '\n'.join(res)
392
394 """ Apply a given function to all bin weights."""
395
396 for bin in self.bins:
397 bin.alter_weights(func)
398
399 @classmethod
400 - def combine(cls, histoA, histoB, func):
401 """ Function to combine two Histograms. The 'func' is such that it takes
402 two weight dictionaries and merge them into one."""
403
404 res_histogram = copy.copy(histoA)
405 if histoA.title != histoB.title:
406 res_histogram.title = "[%s]__%s__[%s]"%(histoA.title,func.__name__,
407 histoB.title)
408 else:
409 res_histogram.title = histoA.title
410
411 res_histogram.bins = BinList([])
412 if len(histoA.bins)!=len(histoB.bins):
413 raise MadGraph5Error, 'The two histograms to combine have a '+\
414 'different number of bins, %d!=%d.'%(len(histoA.bins),len(histoB.bins))
415
416 if histoA.dimension!=histoB.dimension:
417 raise MadGraph5Error, 'The two histograms to combine have a '+\
418 'different dimensions, %d!=%d.'%(histoA.dimension,histoB.dimension)
419 res_histogram.dimension = histoA.dimension
420
421 for i, bin in enumerate(histoA.bins):
422 res_histogram.bins.append(Bin.combine(bin, histoB.bins[i],func))
423
424
425
426 res_histogram.bins.weight_labels = [label for label in histoA.bins.\
427 weight_labels if label in res_histogram.bins.weight_labels] + \
428 sorted([label for label in res_histogram.bins.weight_labels if\
429 label not in histoA.bins.weight_labels])
430
431
432 return res_histogram
433
434
435
436
437 @staticmethod
439 """ Apply the multiplication to the weights of two bins."""
440
441 new_wgts = {}
442
443 new_wgts['stat_error'] = math.sqrt(
444 (wgtsA['stat_error']*wgtsB['central'])**2+
445 (wgtsA['central']*wgtsB['stat_error'])**2)
446
447 for label, wgt in wgtsA.items():
448 if label=='stat_error':
449 continue
450 new_wgts[label] = wgt*wgtsB[label]
451
452 return new_wgts
453
454 @staticmethod
456 """ Apply the division to the weights of two bins."""
457
458 new_wgts = {}
459 if wgtsB['central'] == 0.0:
460 new_wgts['stat_error'] = 0.0
461 else:
462
463 new_wgts['stat_error'] = math.sqrt(wgtsA['stat_error']**2+
464 ((wgtsA['central']*wgtsB['stat_error'])/
465 wgtsB['central'])**2)/wgtsB['central']
466
467 for label, wgt in wgtsA.items():
468 if label=='stat_error':
469 continue
470 if wgtsB[label]==0.0 and wgt==0.0:
471 new_wgts[label] = 0.0
472 elif wgtsB[label]==0.0:
473
474
475
476
477 new_wgts[label] = 0.0
478 else:
479 new_wgts[label] = wgt/wgtsB[label]
480
481 return new_wgts
482
483 @staticmethod
484 - def OPERATION(wgtsA, wgtsB, wgt_operation, stat_error_operation):
485 """ Apply the operation to the weights of two bins. Notice that we
486 assume here the two dict operands to have the same weight labels.
487 The operation is a function that takes two floats as input."""
488
489 new_wgts = {}
490 for label, wgt in wgtsA.items():
491 if label!='stat_error':
492 new_wgts[label] = wgt_operation(wgt, wgtsB[label])
493 else:
494 new_wgts[label] = stat_error_operation(wgt, wgtsB[label])
495
496
497
498
499
500 return new_wgts
501
502
503 @staticmethod
505 """ Apply the operation to the weights of a *single* bins.
506 The operation is a function that takes a single float as input."""
507
508 new_wgts = {}
509 for label, wgt in wgts.items():
510 if label!='stat_error':
511 new_wgts[label] = wgt_operation(wgt)
512 else:
513 new_wgts[label] = stat_error_operation(wgt)
514
515 return new_wgts
516
517 @staticmethod
518 - def ADD(wgtsA, wgtsB):
519 """ Implements the addition using OPERATION above. """
520 return Histogram.OPERATION(wgtsA, wgtsB,
521 (lambda a,b: a+b),
522 (lambda a,b: math.sqrt(a**2+b**2)))
523
524 @staticmethod
526 """ Implements the subtraction using OPERATION above. """
527
528 return Histogram.OPERATION(wgtsA, wgtsB,
529 (lambda a,b: a-b),
530 (lambda a,b: math.sqrt(a**2+b**2)))
531
532 @staticmethod
534 """ Implements the rescaling using SINGLEHISTO_OPERATION above. """
535
536 def rescaler(wgts):
537 return Histogram.SINGLEHISTO_OPERATION(wgts,(lambda a: a*factor),
538 (lambda a: a*factor))
539
540 return rescaler
541
542 @staticmethod
544 """ Implements the offset using SINGLEBIN_OPERATION above. """
545 def offsetter(wgts):
546 return Histogram.SINGLEHISTO_OPERATION(
547 wgts,(lambda a: a+offset),(lambda a: a))
548
549 return offsetter
550
552 """ Overload the plus function. """
553 if isinstance(other, Histogram):
554 return self.__class__.combine(self,other,Histogram.ADD)
555 elif isinstance(other, int) or isinstance(other, float):
556 self.alter_weights(Histogram.OFFSET(float(other)))
557 return self
558 else:
559 return NotImplemented, 'Histograms can only be added to other '+\
560 ' histograms or scalars.'
561
563 """ Overload the subtraction function. """
564 if isinstance(other, Histogram):
565 return self.__class__.combine(self,other,Histogram.SUBTRACT)
566 elif isinstance(other, int) or isinstance(other, float):
567 self.alter_weights(Histogram.OFFSET(-float(other)))
568 return self
569 else:
570 return NotImplemented, 'Histograms can only be subtracted to other '+\
571 ' histograms or scalars.'
572
574 """ Overload the multiplication function. """
575 if isinstance(other, Histogram):
576 return self.__class__.combine(self,other,Histogram.MULTIPLY)
577 elif isinstance(other, int) or isinstance(other, float):
578 self.alter_weights(Histogram.RESCALE(float(other)))
579 return self
580 else:
581 return NotImplemented, 'Histograms can only be multiplied to other '+\
582 ' histograms or scalars.'
583
585 """ Overload the multiplication function. """
586 if isinstance(other, Histogram):
587 return self.__class__.combine(self,other,Histogram.DIVIDE)
588 elif isinstance(other, int) or isinstance(other, float):
589 self.alter_weights(Histogram.RESCALE(1.0/float(other)))
590 return self
591 else:
592 return NotImplemented, 'Histograms can only be divided with other '+\
593 ' histograms or scalars.'
594
595 __truediv__ = __div__
596
597 -class HwU(Histogram):
598 """A concrete implementation of an histogram plots using the HwU format for
599 reading/writing histogram content."""
600
601 allowed_dimensions = [2]
602 allowed_types = []
603
604
605 output_formats_implemented = ['HwU','gnuplot']
606
607
608
609 mandatory_weights = {'xmin':'boundary_xmin', 'xmax':'boundary_xmax',
610 'central value':'central', 'dy':'stat_error'}
611
612
613
614
615
616 weight_header_start_re = re.compile('^##.*')
617
618
619
620 weight_header_re = re.compile(
621 '&\s*(?P<wgt_name>(\S|(\s(?!\s*(&|$))))+)(\s(?!(&|$)))*')
622
623
624
625
626
627 histo_start_re = re.compile('^\s*<histogram>\s*(?P<n_bins>\d+)\s*"\s*'+
628 '(?P<histo_name>(\S|(\s(?!\s*")))+)\s*"\s*$')
629
630 a_float_re = '[\+|-]?\d+(\.\d*)?([EeDd][\+|-]?\d+)?'
631 histo_bin_weight_re = re.compile('(?P<weight>%s)'%a_float_re)
632
633 histo_end_re = re.compile(r'^\s*<\\histogram>\s*$')
634
635 weight_label_scale = re.compile('^\s*mur\s*=\s*(?P<mur_fact>%s)'%a_float_re+\
636 '\s*muf\s*=\s*(?P<muf_fact>%s)\s*$'%a_float_re,re.IGNORECASE)
637 weight_label_PDF = re.compile('^\s*PDF\s*=\s*(?P<PDF_set>\d+)\s*$')
638
640 """a class for histogram data parsing errors"""
641
642 - def __init__(self, file_path=None, weight_header=None, **opts):
643 """ Read one plot from a file_path or a stream. Notice that this
644 constructor only reads one, and the first one, of the plots specified.
645 If file_path was a path in argument, it would then close the opened stream.
646 If file_path was a stream in argument, it would leave it open.
647 The option weight_header specifies an ordered list of weight names
648 to appear in the file specified."""
649
650 super(HwU, self).__init__(**opts)
651
652 self.dimension = 2
653
654 if file_path is None:
655 return
656 elif isinstance(file_path, str):
657 stream = open(file_path,'r')
658 elif isinstance(file_path, file):
659 stream = file_path
660 else:
661 raise MadGraph5Error, "Argument file_path '%s' for HwU init"\
662 %str(file_path)+"ialization must be either a file path or a stream."
663
664
665 if not weight_header:
666 weight_header = HwU.parse_weight_header(stream)
667
668 if not self.parse_one_histo_from_stream(stream, weight_header):
669
670
671 super(Histogram,self).__setattr__('bins',None)
672
673
674 if isinstance(file_path, str):
675 stream.close()
676
677 - def addEvent(self, x_value, weights = 1.0):
678 """ Add an event to the current plot. """
679
680 for bin in self.bins:
681 if bin.boundaries[0] <= x_value < bin.boundaries[1]:
682 bin.addEvent(weights = weights)
683
701
703 """ Returns the string representation of this histogram using the
704 HwU standard."""
705
706 res = []
707 if print_header:
708 res.append(self.get_formatted_header())
709 res.extend([''])
710 res.append('<histogram> %s "%s"'%(len(self.bins),
711 self.get_HwU_histogram_name(format='HwU')))
712 for bin in self.bins:
713 res.append(' '.join('%+16.7e'%wgt for wgt in list(bin.boundaries)+
714 [bin.wgts['central'],bin.wgts['stat_error']]))
715 res[-1] += ' '.join('%+16.7e'%bin.wgts[key] for key in
716 self.bins.weight_labels if key not in ['central','stat_error'])
717 res.append('<\histogram>')
718 return res
719
720 - def output(self, path=None, format='HwU', print_header=True):
721 """ Ouput this histogram to a file, stream or string if path is kept to
722 None. The supported format are for now. Chose whether to print the header
723 or not."""
724
725 if not format in HwU.output_formats_implemented:
726 raise MadGraph5Error, "The specified output format '%s'"%format+\
727 " is not yet supported. Supported formats are %s."\
728 %HwU.output_formats_implemented
729
730 if format == 'HwU':
731 str_output_list = self.get_HwU_source(print_header=print_header)
732
733 if path is None:
734 return '\n'.join(str_output_list)
735 elif isinstance(path, str):
736 stream = open(path,'w')
737 stream.write('\n'.join(str_output_list))
738 stream.close()
739 elif isinstance(path, file):
740 path.write('\n'.join(str_output_list))
741
742
743 return True
744
746 """ Test whether the defining attributes of self are identical to histo,
747 typically to make sure that they are the same plots but from different
748 runs, and they can be summed safely. We however don't want to
749 overload the __eq__ because it is still a more superficial check."""
750
751 if self.title != other.title or \
752 self.bins.weight_labels != other.bins.weight_labels or \
753 (self.type != other.type and consider_type) or \
754 self.x_axis_mode != self.x_axis_mode or \
755 self.y_axis_mode != self.y_axis_mode or \
756 any(b1.boundaries!=b2.boundaries for (b1,b2) in \
757 zip(self.bins,other.bins)):
758 return False
759
760 return True
761
762
763
764 @classmethod
797
798
800 """ Parse the histogram name for tags which would set its various
801 attributes."""
802
803 for i, tag in enumerate(histogram_name.split('|')):
804 if i==0:
805 self.title = tag.strip()
806 else:
807 stag = tag.split('@')
808 if len(stag)!=2:
809 raise MadGraph5Error, 'Specifier in title must have the'+\
810 " syntax @<attribute_name>:<attribute_value>, not '%s'."%tag.strip()
811
812 stag = [t.strip().upper() for t in stag]
813 if stag[0] in ['T','TYPE']:
814 self.type = stag[1]
815 elif stag[0] in ['X_AXIS', 'X']:
816 self.x_axis_mode = stag[1]
817 elif stag[0] in ['Y_AXIS', 'Y']:
818 self.y_axis_mode = stag[1]
819 else:
820 raise MadGraph5Error, "Specifier '%s' not recognized."%stag[0]
821
823 """ Returns the histogram name in the HwU syntax or human readable."""
824
825 type_map = {'NLO':'NLO', 'LO':'LO', 'AUX':'auxiliary histogram', None:''}
826
827 if format=='human':
828 res = self.title
829 try:
830 res += ', %s'%type_map[self.type]
831 except KeyError:
832 res += ', %s'%str('NLO' if self.type.split()[0]=='NLO' else
833 self.type)
834 return res
835
836 elif format=='human-no_type':
837 res = self.title
838 return res
839
840 elif format=='HwU':
841 res = [self.title]
842 res.append('|X_AXIS@%s'%self.x_axis_mode)
843 res.append('|Y_AXIS@%s'%self.y_axis_mode)
844 if self.type:
845 res.append('|TYPE@%s'%self.type)
846 return ' '.join(res)
847
849 """ Reads *one* histogram from a stream, with the mandatory specification
850 of the ordered list of weight names. Return True or False depending
851 on whether the starting definition of a new plot could be found in this
852 stream."""
853 n_bins = 0
854
855 for line in stream:
856 start = HwU.histo_start_re.match(line)
857 if not start is None:
858 self.process_histogram_name(start.group('histo_name'))
859
860
861 if self.type == 'AUX':
862 continue
863 n_bins = int(start.group('n_bins'))
864
865
866 self.bins = BinList(weight_labels = [ wgt_label for
867 wgt_label in weight_header if wgt_label not in
868 ['boundary_xmin','boundary_xmax']])
869 break
870
871
872 for line_bin in stream:
873 bin_weights = {}
874 boundaries = [0.0,0.0]
875 for j, weight in \
876 enumerate(HwU.histo_bin_weight_re.finditer(line_bin)):
877 if j == len(weight_header):
878 raise ParseError, " There is more bin weights"+\
879 " specified than expected (%i)"%len(weight_header)
880 if weight_header[j] == 'boundary_xmin':
881 boundaries[0] = float(weight.group('weight'))
882 elif weight_header[j] == 'boundary_xmax':
883 boundaries[1] = float(weight.group('weight'))
884 else:
885 bin_weights[weight_header[j]] = \
886 float(weight.group('weight'))
887
888
889
890
891 if len(bin_weights)<(len(weight_header)-2):
892 raise HwU.ParseError, " There are only %i weights"\
893 %len(bin_weights)+" specified and %i were expected."%\
894 (len(weight_header)-2)
895 self.bins.append(Bin(tuple(boundaries), bin_weights))
896 if len(self.bins)==n_bins:
897 break
898
899 if len(self.bins)!=n_bins:
900 raise HwU.ParseError, "%i bin specification "%len(self.bins)+\
901 "were found and %i were expected."%n_bins
902
903
904 for line_end in stream:
905 if HwU.histo_end_re.match(line_end):
906
907 self.trim_auxiliary_weights()
908
909 return True
910
911
912 return False
913
915 """ Remove all weights which are auxiliary (whose name end with '@aux')
916 so that they are not included (they will be regenerated anyway)."""
917
918 for i, wgt_label in enumerate(self.bins.weight_labels):
919 if isinstance(wgt_label, str) and wgt_label.endswith('@aux'):
920 for bin in self.bins:
921 try:
922 del bin.wgts[wgt_label]
923 except KeyError:
924 pass
925 self.bins.weight_labels = [wgt_label for wgt_label in
926 self.bins.weight_labels if (not isinstance(wgt_label, str)
927 or (isinstance(wgt_label, str) and not wgt_label.endswith('@aux')) )]
928
930 """ Adds a weight to the bins which is the envelope of the scale
931 uncertainty, for the scale specified which can be either 'mur', 'muf',
932 'all_scale' or 'PDF'."""
933
934 if type.upper()=='MUR':
935 new_wgt_label = 'delta_mur'
936 scale_position = 0
937 elif type.upper()=='MUF':
938 new_wgt_label = 'delta_muf'
939 scale_position = 1
940 elif type.upper()=='ALL_SCALE':
941 new_wgt_label = 'delta_mu'
942 scale_position = -1
943 elif type.upper()=='PDF':
944 new_wgt_label = 'delta_pdf'
945 scale_position = -2
946 else:
947 raise MadGraph5Error, ' The function set_uncertainty can'+\
948 " only handle the scales 'mur', 'muf', 'all_scale' or 'pdf'."
949
950 if scale_position > -2:
951 wgts_to_consider = [ label for label in self.bins.weight_labels if \
952 isinstance(label, tuple) ]
953 if scale_position > -1:
954 wgts_to_consider = [ lab for lab in wgts_to_consider if \
955 (lab[scale_position]!=1.0 and all(k==1.0 for k in \
956 lab[:scale_position]+lab[scale_position+1:]) ) ]
957 elif scale_position == -2:
958 wgts_to_consider = [ label for label in self.bins.weight_labels if \
959 isinstance(label, int) ]
960 if len(wgts_to_consider)==0:
961
962 return None
963 else:
964
965 new_wgt_labels = ['%s_min @aux'%new_wgt_label,
966 '%s_max @aux'%new_wgt_label]
967 try:
968 position = [(not isinstance(lab, str)) for lab in \
969 self.bins.weight_labels].index(True)
970 self.bins.weight_labels = self.bins.weight_labels[:position]+\
971 new_wgt_labels + self.bins.weight_labels[position:]
972 except ValueError:
973 position = len(self.bins.weight_labels)
974 self.bins.weight_labels.extend(new_wgt_labels)
975
976
977 for bin in self.bins:
978 if type!='PDF':
979 bin.wgts[new_wgt_labels[0]] = min(bin.wgts[label] \
980 for label in wgts_to_consider)
981 bin.wgts[new_wgt_labels[1]] = max(bin.wgts[label] \
982 for label in wgts_to_consider)
983 else:
984 pdfs = [bin.wgts[pdf] for pdf in sorted(wgts_to_consider)]
985 pdf_up = 0.0
986 pdf_down = 0.0
987 cntrl_val = bin.wgts['central']
988 if wgts_to_consider[0] <= 90000:
989
990 if len(pdfs)>2:
991 for i in range(int((len(pdfs)-1)/2)):
992 pdf_up += max(0.0,pdfs[2*i+1]-cntrl_val,
993 pdfs[2*i+2]-cntrl_val)**2
994 pdf_down += max(0.0,cntrl_val-pdfs[2*i+1],
995 cntrl_val-pdfs[2*i+2])**2
996 pdf_up = cntrl_val + math.sqrt(pdf_up)
997 pdf_down = cntrl_val - math.sqrt(pdf_down)
998 else:
999 pdf_up = bin.wgts[pdfs[0]]
1000 pdf_down = bin.wgts[pdfs[0]]
1001 else:
1002
1003 pdf_stdev = 0.0
1004 for pdf in pdfs[1:]:
1005 pdf_stdev += (pdf - cntrl_val)**2
1006 pdf_stdev = math.sqrt(pdf_stdev/float(len(pdfs)-2))
1007 pdf_up = cntrl_val+pdf_stdev
1008 pdf_down = cntrl_val-pdf_stdev
1009
1010 bin.wgts[new_wgt_labels[0]] = pdf_down
1011 bin.wgts[new_wgt_labels[1]] = pdf_up
1012
1013
1014
1015 return position
1016
1017 - def rebin(self, n_rebin):
1018 """ Rebin the x-axis so as to merge n_rebin consecutive bins into a
1019 single one. """
1020
1021 if n_rebin < 1 or not isinstance(n_rebin, int):
1022 raise MadGraph5Error, "The argument 'n_rebin' of the HwU function"+\
1023 " 'rebin' must be larger or equal to 1, not '%s'."%str(n_rebin)
1024 elif n_rebin==1:
1025 return
1026
1027 if 'NOREBIN' in self.type.upper():
1028 return
1029
1030 rebinning_list = list(range(0,len(self.bins),n_rebin))+[len(self.bins),]
1031 concat_list = [self.bins[rebinning_list[i]:rebinning_list[i+1]] for \
1032 i in range(len(rebinning_list)-1)]
1033
1034 new_bins = copy.copy(self.bins)
1035 del new_bins[:]
1036
1037 for bins_to_merge in concat_list:
1038 if len(bins_to_merge)==0:
1039 continue
1040 new_bins.append(Bin(boundaries=(bins_to_merge[0].boundaries[0],
1041 bins_to_merge[-1].boundaries[1]),wgts={'central':0.0}))
1042 for weight in self.bins.weight_labels:
1043 if weight != 'stat_error':
1044 new_bins[-1].wgts[weight] = \
1045 sum(b.wgts[weight] for b in bins_to_merge)
1046 else:
1047 new_bins[-1].wgts['stat_error'] = \
1048 math.sqrt(sum(b.wgts['stat_error']**2 for b in\
1049 bins_to_merge))
1050
1051 self.bins = new_bins
1052
1053 @classmethod
1055 """ Function to determine the optimal x-axis range when plotting
1056 together the histos in histo_list and considering the weights
1057 weight_labels"""
1058
1059
1060 if weight_labels is None:
1061 weight_labels = histo_list[0].bins.weight_labels
1062
1063 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \
1064 for bin in histo.bins if \
1065 (sum(abs(bin.wgts[label]) for label in weight_labels) > 0.0)] ,[])
1066
1067 if len(all_boundaries)==0:
1068 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \
1069 for bin in histo.bins],[])
1070 if len(all_boundaries)==0:
1071 raise MadGraph5Error, "The histograms with title '%s'"\
1072 %histo_list[0].title+" seems to have no bins."
1073
1074 x_min = min(all_boundaries)
1075 x_max = max(all_boundaries)
1076
1077 return (x_min, x_max)
1078
1079 @classmethod
1082 """ Function to determine the optimal y-axis range when plotting
1083 together the histos in histo_list and considering the weights
1084 weight_labels. The option Kratio is present to allow for the couple of
1085 tweaks necessary for the the K-factor ratio histogram y-range."""
1086
1087
1088 if labels is None:
1089 weight_labels = histo_list[0].bins.weight_labels
1090 else:
1091 weight_labels = labels
1092
1093 all_weights = []
1094 for histo in histo_list:
1095 for bin in histo.bins:
1096 for label in weight_labels:
1097
1098
1099 if Kratio and bin.wgts[label]==0.0:
1100 continue
1101 if scale!='LOG':
1102 all_weights.append(bin.wgts[label])
1103 if label == 'stat_error':
1104 all_weights.append(-bin.wgts[label])
1105 elif bin.wgts[label]>0.0:
1106 all_weights.append(bin.wgts[label])
1107
1108 sum([ [bin.wgts[label] for label in weight_labels if \
1109 (scale!='LOG' or bin.wgts[label]!=0.0)] \
1110 for histo in histo_list for bin in histo.bins], [])
1111
1112 all_weights.sort()
1113 if len(all_weights)!=0:
1114 partial_max = all_weights[int(len(all_weights)*0.95)]
1115 partial_min = all_weights[int(len(all_weights)*0.05)]
1116 max = all_weights[-1]
1117 min = all_weights[0]
1118 else:
1119 if scale!='LOG':
1120 return (0.0,1.0)
1121 else:
1122 return (1.0,10.0)
1123
1124 y_max = 0.0
1125 y_min = 0.0
1126
1127
1128 if (max-partial_max)>2.0*(partial_max-partial_min):
1129 y_max = partial_max
1130 else:
1131 y_max = max
1132
1133
1134 if (partial_min - min)>2.0*(partial_max-partial_min) and min != 0.0:
1135 y_min = partial_min
1136 else:
1137 y_min = min
1138
1139 if Kratio:
1140 median = all_weights[len(all_weights)//2]
1141 spread = (y_max-y_min)
1142 if abs(y_max-median)<spread*0.05 or abs(median-y_min)<spread*0.05:
1143 y_max = median + spread/2.0
1144 y_min = median - spread/2.0
1145 if y_min != y_max:
1146 return ( y_min , y_max )
1147
1148
1149 if len(histo_list[0].bins) <= 5:
1150 y_min = min
1151 y_max = max
1152
1153
1154 if y_min == y_max:
1155 if max == min:
1156 y_min -= 1.0
1157 y_max += 1.0
1158 else:
1159 y_min = min
1160 y_max = max
1161
1162 return ( y_min , y_max )
1163
1164 -class HwUList(histograms_PhysicsObjectList):
1165 """ A class implementing features related to a list of Hwu Histograms. """
1166
1167
1168
1169
1170 number_line_colors_defined = 8
1171
1173 """Test wether specified object is of the right type for this list."""
1174
1175 return isinstance(obj, HwU) or isinstance(obj, HwUList)
1176
1177 - def __init__(self, file_path, weight_header=None, accepted_types_order=[],
1178 **opts):
1179 """ Read one plot from a file_path or a stream.
1180 This constructor reads all plots specified in target file.
1181 File_path can be a path or a stream in the argument.
1182 The option weight_header specifies an ordered list of weight names
1183 to appear in the file or stream specified. It accepted_types_order is
1184 empty, no filter is applied, otherwise only histograms of the specified
1185 types will be kept, and in this specified order for a given identical
1186 title.
1187 """
1188
1189 if isinstance(file_path, str):
1190 stream = open(file_path,'r')
1191 elif isinstance(file_path, file):
1192 stream = file_path
1193 else:
1194 return super(HwUList,self).__init__(file_path, **opts)
1195
1196
1197 if not weight_header:
1198 weight_header = HwU.parse_weight_header(stream)
1199
1200 new_histo = HwU(stream, weight_header)
1201 while not new_histo.bins is None:
1202 if accepted_types_order==[] or \
1203 new_histo.type in accepted_types_order:
1204 self.append(new_histo)
1205 new_histo = HwU(stream, weight_header)
1206
1207
1208 titles_order = [h.title for h in self]
1209 def ordering_function(histo):
1210 title_position = titles_order.index(histo.title)
1211 if accepted_types_order==[]:
1212 type_precedence = {'NLO':1,'LO':2,None:3,'AUX':5}
1213 try:
1214 ordering_key = (title_position,type_precedence[histo.type])
1215 except KeyError:
1216 ordering_key = (title_position,4)
1217 else:
1218 ordering_key = (title_position,
1219 accepted_types_order.index(histo.type))
1220 return ordering_key
1221
1222
1223
1224
1225
1226 self.sort(key=ordering_function)
1227
1228
1229 if isinstance(file_path, str):
1230 stream.close()
1231
1232 - def output(self, path, format='gnuplot',number_of_ratios = -1,
1233 uncertainties=['scale','pdf','statitistical'],ratio_correlations=True,arg_string=''):
1234 """ Ouput this histogram to a file, stream or string if path is kept to
1235 None. The supported format are for now. Chose whether to print the header
1236 or not."""
1237
1238 if len(self)==0:
1239 return MadGraph5Error, 'No histograms stored in the list yet.'
1240
1241 if not format in HwU.output_formats_implemented:
1242 raise MadGraph5Error, "The specified output format '%s'"%format+\
1243 " is not yet supported. Supported formats are %s."\
1244 %HwU.output_formats_implemented
1245
1246 if isinstance(path, str) and '.' not in os.path.basename(path):
1247 output_base_name = os.path.basename(path)
1248 HwU_stream = open(path+'.HwU','w')
1249 else:
1250 raise MadGraph5Error, "The path argument of the output function of"+\
1251 " the HwUList instance must be file path without its extension."
1252
1253 HwU_output_list = []
1254
1255
1256 if format == 'HwU':
1257 HwU_output_list.extend(self[0].get_HwU_source(print_header=True))
1258 for histo in self[1:]:
1259 HwU_output_list.extend(histo.get_HwU_source())
1260 HwU_output_list.extend(['',''])
1261 HwU_stream.write('\n'.join(HwU_output_list))
1262 HwU_stream.close()
1263 return
1264
1265
1266 if format == 'gnuplot':
1267 gnuplot_stream = open(path+'.gnuplot','w')
1268
1269
1270 matching_histo_lists = HwUList([HwUList([self[0]])])
1271 for histo in self[1:]:
1272 matched = False
1273 for histo_list in matching_histo_lists:
1274 if histo.test_plot_compability(histo_list[0],consider_type=False):
1275 histo_list.append(histo)
1276 matched = True
1277 break
1278 if not matched:
1279 matching_histo_lists.append(HwUList([histo]))
1280
1281 self[:] = matching_histo_lists
1282
1283
1284 gnuplot_output_list_v4 = [
1285 """
1286 ################################################################################
1287 #
1288 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which
1289 # automatically generates Feynman diagrams and matrix elements for arbitrary
1290 # high-energy processes in the Standard Model and beyond. It also perform the
1291 # integration and/or generate events for these processes, at LO and NLO accuracy.
1292 #
1293 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
1294 #
1295 ################################################################################
1296 # %s
1297 reset
1298
1299 set lmargin 10
1300 set rmargin 0
1301 set terminal postscript portrait enhanced mono dashed lw 1.0 "Helvetica" 9
1302 set key font ",9"
1303 set key samplen "2"
1304 set output "%s.ps"
1305
1306 # This is the "PODO" color palette of gnuplot v.5, but with the order
1307 # changed: palette of colors selected to be easily distinguishable by
1308 # color-blind individuals with either protanopia or deuteranopia. Bang
1309 # Wong [2011] Nature Methods 8, 441.
1310
1311 set style line 1 lt 1 lc rgb "#009e73" lw 2.5
1312 set style line 11 lt 2 lc rgb "#009e73" lw 2.5
1313 set style line 21 lt 4 lc rgb "#009e73" lw 2.5
1314
1315 set style line 2 lt 1 lc rgb "#0072b2" lw 2.5
1316 set style line 12 lt 2 lc rgb "#0072b2" lw 2.5
1317 set style line 22 lt 4 lc rgb "#0072b2" lw 2.5
1318
1319 set style line 3 lt 1 lc rgb "#d55e00" lw 2.5
1320 set style line 13 lt 2 lc rgb "#d55e00" lw 2.5
1321 set style line 23 lt 4 lc rgb "#d55e00" lw 2.5
1322
1323 set style line 4 lt 1 lc rgb "#f0e442" lw 2.5
1324 set style line 14 lt 2 lc rgb "#f0e442" lw 2.5
1325 set style line 24 lt 4 lc rgb "#f0e442" lw 2.5
1326
1327 set style line 5 lt 1 lc rgb "#56b4e9" lw 2.5
1328 set style line 15 lt 2 lc rgb "#56b4e9" lw 2.5
1329 set style line 25 lt 4 lc rgb "#56b4e9" lw 2.5
1330
1331 set style line 6 lt 1 lc rgb "#cc79a7" lw 2.5
1332 set style line 16 lt 2 lc rgb "#cc79a7" lw 2.5
1333 set style line 26 lt 4 lc rgb "#cc79a7" lw 2.5
1334
1335 set style line 7 lt 1 lc rgb "#e69f00" lw 2.5
1336 set style line 17 lt 2 lc rgb "#e69f00" lw 2.5
1337 set style line 27 lt 4 lc rgb "#e69f00" lw 2.5
1338
1339 set style line 8 lt 1 lc rgb "black" lw 2.5
1340 set style line 18 lt 2 lc rgb "black" lw 2.5
1341 set style line 28 lt 4 lc rgb "black" lw 2.5
1342
1343
1344 set style line 999 lt 1 lc rgb "gray" lw 2.5
1345
1346 safe(x,y,a) = (y == 0.0 ? a : x/y)
1347
1348 set style data histeps
1349
1350 """%(arg_string,output_base_name)
1351 ]
1352
1353 gnuplot_output_list_v5 = [
1354 """
1355 ################################################################################
1356 #
1357 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which
1358 # automatically generates Feynman diagrams and matrix elements for arbitrary
1359 # high-energy processes in the Standard Model and beyond. It also perform the
1360 # integration and/or generate events for these processes, at LO and NLO accuracy.
1361 #
1362 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
1363 #
1364 ################################################################################
1365 # %s
1366 reset
1367
1368 set lmargin 10
1369 set rmargin 0
1370 set terminal postscript portrait enhanced color "Helvetica" 9
1371 set key font ",9"
1372 set key samplen "2"
1373 set output "%s.ps"
1374
1375 # This is the "PODO" color palette of gnuplot v.5, but with the order
1376 # changed: palette of colors selected to be easily distinguishable by
1377 # color-blind individuals with either protanopia or deuteranopia. Bang
1378 # Wong [2011] Nature Methods 8, 441.
1379
1380 set style line 1 lt 1 lc rgb "#009e73" lw 1.3
1381 set style line 11 lt 2 lc rgb "#009e73" lw 1.3 dt (6,3)
1382 set style line 21 lt 4 lc rgb "#009e73" lw 1.3 dt (2,2)
1383
1384 set style line 2 lt 1 lc rgb "#0072b2" lw 1.3
1385 set style line 12 lt 2 lc rgb "#0072b2" lw 1.3 dt (6,3)
1386 set style line 22 lt 4 lc rgb "#0072b2" lw 1.3 dt (2,2)
1387
1388 set style line 3 lt 1 lc rgb "#d55e00" lw 1.3
1389 set style line 13 lt 2 lc rgb "#d55e00" lw 1.3 dt (6,3)
1390 set style line 23 lt 4 lc rgb "#d55e00" lw 1.3 dt (2,2)
1391
1392 set style line 4 lt 1 lc rgb "#f0e442" lw 1.3
1393 set style line 14 lt 2 lc rgb "#f0e442" lw 1.3 dt (6,3)
1394 set style line 24 lt 4 lc rgb "#f0e442" lw 1.3 dt (2,2)
1395
1396 set style line 5 lt 1 lc rgb "#56b4e9" lw 1.3
1397 set style line 15 lt 2 lc rgb "#56b4e9" lw 1.3 dt (6,3)
1398 set style line 25 lt 4 lc rgb "#56b4e9" lw 1.3 dt (2,2)
1399
1400 set style line 6 lt 1 lc rgb "#cc79a7" lw 1.3
1401 set style line 16 lt 2 lc rgb "#cc79a7" lw 1.3 dt (6,3)
1402 set style line 26 lt 4 lc rgb "#cc79a7" lw 1.3 dt (2,2)
1403
1404 set style line 7 lt 1 lc rgb "#e69f00" lw 1.3
1405 set style line 17 lt 2 lc rgb "#e69f00" lw 1.3 dt (6,3)
1406 set style line 27 lt 4 lc rgb "#e69f00" lw 1.3 dt (2,2)
1407
1408 set style line 8 lt 1 lc rgb "black" lw 1.3
1409 set style line 18 lt 2 lc rgb "black" lw 1.3 dt (6,3)
1410 set style line 28 lt 4 lc rgb "black" lw 1.3 dt (2,2)
1411
1412
1413 set style line 999 lt 1 lc rgb "gray" lw 1.3
1414
1415 safe(x,y,a) = (y == 0.0 ? a : x/y)
1416
1417 set style data histeps
1418
1419 """%(arg_string,output_base_name)
1420 ]
1421
1422
1423 try:
1424 import subprocess
1425 p = subprocess.Popen(['gnuplot', '--version'], \
1426 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1427 except OSError:
1428
1429
1430 gnuplot_output_list=gnuplot_output_list_v4
1431 else:
1432 output, _ = p.communicate()
1433 if float(output.split()[1]) < 5. :
1434 gnuplot_output_list=gnuplot_output_list_v4
1435 else:
1436 gnuplot_output_list=gnuplot_output_list_v5
1437
1438
1439
1440
1441 block_position = 0
1442 for histo_group in self:
1443
1444 block_position = histo_group.output_group(HwU_output_list,
1445 gnuplot_output_list, block_position,output_base_name+'.HwU',
1446 number_of_ratios=number_of_ratios,
1447 uncertainties = uncertainties,
1448 ratio_correlations = ratio_correlations)
1449
1450
1451 gnuplot_output_list.extend([
1452 "unset multiplot",
1453 '!ps2pdf "%s.ps" &> /dev/null'%output_base_name,
1454 '!open "%s.pdf" &> /dev/null'%output_base_name])
1455
1456
1457 gnuplot_stream.write('\n'.join(gnuplot_output_list))
1458 HwU_stream.write('\n'.join(HwU_output_list))
1459 gnuplot_stream.close()
1460 HwU_stream.close()
1461
1462 logger.debug("Histograms have been written out at "+\
1463 "%s.[HwU|gnuplot]' and can "%output_base_name+\
1464 "now be rendered by invoking gnuplot.")
1465
1466 - def output_group(self, HwU_out, gnuplot_out, block_position, HwU_name,
1467 number_of_ratios = -1, uncertainties =['scale','pdf','statitistical'],
1468 ratio_correlations = True):
1469 """ This functions output a single group of histograms with either one
1470 histograms untyped (i.e. type=None) or two of type 'NLO' and 'LO'
1471 respectively."""
1472
1473 layout_geometry = [(0.0, 0.5, 1.0, 0.4 ),
1474 (0.0, 0.35, 1.0, 0.15),
1475 (0.0, 0.2, 1.0, 0.15)]
1476 layout_geometry.reverse()
1477
1478
1479
1480
1481 def ratio_no_correlations(wgtsA, wgtsB):
1482 new_wgts = {}
1483 for label, wgt in wgtsA.items():
1484 if wgtsB['central']==0.0 and wgt==0.0:
1485 new_wgts[label] = 0.0
1486 continue
1487 elif wgtsB['central']==0.0:
1488
1489
1490
1491 new_wgts[label] = 0.0
1492 continue
1493 new_wgts[label] = (wgtsA[label]/wgtsB['central'])
1494 return new_wgts
1495
1496
1497
1498 n_histograms = len(self)
1499 ratio_histos = HwUList([])
1500 for i, histo in enumerate(self[1:
1501 number_of_ratios+1 if number_of_ratios>=0 else len(self)]):
1502 if ratio_correlations:
1503 ratio_histos.append(histo/self[0])
1504 else:
1505 ratio_histos.append(self[0].__class__.combine(histo, self[0],
1506 ratio_no_correlations))
1507 if self[0].type=='NLO' and self[1].type=='LO':
1508 ratio_histos[-1].title += '1/K-factor'
1509 elif self[0].type=='LO' and self[1].type=='NLO':
1510 ratio_histos[-1].title += 'K-factor'
1511 else:
1512 ratio_histos[-1].title += ' %s/%s'%(
1513 self[1].type if self[1].type else '(%d)'%(i+2),
1514 self[0].type if self[0].type else '(1)')
1515
1516
1517 ratio_histos[-1].type = 'AUX'
1518 self.extend(ratio_histos)
1519
1520
1521 if 'scale' in uncertainties:
1522 mu_var_pos = self[0].set_uncertainty(type='all_scale')
1523 else:
1524 mu_var_pos = None
1525
1526 if 'pdf' in uncertainties:
1527 PDF_var_pos = self[0].set_uncertainty(type='PDF')
1528 else:
1529 PDF_var_pos = None
1530
1531 no_uncertainties = (PDF_var_pos is None or 'PDF' not in uncertainties) \
1532 and (mu_var_pos is None or 'scale' not in uncertainties) and \
1533 'statistical' not in uncertainties
1534
1535 for histo in self[1:]:
1536 if (not mu_var_pos is None) and \
1537 mu_var_pos != histo.set_uncertainty(type='all_scale'):
1538 raise MadGraph5Error, 'Not all histograms in this group specify'+\
1539 ' scale dependencies. It is required to be able to output them'+\
1540 ' together.'
1541 if (not PDF_var_pos is None) and\
1542 PDF_var_pos != histo.set_uncertainty(type='PDF'):
1543 raise MadGraph5Error, 'Not all histograms in this group specify'+\
1544 ' PDF dependencies. It is required to be able to output them'+\
1545 ' together.'
1546
1547
1548 for i, histo in enumerate(self):
1549
1550 HwU_out.extend(histo.get_HwU_source(\
1551 print_header=(block_position==0 and i==0)))
1552 HwU_out.extend(['',''])
1553
1554
1555 global_header =\
1556 """
1557 ################################################################################
1558 ### Rendering of the plot titled '%(title)s'
1559 ################################################################################
1560
1561 set multiplot
1562 set label "%(title)s" font ",13" at graph 0.04, graph 1.05
1563 set xrange [%(xmin).4e:%(xmax).4e]
1564 set bmargin 0
1565 set tmargin 0
1566 set xtics nomirror
1567 set ytics nomirror
1568 set mytics %(mxtics)d
1569 %(set_xtics)s
1570 set key horizontal noreverse maxcols 1 width -4
1571 set label front 'MadGraph5\_aMC\@NLO' font "Courier,11" rotate by 90 at graph 1.02, graph 0.04
1572 """
1573
1574
1575 subhistogram_header = \
1576 """#-- rendering subhistograms '%(subhistogram_type)s'
1577 %(unset label)s
1578 %(set_format_y)s
1579 set yrange [%(ymin).4e:%(ymax).4e]
1580 set origin %(origin_x).4e, %(origin_y).4e
1581 set size %(size_x).4e, %(size_y).4e
1582 set mytics %(mytics)d
1583 %(set_ytics)s
1584 %(set_format_x)s
1585 %(set_yscale)s
1586 %(set_ylabel)s
1587 %(set_histo_label)s
1588 plot \\"""
1589 replacement_dic = {}
1590
1591 replacement_dic['title'] = self[0].get_HwU_histogram_name(format='human-no_type')
1592
1593
1594 wgts_to_consider = ['central']
1595 if not mu_var_pos is None:
1596 wgts_to_consider.append(self[0].bins.weight_labels[mu_var_pos])
1597 wgts_to_consider.append(self[0].bins.weight_labels[mu_var_pos+1])
1598 if not PDF_var_pos is None:
1599 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var_pos])
1600 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var_pos+1])
1601
1602 (xmin, xmax) = HwU.get_x_optimal_range(self[:2],\
1603 weight_labels = wgts_to_consider)
1604 replacement_dic['xmin'] = xmin
1605 replacement_dic['xmax'] = xmax
1606 replacement_dic['mxtics'] = 10
1607 replacement_dic['set_xtics'] = 'set xtics auto'
1608
1609
1610 gnuplot_out.append(global_header%replacement_dic)
1611
1612
1613 replacement_dic['subhistogram_type'] = '%s and %s results'%(
1614 str(self[0].type),str(self[1].type)) if len(self)>1 else \
1615 'single diagram output'
1616 (ymin, ymax) = HwU.get_y_optimal_range(self[:2],
1617 labels = wgts_to_consider, scale=self[0].y_axis_mode)
1618
1619
1620 if ymin< 0.0:
1621 self[0].y_axis_mode = 'LIN'
1622
1623
1624 if self[0].y_axis_mode=='LOG':
1625 ymax += 10.0 * ymax
1626 ymin -= 0.1 * ymin
1627 else:
1628 ymax += 0.3 * (ymax - ymin)
1629 ymin -= 0.3 * (ymax - ymin)
1630
1631 replacement_dic['ymin'] = ymin
1632 replacement_dic['ymax'] = ymax
1633 replacement_dic['unset label'] = ''
1634 (replacement_dic['origin_x'], replacement_dic['origin_y'],
1635 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop()
1636 replacement_dic['mytics'] = 10
1637
1638 replacement_dic['set_ytics'] = 'set ytics auto'
1639 replacement_dic['set_format_x'] = "set format x ''" if \
1640 (len(self)-n_histograms>0 or not no_uncertainties) else "set format x"
1641 replacement_dic['set_ylabel'] = 'set ylabel "{/Symbol s} per bin [pb]"'
1642 replacement_dic['set_yscale'] = "set logscale y" if \
1643 self[0].y_axis_mode=='LOG' else 'unset logscale y'
1644 replacement_dic['set_format_y'] = "set format y '10^{%T}'" if \
1645 self[0].y_axis_mode=='LOG' else 'unset format'
1646
1647 replacement_dic['set_histo_label'] = ""
1648 gnuplot_out.append(subhistogram_header%replacement_dic)
1649
1650
1651 plot_lines = []
1652 for i, histo in enumerate(self[:n_histograms]):
1653 color_index = i%self.number_line_colors_defined+1
1654 title = '%d'%(i+1) if histo.type is None else ('NLO' if \
1655 histo.type.split()[0]=='NLO' else histo.type)
1656 plot_lines.append(
1657 "'%s' index %d using (($1+$2)/2):3 ls %d title '%s'"\
1658 %(HwU_name,block_position+i,color_index,histo.get_HwU_histogram_name(format='human')
1659 if i==0 else (histo.type if histo.type else 'central value for plot (%d)'%(i+1))))
1660 if 'statistical' in uncertainties:
1661 plot_lines.append(
1662 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"\
1663 %(HwU_name,block_position+i,color_index))
1664
1665 if not mu_var_pos is None:
1666 plot_lines.extend([
1667 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\
1668 %(HwU_name,block_position+i,mu_var_pos+3,color_index+10,'%s scale variation'\
1669 %title),
1670 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1671 %(HwU_name,block_position+i,mu_var_pos+4,color_index+10),
1672 ])
1673
1674 if not PDF_var_pos is None:
1675 plot_lines.extend([
1676 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\
1677 %(HwU_name,block_position+i,PDF_var_pos+3,color_index+20,'%s PDF variation'\
1678 %title),
1679 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1680 %(HwU_name,block_position+i,PDF_var_pos+4,color_index+20),
1681 ])
1682
1683
1684 gnuplot_out.append(',\\\n'.join(plot_lines))
1685
1686
1687 replacement_dic['subhistogram_type'] = 'Relative scale and PDF uncertainty'
1688
1689 if 'statistical' in uncertainties:
1690 wgts_to_consider.append('stat_error')
1691
1692
1693
1694 def rel_scale(wgtsA, wgtsB):
1695 new_wgts = {}
1696 for label, wgt in wgtsA.items():
1697 if label in wgts_to_consider:
1698 if wgtsB['central']==0.0 and wgt==0.0:
1699 new_wgts[label] = 0.0
1700 continue
1701 elif wgtsB['central']==0.0:
1702
1703
1704
1705 new_wgts[label] = 0.0
1706 continue
1707 new_wgts[label] = (wgtsA[label]/wgtsB['central'])
1708 if label != 'stat_error':
1709 new_wgts[label] -= 1.0
1710 else:
1711 new_wgts[label] = wgtsA[label]
1712 return new_wgts
1713
1714
1715
1716 (ymin, ymax) = HwU.get_y_optimal_range(
1717 [self[0].__class__.combine(self[0],self[0],rel_scale),],
1718 labels = wgts_to_consider, scale='LIN')
1719
1720
1721 ymax = ymax + 0.2 * (ymax - ymin)
1722 ymin = ymin - 0.2 * (ymax - ymin)
1723 replacement_dic['unset label'] = 'unset label'
1724 replacement_dic['ymin'] = ymin
1725 replacement_dic['ymax'] = ymax
1726 if not no_uncertainties:
1727 (replacement_dic['origin_x'], replacement_dic['origin_y'],
1728 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop()
1729 replacement_dic['mytics'] = 2
1730
1731 replacement_dic['set_ytics'] = 'set ytics auto'
1732 replacement_dic['set_format_x'] = "set format x ''" if \
1733 len(self)-n_histograms>0 else "set format x"
1734 replacement_dic['set_ylabel'] = 'set ylabel "%s rel.unc."'\
1735 %('#1' if self[0].type==None else '%s'%('NLO' if \
1736 self[0].type.split()[0]=='NLO' else self[0].type))
1737 replacement_dic['set_yscale'] = "unset logscale y"
1738 replacement_dic['set_format_y'] = 'unset format'
1739
1740 replacement_dic['set_histo_label'] = \
1741 'set label "Relative uncertainties" font ",9" at graph 0.03, graph 0.13'
1742
1743
1744
1745 if not no_uncertainties:
1746 gnuplot_out.append(subhistogram_header%replacement_dic)
1747
1748
1749 plot_lines = ["0.0 ls 999 title ''"]
1750 for i, histo in enumerate(self[:n_histograms]):
1751 color_index = i%self.number_line_colors_defined+1
1752 if 'statistical' in uncertainties:
1753 plot_lines.append(
1754 "'%s' index %d using (($1+$2)/2):(0.0):(safe($4,$3,0.0)) w yerrorbar ls %d title ''"%\
1755 (HwU_name,block_position+i,color_index))
1756
1757 if not mu_var_pos is None:
1758 plot_lines.extend([
1759 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\
1760 %(HwU_name,block_position+i,mu_var_pos+3,color_index+10),
1761 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\
1762 %(HwU_name,block_position+i,mu_var_pos+4,color_index+10)
1763 ])
1764 if not PDF_var_pos is None:
1765 plot_lines.extend([
1766 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\
1767 %(HwU_name,block_position+i,PDF_var_pos+3,color_index+20),
1768 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\
1769 %(HwU_name,block_position+i,PDF_var_pos+4,color_index+20)
1770 ])
1771
1772
1773 if not no_uncertainties:
1774 gnuplot_out.append(',\\\n'.join(plot_lines))
1775
1776
1777 if len(self)-n_histograms==0:
1778
1779 gnuplot_out.extend(['','unset label','',
1780 '################################################################################'])
1781
1782 return block_position+len(self)
1783
1784
1785 ratio_name_long = '%s/%s'%(
1786 '(2)' if (len(self)-n_histograms)==1 else
1787 '((2) to (%d))'%(len(self)-n_histograms+1),
1788 '(1)' if self[0].type==None else '%s'%('NLO' if \
1789 self[0].type.split()[0]=='NLO' else self[0].type))
1790
1791 ratio_name_short = ratio_name_long
1792
1793 ratio_name = '%s/%s'
1794 replacement_dic['subhistogram_type'] = '%s ratio'%ratio_name_long
1795 replacement_dic['set_ylabel'] = 'set ylabel "%s"'%ratio_name_short
1796
1797 (ymin, ymax) = HwU.get_y_optimal_range(self[n_histograms:],
1798 labels = wgts_to_consider, scale='LIN',Kratio = True)
1799
1800
1801 ymax = ymax + 0.2 * (ymax - ymin)
1802 ymin = ymin - 0.2 * (ymax - ymin)
1803 replacement_dic['unset label'] = 'unset label'
1804 replacement_dic['ymin'] = ymin
1805 replacement_dic['ymax'] = ymax
1806 (replacement_dic['origin_x'], replacement_dic['origin_y'],
1807 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop()
1808 replacement_dic['mytics'] = 2
1809
1810 replacement_dic['set_ytics'] = 'set ytics auto'
1811 replacement_dic['set_format_x'] = "set format x"
1812 replacement_dic['set_yscale'] = "unset logscale y"
1813 replacement_dic['set_format_y'] = 'unset format'
1814 replacement_dic['set_histo_label'] = \
1815 'set label "%s" font ",9" at graph 0.03, graph 0.13'%ratio_name_long
1816
1817 gnuplot_out.append(subhistogram_header%replacement_dic)
1818
1819 plot_lines = []
1820 for i_histo_ratio, histo_ration in enumerate(self[n_histograms:]):
1821 block_ratio_pos = block_position+n_histograms+i_histo_ratio
1822 color_index = color_index = (i_histo_ratio+1)%\
1823 self.number_line_colors_defined+1
1824
1825 plot_lines.extend(["1.0 ls 999 title ''",
1826 "'%s' index %d using (($1+$2)/2):3 ls %d title ''"%\
1827 (HwU_name,block_ratio_pos,color_index)])
1828 if 'statistical' in uncertainties:
1829 plot_lines.append(
1830 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"%\
1831 (HwU_name,block_ratio_pos,color_index))
1832
1833 if not mu_var_pos is None:
1834 plot_lines.extend([
1835 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1836 %(HwU_name,block_ratio_pos,mu_var_pos+3,10+color_index),
1837 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1838 %(HwU_name,block_ratio_pos,mu_var_pos+4,10+color_index)
1839 ])
1840 if not PDF_var_pos is None:
1841 plot_lines.extend([
1842 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1843 %(HwU_name,block_ratio_pos,PDF_var_pos+3,20+color_index),
1844 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\
1845 %(HwU_name,block_ratio_pos,PDF_var_pos+4,20+color_index)
1846 ])
1847
1848
1849 gnuplot_out.append(',\\\n'.join(plot_lines))
1850
1851
1852 gnuplot_out.extend(['','unset label','',
1853 '################################################################################'])
1854
1855
1856 return block_position+len(self)
1857
1858 if __name__ == "__main__":
1859 main_doc = \
1860 """ For testing and standalone use. Usage:
1861 python histograms.py <.HwU input_file_path_1> <.HwU input_file_path_2> ... --out=<output_file_path.format> <options>
1862 Where <options> can be a list of the following:
1863 '--help' See this message.
1864 '--gnuplot' or '' output the histograms read to gnuplot
1865 '--HwU' to output the histograms read to the raw HwU source.
1866 '--types=<type1>,<type2>,...' to keep only the type<i> when importing histograms.
1867 '--n_ratios=<integer>' Specifies how many curves must be considerd for the ratios.
1868 '--no_scale' Turn off the plotting of scale uncertainties
1869 '--no_pdf' Turn off the plotting of PDF uncertainties
1870 '--no_stat' Turn off the plotting of all statistical uncertainties
1871 '--no_open' Turn off the automatic processing of the gnuplot output.
1872 '--show_full' to show the complete output of what was read.
1873 '--show_short' to show a summary of what was read.
1874 '--simple_ratios' to turn off correlations and error propagation in the ratio.
1875 '--sum' To sum all identical histograms together
1876 '--average' To average over all identical histograms
1877 '--rebin=<n>' Rebin the plots by merging n-consecutive bins together.
1878 '--assign_types=<type1>,<type2>,...' to assign a type to all histograms of the first, second, etc... files loaded.
1879 '--multiply=<fact1>,<fact2>,...' to multiply all histograms of the first, second, etc... files by the fact1, fact2, etc...
1880 '--no_suffix' Do no add any suffix (like '#1, #2, etc..) to the histograms types.
1881 """
1882
1883 possible_options=['--help', '--gnuplot', '--HwU', '--types','--n_ratios','--no_scale','--no_pdf','--no_stat',\
1884 '--no_open','--show_full','--show_short','--simple_ratios','--sum','--average','--rebin', \
1885 '--assign_types','--multiply','--no_suffix', '--out']
1886 n_ratios = -1
1887 uncertainties = ['scale','pdf','statistical']
1888 auto_open = True
1889 ratio_correlations = True
1890
1891 - def log(msg):
1892 print "histograms.py :: %s"%str(msg)
1893
1894 if '--help' in sys.argv or len(sys.argv)==1:
1895 log('\n\n%s'%main_doc)
1896 sys.exit(0)
1897
1898 for arg in sys.argv[1:]:
1899 if arg.startswith('--'):
1900 if arg.split('=')[0] not in possible_options:
1901 log('WARNING: option "%s" not valid. It will be ignored' % arg)
1902
1903 arg_string=' '.join(sys.argv)
1904
1905 OutName = ""
1906 for arg in sys.argv[1:]:
1907 if arg.startswith('--out='):
1908 OutName = arg[6:]
1909
1910 accepted_types = []
1911 for arg in sys.argv[1:]:
1912 if arg.startswith('--types='):
1913 accepted_types = [(type if type!='None' else None) for type in \
1914 arg[8:].split(',')]
1915
1916 assigned_types = []
1917 for arg in sys.argv[1:]:
1918 if arg.startswith('--assign_types='):
1919 assigned_types = [(type if type!='None' else None) for type in \
1920 arg[15:].split(',')]
1921
1922 no_suffix = False
1923 if '--no_suffix' in sys.argv:
1924 no_suffix = True
1925
1926 for arg in sys.argv[1:]:
1927 if arg.startswith('--n_ratios='):
1928 n_ratios = int(arg[11:])
1929
1930 if '--no_open' in sys.argv:
1931 auto_open = False
1932
1933 if '--simple_ratios' in sys.argv:
1934 ratio_correlations = False
1935
1936 if '--no_scale' in sys.argv:
1937 uncertainties.remove('scale')
1938
1939 if '--no_pdf' in sys.argv:
1940 uncertainties.remove('pdf')
1941
1942 if '--no_stat' in sys.argv:
1943 uncertainties.remove('statistical')
1944
1945 n_files = len([_ for _ in sys.argv[1:] if not _.startswith('--')])
1946 histo_norm = [1.0]*n_files
1947
1948 for arg in sys.argv[1:]:
1949 if arg.startswith('--multiply='):
1950 histo_norm = [(float(fact) if fact!='' else 1.0) for fact in \
1951 arg[11:].split(',')]
1952
1953 if '--average' in sys.argv:
1954 histo_norm = [hist/float(n_files) for hist in histo_norm]
1955
1956 log("=======")
1957 histo_list = HwUList([])
1958 for i, arg in enumerate(sys.argv[1:]):
1959 if arg.startswith('--'):
1960 break
1961 log("Loading histograms from '%s'."%arg)
1962 if OutName=="":
1963 OutName = os.path.basename(arg).split('.')[0]+'_output'
1964 new_histo_list = HwUList(arg, accepted_types_order=accepted_types)
1965 for histo in new_histo_list:
1966 if no_suffix:
1967 continue
1968 if not histo.type is None:
1969 histo.type += '|'
1970 else:
1971 histo.type = ''
1972
1973
1974
1975
1976
1977
1978 try:
1979 suffix = assigned_types[i]
1980 except IndexError:
1981 suffix = "#%d"%(i+1)
1982 try:
1983 histo.type = histo.type[:histo.type.index('#')] + suffix
1984 except ValueError:
1985 histo.type += suffix
1986
1987 if i==0 or all(_ not in ['--sum','--average'] for _ in sys.argv):
1988 for j,hist in enumerate(new_histo_list):
1989 new_histo_list[j]=hist*histo_norm[i]
1990 histo_list.extend(new_histo_list)
1991 continue
1992
1993 if any(_ in sys.argv for _ in ['--sum','--average']):
1994 for j, hist in enumerate(new_histo_list):
1995
1996 hist.test_plot_compability(histo_list[j])
1997
1998 histo_list[j] += hist*histo_norm[i]
1999
2000 log("A total of %i histograms were found."%len(histo_list))
2001 log("=======")
2002
2003 n_rebin = 1
2004 for arg in sys.argv[1:]:
2005 if arg.startswith('--rebin='):
2006 n_rebin = int(arg[8:])
2007
2008 if n_rebin > 1:
2009 for hist in histo_list:
2010 hist.rebin(n_rebin)
2011
2012 if '--gnuplot' in sys.argv or all(arg not in ['--HwU'] for arg in sys.argv):
2013 histo_list.output(OutName, format='gnuplot', number_of_ratios = n_ratios,
2014 uncertainties=uncertainties, ratio_correlations=ratio_correlations,arg_string=arg_string)
2015 log("%d histograms have been output in " % len(histo_list)+\
2016 "the gnuplot format at '%s.[HwU|gnuplot]'." % OutName)
2017 if auto_open:
2018 command = 'gnuplot %s.gnuplot'%OutName
2019 try:
2020 import subprocess
2021 subprocess.call(command,shell=True)
2022 except:
2023 log("Automatic processing of the gnuplot card failed. Try the"+\
2024 " command by hand:\n%s"%command)
2025 else:
2026 sys.exit(0)
2027
2028 if '--HwU' in sys.argv:
2029 log("Histograms data has been output in the HwU format at "+\
2030 "'%s.HwU'."%OutName)
2031 histo_list.output(OutName, format='HwU')
2032 sys.exit(0)
2033
2034 if '--show_short' in sys.argv or '--show_full' in sys.argv:
2035 for i, histo in enumerate(histo_list):
2036 if i!=0:
2037 log('-------')
2038 log(histo.nice_string(short=(not '--show_full' in sys.argv)))
2039 log("=======")
2040