Package madgraph :: Package various :: Module histograms
[hide private]
[frames] | no frames]

Source Code for Module madgraph.various.histograms

   1  #! /usr/bin/env python 
   2  ################################################################################ 
   3  # 
   4  # Copyright (c) 2010 The MadGraph5_aMC@NLO Development team and Contributors 
   5  # 
   6  # This file is a part of the MadGraph5_aMC@NLO project, an application which  
   7  # automatically generates Feynman diagrams and matrix elements for arbitrary 
   8  # high-energy processes in the Standard Model and beyond. 
   9  # 
  10  # It is subject to the MadGraph5_aMC@NLO license which should accompany this  
  11  # distribution. 
  12  # 
  13  # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 
  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      # import from madgraph directory 
  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      # import from madevent directory 
  43      import internal.misc as misc     
  44      from internal import MadGraph5Error 
  45      logger = logging.getLogger("internal.histograms") 
46 47 # I copy the Physics object list here so as not to add a whole dependency to 48 # base_objects which is annoying when using this histograms module from the 49 # bin/internal location of a process output (i.e. outside an MG5_aMC env.) 50 51 #=============================================================================== 52 # PhysicsObjectList 53 #=============================================================================== 54 -class histograms_PhysicsObjectList(list):
55 """A class to store lists of physics object.""" 56
57 - class PhysicsObjectListError(Exception):
58 """Exception raised if an error occurs in the definition 59 or execution of a physics object list.""" 60 pass
61
62 - def __init__(self, init_list=None):
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
72 - def append(self, object):
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
81 - def is_valid_element(self, obj):
82 """Test if object obj is a valid element for the list.""" 83 return True
84
85 - def __str__(self):
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
113 - def __setattr__(self, name, value):
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
142 - def get_weight(self, key='central'):
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
150 - def set_weight(self, wgt, key='central'):
151 """ Accesses a specific weight from this bin.""" 152 153 # an assert is used here in this intensive function, so as to avoid 154 # slow-down when not in debug mode. 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
163 - def addEvent(self, weights = 1.0):
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
187 - def nice_string(self, order=None, short=True):
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
209 - def alter_weights(self, func):
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 # Set the default weight_labels to something meaningful 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
259 - def is_valid_element(self, obj):
260 """Test whether specified object is of the right type for this list.""" 261 262 return isinstance(obj, Bin)
263
264 - def __setattr__(self, name, value):
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
285 - def append(self, object):
286 """Appends an element, but test if valid before.""" 287 288 super(BinList,self).append(object) 289 # Assign the weight labels to those of the first bin added 290 if len(self)==1 and self.weight_labels is None: 291 self.weight_labels = object.wgts.keys()
292
293 - def nice_string(self, short=True):
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
306 -class Histogram(object):
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
332 - def __setattr__(self, name, value):
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
368 - def nice_string(self, short=True):
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
393 - def alter_weights(self, func):
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 # Reorder the weight labels as in the original histogram and add at the 425 # end the new ones which resulted from the combination, in a sorted order 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 # Some handy function for Histogram combination 436 # ================================================== 437 @staticmethod
438 - def MULTIPLY(wgtsA, wgtsB):
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
455 - def DIVIDE(wgtsA, wgtsB):
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 # d(x/y) = ( (dx/y)**2 + ((x*dy)/(y**2))**2 )**0.5 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 # This situation is most often harmless and just happens in regions 474 # with low statistics, so I'll bypass the warning here. 475 # logger.debug('Warning:: A bin with finite weight was divided '+\ 476 # 'by a bin with zero weight.') 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 # if new_wgts[label]>1.0e+10: 496 # print "stat_error_operation is ",stat_error_operation.__name__ 497 # print " inputs were ",wgt, wgtsB[label] 498 # print "for label", label 499 500 return new_wgts
501 502 503 @staticmethod
504 - def SINGLEHISTO_OPERATION(wgts, wgt_operation, stat_error_operation):
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
525 - def SUBTRACT(wgtsA, wgtsB):
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
533 - def RESCALE(factor):
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
543 - def OFFSET(offset):
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
551 - def __add__(self, other):
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
562 - def __sub__(self, other):
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
573 - def __mul__(self, other):
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
584 - def __div__(self, other):
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 # For now only HwU output format is implemented. 605 output_formats_implemented = ['HwU','gnuplot'] 606 # Lists the mandatory named weights that must be specified for each bin and 607 # what corresponding label we assign them to in the Bin weight dictionary, 608 # (if any). 609 mandatory_weights = {'xmin':'boundary_xmin', 'xmax':'boundary_xmax', 610 'central value':'central', 'dy':'stat_error'} 611 612 # ======================== 613 # Weight name parser RE's 614 # ======================== 615 # This marks the start of the line that defines the name of the weights 616 weight_header_start_re = re.compile('^##.*') 617 # This is the format of a weight name specifier. It is much more complicated 618 # than necessary because the HwU standard allows for spaces from within 619 # the name of a weight 620 weight_header_re = re.compile( 621 '&\s*(?P<wgt_name>(\S|(\s(?!\s*(&|$))))+)(\s(?!(&|$)))*') 622 623 # ================================ 624 # Histo weight specification RE's 625 # ================================ 626 # The start of a plot 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 # A given weight specifier 630 a_float_re = '[\+|-]?\d+(\.\d*)?([EeDd][\+|-]?\d+)?' 631 histo_bin_weight_re = re.compile('(?P<weight>%s)'%a_float_re) 632 # The end of a plot 633 histo_end_re = re.compile(r'^\s*<\\histogram>\s*$') 634 # A scale type of weight 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
639 - class ParseError(MadGraph5Error):
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 # Attempt to find the weight headers if not specified 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 # Indicate that the initialization of the histogram was unsuccessful 670 # by setting the BinList property to None. 671 super(Histogram,self).__setattr__('bins',None) 672 673 # Explicitly close the opened stream for clarity. 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
684 - def get_formatted_header(self):
685 """ Return a HwU formatted header for the weight label definition.""" 686 687 res = '##& xmin & xmax & central value & dy & ' 688 689 others = [] 690 for label in self.bins.weight_labels: 691 if label in ['central', 'stat_error']: 692 continue 693 if isinstance(label, str): 694 others.append(label) 695 elif isinstance(label, tuple): 696 others.append('muR=%4.2f muF=%4.2f'%(label[0],label[1])) 697 elif isinstance(label, int): 698 others.append('PDF= %d'%label) 699 700 return res+' & '.join(others)
701
702 - def get_HwU_source(self, print_header=True):
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 # Successful writeout 743 return True
744
745 - def test_plot_compability(self, other, consider_type=True):
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
765 - def parse_weight_header(cls, stream):
766 """ Read a given stream until it finds a header specifying the weights 767 and then returns them.""" 768 769 for line in stream: 770 if cls.weight_header_start_re.match(line): 771 header = [h.group('wgt_name') for h in 772 cls.weight_header_re.finditer(line)] 773 if any((name not in header) for name in cls.mandatory_weights): 774 raise HwU.ParseError, "The mandatory weight names %s were"\ 775 %str(cls.mandatory_weights.keys())+" are not all present"+\ 776 " in the following HwU header definition:\n %s"%line 777 778 # Apply replacement rules specified in mandatory_weights 779 header = [ (h if h not in cls.mandatory_weights else 780 cls.mandatory_weights[h]) for h in header ] 781 782 # We use a special rule for the weight labeled as a 783 # muR=2.0 muF=1.0 scale specification, in which case we store 784 # it as a tuple 785 for i, h in enumerate(header): 786 scale_wgt = HwU.weight_label_scale.match(h) 787 PDF_wgt = HwU.weight_label_PDF.match(h) 788 if scale_wgt: 789 header[i] = (float(scale_wgt.group('mur_fact')), 790 float(scale_wgt.group('muf_fact'))) 791 elif PDF_wgt: 792 header[i] = int(PDF_wgt.group('PDF_set')) 793 794 return header 795 796 raise HwU.ParseError, "The weight headers could not be found."
797 798
799 - def process_histogram_name(self, histogram_name):
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 # Now list all supported modifiers here 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
822 - def get_HwU_histogram_name(self, format='human'):
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
848 - def parse_one_histo_from_stream(self, stream, weight_header):
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 # Find the starting point of the stream 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 # We do not want to include auxiliary diagrams which would be 860 # recreated anyway. 861 if self.type == 'AUX': 862 continue 863 n_bins = int(start.group('n_bins')) 864 # Make sure to exclude the boundaries from the weight 865 # specification 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 # Now look for the bin weights definition 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 # For the HwU format, we know that exactly two 'weights' 889 # specified in the weight_header are in fact the boundary 890 # coordinate, so we must subtract two. 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 # Now jump to the next <\histo> tag. 904 for line_end in stream: 905 if HwU.histo_end_re.match(line_end): 906 # Finally, remove all the auxiliary weights 907 self.trim_auxiliary_weights() 908 # End of successful parsing this histogram, so return True. 909 return True 910 911 # Could not find a plot definition starter in this stream, return False 912 return False
913
914 - def trim_auxiliary_weights(self):
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
929 - def set_uncertainty(self, type='all_scale'):
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 # No envelope can be constructed, it is not worth adding the weights 962 return None 963 else: 964 # Place the new weight label last before the first tuple 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 # Now add the corresponding weight to all Bins 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 # use Hessian method (CTEQ & MSTW) 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 # use Gaussian method (NNPDF) 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 # Finally add them to the corresponding new weight 1010 bin.wgts[new_wgt_labels[0]] = pdf_down 1011 bin.wgts[new_wgt_labels[1]] = pdf_up 1012 1013 # And return the position in self.bins.weight_labels of the first 1014 # of the two new weight label added. 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
1054 - def get_x_optimal_range(cls, histo_list, weight_labels=None):
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 # If no list of weight labels to consider is given, use them all. 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
1080 - def get_y_optimal_range(cls,histo_list, labels=None, 1081 scale='LOG', Kratio = False):
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 # If no list of weight labels to consider is given, use them all. 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 # Filter out bin weights at *exactly* because they often 1098 # come from pathological division by zero for empty bins. 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 # If the maximum is too far from the 90% max, then take the partial max 1128 if (max-partial_max)>2.0*(partial_max-partial_min): 1129 y_max = partial_max 1130 else: 1131 y_max = max 1132 1133 # If the maximum is too far from the 90% max, then take the partial max 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 # Enforce the maximum if there is 5 bins or less 1149 if len(histo_list[0].bins) <= 5: 1150 y_min = min 1151 y_max = max 1152 1153 # Finally make sure the range has finite length 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 # Define here the number of line color schemes defined. If you need more, 1168 # simply define them in the gnuplot header and increase the number below. 1169 # It must be <= 9. 1170 number_line_colors_defined = 8 1171
1172 - def is_valid_element(self, obj):
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 # Attempt to find the weight headers if not specified 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 # Order the histograms according to their type. 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 # The command below is to first order them in alphabetical order, but it 1223 # is often better to keep the order of the original HwU source. 1224 # self.sort(key=lambda histo: '%s_%d'%(histo.title, 1225 # type_order.index(histo.type))) 1226 self.sort(key=ordering_function) 1227 1228 # Explicitly close the opened stream for clarity. 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 # If the format is just the raw HwU source, then simply write them 1255 # out all in sequence. 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 # Now we consider that we are attempting a gnuplot output. 1266 if format == 'gnuplot': 1267 gnuplot_stream = open(path+'.gnuplot','w') 1268 1269 # Now group all the identified matching histograms in a list 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 # Write the gnuplot header 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 # determine the gnuplot version 1423 try: 1424 import subprocess 1425 p = subprocess.Popen(['gnuplot', '--version'], \ 1426 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 1427 except OSError: 1428 # assume that version 4 of gnuplot is the default if 1429 # gnuplot could not be found 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 # Now output each group one by one 1440 # Block position keeps track of the gnuplot data_block index considered 1441 block_position = 0 1442 for histo_group in self: 1443 # Output this group 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 # Now write the tail of the gnuplot command file 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 # Now write result to stream and close it 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 # This function is to create the ratio histograms if the user turned off 1480 # correlations. 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 # It is ok to skip the warning here. 1489 # logger.debug('Warning:: A bin with finite weight '+ 1490 # 'was divided by a bin with zero weight.') 1491 new_wgts[label] = 0.0 1492 continue 1493 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 1494 return new_wgts
1495 1496 # First compute the ratio of all the histograms from the second to the 1497 # number_of_ratios+1 ones in the list to the first histogram. 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 # By setting its type to aux, we make sure this histogram will be 1516 # filtered out if the .HwU file output here would be re-loaded later. 1517 ratio_histos[-1].type = 'AUX' 1518 self.extend(ratio_histos) 1519 1520 # Compute scale variation envelope for all diagrams 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 # Now output the corresponding HwU histogram data 1548 for i, histo in enumerate(self): 1549 # Print the header the first time only 1550 HwU_out.extend(histo.get_HwU_source(\ 1551 print_header=(block_position==0 and i==0))) 1552 HwU_out.extend(['','']) 1553 1554 # First the global gnuplot header for this histogram group 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 # Now the header for each subhistogram 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 # Determine what weight to consider when computing the optimal 1593 # range for the y-axis. 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 # Add the global header which is now ready 1610 gnuplot_out.append(global_header%replacement_dic) 1611 1612 # Now add the main plot 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 # Force a linear scale if the detected range is negative 1620 if ymin< 0.0: 1621 self[0].y_axis_mode = 'LIN' 1622 1623 # Already add a margin on upper bound. 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 # Use default choise for the main histogram 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 # Now add the main layout 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 # And show scale variation if available 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 # And now PDF_variation if available 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 # Add the plot lines 1684 gnuplot_out.append(',\\\n'.join(plot_lines)) 1685 1686 # Now we can add the scale variation ratio 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 # This function is just to temporarily create the scale ratio histogram with 1693 # the hwu.combine function. 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 # It is ok to skip the warning here. 1703 # logger.debug('Warning:: A bin with finite weight '+ 1704 # 'was divided by a bin with zero weight.') 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 # Notice even though a ratio histogram is created here, it 1714 # is not actually used to plot the quantity in gnuplot, but just to 1715 # compute the y range. 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 # Add a margin on upper and lower bound. 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 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/3.0) 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 # 'set label "Relative uncertainties" font ",9" at graph 0.79, graph 0.13' 1743 # Simply don't add these lines if there are no uncertainties. 1744 # This meant uncessary extra work, but I no longer car at this point 1745 if not no_uncertainties: 1746 gnuplot_out.append(subhistogram_header%replacement_dic) 1747 1748 # Now add the first subhistogram 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 # Then the scale variations 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 # Add the plot lines 1773 if not no_uncertainties: 1774 gnuplot_out.append(',\\\n'.join(plot_lines)) 1775 1776 # We finish here when no ratio plot are asked for. 1777 if len(self)-n_histograms==0: 1778 # Now add the tail for this group 1779 gnuplot_out.extend(['','unset label','', 1780 '################################################################################']) 1781 # Return the starting data_block position for the next histogram group 1782 return block_position+len(self) 1783 1784 # We can finally add the last subhistograms for the ratios. 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 # Add a margin on upper and lower bound. 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 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/10.0) 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 # 'set label "NLO/LO (K-factor)" font ",9" at graph 0.82, graph 0.13' 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 # Now add the subhistograms 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 # Then the scale variations 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 # Add the plot lines 1849 gnuplot_out.append(',\\\n'.join(plot_lines)) 1850 1851 # Now add the tail for this group 1852 gnuplot_out.extend(['','unset label','', 1853 '################################################################################']) 1854 1855 # Return the starting data_block position for the next histogram group 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 # Firs option is to give a bit of the name of the source HwU file. 1973 #histo.type += " %s, #%d"%\ 1974 # (os.path.basename(arg).split('.')[0][:3],i+1) 1975 # But it is more elegant to give just the number. 1976 # Overwrite existing number if present. We assume here that one never 1977 # uses the '#' in its custom-defined types, which is a fair assumptions. 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 # First make sure the plots have the same weight labels and such 1996 hist.test_plot_compability(histo_list[j]) 1997 # Now let the histogram module do the magic and add them. 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