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  import StringIO 
  32  import subprocess 
  33  import xml.dom.minidom as minidom 
  34  from xml.parsers.expat import ExpatError as XMLParsingError 
  35   
  36  root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0] 
  37  sys.path.append(os.path.join(root_path))  
  38  sys.path.append(os.path.join(root_path,os.pardir)) 
  39  try: 
  40      # import from madgraph directory 
  41      import madgraph.various.misc as misc 
  42      from madgraph import MadGraph5Error 
  43      logger = logging.getLogger("madgraph.various.histograms") 
  44   
  45  except ImportError, error: 
  46      # import from madevent directory 
  47      import internal.misc as misc     
  48      from internal import MadGraph5Error 
  49      logger = logging.getLogger("internal.histograms") 
50 51 # I copy the Physics object list here so as not to add a whole dependency to 52 # base_objects which is annoying when using this histograms module from the 53 # bin/internal location of a process output (i.e. outside an MG5_aMC env.) 54 55 #=============================================================================== 56 # PhysicsObjectList 57 #=============================================================================== 58 -class histograms_PhysicsObjectList(list):
59 """A class to store lists of physics object.""" 60
61 - class PhysicsObjectListError(Exception):
62 """Exception raised if an error occurs in the definition 63 or execution of a physics object list.""" 64 pass
65
66 - def __init__(self, init_list=None):
67 """Creates a new particle list object. If a list of physics 68 object is given, add them.""" 69 70 list.__init__(self) 71 72 if init_list is not None: 73 for object in init_list: 74 self.append(object)
75
76 - def append(self, object):
77 """Appends an element, but test if valid before.""" 78 79 assert self.is_valid_element(object), \ 80 "Object %s is not a valid object for the current list" % repr(object) 81 82 list.append(self, object)
83 84
85 - def is_valid_element(self, obj):
86 """Test if object obj is a valid element for the list.""" 87 return True
88
89 - def __str__(self):
90 """String representation of the physics object list object. 91 Outputs valid Python with improved format.""" 92 93 mystr = '[' 94 95 for obj in self: 96 mystr = mystr + str(obj) + ',\n' 97 98 mystr = mystr.rstrip(',\n') 99 100 return mystr + ']'
101 #===============================================================================
102 103 -class Bin(object):
104 """A class to store Bin related features and function. 105 """ 106
107 - def __init__(self, boundaries=(0.0,0.0), wgts=None, n_entries = 0):
108 """ Initializes an empty bin, necessarily with boundaries. """ 109 110 self.boundaries = boundaries 111 self.n_entries = n_entries 112 if not wgts: 113 self.wgts = {'central':0.0} 114 else: 115 self.wgts = wgts
116
117 - def __setattr__(self, name, value):
118 if name=='boundaries': 119 if not isinstance(value, tuple): 120 raise MadGraph5Error, "Argument '%s' for bin property "+\ 121 "'boundaries' must be a tuple."%str(value) 122 else: 123 for coordinate in value: 124 if isinstance(coordinate, tuple): 125 for dim in coordinate: 126 if not isinstance(dim, float): 127 raise MadGraph5Error, "Coordinate '%s' of the bin"+\ 128 " boundary '%s' must be a float."%str(dim,value) 129 elif not isinstance(coordinate, float): 130 raise MadGraph5Error, "Element '%s' of the bin boundaries"+\ 131 " specified must be a float."%str(bound) 132 elif name=='wgts': 133 if not isinstance(value, dict): 134 raise MadGraph5Error, "Argument '%s' for bin uncertainty "+\ 135 "'wgts' must be a dictionary."%str(value) 136 for val in value.values(): 137 if not isinstance(val,float): 138 raise MadGraph5Error, "The bin weight value '%s' is not a "+\ 139 "float."%str(val) 140 141 super(Bin, self).__setattr__(name,value)
142
143 - def get_weight(self, key='central'):
144 """ Accesses a specific weight from this bin.""" 145 try: 146 return self.wgts[key] 147 except KeyError: 148 raise MadGraph5Error, "Weight with ID '%s' is not defined for"+\ 149 " this bin"%str(key)
150
151 - def set_weight(self, wgt, key='central'):
152 """ Accesses a specific weight from this bin.""" 153 154 # an assert is used here in this intensive function, so as to avoid 155 # slow-down when not in debug mode. 156 assert(isinstance(wgt, float)) 157 158 try: 159 self.wgts[key] = wgt 160 except KeyError: 161 raise MadGraph5Error, "Weight with ID '%s' is not defined for"+\ 162 " this bin"%str(key)
163
164 - def addEvent(self, weights = 1.0):
165 """ Add an event to this bin. """ 166 167 168 if isinstance(weights, float): 169 weights = {'central': weights} 170 171 for key in weights: 172 if key == 'stat_error': 173 continue 174 try: 175 self.wgts[key] += weights[key] 176 except KeyError: 177 raise MadGraph5Error('The event added defines the weight '+ 178 '%s which was not '%key+'registered in this histogram.') 179 180 self.n_entries += 1 181 182 if 'stat_error' not in weights: 183 self.wgts['stat_error'] = self.wgts['central']/math.sqrt(float(self.n_entries)) 184 else: 185 self.wgts['stat_error'] = math.sqrt( self.wgts['stat_error']**2 + 186 weights['stat_error']**2 )
187
188 - def nice_string(self, order=None, short=True):
189 """ Nice representation of this Bin. 190 One can order the weight according to the argument if provided.""" 191 192 res = ["Bin boundaries : %s"%str(self.boundaries)] 193 if not short: 194 res.append("Bin weights :") 195 if order is None: 196 label_list = self.wgts.keys() 197 else: 198 label_list = order 199 200 for label in label_list: 201 try: 202 res.append(" -> '%s' : %4.3e"%(str(label),self.wgts[label])) 203 except KeyError: 204 pass 205 else: 206 res.append("Central weight : %4.3e"%self.get_weight()) 207 208 return '\n'.join(res)
209
210 - def alter_weights(self, func):
211 """ Apply a given function to all bin weights.""" 212 self.wgts = func(self.wgts)
213 214 @classmethod
215 - def combine(cls, binA, binB, func):
216 """ Function to combine two bins. The 'func' is such that it takes 217 two weight dictionaries and merge them into one.""" 218 219 res_bin = cls() 220 if binA.boundaries != binB.boundaries: 221 raise MadGraph5Error, 'The two bins to combine have'+\ 222 ' different boundaries, %s!=%s.'%(str(binA.boundaries),str(binB.boundaries)) 223 res_bin.boundaries = binA.boundaries 224 225 try: 226 res_bin.wgts = func(binA.wgts, binB.wgts) 227 except Exception as e: 228 raise MadGraph5Error, "When combining two bins, the provided"+\ 229 " function '%s' triggered the following error:\n\"%s\"\n"%\ 230 (func.__name__,str(e))+" when combining the following two bins:\n"+\ 231 binA.nice_string(short=False)+"\n and \n"+binB.nice_string(short=False) 232 233 return res_bin
234
235 -class BinList(histograms_PhysicsObjectList):
236 """ A class implementing features related to a list of Bins. """ 237
238 - def __init__(self, list = [], bin_range = None, 239 weight_labels = None):
240 """ Initialize a list of Bins. It is possible to define the range 241 as a list of three floats: [min_x, max_x, bin_width]""" 242 243 self.weight_labels = weight_labels 244 if bin_range: 245 # Set the default weight_labels to something meaningful 246 if not self.weight_labels: 247 self.weight_labels = ['central', 'stat_error'] 248 if len(bin_range)!=3 or any(not isinstance(f, float) for f in bin_range): 249 raise MadGraph5Error, "The range argument to build a BinList"+\ 250 " must be a list of exactly three floats." 251 current = bin_range[0] 252 while current < bin_range[1]: 253 self.append(Bin(boundaries = 254 (current, min(current+bin_range[2],bin_range[1])), 255 wgts = dict((wgt,0.0) for wgt in weight_labels))) 256 current += bin_range[2] 257 else: 258 super(BinList, self).__init__(list)
259
260 - def is_valid_element(self, obj):
261 """Test whether specified object is of the right type for this list.""" 262 263 return isinstance(obj, Bin)
264
265 - def __setattr__(self, name, value):
266 if name=='weight_labels': 267 if not value is None and not isinstance(value, list): 268 raise MadGraph5Error, "Argument '%s' for BinList property '%s'"\ 269 %(str(value),name)+' must be a list.' 270 elif not value is None: 271 for label in value: 272 if all((not isinstance(label,cls)) for cls in \ 273 [str, int, float, tuple]): 274 raise MadGraph5Error, "Element '%s' of the BinList property '%s'"\ 275 %(str(value),name)+' must be a string, an '+\ 276 'integer, a float or a tuple of float.' 277 if isinstance(label, tuple): 278 if len(label)>=1: 279 if not isinstance(label[0], (float, str)): 280 raise MadGraph5Error, "Argument "+\ 281 "'%s' for BinList property '%s'"%(str(value),name)+\ 282 ' can be a tuple, but its first element must be a float or string.' 283 for elem in label[1:]: 284 if not isinstance(elem, (float,int,str)): 285 raise MadGraph5Error, "Argument "+\ 286 "'%s' for BinList property '%s'"%(str(value),name)+\ 287 ' can be a tuple, but its elements past the first one must be either floats, integers or strings' 288 289 290 super(BinList, self).__setattr__(name, value)
291
292 - def append(self, object):
293 """Appends an element, but test if valid before.""" 294 295 super(BinList,self).append(object) 296 # Assign the weight labels to those of the first bin added 297 if len(self)==1 and self.weight_labels is None: 298 self.weight_labels = object.wgts.keys()
299
300 - def nice_string(self, short=True):
301 """ Nice representation of this BinList.""" 302 303 res = ["Number of bin in the list : %d"%len(self)] 304 res.append("Registered weight labels : [%s]"%(', '.join([ 305 str(label) for label in self.weight_labels]))) 306 if not short: 307 for i, bin in enumerate(self): 308 res.append('Bin number %d :'%i) 309 res.append(bin.nice_string(order=self.weight_labels, short=short)) 310 311 return '\n'.join(res)
312
313 -class Histogram(object):
314 """A mother class for all specific implementations of Histogram conventions 315 """ 316 317 allowed_dimensions = None 318 allowed_types = [] 319 allowed_axis_modes = ['LOG','LIN'] 320
321 - def __init__(self, title = "NoName", n_dimensions = 2, type=None, 322 x_axis_mode = 'LIN', y_axis_mode = 'LOG', bins=None):
323 """ Initializes an empty histogram, possibly specifying 324 > a title 325 > a number of dimensions 326 > a bin content 327 """ 328 329 self.title = title 330 self.dimension = n_dimensions 331 if not bins: 332 self.bins = BinList([]) 333 else: 334 self.bins = bins 335 self.type = type 336 self.x_axis_mode = x_axis_mode 337 self.y_axis_mode = y_axis_mode
338
339 - def __setattr__(self, name, value):
340 if name=='title': 341 if not isinstance(value, str): 342 raise MadGraph5Error, "Argument '%s' for the histogram property "+\ 343 "'title' must be a string."%str(value) 344 elif name=='dimension': 345 if not isinstance(value, int): 346 raise MadGraph5Error, "Argument '%s' for histogram property "+\ 347 "'dimension' must be an integer."%str(value) 348 if self.allowed_dimensions and value not in self.allowed_dimensions: 349 raise MadGraph5Error, "%i-Dimensional histograms not supported "\ 350 %value+"by class '%s'. Supported dimensions are '%s'."\ 351 %(self.__class__.__name__,self.allowed_dimensions) 352 elif name=='bins': 353 if not isinstance(value, BinList): 354 raise MadGraph5Error, "Argument '%s' for histogram property "+\ 355 "'bins' must be a BinList."%str(value) 356 else: 357 for bin in value: 358 if not isinstance(bin, Bin): 359 raise MadGraph5Error, "Element '%s' of the "%str(bin)+\ 360 " histogram bin list specified must be a bin." 361 elif name=='type': 362 if not (value is None or value in self.allowed_types or 363 self.allowed_types==[]): 364 raise MadGraph5Error, "Argument '%s' for histogram"%str(value)+\ 365 " property 'type' must be a string in %s or None."\ 366 %([str(t) for t in self.allowed_types]) 367 elif name in ['x_axis_mode','y_axis_mode']: 368 if not value in self.allowed_axis_modes: 369 raise MadGraph5Error, "Attribute '%s' of the histogram"%str(name)+\ 370 " must be in [%s], ('%s' given)"%(str(self.allowed_axis_modes), 371 str(value)) 372 373 super(Histogram, self).__setattr__(name,value)
374
375 - def nice_string(self, short=True):
376 """ Nice representation of this histogram. """ 377 378 res = ['<%s> histogram:'%self.__class__.__name__] 379 res.append(' -> title : "%s"'%self.title) 380 res.append(' -> dimensions : %d'%self.dimension) 381 if not self.type is None: 382 res.append(' -> type : %s'%self.type) 383 else: 384 res.append(' -> type : None') 385 res.append(' -> (x, y)_axis : ( %s, %s)'%\ 386 (tuple([('Linear' if mode=='LIN' else 'Logarithmic') for mode in \ 387 [self.x_axis_mode, self.y_axis_mode]]))) 388 if short: 389 res.append(' -> n_bins : %s'%len(self.bins)) 390 res.append(' -> weight types : [ %s ]'% 391 (', '.join([str(label) for label in self.bins.weight_labels]) \ 392 if (not self.bins.weight_labels is None) else 'None')) 393 394 else: 395 res.append(' -> Bins content :') 396 res.append(self.bins.nice_string(short)) 397 398 return '\n'.join(res)
399
400 - def alter_weights(self, func):
401 """ Apply a given function to all bin weights.""" 402 403 for bin in self.bins: 404 bin.alter_weights(func)
405 406 @classmethod
407 - def combine(cls, histoA, histoB, func):
408 """ Function to combine two Histograms. The 'func' is such that it takes 409 two weight dictionaries and merge them into one.""" 410 411 res_histogram = copy.copy(histoA) 412 if histoA.title != histoB.title: 413 res_histogram.title = "[%s]__%s__[%s]"%(histoA.title,func.__name__, 414 histoB.title) 415 else: 416 res_histogram.title = histoA.title 417 418 res_histogram.bins = BinList([]) 419 if len(histoA.bins)!=len(histoB.bins): 420 raise MadGraph5Error, 'The two histograms to combine have a '+\ 421 'different number of bins, %d!=%d.'%(len(histoA.bins),len(histoB.bins)) 422 423 if histoA.dimension!=histoB.dimension: 424 raise MadGraph5Error, 'The two histograms to combine have a '+\ 425 'different dimensions, %d!=%d.'%(histoA.dimension,histoB.dimension) 426 res_histogram.dimension = histoA.dimension 427 428 for i, bin in enumerate(histoA.bins): 429 res_histogram.bins.append(Bin.combine(bin, histoB.bins[i],func)) 430 431 # Reorder the weight labels as in the original histogram and add at the 432 # end the new ones which resulted from the combination, in a sorted order 433 res_histogram.bins.weight_labels = [label for label in histoA.bins.\ 434 weight_labels if label in res_histogram.bins.weight_labels] + \ 435 sorted([label for label in res_histogram.bins.weight_labels if\ 436 label not in histoA.bins.weight_labels]) 437 438 439 return res_histogram
440 441 # ================================================== 442 # Some handy function for Histogram combination 443 # ================================================== 444 @staticmethod
445 - def MULTIPLY(wgtsA, wgtsB):
446 """ Apply the multiplication to the weights of two bins.""" 447 448 new_wgts = {} 449 450 new_wgts['stat_error'] = math.sqrt( 451 (wgtsA['stat_error']*wgtsB['central'])**2+ 452 (wgtsA['central']*wgtsB['stat_error'])**2) 453 454 for label, wgt in wgtsA.items(): 455 if label=='stat_error': 456 continue 457 new_wgts[label] = wgt*wgtsB[label] 458 459 return new_wgts
460 461 @staticmethod
462 - def DIVIDE(wgtsA, wgtsB):
463 """ Apply the division to the weights of two bins.""" 464 465 new_wgts = {} 466 if wgtsB['central'] == 0.0: 467 new_wgts['stat_error'] = 0.0 468 else: 469 # d(x/y) = ( (dx/y)**2 + ((x*dy)/(y**2))**2 )**0.5 470 new_wgts['stat_error'] = math.sqrt(wgtsA['stat_error']**2+ 471 ((wgtsA['central']*wgtsB['stat_error'])/ 472 wgtsB['central'])**2)/wgtsB['central'] 473 474 for label, wgt in wgtsA.items(): 475 if label=='stat_error': 476 continue 477 if wgtsB[label]==0.0 and wgt==0.0: 478 new_wgts[label] = 0.0 479 elif wgtsB[label]==0.0: 480 # This situation is most often harmless and just happens in regions 481 # with low statistics, so I'll bypass the warning here. 482 # logger.debug('Warning:: A bin with finite weight was divided '+\ 483 # 'by a bin with zero weight.') 484 new_wgts[label] = 0.0 485 else: 486 new_wgts[label] = wgt/wgtsB[label] 487 488 return new_wgts
489 490 @staticmethod
491 - def OPERATION(wgtsA, wgtsB, wgt_operation, stat_error_operation):
492 """ Apply the operation to the weights of two bins. Notice that we 493 assume here the two dict operands to have the same weight labels. 494 The operation is a function that takes two floats as input.""" 495 496 new_wgts = {} 497 for label, wgt in wgtsA.items(): 498 if label!='stat_error': 499 new_wgts[label] = wgt_operation(wgt, wgtsB[label]) 500 else: 501 new_wgts[label] = stat_error_operation(wgt, wgtsB[label]) 502 # if new_wgts[label]>1.0e+10: 503 # print "stat_error_operation is ",stat_error_operation.__name__ 504 # print " inputs were ",wgt, wgtsB[label] 505 # print "for label", label 506 507 return new_wgts
508 509 510 @staticmethod
511 - def SINGLEHISTO_OPERATION(wgts, wgt_operation, stat_error_operation):
512 """ Apply the operation to the weights of a *single* bins. 513 The operation is a function that takes a single float as input.""" 514 515 new_wgts = {} 516 for label, wgt in wgts.items(): 517 if label!='stat_error': 518 new_wgts[label] = wgt_operation(wgt) 519 else: 520 new_wgts[label] = stat_error_operation(wgt) 521 522 return new_wgts
523 524 @staticmethod
525 - def ADD(wgtsA, wgtsB):
526 """ Implements the addition using OPERATION above. """ 527 return Histogram.OPERATION(wgtsA, wgtsB, 528 (lambda a,b: a+b), 529 (lambda a,b: math.sqrt(a**2+b**2)))
530 531 @staticmethod
532 - def SUBTRACT(wgtsA, wgtsB):
533 """ Implements the subtraction using OPERATION above. """ 534 535 return Histogram.OPERATION(wgtsA, wgtsB, 536 (lambda a,b: a-b), 537 (lambda a,b: math.sqrt(a**2+b**2)))
538 539 @staticmethod
540 - def RESCALE(factor):
541 """ Implements the rescaling using SINGLEHISTO_OPERATION above. """ 542 543 def rescaler(wgts): 544 return Histogram.SINGLEHISTO_OPERATION(wgts,(lambda a: a*factor), 545 (lambda a: a*factor))
546 547 return rescaler
548 549 @staticmethod
550 - def OFFSET(offset):
551 """ Implements the offset using SINGLEBIN_OPERATION above. """ 552 def offsetter(wgts): 553 return Histogram.SINGLEHISTO_OPERATION( 554 wgts,(lambda a: a+offset),(lambda a: a))
555 556 return offsetter 557
558 - def __add__(self, other):
559 """ Overload the plus function. """ 560 if isinstance(other, Histogram): 561 return self.__class__.combine(self,other,Histogram.ADD) 562 elif isinstance(other, int) or isinstance(other, float): 563 self.alter_weights(Histogram.OFFSET(float(other))) 564 return self 565 else: 566 return NotImplemented, 'Histograms can only be added to other '+\ 567 ' histograms or scalars.'
568
569 - def __sub__(self, other):
570 """ Overload the subtraction function. """ 571 if isinstance(other, Histogram): 572 return self.__class__.combine(self,other,Histogram.SUBTRACT) 573 elif isinstance(other, int) or isinstance(other, float): 574 self.alter_weights(Histogram.OFFSET(-float(other))) 575 return self 576 else: 577 return NotImplemented, 'Histograms can only be subtracted to other '+\ 578 ' histograms or scalars.'
579
580 - def __mul__(self, other):
581 """ Overload the multiplication function. """ 582 if isinstance(other, Histogram): 583 return self.__class__.combine(self,other,Histogram.MULTIPLY) 584 elif isinstance(other, int) or isinstance(other, float): 585 self.alter_weights(Histogram.RESCALE(float(other))) 586 return self 587 else: 588 return NotImplemented, 'Histograms can only be multiplied to other '+\ 589 ' histograms or scalars.'
590
591 - def __div__(self, other):
592 """ Overload the multiplication function. """ 593 if isinstance(other, Histogram): 594 return self.__class__.combine(self,other,Histogram.DIVIDE) 595 elif isinstance(other, int) or isinstance(other, float): 596 self.alter_weights(Histogram.RESCALE(1.0/float(other))) 597 return self 598 else: 599 return NotImplemented, 'Histograms can only be divided with other '+\ 600 ' histograms or scalars.'
601 602 __truediv__ = __div__ 603
604 -class HwU(Histogram):
605 """A concrete implementation of an histogram plots using the HwU format for 606 reading/writing histogram content.""" 607 608 allowed_dimensions = [2] 609 allowed_types = [] 610 611 # For now only HwU output format is implemented. 612 output_formats_implemented = ['HwU','gnuplot'] 613 # Lists the mandatory named weights that must be specified for each bin and 614 # what corresponding label we assign them to in the Bin weight dictionary, 615 # (if any). 616 mandatory_weights = {'xmin':'boundary_xmin', 'xmax':'boundary_xmax', 617 'central value':'central', 'dy':'stat_error'} 618 619 # ======================== 620 # Weight name parser RE's 621 # ======================== 622 # This marks the start of the line that defines the name of the weights 623 weight_header_start_re = re.compile('^##.*') 624 # This is the format of a weight name specifier. It is much more complicated 625 # than necessary because the HwU standard allows for spaces from within 626 # the name of a weight 627 weight_header_re = re.compile( 628 '&\s*(?P<wgt_name>(\S|(\s(?!\s*(&|$))))+)(\s(?!(&|$)))*') 629 630 # ================================ 631 # Histo weight specification RE's 632 # ================================ 633 # The start of a plot 634 histo_start_re = re.compile('^\s*<histogram>\s*(?P<n_bins>\d+)\s*"\s*'+ 635 '(?P<histo_name>(\S|(\s(?!\s*")))+)\s*"\s*$') 636 # A given weight specifier 637 a_float_re = '[\+|-]?\d+(\.\d*)?([EeDd][\+|-]?\d+)?' 638 histo_bin_weight_re = re.compile('(?P<weight>%s|NaN)'%a_float_re,re.IGNORECASE) 639 a_int_re = '[\+|-]?\d+' 640 641 # The end of a plot 642 histo_end_re = re.compile(r'^\s*<\\histogram>\s*$') 643 # A scale type of weight 644 weight_label_scale = re.compile('^\s*mur\s*=\s*(?P<mur_fact>%s)'%a_float_re+\ 645 '\s*muf\s*=\s*(?P<muf_fact>%s)\s*$'%a_float_re,re.IGNORECASE) 646 weight_label_PDF = re.compile('^\s*PDF\s*=\s*(?P<PDF_set>\d+)\s*$') 647 weight_label_PDF_XML = re.compile('^\s*pdfset\s*=\s*(?P<PDF_set>\d+)\s*$') 648 weight_label_TMS = re.compile('^\s*TMS\s*=\s*(?P<Merging_scale>%s)\s*$'%a_float_re) 649 weight_label_alpsfact = re.compile('^\s*alpsfact\s*=\s*(?P<alpsfact>%s)\s*$'%a_float_re, 650 re.IGNORECASE) 651 652 weight_label_scale_adv = re.compile('^\s*dyn\s*=\s*(?P<dyn_choice>%s)'%a_int_re+\ 653 '\s*mur\s*=\s*(?P<mur_fact>%s)'%a_float_re+\ 654 '\s*muf\s*=\s*(?P<muf_fact>%s)\s*$'%a_float_re,re.IGNORECASE) 655 weight_label_PDF_adv = re.compile('^\s*PDF\s*=\s*(?P<PDF_set>\d+)\s+(?P<PDF_set_cen>\S+)\s*$') 656 657
658 - class ParseError(MadGraph5Error):
659 """a class for histogram data parsing errors"""
660 661 @classmethod
662 - def get_HwU_wgt_label_type(cls, wgt_label):
663 """ From the format of the weight label given in argument, it returns 664 a string identifying the type of standard weight it is.""" 665 666 if isinstance(wgt_label,str): 667 return 'UNKNOWN_TYPE' 668 if isinstance(wgt_label,tuple): 669 if len(wgt_label)==0: 670 return 'UNKNOWN_TYPE' 671 if isinstance(wgt_label[0],float): 672 return 'murmuf_scales' 673 if isinstance(wgt_label[0],str): 674 return wgt_label[0] 675 if isinstance(wgt_label,float): 676 return 'merging_scale' 677 if isinstance(wgt_label,int): 678 return 'pdfset' 679 # No clue otherwise 680 return 'UNKNOWN_TYPE'
681 682
683 - def __init__(self, file_path=None, weight_header=None, 684 raw_labels=False, consider_reweights='ALL', **opts):
685 """ Read one plot from a file_path or a stream. Notice that this 686 constructor only reads one, and the first one, of the plots specified. 687 If file_path was a path in argument, it would then close the opened stream. 688 If file_path was a stream in argument, it would leave it open. 689 The option weight_header specifies an ordered list of weight names 690 to appear in the file specified. 691 The option 'raw_labels' specifies that one wants to import the 692 histogram data with no treatment of the weight labels at all 693 (this is used for the matplotlib output).""" 694 695 super(HwU, self).__init__(**opts) 696 697 self.dimension = 2 698 699 if file_path is None: 700 return 701 elif isinstance(file_path, str): 702 stream = open(file_path,'r') 703 elif isinstance(file_path, file): 704 stream = file_path 705 else: 706 raise MadGraph5Error, "Argument file_path '%s' for HwU init"\ 707 %str(file_path)+"ialization must be either a file path or a stream." 708 709 # Attempt to find the weight headers if not specified 710 if not weight_header: 711 weight_header = HwU.parse_weight_header(stream, raw_labels=raw_labels) 712 713 if not self.parse_one_histo_from_stream(stream, weight_header, 714 consider_reweights=consider_reweights, raw_labels=raw_labels): 715 # Indicate that the initialization of the histogram was unsuccessful 716 # by setting the BinList property to None. 717 super(Histogram,self).__setattr__('bins',None) 718 719 # Explicitly close the opened stream for clarity. 720 if isinstance(file_path, str): 721 stream.close()
722
723 - def addEvent(self, x_value, weights = 1.0):
724 """ Add an event to the current plot. """ 725 726 for bin in self.bins: 727 if bin.boundaries[0] <= x_value < bin.boundaries[1]: 728 bin.addEvent(weights = weights)
729
730 - def get(self, name):
731 732 if name == 'bins': 733 return [b.boundaries[0] for b in self.bins] 734 else: 735 return [b.wgts[name] for b in self.bins]
736
737 - def get_uncertainty_band(self, selector, mode=0):
738 """return two list of entry one with the minimum and one with the maximum value. 739 selector can be: 740 - a regular expression on the label name 741 - a function returning T/F (applying on the label name) 742 - a list of labels 743 - a keyword 744 """ 745 746 # find the set of weights to consider 747 if isinstance(selector, str): 748 if selector == 'QCUT': 749 selector = r'^Weight_MERGING=[\d]*[.]?\d*$' 750 elif selector == 'SCALE': 751 selector = r'(MUF=\d*[.]?\d*_MUR=([^1]\d*|1\d+)_PDF=\d*)[.]?\d*|(MUF=([^1]\d*|1\d+)[.]?\d*_MUR=\d*[.]?\d*_PDF=\d*)' 752 elif selector == 'ALPSFACT': 753 selector = r'ALPSFACT' 754 elif selector == 'PDF': 755 selector = r'MUF=1_MUR=1_PDF=(\d*)' 756 if not mode: 757 pdfs = [int(re.findall(selector, n)[0]) for n in self.bins[0].wgts if re.search(selector,n, re.IGNORECASE)] 758 min_pdf, max_pdf = min(pdfs), max(pdfs) 759 if max_pdf - min_pdf > 100: 760 mode == 'min/max' 761 elif max_pdf <= 90000: 762 mode = 'hessian' 763 else: 764 mode = 'gaussian' 765 selections = [n for n in self.bins[0].wgts if re.search(selector,n, re.IGNORECASE)] 766 elif hasattr(selector, '__call__'): 767 selections = [n for n in self.bins[0].wgts if selector(n)] 768 elif isinstance(selector, (list, tuple)): 769 selections = selector 770 771 # find the way to find the minimal/maximal curve 772 if not mode: 773 mode = 'min/max' 774 775 # build the collection of values 776 values = [] 777 for s in selections: 778 values.append(self.get(s)) 779 780 #sanity check 781 if not len(values): 782 return [0] * len(self.bins), [0]* len(self.bins) 783 elif len(values) ==1: 784 return values[0], values[0] 785 786 787 # Start the real work 788 if mode == 'min/max': 789 min_value, max_value = [], [] 790 for i in xrange(len(values[0])): 791 data = [values[s][i] for s in xrange(len(values))] 792 min_value.append(min(data)) 793 max_value.append(max(data)) 794 elif mode == 'gaussian': 795 # use Gaussian method (NNPDF) 796 min_value, max_value = [], [] 797 for i in xrange(len(values[0])): 798 pdf_stdev = 0.0 799 data = [values[s][i] for s in xrange(len(values))] 800 sdata = sum(data) 801 sdata2 = sum(x**2 for x in data) 802 pdf_stdev = math.sqrt(sdata2 -sdata**2/float(len(values)-2)) 803 min_value.append(sdata - pdf_stdev) 804 max_value.append(sdata + pdf_stdev) 805 806 elif mode == 'hessian': 807 # For old PDF this is based on the set ordering -> 808 #need to order the pdf sets: 809 pdfs = [(int(re.findall(selector, n)[0]),n) for n in self.bins[0].wgts if re.search(selector,n, re.IGNORECASE)] 810 pdfs.sort() 811 812 # check if the central was put or not in this sets: 813 if len(pdfs) % 2: 814 # adding the central automatically 815 pdf1 = pdfs[0][0] 816 central = pdf1 -1 817 name = pdfs[0][1].replace(str(pdf1), str(central)) 818 central = self.get(name) 819 else: 820 central = self.get(pdfs.pop(0)[1]) 821 822 #rebuilt the collection of values but this time ordered correctly 823 values = [] 824 for _, name in pdfs: 825 values.append(self.get(name)) 826 827 #Do the computation 828 min_value, max_value = [], [] 829 for i in xrange(len(values[0])): 830 pdf_up = 0 831 pdf_down = 0 832 cntrl_val = central[i] 833 for s in range(int((len(pdfs))/2)): 834 pdf_up += max(0.0,values[2*s][i] - cntrl_val, 835 values[2*s+1][i] - cntrl_val)**2 836 pdf_down += max(0.0,cntrl_val - values[2*s][i], 837 cntrl_val - values[2*s+1][i])**2 838 839 min_value.append(cntrl_val - math.sqrt(pdf_down)) 840 max_value.append(cntrl_val + math.sqrt(pdf_up)) 841 842 843 844 845 return min_value, max_value
846
847 - def get_formatted_header(self):
848 """ Return a HwU formatted header for the weight label definition.""" 849 850 res = '##& xmin & xmax & central value & dy & ' 851 852 others = [] 853 for label in self.bins.weight_labels: 854 if label in ['central', 'stat_error']: 855 continue 856 label_type = HwU.get_HwU_wgt_label_type(label) 857 if label_type == 'UNKNOWN_TYPE': 858 others.append(label) 859 elif label_type == 'scale': 860 others.append('muR=%6.3f muF=%6.3f'%(label[1],label[2])) 861 elif label_type == 'scale_adv': 862 others.append('dyn=%i muR=%6.3f muF=%6.3f'%(label[1],label[2],label[3])) 863 elif label_type == 'merging_scale': 864 others.append('TMS=%4.2f'%label[1]) 865 elif label_type == 'pdf': 866 others.append('PDF=%i'%(label[1])) 867 elif label_type == 'pdf_adv': 868 others.append('PDF=%i %s'%(label[1],label[2])) 869 elif label_type == 'alpsfact': 870 others.append('alpsfact=%d'%label[1]) 871 872 return res+' & '.join(others)
873
874 - def get_HwU_source(self, print_header=True):
875 """ Returns the string representation of this histogram using the 876 HwU standard.""" 877 878 res = [] 879 if print_header: 880 res.append(self.get_formatted_header()) 881 res.extend(['']) 882 res.append('<histogram> %s "%s"'%(len(self.bins), 883 self.get_HwU_histogram_name(format='HwU'))) 884 for bin in self.bins: 885 res.append(' '.join('%+16.7e'%wgt for wgt in list(bin.boundaries)+ 886 [bin.wgts['central'],bin.wgts['stat_error']])) 887 res[-1] += ' '.join('%+16.7e'%bin.wgts[key] for key in 888 self.bins.weight_labels if key not in ['central','stat_error']) 889 res.append('<\histogram>') 890 return res
891
892 - def output(self, path=None, format='HwU', print_header=True):
893 """ Ouput this histogram to a file, stream or string if path is kept to 894 None. The supported format are for now. Chose whether to print the header 895 or not.""" 896 897 if not format in HwU.output_formats_implemented: 898 raise MadGraph5Error, "The specified output format '%s'"%format+\ 899 " is not yet supported. Supported formats are %s."\ 900 %HwU.output_formats_implemented 901 902 if format == 'HwU': 903 str_output_list = self.get_HwU_source(print_header=print_header) 904 905 if path is None: 906 return '\n'.join(str_output_list) 907 elif isinstance(path, str): 908 stream = open(path,'w') 909 stream.write('\n'.join(str_output_list)) 910 stream.close() 911 elif isinstance(path, file): 912 path.write('\n'.join(str_output_list)) 913 914 # Successful writeout 915 return True
916
917 - def test_plot_compability(self, other, consider_type=True, 918 consider_unknown_weight_labels=True):
919 """ Test whether the defining attributes of self are identical to histo, 920 typically to make sure that they are the same plots but from different 921 runs, and they can be summed safely. We however don't want to 922 overload the __eq__ because it is still a more superficial check.""" 923 924 this_known_weight_labels = [label for label in self.bins.weight_labels if 925 HwU.get_HwU_wgt_label_type(label)!='UNKNOWN_TYPE'] 926 other_known_weight_labels = [label for label in other.bins.weight_labels if 927 HwU.get_HwU_wgt_label_type(label)!='UNKNOWN_TYPE'] 928 this_unknown_weight_labels = [label for label in self.bins.weight_labels if 929 HwU.get_HwU_wgt_label_type(label)=='UNKNOWN_TYPE'] 930 other_unknown_weight_labels = [label for label in other.bins.weight_labels if 931 HwU.get_HwU_wgt_label_type(label)=='UNKNOWN_TYPE'] 932 933 if self.title != other.title or \ 934 set(this_known_weight_labels) != set(other_known_weight_labels) or \ 935 (set(this_unknown_weight_labels) != set(other_unknown_weight_labels) and\ 936 consider_unknown_weight_labels) or \ 937 (self.type != other.type and consider_type) or \ 938 self.x_axis_mode != self.x_axis_mode or \ 939 self.y_axis_mode != self.y_axis_mode or \ 940 any(b1.boundaries!=b2.boundaries for (b1,b2) in \ 941 zip(self.bins,other.bins)): 942 return False 943 944 return True
945 946 947 948 @classmethod
949 - def parse_weight_header(cls, stream, raw_labels=False):
950 """ Read a given stream until it finds a header specifying the weights 951 and then returns them.""" 952 953 for line in stream: 954 if cls.weight_header_start_re.match(line): 955 header = [h.group('wgt_name') for h in 956 cls.weight_header_re.finditer(line)] 957 if any((name not in header) for name in cls.mandatory_weights): 958 raise HwU.ParseError, "The mandatory weight names %s were"\ 959 %str(cls.mandatory_weights.keys())+" are not all present"+\ 960 " in the following HwU header definition:\n %s"%line 961 962 # Apply replacement rules specified in mandatory_weights 963 if raw_labels: 964 # If using raw labels, then just change the name of the 965 # labels corresponding to the bin edges 966 header = [ (h if h not in ['xmin','xmax'] else 967 cls.mandatory_weights[h]) for h in header ] 968 # And return it with no further modification 969 return header 970 else: 971 header = [ (h if h not in cls.mandatory_weights else 972 cls.mandatory_weights[h]) for h in header ] 973 974 # We use a special rule for the weight labeled as a 975 # muR=2.0 muF=1.0 scale specification, in which case we store 976 # it as a tuple 977 for i, h in enumerate(header): 978 scale_wgt = HwU.weight_label_scale.match(h) 979 PDF_wgt = HwU.weight_label_PDF.match(h) 980 Merging_wgt = HwU.weight_label_TMS.match(h) 981 alpsfact_wgt = HwU.weight_label_alpsfact.match(h) 982 scale_wgt_adv = HwU.weight_label_scale_adv.match(h) 983 PDF_wgt_adv = HwU.weight_label_PDF_adv.match(h) 984 if scale_wgt_adv: 985 header[i] = ('scale_adv', 986 int(scale_wgt_adv.group('dyn_choice')), 987 float(scale_wgt_adv.group('mur_fact')), 988 float(scale_wgt_adv.group('muf_fact'))) 989 elif scale_wgt: 990 header[i] = ('scale', 991 float(scale_wgt.group('mur_fact')), 992 float(scale_wgt.group('muf_fact'))) 993 elif PDF_wgt_adv: 994 header[i] = ('pdf_adv', 995 int(PDF_wgt_adv.group('PDF_set')), 996 PDF_wgt_adv.group('PDF_set_cen')) 997 elif PDF_wgt: 998 header[i] = ('pdf',int(PDF_wgt.group('PDF_set'))) 999 elif Merging_wgt: 1000 header[i] = ('merging_scale',float(Merging_wgt.group('Merging_scale'))) 1001 elif alpsfact_wgt: 1002 header[i] = ('alpsfact',float(alpsfact_wgt.group('alpsfact'))) 1003 1004 return header 1005 1006 raise HwU.ParseError, "The weight headers could not be found."
1007 1008
1009 - def process_histogram_name(self, histogram_name):
1010 """ Parse the histogram name for tags which would set its various 1011 attributes.""" 1012 1013 for i, tag in enumerate(histogram_name.split('|')): 1014 if i==0: 1015 self.title = tag.strip() 1016 else: 1017 stag = tag.split('@') 1018 if len(stag)==1 and stag[0].startswith('#'): continue 1019 if len(stag)!=2: 1020 raise MadGraph5Error, 'Specifier in title must have the'+\ 1021 " syntax @<attribute_name>:<attribute_value>, not '%s'."%tag.strip() 1022 # Now list all supported modifiers here 1023 stag = [t.strip().upper() for t in stag] 1024 if stag[0] in ['T','TYPE']: 1025 self.type = stag[1] 1026 elif stag[0] in ['X_AXIS', 'X']: 1027 self.x_axis_mode = stag[1] 1028 elif stag[0] in ['Y_AXIS', 'Y']: 1029 self.y_axis_mode = stag[1] 1030 elif stag[0] in ['JETSAMPLE', 'JS']: 1031 self.jetsample = int(stag[1]) 1032 else: 1033 raise MadGraph5Error, "Specifier '%s' not recognized."%stag[0]
1034
1035 - def get_HwU_histogram_name(self, format='human'):
1036 """ Returns the histogram name in the HwU syntax or human readable.""" 1037 1038 type_map = {'NLO':'NLO', 'LO':'LO', 'AUX':'auxiliary histogram'} 1039 1040 if format=='human': 1041 res = self.title 1042 if not self.type is None: 1043 try: 1044 res += ', %s'%type_map[self.type] 1045 except KeyError: 1046 res += ', %s'%str('NLO' if self.type.split()[0]=='NLO' else 1047 self.type) 1048 if hasattr(self,'jetsample'): 1049 if self.jetsample==-1: 1050 res += ', all jet samples' 1051 else: 1052 res += ', Jet sample %d'%self.jetsample 1053 1054 return res 1055 1056 elif format=='human-no_type': 1057 res = self.title 1058 return res 1059 1060 elif format=='HwU': 1061 res = [self.title] 1062 res.append('|X_AXIS@%s'%self.x_axis_mode) 1063 res.append('|Y_AXIS@%s'%self.y_axis_mode) 1064 if hasattr(self,'jetsample'): 1065 res.append('|JETSAMPLE@%d'%self.jetsample) 1066 if self.type: 1067 res.append('|TYPE@%s'%self.type) 1068 return ' '.join(res)
1069
1070 - def parse_one_histo_from_stream(self, stream, all_weight_header, 1071 consider_reweights='ALL', raw_labels=False):
1072 """ Reads *one* histogram from a stream, with the mandatory specification 1073 of the ordered list of weight names. Return True or False depending 1074 on whether the starting definition of a new plot could be found in this 1075 stream.""" 1076 n_bins = 0 1077 1078 if consider_reweights=='ALL' or raw_labels: 1079 weight_header = all_weight_header 1080 else: 1081 new_weight_header = [] 1082 # Filter the weights to consider based on the user selection 1083 for wgt_label in all_weight_header: 1084 if wgt_label in ['central','stat_error','boundary_xmin','boundary_xmax'] or\ 1085 HwU.get_HwU_wgt_label_type(wgt_label) in consider_reweights: 1086 new_weight_header.append(wgt_label) 1087 weight_header = new_weight_header 1088 1089 # Find the starting point of the stream 1090 for line in stream: 1091 start = HwU.histo_start_re.match(line) 1092 if not start is None: 1093 self.process_histogram_name(start.group('histo_name')) 1094 # We do not want to include auxiliary diagrams which would be 1095 # recreated anyway. 1096 if self.type == 'AUX': 1097 continue 1098 n_bins = int(start.group('n_bins')) 1099 # Make sure to exclude the boundaries from the weight 1100 # specification 1101 self.bins = BinList(weight_labels = [ wgt_label for 1102 wgt_label in weight_header if wgt_label not in 1103 ['boundary_xmin','boundary_xmax']]) 1104 break 1105 1106 # Now look for the bin weights definition 1107 for line_bin in stream: 1108 bin_weights = {} 1109 boundaries = [0.0,0.0] 1110 for j, weight in \ 1111 enumerate(HwU.histo_bin_weight_re.finditer(line_bin)): 1112 if j == len(all_weight_header): 1113 raise HwU.ParseError, "There is more bin weights"+\ 1114 " specified than expected (%i)"%len(weight_header) 1115 if all_weight_header[j] == 'boundary_xmin': 1116 boundaries[0] = float(weight.group('weight')) 1117 elif all_weight_header[j] == 'boundary_xmax': 1118 boundaries[1] = float(weight.group('weight')) 1119 elif all_weight_header[j] in weight_header: 1120 bin_weights[all_weight_header[j]] = \ 1121 float(weight.group('weight')) 1122 1123 # For the HwU format, we know that exactly two 'weights' 1124 # specified in the weight_header are in fact the boundary 1125 # coordinate, so we must subtract two. 1126 if len(bin_weights)<(len(weight_header)-2): 1127 raise HwU.ParseError, " There are only %i weights"\ 1128 %len(bin_weights)+" specified and %i were expected."%\ 1129 (len(weight_header)-2) 1130 self.bins.append(Bin(tuple(boundaries), bin_weights)) 1131 if len(self.bins)==n_bins: 1132 break 1133 1134 if len(self.bins)!=n_bins: 1135 raise HwU.ParseError, "%i bin specification "%len(self.bins)+\ 1136 "were found and %i were expected."%n_bins 1137 1138 # Now jump to the next <\histo> tag. 1139 for line_end in stream: 1140 if HwU.histo_end_re.match(line_end): 1141 # Finally, remove all the auxiliary weights, but only if not 1142 # asking for raw labels 1143 if not raw_labels: 1144 self.trim_auxiliary_weights() 1145 # End of successful parsing this histogram, so return True. 1146 return True 1147 1148 # Could not find a plot definition starter in this stream, return False 1149 return False
1150
1151 - def trim_auxiliary_weights(self):
1152 """ Remove all weights which are auxiliary (whose name end with '@aux') 1153 so that they are not included (they will be regenerated anyway).""" 1154 1155 for i, wgt_label in enumerate(self.bins.weight_labels): 1156 if isinstance(wgt_label, str) and wgt_label.endswith('@aux'): 1157 for bin in self.bins: 1158 try: 1159 del bin.wgts[wgt_label] 1160 except KeyError: 1161 pass 1162 self.bins.weight_labels = [wgt_label for wgt_label in 1163 self.bins.weight_labels if (not isinstance(wgt_label, str) 1164 or (isinstance(wgt_label, str) and not wgt_label.endswith('@aux')) )]
1165
1166 - def set_uncertainty(self, type='all_scale',lhapdfconfig='lhapdf-config'):
1167 """ Adds a weight to the bins which is the envelope of the scale 1168 uncertainty, for the scale specified which can be either 'mur', 'muf', 1169 'all_scale' or 'PDF'.""" 1170 1171 if type.upper()=='MUR': 1172 new_wgt_label = 'delta_mur' 1173 scale_position = 1 1174 elif type.upper()=='MUF': 1175 new_wgt_label = 'delta_muf' 1176 scale_position = 2 1177 elif type.upper()=='ALL_SCALE': 1178 new_wgt_label = 'delta_mu' 1179 scale_position = -1 1180 elif type.upper()=='PDF': 1181 new_wgt_label = 'delta_pdf' 1182 scale_position = -2 1183 elif type.upper()=='MERGING': 1184 new_wgt_label = 'delta_merging' 1185 elif type.upper()=='ALPSFACT': 1186 new_wgt_label = 'delta_alpsfact' 1187 else: 1188 raise MadGraph5Error, ' The function set_uncertainty can'+\ 1189 " only handle the scales 'mur', 'muf', 'all_scale', 'pdf',"+\ 1190 "'merging' or 'alpsfact'." 1191 1192 wgts_to_consider=[] 1193 label_to_consider=[] 1194 if type.upper() == 'MERGING': 1195 # It is a list of list because we consider only the possibility of 1196 # a single "central value" in this case, so the outtermost list is 1197 # always of length 1. 1198 wgts_to_consider.append([ label for label in self.bins.weight_labels if \ 1199 HwU.get_HwU_wgt_label_type(label)=='merging_scale' ]) 1200 label_to_consider.append('none') 1201 1202 elif type.upper() == 'ALPSFACT': 1203 # It is a list of list because we consider only the possibility of 1204 # a single "central value" in this case, so the outtermost list is 1205 # always of length 1. 1206 wgts_to_consider.append([ label for label in self.bins.weight_labels if \ 1207 HwU.get_HwU_wgt_label_type(label)=='alpsfact' ]) 1208 label_to_consider.append('none') 1209 elif scale_position > -2: 1210 ##########: advanced scale 1211 dyn_scales=[label[1] for label in self.bins.weight_labels if \ 1212 HwU.get_HwU_wgt_label_type(label)=='scale_adv'] 1213 # remove doubles in list but keep the order! 1214 dyn_scales=[scale for n,scale in enumerate(dyn_scales) if scale not in dyn_scales[:n]] 1215 for dyn_scale in dyn_scales: 1216 wgts=[label for label in self.bins.weight_labels if \ 1217 HwU.get_HwU_wgt_label_type(label)=='scale_adv' and label[1]==dyn_scale] 1218 if wgts: 1219 wgts_to_consider.append(wgts) 1220 label_to_consider.append(dyn_scale) 1221 ##########: normal scale 1222 wgts=[label for label in self.bins.weight_labels if \ 1223 HwU.get_HwU_wgt_label_type(label)=='scale'] 1224 ## this is for the 7-point variations (excludes mur/muf = 4, 1/4) 1225 #wgts_to_consider = [ label for label in self.bins.weight_labels if \ 1226 # isinstance(label,tuple) and label[0]=='scale' and \ 1227 # not (0.5 in label and 2.0 in label)] 1228 if wgts: 1229 wgts_to_consider.append(wgts) 1230 label_to_consider.append('none') 1231 ##########: remove renormalisation OR factorisation scale dependence... 1232 1233 if scale_position > -1: 1234 for wgts in wgts_to_consider: 1235 wgts_to_consider.remove(wgts) 1236 wgts = [ label for label in wgts if label[-scale_position]==1.0 ] 1237 wgts_to_consider.append(wgts) 1238 elif scale_position == -2: 1239 ##########: advanced PDF 1240 pdf_sets=[label[2] for label in self.bins.weight_labels if \ 1241 HwU.get_HwU_wgt_label_type(label)=='pdf_adv'] 1242 # remove doubles in list but keep the order! 1243 pdf_sets=[ii for n,ii in enumerate(pdf_sets) if ii not in pdf_sets[:n]] 1244 for pdf_set in pdf_sets: 1245 wgts=[label for label in self.bins.weight_labels if \ 1246 HwU.get_HwU_wgt_label_type(label)=='pdf_adv' and label[2]==pdf_set] 1247 if wgts: 1248 wgts_to_consider.append(wgts) 1249 label_to_consider.append(pdf_set) 1250 ##########: normal PDF 1251 wgts = [ label for label in self.bins.weight_labels if \ 1252 HwU.get_HwU_wgt_label_type(label)=='pdf'] 1253 if wgts: 1254 wgts_to_consider.append(wgts) 1255 label_to_consider.append('none') 1256 1257 if len(wgts_to_consider)==0 or all(len(wgts)==0 for wgts in wgts_to_consider): 1258 # No envelope can be constructed, it is not worth adding the weights 1259 return (None,[None]) 1260 1261 # find and import python version of lhapdf if doing PDF uncertainties 1262 if type=='PDF': 1263 use_lhapdf=False 1264 try: 1265 lhapdf_libdir=subprocess.Popen([lhapdfconfig,'--libdir'],\ 1266 stdout=subprocess.PIPE).stdout.read().strip() 1267 except: 1268 use_lhapdf=False 1269 else: 1270 try: 1271 candidates=[dirname for dirname in os.listdir(lhapdf_libdir) \ 1272 if os.path.isdir(os.path.join(lhapdf_libdir,dirname))] 1273 except OSError: 1274 candidates=[] 1275 for candidate in candidates: 1276 if os.path.isfile(os.path.join(lhapdf_libdir,candidate,'site-packages','lhapdf.so')): 1277 sys.path.insert(0,os.path.join(lhapdf_libdir,candidate,'site-packages')) 1278 try: 1279 import lhapdf 1280 use_lhapdf=True 1281 break 1282 except ImportError: 1283 sys.path.pop(0) 1284 continue 1285 1286 if not use_lhapdf: 1287 try: 1288 candidates=[dirname for dirname in os.listdir(lhapdf_libdir+'64') \ 1289 if os.path.isdir(os.path.join(lhapdf_libdir+'64',dirname))] 1290 except OSError: 1291 candidates=[] 1292 for candidate in candidates: 1293 if os.path.isfile(os.path.join(lhapdf_libdir+'64',candidate,'site-packages','lhapdf.so')): 1294 sys.path.insert(0,os.path.join(lhapdf_libdir+'64',candidate,'site-packages')) 1295 try: 1296 import lhapdf 1297 use_lhapdf=True 1298 break 1299 except ImportError: 1300 sys.path.pop(0) 1301 continue 1302 1303 if not use_lhapdf: 1304 try: 1305 import lhapdf 1306 use_lhapdf=True 1307 except ImportError: 1308 logger.warning("Failed to access python version of LHAPDF: "\ 1309 "cannot compute PDF uncertainty from the "\ 1310 "weights in the histograms. The weights in the HwU data files " \ 1311 "still cover all PDF set members, "\ 1312 "but the automatic computation of the uncertainties from "\ 1313 "those weights might not be correct. \n "\ 1314 "If the python interface to LHAPDF is available on your system, try "\ 1315 "adding its location to the PYTHONPATH environment variable and the"\ 1316 "LHAPDF library location to LD_LIBRARY_PATH (linux) or DYLD_LIBRARY_PATH (mac os x).") 1317 1318 if type=='PDF' and use_lhapdf: 1319 lhapdf.setVerbosity(0) 1320 1321 # Place the new weight label last before the first tuple 1322 position=[] 1323 labels=[] 1324 for i,label in enumerate(label_to_consider): 1325 wgts=wgts_to_consider[i] 1326 if label != 'none': 1327 new_wgt_labels=['%s_cen %s @aux' % (new_wgt_label,label), 1328 '%s_min %s @aux' % (new_wgt_label,label), 1329 '%s_max %s @aux' % (new_wgt_label,label)] 1330 else: 1331 new_wgt_labels=['%s_cen @aux' % new_wgt_label, 1332 '%s_min @aux' % new_wgt_label, 1333 '%s_max @aux' % new_wgt_label] 1334 try: 1335 pos=[(not isinstance(lab, str)) for lab in \ 1336 self.bins.weight_labels].index(True) 1337 position.append(pos) 1338 labels.append(label) 1339 self.bins.weight_labels = self.bins.weight_labels[:pos]+\ 1340 new_wgt_labels + self.bins.weight_labels[pos:] 1341 except ValueError: 1342 pos=len(self.bins.weight_labels) 1343 position.append(pos) 1344 labels.append(label) 1345 self.bins.weight_labels.extend(new_wgt_labels) 1346 1347 if type=='PDF' and use_lhapdf and label != 'none': 1348 p=lhapdf.getPDFSet(label) 1349 1350 # Now add the corresponding weight to all Bins 1351 for bin in self.bins: 1352 if type!='PDF': 1353 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1354 bin.wgts[new_wgt_labels[1]] = min(bin.wgts[label] \ 1355 for label in wgts) 1356 bin.wgts[new_wgt_labels[2]] = max(bin.wgts[label] \ 1357 for label in wgts) 1358 elif type=='PDF' and use_lhapdf and label != 'none' and len(wgts) > 1: 1359 pdfs = [bin.wgts[pdf] for pdf in sorted(wgts)] 1360 ep=p.uncertainty(pdfs,-1) 1361 bin.wgts[new_wgt_labels[0]] = ep.central 1362 bin.wgts[new_wgt_labels[1]] = ep.central-ep.errminus 1363 bin.wgts[new_wgt_labels[2]] = ep.central+ep.errplus 1364 elif type=='PDF' and use_lhapdf and label != 'none' and len(bin.wgts) == 1: 1365 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1366 bin.wgts[new_wgt_labels[1]] = bin.wgts[wgts[0]] 1367 bin.wgts[new_wgt_labels[2]] = bin.wgts[wgts[0]] 1368 else: 1369 pdfs = [bin.wgts[pdf] for pdf in sorted(wgts)] 1370 pdf_up = 0.0 1371 pdf_down = 0.0 1372 cntrl_val = bin.wgts['central'] 1373 if wgts[0] <= 90000: 1374 # use Hessian method (CTEQ & MSTW) 1375 if len(pdfs)>2: 1376 for i in range(int((len(pdfs)-1)/2)): 1377 pdf_up += max(0.0,pdfs[2*i+1]-cntrl_val, 1378 pdfs[2*i+2]-cntrl_val)**2 1379 pdf_down += max(0.0,cntrl_val-pdfs[2*i+1], 1380 cntrl_val-pdfs[2*i+2])**2 1381 pdf_up = cntrl_val + math.sqrt(pdf_up) 1382 pdf_down = cntrl_val - math.sqrt(pdf_down) 1383 else: 1384 pdf_up = bin.wgts[pdfs[0]] 1385 pdf_down = bin.wgts[pdfs[0]] 1386 elif wgts[0] in range(90200, 90303) or \ 1387 wgts[0] in range(90400, 90433) or \ 1388 wgts[0] in range(90700, 90801) or \ 1389 wgts[0] in range(90900, 90931) or \ 1390 wgts[0] in range(91200, 91303) or \ 1391 wgts[0] in range(91400, 91433) or \ 1392 wgts[0] in range(91700, 91801) or \ 1393 wgts[0] in range(91900, 90931): 1394 # PDF4LHC15 Hessian sets 1395 pdf_stdev = 0.0 1396 for pdf in pdfs[1:]: 1397 pdf_stdev += (pdf - cntrl_val)**2 1398 pdf_stdev = math.sqrt(pdf_stdev) 1399 pdf_up = cntrl_val+pdf_stdev 1400 pdf_down = cntrl_val-pdf_stdev 1401 else: 1402 # use Gaussian method (NNPDF) 1403 pdf_stdev = 0.0 1404 for pdf in pdfs[1:]: 1405 pdf_stdev += (pdf - cntrl_val)**2 1406 pdf_stdev = math.sqrt(pdf_stdev/float(len(pdfs)-2)) 1407 pdf_up = cntrl_val+pdf_stdev 1408 pdf_down = cntrl_val-pdf_stdev 1409 # Finally add them to the corresponding new weight 1410 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1411 bin.wgts[new_wgt_labels[1]] = pdf_down 1412 bin.wgts[new_wgt_labels[2]] = pdf_up 1413 1414 # And return the position in self.bins.weight_labels of the first 1415 # of the two new weight label added. 1416 return (position,labels)
1417
1418 - def rebin(self, n_rebin):
1419 """ Rebin the x-axis so as to merge n_rebin consecutive bins into a 1420 single one. """ 1421 1422 if n_rebin < 1 or not isinstance(n_rebin, int): 1423 raise MadGraph5Error, "The argument 'n_rebin' of the HwU function"+\ 1424 " 'rebin' must be larger or equal to 1, not '%s'."%str(n_rebin) 1425 elif n_rebin==1: 1426 return 1427 1428 if self.type and 'NOREBIN' in self.type.upper(): 1429 return 1430 1431 rebinning_list = list(range(0,len(self.bins),n_rebin))+[len(self.bins),] 1432 concat_list = [self.bins[rebinning_list[i]:rebinning_list[i+1]] for \ 1433 i in range(len(rebinning_list)-1)] 1434 1435 new_bins = copy.copy(self.bins) 1436 del new_bins[:] 1437 1438 for bins_to_merge in concat_list: 1439 if len(bins_to_merge)==0: 1440 continue 1441 new_bins.append(Bin(boundaries=(bins_to_merge[0].boundaries[0], 1442 bins_to_merge[-1].boundaries[1]),wgts={'central':0.0})) 1443 for weight in self.bins.weight_labels: 1444 if weight != 'stat_error': 1445 new_bins[-1].wgts[weight] = \ 1446 sum(b.wgts[weight] for b in bins_to_merge) 1447 else: 1448 new_bins[-1].wgts['stat_error'] = \ 1449 math.sqrt(sum(b.wgts['stat_error']**2 for b in\ 1450 bins_to_merge)) 1451 1452 self.bins = new_bins
1453 1454 @classmethod
1455 - def get_x_optimal_range(cls, histo_list, weight_labels=None):
1456 """ Function to determine the optimal x-axis range when plotting 1457 together the histos in histo_list and considering the weights 1458 weight_labels""" 1459 1460 # If no list of weight labels to consider is given, use them all. 1461 if weight_labels is None: 1462 weight_labels = histo_list[0].bins.weight_labels 1463 1464 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1465 for bin in histo.bins if \ 1466 (sum(abs(bin.wgts[label]) for label in weight_labels) > 0.0)] ,[]) 1467 1468 if len(all_boundaries)==0: 1469 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1470 for bin in histo.bins],[]) 1471 if len(all_boundaries)==0: 1472 raise MadGraph5Error, "The histograms with title '%s'"\ 1473 %histo_list[0].title+" seems to have no bins." 1474 1475 x_min = min(all_boundaries) 1476 x_max = max(all_boundaries) 1477 1478 return (x_min, x_max)
1479 1480 @classmethod
1481 - def get_y_optimal_range(cls,histo_list, labels=None, 1482 scale='LOG', Kratio = False):
1483 """ Function to determine the optimal y-axis range when plotting 1484 together the histos in histo_list and considering the weights 1485 weight_labels. The option Kratio is present to allow for the couple of 1486 tweaks necessary for the the K-factor ratio histogram y-range.""" 1487 1488 # If no list of weight labels to consider is given, use them all. 1489 if labels is None: 1490 weight_labels = histo_list[0].bins.weight_labels 1491 else: 1492 weight_labels = labels 1493 1494 all_weights = [] 1495 for histo in histo_list: 1496 for bin in histo.bins: 1497 for label in weight_labels: 1498 # Filter out bin weights at *exactly* because they often 1499 # come from pathological division by zero for empty bins. 1500 if Kratio and bin.wgts[label]==0.0: 1501 continue 1502 if scale!='LOG': 1503 all_weights.append(bin.wgts[label]) 1504 if label == 'stat_error': 1505 all_weights.append(-bin.wgts[label]) 1506 elif bin.wgts[label]>0.0: 1507 all_weights.append(bin.wgts[label]) 1508 1509 1510 sum([ [bin.wgts[label] for label in weight_labels if \ 1511 (scale!='LOG' or bin.wgts[label]!=0.0)] \ 1512 for histo in histo_list for bin in histo.bins], []) 1513 1514 all_weights.sort() 1515 if len(all_weights)!=0: 1516 partial_max = all_weights[int(len(all_weights)*0.95)] 1517 partial_min = all_weights[int(len(all_weights)*0.05)] 1518 max = all_weights[-1] 1519 min = all_weights[0] 1520 else: 1521 if scale!='LOG': 1522 return (0.0,1.0) 1523 else: 1524 return (1.0,10.0) 1525 1526 y_max = 0.0 1527 y_min = 0.0 1528 1529 # If the maximum is too far from the 90% max, then take the partial max 1530 if (max-partial_max)>2.0*(partial_max-partial_min): 1531 y_max = partial_max 1532 else: 1533 y_max = max 1534 1535 # If the maximum is too far from the 90% max, then take the partial max 1536 if (partial_min - min)>2.0*(partial_max-partial_min) and min != 0.0: 1537 y_min = partial_min 1538 else: 1539 y_min = min 1540 1541 if Kratio: 1542 median = all_weights[len(all_weights)//2] 1543 spread = (y_max-y_min) 1544 if abs(y_max-median)<spread*0.05 or abs(median-y_min)<spread*0.05: 1545 y_max = median + spread/2.0 1546 y_min = median - spread/2.0 1547 if y_min != y_max: 1548 return ( y_min , y_max ) 1549 1550 # Enforce the maximum if there is 5 bins or less 1551 if len(histo_list[0].bins) <= 5: 1552 y_min = min 1553 y_max = max 1554 1555 # Finally make sure the range has finite length 1556 if y_min == y_max: 1557 if max == min: 1558 y_min -= 1.0 1559 y_max += 1.0 1560 else: 1561 y_min = min 1562 y_max = max 1563 1564 return ( y_min , y_max )
1565
1566 -class HwUList(histograms_PhysicsObjectList):
1567 """ A class implementing features related to a list of Hwu Histograms. """ 1568 1569 # Define here the number of line color schemes defined. If you need more, 1570 # simply define them in the gnuplot header and increase the number below. 1571 # It must be <= 9. 1572 number_line_colors_defined = 8 1573
1574 - def is_valid_element(self, obj):
1575 """Test wether specified object is of the right type for this list.""" 1576 1577 return isinstance(obj, HwU) or isinstance(obj, HwUList)
1578
1579 - def __init__(self, file_path, weight_header=None, run_id=None, 1580 merging_scale=None, accepted_types_order=[], consider_reweights='ALL', 1581 raw_labels=False, **opts):
1582 """ Read one plot from a file_path or a stream. 1583 This constructor reads all plots specified in target file. 1584 File_path can be a path or a stream in the argument. 1585 The option weight_header specifies an ordered list of weight names 1586 to appear in the file or stream specified. It accepted_types_order is 1587 empty, no filter is applied, otherwise only histograms of the specified 1588 types will be kept, and in this specified order for a given identical 1589 title. The option 'consider_reweights' selects whether one wants to 1590 include all the extra scale/pdf/merging variation weights. Possible values 1591 are 'ALL' or a list of the return types of the function get_HwU_wgt_label_type(). 1592 The option 'raw_labels' specifies that one wants to import the 1593 histogram data with no treatment of the weight labels at all 1594 (this is used for the matplotlib output). 1595 """ 1596 1597 if isinstance(file_path, str): 1598 stream = open(file_path,'r') 1599 elif isinstance(file_path, file): 1600 stream = file_path 1601 else: 1602 return super(HwUList,self).__init__(file_path, **opts) 1603 1604 try: 1605 # Try to read it in XML format 1606 self.parse_histos_from_PY8_XML_stream(stream, run_id, 1607 merging_scale, accepted_types_order, 1608 consider_reweights=consider_reweights, 1609 raw_labels=raw_labels) 1610 except XMLParsingError: 1611 # Rewinding the stream 1612 stream.seek(0) 1613 # Attempt to find the weight headers if not specified 1614 if not weight_header: 1615 weight_header = HwU.parse_weight_header(stream,raw_labels=raw_labels) 1616 1617 new_histo = HwU(stream, weight_header,raw_labels=raw_labels, 1618 consider_reweights=consider_reweights) 1619 while not new_histo.bins is None: 1620 if accepted_types_order==[] or \ 1621 new_histo.type in accepted_types_order: 1622 self.append(new_histo) 1623 new_histo = HwU(stream, weight_header, raw_labels=raw_labels, 1624 consider_reweights=consider_reweights) 1625 1626 if not run_id is None: 1627 logger.info("The run_id '%s' was specified, but "%run_id+ 1628 "format of the HwU plot source is the MG5aMC"+ 1629 " so that the run_id information is ignored.") 1630 1631 # Order the histograms according to their type. 1632 titles_order = [h.title for h in self] 1633 def ordering_function(histo): 1634 title_position = titles_order.index(histo.title) 1635 if accepted_types_order==[]: 1636 type_precedence = {'NLO':1,'LO':2,None:3,'AUX':5} 1637 try: 1638 ordering_key = (title_position,type_precedence[histo.type]) 1639 except KeyError: 1640 ordering_key = (title_position,4) 1641 else: 1642 ordering_key = (title_position, 1643 accepted_types_order.index(histo.type)) 1644 return ordering_key
1645 1646 # The command below is to first order them in alphabetical order, but it 1647 # is often better to keep the order of the original HwU source. 1648 # self.sort(key=lambda histo: '%s_%d'%(histo.title, 1649 # type_order.index(histo.type))) 1650 self.sort(key=ordering_function) 1651 1652 # Explicitly close the opened stream for clarity. 1653 if isinstance(file_path, str): 1654 stream.close()
1655
1656 - def get_hist_names(self):
1657 """return a list of all the names of define histograms""" 1658 1659 output = [] 1660 for hist in self: 1661 output.append(hist.get_HwU_histogram_name()) 1662 return output
1663
1664 - def get_wgt_names(self):
1665 """ return the list of all weights define in each histograms""" 1666 1667 return self[0].bins.weight_labels
1668 1669
1670 - def get(self, name):
1671 """return the HWU histograms related to a given name""" 1672 for hist in self: 1673 if hist.get_HwU_histogram_name() == name: 1674 return hist 1675 1676 raise NameError, "no histogram with name: %s" % name
1677
1678 - def parse_histos_from_PY8_XML_stream(self, stream, run_id=None, 1679 merging_scale=None, accepted_types_order=[], 1680 consider_reweights='ALL', raw_labels=False):
1681 """Initialize the HwU histograms from an XML stream. Only one run is 1682 used: the first one if run_id is None or the specified run otherwise. 1683 Accepted type order is a filter to select histograms of only a certain 1684 type. The option 'consider_reweights' selects whether one wants to 1685 include all the extra scale/pdf/merging variation weights. 1686 Possible values are 'ALL' or a list of the return types of the 1687 function get_HwU_wgt_label_type().""" 1688 1689 run_nodes = minidom.parse(stream).getElementsByTagName("run") 1690 all_nodes = dict((int(node.getAttribute('id')),node) for 1691 node in run_nodes) 1692 selected_run_node = None 1693 weight_header = None 1694 if run_id is None: 1695 if len(run_nodes)>0: 1696 selected_run_node = all_nodes[min(all_nodes.keys())] 1697 else: 1698 try: 1699 selected_run_node = all_nodes[int(run_id)] 1700 except: 1701 selected_run_node = None 1702 1703 if selected_run_node is None: 1704 if run_id is None: 1705 raise MadGraph5Error, \ 1706 'No histogram was found in the specified XML source.' 1707 else: 1708 raise MadGraph5Error, \ 1709 "Histogram with run_id '%d' was not found in the "%run_id+\ 1710 "specified XML source." 1711 1712 # If raw weight label are asked for, then simply read the weight_labels 1713 # directly as specified in the XML header 1714 if raw_labels: 1715 # Filter empty weights coming from the split 1716 weight_label_list = [wgt.strip() for wgt in 1717 str(selected_run_node.getAttribute('header')).split(';') if 1718 not re.match('^\s*$',wgt)] 1719 ordered_weight_label_list = [w for w in weight_label_list if w not\ 1720 in ['xmin','xmax']] 1721 # Remove potential repetition of identical weight labels 1722 filtered_ordered_weight_label_list = [] 1723 for wgt_label in ordered_weight_label_list: 1724 if wgt_label not in filtered_ordered_weight_label_list: 1725 filtered_ordered_weight_label_list.append(wgt_label) 1726 1727 selected_weights = dict([ (wgt_pos, 1728 [wgt if wgt not in ['xmin','xmax'] else HwU.mandatory_weights[wgt]]) 1729 for wgt_pos, wgt in enumerate(weight_label_list) if wgt in 1730 filtered_ordered_weight_label_list+['xmin','xmax']]) 1731 1732 return self.retrieve_plots_from_XML_source(selected_run_node, 1733 selected_weights, filtered_ordered_weight_label_list, 1734 raw_labels=True) 1735 1736 # Now retrieve the header and save all weight labels as dictionaries 1737 # with key being properties and their values as value. If the property 1738 # does not defined a value, then put None as a value 1739 all_weights = [] 1740 for wgt_position, wgt_label in \ 1741 enumerate(str(selected_run_node.getAttribute('header')).split(';')): 1742 if not re.match('^\s*$',wgt_label) is None: 1743 continue 1744 all_weights.append({'POSITION':wgt_position}) 1745 for wgt_item in wgt_label.strip().split('_'): 1746 property = wgt_item.strip().split('=') 1747 if len(property) == 2: 1748 all_weights[-1][property[0].strip()] = property[1].strip() 1749 elif len(property)==1: 1750 all_weights[-1][property[0].strip()] = None 1751 else: 1752 raise MadGraph5Error, \ 1753 "The weight label property %s could not be parsed."%wgt_item 1754 1755 # Now make sure that for all weights, there is 'PDF', 'MUF' and 'MUR' 1756 # and 'MERGING' defined. If absent we specify '-1' which implies that 1757 # the 'default' value was used (whatever it was). 1758 # Also cast them in the proper type 1759 for wgt_label in all_weights: 1760 for mandatory_attribute in ['PDF','MUR','MUF','MERGING','ALPSFACT']: 1761 if mandatory_attribute not in wgt_label: 1762 wgt_label[mandatory_attribute] = '-1' 1763 if mandatory_attribute=='PDF': 1764 wgt_label[mandatory_attribute] = int(wgt_label[mandatory_attribute]) 1765 elif mandatory_attribute in ['MUR','MUF','MERGING','ALPSFACT']: 1766 wgt_label[mandatory_attribute] = float(wgt_label[mandatory_attribute]) 1767 1768 # If merging cut is negative, then pick only the one of the central scale 1769 # If not specified, then take them all but use the PDF and scale weight 1770 # of the central merging_scale for the variation. 1771 if merging_scale is None or merging_scale < 0.0: 1772 merging_scale_chosen = all_weights[2]['MERGING'] 1773 else: 1774 merging_scale_chosen = merging_scale 1775 1776 # Central weight parameters are enforced to be those of the third weight 1777 central_PDF = all_weights[2]['PDF'] 1778 # Assume central scale is one, unless specified. 1779 central_MUR = all_weights[2]['MUR'] if all_weights[2]['MUR']!=-1.0 else 1.0 1780 central_MUF = all_weights[2]['MUF'] if all_weights[2]['MUF']!=-1.0 else 1.0 1781 central_alpsfact = all_weights[2]['ALPSFACT'] if all_weights[2]['ALPSFACT']!=-1.0 else 1.0 1782 1783 # Dictionary of selected weights with their position as key and the 1784 # list of weight labels they correspond to. 1785 selected_weights = {} 1786 # Treat the first four weights in a special way: 1787 if 'xmin' not in all_weights[0] or \ 1788 'xmax' not in all_weights[1] or \ 1789 'Weight' not in all_weights[2] or \ 1790 'WeightError' not in all_weights[3]: 1791 raise MadGraph5Error, 'The first weight entries in the XML HwU '+\ 1792 ' source are not the standard expected ones (xmin, xmax, sigmaCentral, errorCentral)' 1793 selected_weights[0] = ['xmin'] 1794 selected_weights[1] = ['xmax'] 1795 1796 # =========== BEGIN HELPER FUNCTIONS =========== 1797 def get_difference_to_central(weight): 1798 """ Return the list of properties which differ from the central weight. 1799 This disregards the merging scale value for which any central value 1800 can be picked anyway.""" 1801 1802 differences = [] 1803 # If the tag 'Weight' is in the weight label, then this is 1804 # automatically considered as the Event weight (central) for which 1805 # only the merging scale can be different 1806 if 'Weight' in weight: 1807 return set([]) 1808 if weight['MUR'] not in [central_MUR, -1.0] or \ 1809 weight['MUF'] not in [central_MUF, -1.0]: 1810 differences.append('mur_muf_scale') 1811 if weight['PDF'] not in [central_PDF,-1]: 1812 differences.append('pdf') 1813 if weight['ALPSFACT'] not in [central_alpsfact, -1]: 1814 differences.append('ALPSFACT') 1815 return set(differences)
1816 1817 def format_weight_label(weight): 1818 """ Print the weight attributes in a nice order.""" 1819 1820 all_properties = weight.keys() 1821 all_properties.pop(all_properties.index('POSITION')) 1822 ordered_properties = [] 1823 # First add the attributes without value 1824 for property in all_properties: 1825 if weight[property] is None: 1826 ordered_properties.append(property) 1827 1828 ordered_properties.sort() 1829 all_properties = [property for property in all_properties if 1830 not weight[property] is None] 1831 1832 # then add PDF, MUR, MUF and MERGING if present 1833 for property in ['PDF','MUR','MUF','ALPSFACT','MERGING']: 1834 all_properties.pop(all_properties.index(property)) 1835 if weight[property]!=-1: 1836 ordered_properties.append(property) 1837 1838 ordered_properties.extend(sorted(all_properties)) 1839 1840 return '_'.join('%s%s'\ 1841 %(key,'' if weight[key] is None else '=%s'%str(weight[key])) for 1842 key in ordered_properties) 1843 # =========== END HELPER FUNCTIONS =========== 1844 1845 1846 # The central value is not necessarily the 3rd one if a different merging 1847 # cut was selected. 1848 if float(all_weights[2]['MERGING']) == merging_scale_chosen: 1849 selected_weights[2]=['central value'] 1850 else: 1851 for weight_position, weight in enumerate(all_weights): 1852 # Check if that weight corresponds to a central weight 1853 # (conventional label for central weight is 'Weight' 1854 if get_difference_to_central(weight)==set([]): 1855 # Check if the merging scale matches this time 1856 if weight['MERGING']==merging_scale_chosen: 1857 selected_weights[weight_position] = ['central value'] 1858 break 1859 # Make sure a central value was found, throw a warning if found 1860 if 'central value' not in sum(selected_weights.values(),[]): 1861 central_merging_scale = all_weights[2]['MERGING'] 1862 logger.warning('Could not find the central weight for the'+\ 1863 ' chosen merging scale (%f).\n'%merging_scale_chosen+\ 1864 'MG5aMC will chose the original central scale provided which '+\ 1865 'correspond to a merging scale of %s'%("'inclusive'" if 1866 central_merging_scale in [0.0,-1.0] else '%f'%central_merging_scale)) 1867 selected_weights[2]=['central value'] 1868 1869 # The error is always the third entry for now. 1870 selected_weights[3]=['dy'] 1871 1872 # Now process all other weights 1873 for weight_position, weight in enumerate(all_weights[4:]): 1874 # Apply special transformation for the weight label: 1875 # scale variation are stored as: 1876 # ('scale', mu_r, mu_f) for scale variation 1877 # ('pdf',PDF) for PDF variation 1878 # ('merging_scale',float) for merging scale 1879 # ('type',value) for all others (e.g. alpsfact) 1880 variations = get_difference_to_central(weight) 1881 # We know select the 'diagonal' variations where each parameter 1882 # is varied one at a time. 1883 1884 # Accept also if both pdf and mur_muf_scale differ because 1885 # the PDF used for the Event weight is often unknown but the 1886 # mu_r and mu_f variational weight specify it. Same story for 1887 # alpsfact. 1888 if variations in [set(['mur_muf_scale']),set(['pdf','mur_muf_scale'])]: 1889 wgt_label = ('scale',weight['MUR'],weight['MUF']) 1890 if variations in [set(['ALPSFACT']),set(['pdf','ALPSFACT'])]: 1891 wgt_label = ('alpsfact',weight['ALPSFACT']) 1892 if variations == set(['pdf']): 1893 wgt_label = ('pdf',weight['PDF']) 1894 if variations == set([]): 1895 # Unknown weight (might turn out to be taken as a merging variation weight below) 1896 wgt_label = format_weight_label(weight) 1897 1898 # Make sure the merging scale matches the chosen one 1899 if weight['MERGING'] != merging_scale_chosen: 1900 # If a merging_scale was specified, then ignore all other weights 1901 if merging_scale: 1902 continue 1903 # Otherwise consider them also, but for now only if it is for 1904 # the central value parameter (central PDF, central mu_R and mu_F) 1905 if variations == set([]): 1906 # We choose to store the merging variation weight labels as floats 1907 wgt_label = ('merging_scale', weight['MERGING']) 1908 # Make sure that the weight label does not already exist. If it does, 1909 # this means that the source has redundant information and that 1910 # there is no need to specify it again. 1911 if wgt_label in sum(selected_weights.values(),[]): 1912 continue 1913 1914 # Now register the selected weight 1915 try: 1916 selected_weights[weight_position+4].append(wgt_label) 1917 except KeyError: 1918 selected_weights[weight_position+4]=[wgt_label,] 1919 1920 if merging_scale and merging_scale > 0.0 and \ 1921 len(sum(selected_weights.values(),[]))==4: 1922 logger.warning('No additional variation weight was found for the '+\ 1923 'chosen merging scale %f.'%merging_scale) 1924 1925 # Make sure to use the predefined keywords for the mandatory weight labels 1926 for wgt_pos in selected_weights: 1927 for i, weight_label in enumerate(selected_weights[wgt_pos]): 1928 try: 1929 selected_weights[wgt_pos][i] = HwU.mandatory_weights[weight_label] 1930 except KeyError: 1931 pass 1932 1933 # Keep only the weights asked for 1934 if consider_reweights!='ALL': 1935 new_selected_weights = {} 1936 for wgt_position, wgt_labels in selected_weights.items(): 1937 for wgt_label in wgt_labels: 1938 if wgt_label in ['central','stat_error','boundary_xmin','boundary_xmax'] or\ 1939 HwU.get_HwU_wgt_label_type(wgt_label) in consider_reweights: 1940 try: 1941 new_selected_weights[wgt_position].append(wgt_label) 1942 except KeyError: 1943 new_selected_weights[wgt_position] = [wgt_label] 1944 selected_weights = new_selected_weights 1945 1946 # Cache the list of selected weights to be defined at each line 1947 weight_label_list = sum(selected_weights.values(),[]) 1948 1949 # The weight_label list to set to self.bins 1950 ordered_weight_label_list = ['central','stat_error'] 1951 for weight_label in weight_label_list: 1952 if not isinstance(weight_label, str): 1953 ordered_weight_label_list.append(weight_label) 1954 for weight_label in weight_label_list: 1955 if weight_label in ['central','stat_error','boundary_xmin','boundary_xmax']: 1956 continue 1957 if isinstance(weight_label, str): 1958 ordered_weight_label_list.append(weight_label) 1959 1960 # Now that we know the desired weights, retrieve all plots from the 1961 # XML source node. 1962 return self.retrieve_plots_from_XML_source(selected_run_node, 1963 selected_weights, ordered_weight_label_list, raw_labels=False) 1964
1965 - def retrieve_plots_from_XML_source(self, xml_node, 1966 selected_weights, ordered_weight_label_list,raw_labels=False):
1967 """Given an XML node and the selected weights and their ordered list, 1968 import all histograms from the specified XML node.""" 1969 1970 # We now start scanning all the plots 1971 for multiplicity_node in xml_node.getElementsByTagName("jethistograms"): 1972 multiplicity = int(multiplicity_node.getAttribute('njet')) 1973 for histogram in multiplicity_node.getElementsByTagName("histogram"): 1974 # We only consider the histograms with all the weight information 1975 if histogram.getAttribute("weight")!='all': 1976 continue 1977 new_histo = HwU() 1978 hist_name = str(histogram.getAttribute('name')) 1979 # prepend the jet multiplicity to the histogram name 1980 new_histo.process_histogram_name('%s |JETSAMPLE@%d'%(hist_name,multiplicity)) 1981 # We do not want to include auxiliary diagrams which would be 1982 # recreated anyway. 1983 if new_histo.type == 'AUX': 1984 continue 1985 # Make sure to exclude the boundaries from the weight 1986 # specification 1987 # Order the weights so that the unreckognized ones go last 1988 new_histo.bins = BinList(weight_labels = ordered_weight_label_list) 1989 hist_data = str(histogram.childNodes[0].data) 1990 for line in hist_data.split('\n'): 1991 if line.strip()=='': 1992 continue 1993 bin_weights = {} 1994 boundaries = [0.0,0.0] 1995 for j, weight in \ 1996 enumerate(HwU.histo_bin_weight_re.finditer(line)): 1997 try: 1998 for wgt_label in selected_weights[j]: 1999 if wgt_label == 'boundary_xmin': 2000 boundaries[0] = float(weight.group('weight')) 2001 elif wgt_label == 'boundary_xmax': 2002 boundaries[1] = float(weight.group('weight')) 2003 else: 2004 if weight.group('weight').upper()=='NAN': 2005 raise MadGraph5Error, \ 2006 "Some weights are found to be 'NAN' in histogram with name '%s'"%hist_name+\ 2007 " and jet sample multiplicity %d."%multiplicity 2008 else: 2009 bin_weights[wgt_label] = \ 2010 float(weight.group('weight')) 2011 except KeyError: 2012 continue 2013 # For this check, we subtract two because of the bin boundaries 2014 if len(bin_weights)!=len(ordered_weight_label_list): 2015 raise MadGraph5Error, \ 2016 'Not all defined weights were found in the XML source.\n'+\ 2017 '%d found / %d expected.'%(len(bin_weights),len(ordered_weight_label_list))+\ 2018 '\nThe missing ones are: %s.'%\ 2019 str(list(set(ordered_weight_label_list)-set(bin_weights.keys())))+\ 2020 "\nIn plot with title '%s' and jet sample multiplicity %d."%\ 2021 (hist_name, multiplicity) 2022 2023 new_histo.bins.append(Bin(tuple(boundaries), bin_weights)) 2024 2025 # if bin_weights['central']!=0.0: 2026 # print '---------' 2027 # print 'multiplicity =',multiplicity 2028 # print 'central =', bin_weights['central'] 2029 # print 'PDF = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,int)] 2030 # print 'PDF min/max =',min(bin_weights[key] for key in bin_weights if isinstance(key,int)),max(bin_weights[key] for key in bin_weights if isinstance(key,int)) 2031 # print 'scale = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,tuple)] 2032 # print 'scale min/max =',min(bin_weights[key] for key in bin_weights if isinstance(key,tuple)),max(bin_weights[key] for key in bin_weights if isinstance(key,tuple)) 2033 # print 'merging = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,float)] 2034 # print 'merging min/max =',min(bin_weights[key] for key in bin_weights if isinstance(key,float)),max(bin_weights[key] for key in bin_weights if isinstance(key,float)) 2035 # print 'alpsfact = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='alpsfact'] 2036 # print 'alpsfact min/max =',min(bin_weights[key] for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='alpsfact'),max(bin_weights[key] for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='alpsfact') 2037 # print '---------' 2038 2039 # Finally remove auxiliary weights 2040 if not raw_labels: 2041 new_histo.trim_auxiliary_weights() 2042 2043 # And add it to the list 2044 self.append(new_histo)
2045
2046 - def output(self, path, format='gnuplot',number_of_ratios = -1, 2047 uncertainties=['scale','pdf','statitistical','merging_scale','alpsfact'], 2048 use_band = None, 2049 ratio_correlations=True, arg_string='', 2050 jet_samples_to_keep=None, 2051 auto_open=True, 2052 lhapdfconfig='lhapdf-config'):
2053 """ Ouput this histogram to a file, stream or string if path is kept to 2054 None. The supported format are for now. Chose whether to print the header 2055 or not.""" 2056 2057 if len(self)==0: 2058 return MadGraph5Error, 'No histograms stored in the list yet.' 2059 2060 if not format in HwU.output_formats_implemented: 2061 raise MadGraph5Error, "The specified output format '%s'"%format+\ 2062 " is not yet supported. Supported formats are %s."\ 2063 %HwU.output_formats_implemented 2064 2065 if isinstance(path, str) and '.' not in os.path.basename(path): 2066 output_base_name = os.path.basename(path) 2067 HwU_stream = open(path+'.HwU','w') 2068 else: 2069 raise MadGraph5Error, "The path argument of the output function of"+\ 2070 " the HwUList instance must be file path without its extension." 2071 2072 HwU_output_list = [] 2073 # If the format is just the raw HwU source, then simply write them 2074 # out all in sequence. 2075 if format == 'HwU': 2076 HwU_output_list.extend(self[0].get_HwU_source(print_header=True)) 2077 for histo in self[1:]: 2078 HwU_output_list.extend(histo.get_HwU_source()) 2079 HwU_output_list.extend(['','']) 2080 HwU_stream.write('\n'.join(HwU_output_list)) 2081 HwU_stream.close() 2082 return 2083 2084 # Now we consider that we are attempting a gnuplot output. 2085 if format == 'gnuplot': 2086 gnuplot_stream = open(path+'.gnuplot','w') 2087 2088 # Now group all the identified matching histograms in a list 2089 matching_histo_lists = HwUList([HwUList([self[0]])]) 2090 for histo in self[1:]: 2091 matched = False 2092 for histo_list in matching_histo_lists: 2093 if histo.test_plot_compability(histo_list[0], 2094 consider_type=False, consider_unknown_weight_labels=True): 2095 histo_list.append(histo) 2096 matched = True 2097 break 2098 if not matched: 2099 matching_histo_lists.append(HwUList([histo])) 2100 2101 self[:] = matching_histo_lists 2102 2103 # Write the gnuplot header 2104 gnuplot_output_list_v4 = [ 2105 """ 2106 ################################################################################ 2107 # 2108 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2109 # automatically generates Feynman diagrams and matrix elements for arbitrary 2110 # high-energy processes in the Standard Model and beyond. It also perform the 2111 # integration and/or generate events for these processes, at LO and NLO accuracy. 2112 # 2113 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2114 # 2115 ################################################################################ 2116 # %s 2117 reset 2118 2119 set lmargin 10 2120 set rmargin 0 2121 set terminal postscript portrait enhanced mono dashed lw 1.0 "Helvetica" 9 2122 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2123 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2124 set key font ",9" 2125 set key samplen "2" 2126 set output "%s.ps" 2127 2128 # This is the "PODO" color palette of gnuplot v.5, but with the order 2129 # changed: palette of colors selected to be easily distinguishable by 2130 # color-blind individuals with either protanopia or deuteranopia. Bang 2131 # Wong [2011] Nature Methods 8, 441. 2132 2133 set style line 1 lt 1 lc rgb "#009e73" lw 2.5 2134 set style line 11 lt 2 lc rgb "#009e73" lw 2.5 2135 set style line 21 lt 4 lc rgb "#009e73" lw 2.5 2136 set style line 31 lt 6 lc rgb "#009e73" lw 2.5 2137 set style line 41 lt 8 lc rgb "#009e73" lw 2.5 2138 2139 set style line 2 lt 1 lc rgb "#0072b2" lw 2.5 2140 set style line 12 lt 2 lc rgb "#0072b2" lw 2.5 2141 set style line 22 lt 4 lc rgb "#0072b2" lw 2.5 2142 set style line 32 lt 6 lc rgb "#0072b2" lw 2.5 2143 set style line 42 lt 8 lc rgb "#0072b2" lw 2.5 2144 2145 set style line 3 lt 1 lc rgb "#d55e00" lw 2.5 2146 set style line 13 lt 2 lc rgb "#d55e00" lw 2.5 2147 set style line 23 lt 4 lc rgb "#d55e00" lw 2.5 2148 set style line 33 lt 6 lc rgb "#d55e00" lw 2.5 2149 set style line 43 lt 8 lc rgb "#d55e00" lw 2.5 2150 2151 set style line 4 lt 1 lc rgb "#f0e442" lw 2.5 2152 set style line 14 lt 2 lc rgb "#f0e442" lw 2.5 2153 set style line 24 lt 4 lc rgb "#f0e442" lw 2.5 2154 set style line 34 lt 6 lc rgb "#f0e442" lw 2.5 2155 set style line 44 lt 8 lc rgb "#f0e442" lw 2.5 2156 2157 set style line 5 lt 1 lc rgb "#56b4e9" lw 2.5 2158 set style line 15 lt 2 lc rgb "#56b4e9" lw 2.5 2159 set style line 25 lt 4 lc rgb "#56b4e9" lw 2.5 2160 set style line 35 lt 6 lc rgb "#56b4e9" lw 2.5 2161 set style line 45 lt 8 lc rgb "#56b4e9" lw 2.5 2162 2163 set style line 6 lt 1 lc rgb "#cc79a7" lw 2.5 2164 set style line 16 lt 2 lc rgb "#cc79a7" lw 2.5 2165 set style line 26 lt 4 lc rgb "#cc79a7" lw 2.5 2166 set style line 36 lt 6 lc rgb "#cc79a7" lw 2.5 2167 set style line 46 lt 8 lc rgb "#cc79a7" lw 2.5 2168 2169 set style line 7 lt 1 lc rgb "#e69f00" lw 2.5 2170 set style line 17 lt 2 lc rgb "#e69f00" lw 2.5 2171 set style line 27 lt 4 lc rgb "#e69f00" lw 2.5 2172 set style line 37 lt 6 lc rgb "#e69f00" lw 2.5 2173 set style line 47 lt 8 lc rgb "#e69f00" lw 2.5 2174 2175 set style line 8 lt 1 lc rgb "black" lw 2.5 2176 set style line 18 lt 2 lc rgb "black" lw 2.5 2177 set style line 28 lt 4 lc rgb "black" lw 2.5 2178 set style line 38 lt 6 lc rgb "black" lw 2.5 2179 set style line 48 lt 7 lc rgb "black" lw 2.5 2180 2181 2182 set style line 999 lt 1 lc rgb "gray" lw 2.5 2183 2184 safe(x,y,a) = (y == 0.0 ? a : x/y) 2185 2186 set style data histeps 2187 set key invert 2188 2189 """%(arg_string,output_base_name) 2190 ] 2191 2192 gnuplot_output_list_v5 = [ 2193 """ 2194 ################################################################################ 2195 # 2196 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2197 # automatically generates Feynman diagrams and matrix elements for arbitrary 2198 # high-energy processes in the Standard Model and beyond. It also perform the 2199 # integration and/or generate events for these processes, at LO and NLO accuracy. 2200 # 2201 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2202 # 2203 ################################################################################ 2204 # %s 2205 reset 2206 2207 set lmargin 10 2208 set rmargin 0 2209 set terminal postscript portrait enhanced color "Helvetica" 9 2210 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2211 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2212 set key font ",9" 2213 set key samplen "2" 2214 set output "%s.ps" 2215 2216 # This is the "PODO" color palette of gnuplot v.5, but with the order 2217 # changed: palette of colors selected to be easily distinguishable by 2218 # color-blind individuals with either protanopia or deuteranopia. Bang 2219 # Wong [2011] Nature Methods 8, 441. 2220 2221 set style line 1 lt 1 lc rgb "#009e73" lw 1.3 2222 set style line 101 lt 1 lc rgb "#009e73" lw 1.3 dt (6,3) 2223 set style line 11 lt 2 lc rgb "#009e73" lw 1.3 dt (6,3) 2224 set style line 21 lt 4 lc rgb "#009e73" lw 1.3 dt (3,2) 2225 set style line 31 lt 6 lc rgb "#009e73" lw 1.3 dt (2,1) 2226 set style line 41 lt 8 lc rgb "#009e73" lw 1.3 dt (4,3) 2227 2228 set style line 2 lt 1 lc rgb "#0072b2" lw 1.3 2229 set style line 102 lt 1 lc rgb "#0072b2" lw 1.3 dt (6,3) 2230 set style line 12 lt 2 lc rgb "#0072b2" lw 1.3 dt (6,3) 2231 set style line 22 lt 4 lc rgb "#0072b2" lw 1.3 dt (3,2) 2232 set style line 32 lt 6 lc rgb "#0072b2" lw 1.3 dt (2,1) 2233 set style line 42 lt 8 lc rgb "#0072b2" lw 1.3 dt (4,3) 2234 2235 2236 set style line 3 lt 1 lc rgb "#d55e00" lw 1.3 2237 set style line 103 lt 1 lc rgb "#d55e00" lw 1.3 dt (6,3) 2238 set style line 13 lt 2 lc rgb "#d55e00" lw 1.3 dt (6,3) 2239 set style line 23 lt 4 lc rgb "#d55e00" lw 1.3 dt (3,2) 2240 set style line 33 lt 6 lc rgb "#d55e00" lw 1.3 dt (2,1) 2241 set style line 43 lt 8 lc rgb "#d55e00" lw 1.3 dt (4,3) 2242 2243 set style line 4 lt 1 lc rgb "#f0e442" lw 1.3 2244 set style line 104 lt 1 lc rgb "#f0e442" lw 1.3 dt (6,3) 2245 set style line 14 lt 2 lc rgb "#f0e442" lw 1.3 dt (6,3) 2246 set style line 24 lt 4 lc rgb "#f0e442" lw 1.3 dt (3,2) 2247 set style line 34 lt 6 lc rgb "#f0e442" lw 1.3 dt (2,1) 2248 set style line 44 lt 8 lc rgb "#f0e442" lw 1.3 dt (4,3) 2249 2250 set style line 5 lt 1 lc rgb "#56b4e9" lw 1.3 2251 set style line 105 lt 1 lc rgb "#56b4e9" lw 1.3 dt (6,3) 2252 set style line 15 lt 2 lc rgb "#56b4e9" lw 1.3 dt (6,3) 2253 set style line 25 lt 4 lc rgb "#56b4e9" lw 1.3 dt (3,2) 2254 set style line 35 lt 6 lc rgb "#56b4e9" lw 1.3 dt (2,1) 2255 set style line 45 lt 8 lc rgb "#56b4e9" lw 1.3 dt (4,3) 2256 2257 set style line 6 lt 1 lc rgb "#cc79a7" lw 1.3 2258 set style line 106 lt 1 lc rgb "#cc79a7" lw 1.3 dt (6,3) 2259 set style line 16 lt 2 lc rgb "#cc79a7" lw 1.3 dt (6,3) 2260 set style line 26 lt 4 lc rgb "#cc79a7" lw 1.3 dt (3,2) 2261 set style line 36 lt 6 lc rgb "#cc79a7" lw 1.3 dt (2,1) 2262 set style line 46 lt 8 lc rgb "#cc79a7" lw 1.3 dt (4,3) 2263 2264 set style line 7 lt 1 lc rgb "#e69f00" lw 1.3 2265 set style line 107 lt 1 lc rgb "#e69f00" lw 1.3 dt (6,3) 2266 set style line 17 lt 2 lc rgb "#e69f00" lw 1.3 dt (6,3) 2267 set style line 27 lt 4 lc rgb "#e69f00" lw 1.3 dt (3,2) 2268 set style line 37 lt 6 lc rgb "#e69f00" lw 1.3 dt (2,1) 2269 set style line 47 lt 8 lc rgb "#e69f00" lw 1.3 dt (4,3) 2270 2271 set style line 8 lt 1 lc rgb "black" lw 1.3 2272 set style line 108 lt 1 lc rgb "black" lw 1.3 dt (6,3) 2273 set style line 18 lt 2 lc rgb "black" lw 1.3 dt (6,3) 2274 set style line 28 lt 4 lc rgb "black" lw 1.3 dt (3,2) 2275 set style line 38 lt 6 lc rgb "black" lw 1.3 dt (2,1) 2276 set style line 48 lt 8 lc rgb "black" lw 1.3 dt (4,3) 2277 2278 2279 set style line 999 lt 1 lc rgb "gray" lw 1.3 2280 2281 safe(x,y,a) = (y == 0.0 ? a : x/y) 2282 2283 set style data histeps 2284 set key invert 2285 2286 """%(arg_string,output_base_name) 2287 ] 2288 2289 # determine the gnuplot version 2290 try: 2291 p = subprocess.Popen(['gnuplot', '--version'], \ 2292 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 2293 except OSError: 2294 # assume that version 4 of gnuplot is the default if 2295 # gnuplot could not be found 2296 gnuplot_output_list=gnuplot_output_list_v4 2297 else: 2298 output, _ = p.communicate() 2299 if float(output.split()[1]) < 5. : 2300 gnuplot_output_list=gnuplot_output_list_v4 2301 else: 2302 gnuplot_output_list=gnuplot_output_list_v5 2303 2304 2305 # Now output each group one by one 2306 # Block position keeps track of the gnuplot data_block index considered 2307 block_position = 0 2308 for histo_group in self: 2309 # Output this group 2310 block_position = histo_group.output_group(HwU_output_list, 2311 gnuplot_output_list, block_position,output_base_name+'.HwU', 2312 number_of_ratios=number_of_ratios, 2313 uncertainties = uncertainties, 2314 use_band = use_band, 2315 ratio_correlations = ratio_correlations, 2316 jet_samples_to_keep=jet_samples_to_keep, 2317 lhapdfconfig = lhapdfconfig) 2318 2319 # Now write the tail of the gnuplot command file 2320 gnuplot_output_list.extend([ 2321 "unset multiplot", 2322 '!ps2pdf "%s.ps" &> /dev/null'%output_base_name]) 2323 if auto_open: 2324 gnuplot_output_list.append( 2325 '!open "%s.pdf" &> /dev/null'%output_base_name) 2326 2327 # Now write result to stream and close it 2328 gnuplot_stream.write('\n'.join(gnuplot_output_list)) 2329 HwU_stream.write('\n'.join(HwU_output_list)) 2330 gnuplot_stream.close() 2331 HwU_stream.close() 2332 2333 logger.debug("Histograms have been written out at "+\ 2334 "%s.[HwU|gnuplot]' and can "%output_base_name+\ 2335 "now be rendered by invoking gnuplot.")
2336
2337 - def output_group(self, HwU_out, gnuplot_out, block_position, HwU_name, 2338 number_of_ratios = -1, 2339 uncertainties = ['scale','pdf','statitistical','merging_scale','alpsfact'], 2340 use_band = None, 2341 ratio_correlations = True, 2342 jet_samples_to_keep=None, 2343 lhapdfconfig='lhapdf-config'):
2344 2345 """ This functions output a single group of histograms with either one 2346 histograms untyped (i.e. type=None) or two of type 'NLO' and 'LO' 2347 respectively.""" 2348 2349 # This function returns the main central plot line, making sure that 2350 # negative distribution are displayed in dashed style 2351 def get_main_central_plot_lines(HwU_name, block_position, color_index, 2352 title, show_mc_uncertainties): 2353 """ Returns two plot lines, one for the negative contributions in 2354 dashed and one with the positive ones in solid.""" 2355 2356 template = "'%(hwu)s' index %(ind)d using (($1+$2)/2):%(data)s%(stat_col)s%(stat_err)s%(ls)s%(title)s" 2357 template_no_stat = "'%(hwu)s' index %(ind)d using (($1+$2)/2):%(data)s%(ls)s%(title)s" 2358 rep_dic = {'hwu':HwU_name, 2359 'ind':block_position, 2360 'ls':' ls %d'%color_index, 2361 'title':" title '%s'"%title, 2362 'stat_col': ':4', 2363 'stat_err': ' w yerrorbar', 2364 'data':'3', 2365 'linetype':''} 2366 2367 # This would be the original output 2368 # return [template_no_stat%rep_dic]+\ 2369 # ([template%rep_dic] if show_mc_uncertainties else []) 2370 2371 # The use of sqrt(-1) is just a trick to prevent the line to display 2372 res = [] 2373 rep_dic['data'] = '($3 < 0 ? sqrt(-1) : $3)' 2374 res.append(template_no_stat%rep_dic) 2375 rep_dic['title'] = " title ''" 2376 if show_mc_uncertainties: 2377 res.append(template%rep_dic) 2378 rep_dic['data'] = '($3 >= 0 ? sqrt(-1) : abs($3))' 2379 rep_dic['ls'] = ' ls %d'%(100+color_index) 2380 res.append(template_no_stat%rep_dic) 2381 if show_mc_uncertainties: 2382 res.append(template%rep_dic) 2383 return res
2384 2385 # This bool can be modified later to decide whether to use uncertainty 2386 # bands or not 2387 # ======== 2388 def get_uncertainty_lines(HwU_name, block_position, 2389 var_pos, color_index,title, ratio=False, band=False): 2390 """ Return a string line corresponding to the plotting of the 2391 uncertainty. Band is to chose wether to display uncertainty with 2392 a band or two lines.""" 2393 2394 # This perl substitution regular expression copies each line of the 2395 # HwU source and swap the x1 and x2 coordinate of the second copy. 2396 # So if input is: 2397 # 2398 # blabla 2399 # +0.01e+01 0.3 4 5 6 2400 # +0.03e+01 0.5 7 8 9 2401 # ... 2402 # 2403 # The output will be 2404 # 2405 # blabla 2406 # +0.01e+01 0.3 4 5 6 2407 # 0.3 +0.01e+01 4 5 6 2408 # +0.03e+01 0.5 7 8 9 2409 # 0.5 +0.03e+01 7 8 9 2410 # ... 2411 # 2412 copy_swap_re = r"perl -pe 's/^\s*(?<x1>[\+|-]?\d+(\.\d*)?([EeDd][\+|-]?\d+)?)\s*(?<x2>[\+|-]?\d+(\.\d*)?([EeDd][\+|-]?\d+)?)(?<rest>.*)\n/ $+{x1} $+{x2} $+{rest}\n$+{x2} $+{x1} $+{rest}\n/g'" 2413 # Gnuplot escapes the antislash, so we must esacape then once more O_o. 2414 # Gnuplot doesn't have raw strings, what a shame... 2415 copy_swap_re = copy_swap_re.replace('\\','\\\\') 2416 # For the ratio, we must divide by the central value 2417 position = '(safe($%d,$3,1.0)-1.0)' if ratio else '%d' 2418 if not band: 2419 return ["'%s' index %d using (($1+$2)/2):%s ls %d title '%s'"\ 2420 %(HwU_name,block_position, position%(var_pos),color_index,title), 2421 "'%s' index %d using (($1+$2)/2):%s ls %d title ''"\ 2422 %(HwU_name,block_position, position%(var_pos+1),color_index)] 2423 else: 2424 return [' "<%s %s" index %d using 1:%s:%s with filledcurve ls %d fs transparent solid 0.2 title \'%s\''%\ 2425 (copy_swap_re,HwU_name,block_position, 2426 position%var_pos,position%(var_pos+1),color_index,title)] 2427 # ======== 2428 2429 2430 layout_geometry = [(0.0, 0.5, 1.0, 0.4 ), 2431 (0.0, 0.35, 1.0, 0.15), 2432 (0.0, 0.2, 1.0, 0.15)] 2433 layout_geometry.reverse() 2434 2435 # Group histograms which just differ by jet multiplicity and add their 2436 # sum as first plot 2437 matching_histo_lists = HwUList([HwUList([self[0]])]) 2438 for histo in self[1:]: 2439 matched = False 2440 for histo_list in matching_histo_lists: 2441 if hasattr(histo, 'jetsample') and histo.jetsample >= 0 and \ 2442 histo.type == histo_list[0].type: 2443 matched = True 2444 histo_list.append(histo) 2445 break 2446 if not matched: 2447 matching_histo_lists.append(HwUList([histo])) 2448 2449 # For each group of histograms with different jet multiplicities, we 2450 # define one at the beginning which is the sum. 2451 self[:] = [] 2452 for histo_group in matching_histo_lists: 2453 # First create a plot that sums all jet multiplicities for each type 2454 # (that is, only if jet multiplicities are defined) 2455 if len(histo_group)==1: 2456 self.append(histo_group[0]) 2457 continue 2458 # If there is already a histogram summing them, then don't create 2459 # a copy of it. 2460 if any(hist.jetsample==-1 for hist in histo_group if 2461 hasattr(hist, 'jetsample')): 2462 self.extend(histo_group) 2463 continue 2464 summed_histogram = copy.copy(histo_group[0]) 2465 for histo in histo_group[1:]: 2466 summed_histogram = summed_histogram + histo 2467 summed_histogram.jetsample = -1 2468 self.append(summed_histogram) 2469 self.extend(histo_group) 2470 2471 # Remove the curve of individual jet samples if they are not desired 2472 if not jet_samples_to_keep is None: 2473 self[:] = filter(lambda histo: 2474 (not hasattr(histo,'jetsample')) or (histo.jetsample == -1) or 2475 (histo.jetsample in jet_samples_to_keep), self) 2476 2477 # This function is to create the ratio histograms if the user turned off 2478 # correlations. 2479 def ratio_no_correlations(wgtsA, wgtsB): 2480 new_wgts = {} 2481 for label, wgt in wgtsA.items(): 2482 if wgtsB['central']==0.0 and wgt==0.0: 2483 new_wgts[label] = 0.0 2484 continue 2485 elif wgtsB['central']==0.0: 2486 # It is ok to skip the warning here. 2487 # logger.debug('Warning:: A bin with finite weight '+ 2488 # 'was divided by a bin with zero weight.') 2489 new_wgts[label] = 0.0 2490 continue 2491 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2492 return new_wgts 2493 2494 # First compute the ratio of all the histograms from the second to the 2495 # number_of_ratios+1 ones in the list to the first histogram. 2496 n_histograms = len(self) 2497 ratio_histos = HwUList([]) 2498 # A counter to keep track of the number of ratios included 2499 n_ratios_included = 0 2500 for i, histo in enumerate(self[1:]): 2501 if not hasattr(histo,'jetsample') or histo.jetsample==self[0].jetsample: 2502 n_ratios_included += 1 2503 else: 2504 continue 2505 2506 if number_of_ratios >=0 and n_ratios_included > number_of_ratios: 2507 break 2508 2509 if ratio_correlations: 2510 ratio_histos.append(histo/self[0]) 2511 else: 2512 ratio_histos.append(self[0].__class__.combine(histo, self[0], 2513 ratio_no_correlations)) 2514 if self[0].type=='NLO' and self[1].type=='LO': 2515 ratio_histos[-1].title += '1/K-factor' 2516 elif self[0].type=='LO' and self[1].type=='NLO': 2517 ratio_histos[-1].title += 'K-factor' 2518 else: 2519 ratio_histos[-1].title += ' %s/%s'%( 2520 self[1].type if self[1].type else '(%d)'%(i+2), 2521 self[0].type if self[0].type else '(1)') 2522 # By setting its type to aux, we make sure this histogram will be 2523 # filtered out if the .HwU file output here would be re-loaded later. 2524 ratio_histos[-1].type = 'AUX' 2525 self.extend(ratio_histos) 2526 2527 # Compute scale variation envelope for all diagrams 2528 if 'scale' in uncertainties: 2529 (mu_var_pos,mu) = self[0].set_uncertainty(type='all_scale') 2530 else: 2531 (mu_var_pos,mu) = (None,[None]) 2532 2533 if 'pdf' in uncertainties: 2534 (PDF_var_pos,pdf) = self[0].set_uncertainty(type='PDF',lhapdfconfig=lhapdfconfig) 2535 else: 2536 (PDF_var_pos,pdf) = (None,[None]) 2537 2538 if 'merging_scale' in uncertainties: 2539 (merging_var_pos,merging) = self[0].set_uncertainty(type='merging') 2540 else: 2541 (merging_var_pos,merging) = (None,[None]) 2542 if 'alpsfact' in uncertainties: 2543 (alpsfact_var_pos,alpsfact) = self[0].set_uncertainty(type='alpsfact') 2544 else: 2545 (alpsfact_var_pos,alpsfact) = (None,[None]) 2546 2547 uncertainties_present = list(uncertainties) 2548 if PDF_var_pos is None and 'pdf' in uncertainties_present: 2549 uncertainties_present.remove('pdf') 2550 if mu_var_pos is None and 'scale' in uncertainties_present: 2551 uncertainties_present.remove('scale') 2552 if merging_var_pos is None and 'merging' in uncertainties_present: 2553 uncertainties_present.remove('merging') 2554 if alpsfact_var_pos is None and 'alpsfact' in uncertainties_present: 2555 uncertainties_present.remove('alpsfact') 2556 no_uncertainties = len(uncertainties_present)==0 2557 2558 # If the 'use_band' option is None we should adopt a default which is 2559 try: 2560 uncertainties_present.remove('statistical') 2561 except: 2562 pass 2563 if use_band is None: 2564 # For clarity, it is better to only use bands only for one source 2565 # of uncertainty 2566 if len(uncertainties_present)==0: 2567 use_band = [] 2568 elif len(uncertainties_present)==1: 2569 use_band = uncertainties_present 2570 elif 'scale' in uncertainties_present: 2571 use_band = ['scale'] 2572 else: 2573 use_band = [uncertainties_present[0]] 2574 2575 for histo in self[1:]: 2576 if (not mu_var_pos is None) and \ 2577 mu_var_pos != histo.set_uncertainty(type='all_scale')[0]: 2578 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2579 ' scale uncertainties. It is required to be able to output them'+\ 2580 ' together.' 2581 if (not PDF_var_pos is None) and\ 2582 PDF_var_pos != histo.set_uncertainty(type='PDF',\ 2583 lhapdfconfig=lhapdfconfig)[0]: 2584 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2585 ' PDF uncertainties. It is required to be able to output them'+\ 2586 ' together.' 2587 if (not merging_var_pos is None) and\ 2588 merging_var_pos != histo.set_uncertainty(type='merging')[0]: 2589 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2590 ' merging uncertainties. It is required to be able to output them'+\ 2591 ' together.' 2592 if (not alpsfact_var_pos is None) and\ 2593 alpsfact_var_pos != histo.set_uncertainty(type='alpsfact')[0]: 2594 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2595 ' alpsfact uncertainties. It is required to be able to output them'+\ 2596 ' together.' 2597 2598 2599 # Now output the corresponding HwU histogram data 2600 for i, histo in enumerate(self): 2601 # Print the header the first time only 2602 HwU_out.extend(histo.get_HwU_source(\ 2603 print_header=(block_position==0 and i==0))) 2604 HwU_out.extend(['','']) 2605 2606 # First the global gnuplot header for this histogram group 2607 global_header =\ 2608 """ 2609 ################################################################################ 2610 ### Rendering of the plot titled '%(title)s' 2611 ################################################################################ 2612 2613 set multiplot 2614 set label "%(title)s" font ",13" at graph 0.04, graph 1.05 2615 set xrange [%(xmin).4e:%(xmax).4e] 2616 set bmargin 0 2617 set tmargin 0 2618 set xtics nomirror 2619 set ytics nomirror 2620 set mytics %(mxtics)d 2621 %(set_xtics)s 2622 set key horizontal noreverse maxcols 1 width -4 2623 set label front 'MadGraph5\_aMC\@NLO' font "Courier,11" rotate by 90 at graph 1.02, graph 0.04 2624 """ 2625 2626 # Now the header for each subhistogram 2627 subhistogram_header = \ 2628 """#-- rendering subhistograms '%(subhistogram_type)s' 2629 %(unset label)s 2630 %(set_format_y)s 2631 set yrange [%(ymin).4e:%(ymax).4e] 2632 set origin %(origin_x).4e, %(origin_y).4e 2633 set size %(size_x).4e, %(size_y).4e 2634 set mytics %(mytics)d 2635 %(set_ytics)s 2636 %(set_format_x)s 2637 %(set_yscale)s 2638 %(set_ylabel)s 2639 %(set_histo_label)s 2640 plot \\""" 2641 replacement_dic = {} 2642 2643 replacement_dic['title'] = self[0].get_HwU_histogram_name(format='human-no_type') 2644 # Determine what weight to consider when computing the optimal 2645 # range for the y-axis. 2646 wgts_to_consider = ['central'] 2647 if not mu_var_pos is None: 2648 for mu_var in mu_var_pos: 2649 wgts_to_consider.append(self[0].bins.weight_labels[mu_var]) 2650 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+1]) 2651 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+2]) 2652 if not PDF_var_pos is None: 2653 for PDF_var in PDF_var_pos: 2654 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var]) 2655 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+1]) 2656 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+2]) 2657 if not merging_var_pos is None: 2658 for merging_var in merging_var_pos: 2659 wgts_to_consider.append(self[0].bins.weight_labels[merging_var]) 2660 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+1]) 2661 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+2]) 2662 if not alpsfact_var_pos is None: 2663 for alpsfact_var in alpsfact_var_pos: 2664 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var]) 2665 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+1]) 2666 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+2]) 2667 2668 (xmin, xmax) = HwU.get_x_optimal_range(self[:2],\ 2669 weight_labels = wgts_to_consider) 2670 replacement_dic['xmin'] = xmin 2671 replacement_dic['xmax'] = xmax 2672 replacement_dic['mxtics'] = 10 2673 replacement_dic['set_xtics'] = 'set xtics auto' 2674 2675 # Add the global header which is now ready 2676 gnuplot_out.append(global_header%replacement_dic) 2677 2678 # Now add the main plot 2679 replacement_dic['subhistogram_type'] = '%s and %s results'%( 2680 str(self[0].type),str(self[1].type)) if len(self)>1 else \ 2681 'single diagram output' 2682 (ymin, ymax) = HwU.get_y_optimal_range(self[:2], 2683 labels = wgts_to_consider, scale=self[0].y_axis_mode) 2684 2685 # Force a linear scale if the detected range is negative 2686 if ymin< 0.0: 2687 self[0].y_axis_mode = 'LIN' 2688 2689 # Already add a margin on upper bound. 2690 if self[0].y_axis_mode=='LOG': 2691 ymax += 10.0 * ymax 2692 ymin -= 0.1 * ymin 2693 else: 2694 ymax += 0.3 * (ymax - ymin) 2695 ymin -= 0.3 * (ymax - ymin) 2696 2697 replacement_dic['ymin'] = ymin 2698 replacement_dic['ymax'] = ymax 2699 replacement_dic['unset label'] = '' 2700 (replacement_dic['origin_x'], replacement_dic['origin_y'], 2701 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 2702 replacement_dic['mytics'] = 10 2703 # Use default choise for the main histogram 2704 replacement_dic['set_ytics'] = 'set ytics auto' 2705 replacement_dic['set_format_x'] = "set format x ''" if \ 2706 (len(self)-n_histograms>0 or not no_uncertainties) else "set format x" 2707 replacement_dic['set_ylabel'] = 'set ylabel "{/Symbol s} per bin [pb]"' 2708 replacement_dic['set_yscale'] = "set logscale y" if \ 2709 self[0].y_axis_mode=='LOG' else 'unset logscale y' 2710 replacement_dic['set_format_y'] = "set format y '10^{%T}'" if \ 2711 self[0].y_axis_mode=='LOG' else 'unset format' 2712 2713 replacement_dic['set_histo_label'] = "" 2714 gnuplot_out.append(subhistogram_header%replacement_dic) 2715 2716 # Now add the main layout 2717 plot_lines = [] 2718 uncertainty_plot_lines = [] 2719 n=-1 2720 2721 for i, histo in enumerate(self[:n_histograms]): 2722 n=n+1 2723 color_index = n%self.number_line_colors_defined+1 2724 # Label to appear for the lower curves 2725 title = [] 2726 if histo.type is None and not hasattr(histo, 'jetsample'): 2727 title.append('%d'%(i+1)) 2728 else: 2729 if histo.type: 2730 title.append('NLO' if \ 2731 histo.type.split()[0]=='NLO' else histo.type) 2732 if hasattr(histo, 'jetsample'): 2733 if histo.jetsample!=-1: 2734 title.append('jet sample %d'%histo.jetsample) 2735 else: 2736 title.append('all jet samples') 2737 2738 title = ', '.join(title) 2739 # Label for the first curve in the upper plot 2740 if histo.type is None and not hasattr(histo, 'jetsample'): 2741 major_title = 'central value for plot (%d)'%(i+1) 2742 else: 2743 major_title = [] 2744 if not histo.type is None: 2745 major_title.append(histo.type) 2746 if hasattr(histo, 'jetsample'): 2747 if histo.jetsample!=-1: 2748 major_title.append('jet sample %d'%histo.jetsample) 2749 else: 2750 major_title.append('all jet samples') 2751 else: 2752 major_title.append('central value') 2753 major_title = ', '.join(major_title) 2754 2755 if not mu[0] in ['none',None]: 2756 major_title += ', dynamical\_scale\_choice=%s'%mu[0] 2757 if not pdf[0] in ['none',None]: 2758 major_title += ', PDF=%s'%pdf[0].replace('_','\_') 2759 2760 # Do not show uncertainties for individual jet samples (unless first 2761 # or specified explicitely and uniquely) 2762 if not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 2763 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 2764 jet_samples_to_keep[0] == histo.jetsample)): 2765 2766 uncertainty_plot_lines.append({}) 2767 2768 # We decide to show uncertainties in the main plot only if they 2769 # are part of a monocolor band. Otherwise, they will only be 2770 # shown in the first subplot. Notice that plotting 'sqrt(-1)' 2771 # is just a trick so as to have only the key printed with no 2772 # line 2773 2774 # Show scale variation for the first central value if available 2775 if not mu_var_pos is None and len(mu_var_pos)>0: 2776 if 'scale' in use_band: 2777 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 2778 HwU_name, block_position+i, mu_var_pos[0]+4, color_index+10, 2779 '%s, scale variation'%title, band='scale' in use_band) 2780 else: 2781 uncertainty_plot_lines[-1]['scale'] = \ 2782 ["sqrt(-1) ls %d title '%s'"%(color_index+10,'%s, scale variation'%title)] 2783 # And now PDF_variation if available 2784 if not PDF_var_pos is None and len(PDF_var_pos)>0: 2785 if 'pdf' in use_band: 2786 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 2787 HwU_name,block_position+i, PDF_var_pos[0]+4, color_index+20, 2788 '%s, PDF variation'%title, band='pdf' in use_band) 2789 else: 2790 uncertainty_plot_lines[-1]['pdf'] = \ 2791 ["sqrt(-1) ls %d title '%s'"%(color_index+20,'%s, PDF variation'%title)] 2792 # And now merging variation if available 2793 if not merging_var_pos is None and len(merging_var_pos)>0: 2794 if 'merging_scale' in use_band: 2795 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 2796 HwU_name,block_position+i, merging_var_pos[0]+4, color_index+30, 2797 '%s, merging scale variation'%title, band='merging_scale' in use_band) 2798 else: 2799 uncertainty_plot_lines[-1]['merging_scale'] = \ 2800 ["sqrt(-1) ls %d title '%s'"%(color_index+30,'%s, merging scale variation'%title)] 2801 # And now alpsfact variation if available 2802 if not alpsfact_var_pos is None and len(alpsfact_var_pos)>0: 2803 if 'alpsfact' in use_band: 2804 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 2805 HwU_name,block_position+i, alpsfact_var_pos[0]+4, color_index+40, 2806 '%s, alpsfact variation'%title, band='alpsfact' in use_band) 2807 else: 2808 uncertainty_plot_lines[-1]['alpsfact'] = \ 2809 ["sqrt(-1) ls %d title '%s'"%(color_index+40,'%s, alpsfact variation'%title)] 2810 2811 # plot_lines.append( 2812 # "'%s' index %d using (($1+$2)/2):3 ls %d title '%s'"\ 2813 # %(HwU_name,block_position+i,color_index, major_title)) 2814 # if 'statistical' in uncertainties: 2815 # plot_lines.append( 2816 # "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"\ 2817 # %(HwU_name,block_position+i,color_index)) 2818 2819 plot_lines.extend( 2820 get_main_central_plot_lines(HwU_name, block_position+i, 2821 color_index, major_title, 'statistical' in uncertainties)) 2822 2823 # Add additional central scale/PDF curves 2824 if not mu_var_pos is None: 2825 for j,mu_var in enumerate(mu_var_pos): 2826 if j!=0: 2827 n=n+1 2828 color_index = n%self.number_line_colors_defined+1 2829 plot_lines.append( 2830 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2831 %(HwU_name,block_position+i,mu_var+3,color_index,\ 2832 '%s dynamical\_scale\_choice=%s' % (title,mu[j]))) 2833 # And now PDF_variation if available 2834 if not PDF_var_pos is None: 2835 for j,PDF_var in enumerate(PDF_var_pos): 2836 if j!=0: 2837 n=n+1 2838 color_index = n%self.number_line_colors_defined+1 2839 plot_lines.append( 2840 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2841 %(HwU_name,block_position+i,PDF_var+3,color_index,\ 2842 '%s PDF=%s' % (title,pdf[j].replace('_','\_')))) 2843 2844 # Now add the uncertainty lines, those not using a band so that they 2845 # are not covered by those using a band after we reverse plo_lines 2846 for one_plot in uncertainty_plot_lines: 2847 for uncertainty_type, lines in one_plot.items(): 2848 if not uncertainty_type in use_band: 2849 plot_lines.extend(lines) 2850 # then those using a band 2851 for one_plot in uncertainty_plot_lines: 2852 for uncertainty_type, lines in one_plot.items(): 2853 if uncertainty_type in use_band: 2854 plot_lines.extend(lines) 2855 2856 # Reverse so that bands appear first 2857 plot_lines.reverse() 2858 2859 # Add the plot lines 2860 gnuplot_out.append(',\\\n'.join(plot_lines)) 2861 2862 # Now we can add the scale variation ratio 2863 replacement_dic['subhistogram_type'] = 'Relative scale and PDF uncertainty' 2864 2865 if 'statistical' in uncertainties: 2866 wgts_to_consider.append('stat_error') 2867 2868 # This function is just to temporarily create the scale ratio histogram with 2869 # the hwu.combine function. 2870 def rel_scale(wgtsA, wgtsB): 2871 new_wgts = {} 2872 for label, wgt in wgtsA.items(): 2873 if label in wgts_to_consider: 2874 if wgtsB['central']==0.0 and wgt==0.0: 2875 new_wgts[label] = 0.0 2876 continue 2877 elif wgtsB['central']==0.0: 2878 # It is ok to skip the warning here. 2879 # logger.debug('Warning:: A bin with finite weight '+ 2880 # 'was divided by a bin with zero weight.') 2881 new_wgts[label] = 0.0 2882 continue 2883 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2884 if label != 'stat_error': 2885 new_wgts[label] -= 1.0 2886 else: 2887 new_wgts[label] = wgtsA[label] 2888 return new_wgts 2889 2890 histos_for_subplots = [(i,histo) for i, histo in enumerate(self[:n_histograms]) if 2891 ( not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 2892 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 2893 jet_samples_to_keep[0] == histo.jetsample)) )] 2894 2895 # Notice even though a ratio histogram is created here, it 2896 # is not actually used to plot the quantity in gnuplot, but just to 2897 # compute the y range. 2898 (ymin, ymax) = HwU.get_y_optimal_range([histo[1].__class__.combine( 2899 histo[1],histo[1],rel_scale) for histo in histos_for_subplots], 2900 labels = wgts_to_consider, scale='LIN') 2901 2902 # Add a margin on upper and lower bound. 2903 ymax = ymax + 0.2 * (ymax - ymin) 2904 ymin = ymin - 0.2 * (ymax - ymin) 2905 replacement_dic['unset label'] = 'unset label' 2906 replacement_dic['ymin'] = ymin 2907 replacement_dic['ymax'] = ymax 2908 if not no_uncertainties: 2909 (replacement_dic['origin_x'], replacement_dic['origin_y'], 2910 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 2911 replacement_dic['mytics'] = 2 2912 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/3.0) 2913 replacement_dic['set_ytics'] = 'set ytics auto' 2914 replacement_dic['set_format_x'] = "set format x ''" if \ 2915 len(self)-n_histograms>0 else "set format x" 2916 replacement_dic['set_ylabel'] = 'set ylabel "%s rel.unc."'\ 2917 %('(1)' if self[0].type==None else '%s'%('NLO' if \ 2918 self[0].type.split()[0]=='NLO' else self[0].type)) 2919 replacement_dic['set_yscale'] = "unset logscale y" 2920 replacement_dic['set_format_y'] = 'unset format' 2921 2922 2923 tit='Relative uncertainties w.r.t. central value' 2924 if n_histograms > 1: 2925 tit=tit+'s' 2926 # if (not mu_var_pos is None and 'scale' not in use_band): 2927 # tit=tit+', scale is dashed' 2928 # if (not PDF_var_pos is None and 'pdf' not in use_band): 2929 # tit=tit+', PDF is dotted' 2930 replacement_dic['set_histo_label'] = \ 2931 'set label "%s" font ",9" front at graph 0.03, graph 0.13' % tit 2932 # Simply don't add these lines if there are no uncertainties. 2933 # This meant uncessary extra work, but I no longer car at this point 2934 if not no_uncertainties: 2935 gnuplot_out.append(subhistogram_header%replacement_dic) 2936 2937 # Now add the first subhistogram 2938 plot_lines = [] 2939 uncertainty_plot_lines = [] 2940 n=-1 2941 for (i,histo) in histos_for_subplots: 2942 n=n+1 2943 k=n 2944 color_index = n%self.number_line_colors_defined+1 2945 # Plot uncertainties 2946 if not mu_var_pos is None: 2947 for j,mu_var in enumerate(mu_var_pos): 2948 uncertainty_plot_lines.append({}) 2949 if j==0: 2950 color_index = k%self.number_line_colors_defined+1 2951 else: 2952 n=n+1 2953 color_index = n%self.number_line_colors_defined+1 2954 # Add the central line only if advanced scale variation 2955 if j>0 or mu[j]!='none': 2956 plot_lines.append( 2957 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2958 %(HwU_name,block_position+i,mu_var+3,color_index)) 2959 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 2960 HwU_name, block_position+i, mu_var+4, color_index+10,'', 2961 ratio=True, band='scale' in use_band) 2962 if not PDF_var_pos is None: 2963 for j,PDF_var in enumerate(PDF_var_pos): 2964 uncertainty_plot_lines.append({}) 2965 if j==0: 2966 color_index = k%self.number_line_colors_defined+1 2967 else: 2968 n=n+1 2969 color_index = n%self.number_line_colors_defined+1 2970 # Add the central line only if advanced pdf variation 2971 if j>0 or pdf[j]!='none': 2972 plot_lines.append( 2973 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2974 %(HwU_name,block_position+i,PDF_var+3,color_index)) 2975 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 2976 HwU_name, block_position+i, PDF_var+4, color_index+20,'', 2977 ratio=True, band='pdf' in use_band) 2978 if not merging_var_pos is None: 2979 for j,merging_var in enumerate(merging_var_pos): 2980 uncertainty_plot_lines.append({}) 2981 if j==0: 2982 color_index = k%self.number_line_colors_defined+1 2983 else: 2984 n=n+1 2985 color_index = n%self.number_line_colors_defined+1 2986 if j>0 or merging[j]!='none': 2987 plot_lines.append( 2988 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2989 %(HwU_name,block_position+i,merging_var+3,color_index)) 2990 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 2991 HwU_name, block_position+i, merging_var+4, color_index+30,'', 2992 ratio=True, band='merging_scale' in use_band) 2993 if not alpsfact_var_pos is None: 2994 for j,alpsfact_var in enumerate(alpsfact_var_pos): 2995 uncertainty_plot_lines.append({}) 2996 if j==0: 2997 color_index = k%self.number_line_colors_defined+1 2998 else: 2999 n=n+1 3000 color_index = n%self.number_line_colors_defined+1 3001 if j>0 or alpsfact[j]!='none': 3002 plot_lines.append( 3003 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 3004 %(HwU_name,block_position+i,alpsfact_var+3,color_index)) 3005 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 3006 HwU_name, block_position+i, alpsfact_var+4, color_index+40,'', 3007 ratio=True, band='alpsfact' in use_band) 3008 3009 if 'statistical' in uncertainties: 3010 plot_lines.append( 3011 "'%s' index %d using (($1+$2)/2):(0.0):(safe($4,$3,0.0)) w yerrorbar ls %d title ''"%\ 3012 (HwU_name,block_position+i,color_index)) 3013 3014 plot_lines.append("0.0 ls 999 title ''") 3015 3016 # Now add the uncertainty lines, those not using a band so that they 3017 # are not covered by those using a band after we reverse plo_lines 3018 for one_plot in uncertainty_plot_lines: 3019 for uncertainty_type, lines in one_plot.items(): 3020 if not uncertainty_type in use_band: 3021 plot_lines.extend(lines) 3022 # then those using a band 3023 for one_plot in uncertainty_plot_lines: 3024 for uncertainty_type, lines in one_plot.items(): 3025 if uncertainty_type in use_band: 3026 plot_lines.extend(lines) 3027 3028 # Reverse so that bands appear first 3029 plot_lines.reverse() 3030 # Add the plot lines 3031 if not no_uncertainties: 3032 gnuplot_out.append(',\\\n'.join(plot_lines)) 3033 3034 # We finish here when no ratio plot are asked for. 3035 if len(self)-n_histograms==0: 3036 # Now add the tail for this group 3037 gnuplot_out.extend(['','unset label','', 3038 '################################################################################']) 3039 # Return the starting data_block position for the next histogram group 3040 return block_position+len(self) 3041 3042 # We can finally add the last subhistograms for the ratios. 3043 ratio_name_long='(' 3044 for i, histo in enumerate(self[:n_histograms]): 3045 if i==0: continue 3046 ratio_name_long+='%d'%(i+1) if histo.type is None else ('NLO' if \ 3047 histo.type.split()[0]=='NLO' else histo.type) 3048 ratio_name_long+=')/' 3049 ratio_name_long+=('(1' if self[0].type==None else '(%s'%('NLO' if \ 3050 self[0].type.split()[0]=='NLO' else self[0].type))+' central value)' 3051 3052 ratio_name_short = 'ratio w.r.t. '+('1' if self[0].type==None else '%s'%('NLO' if \ 3053 self[0].type.split()[0]=='NLO' else self[0].type)) 3054 3055 replacement_dic['subhistogram_type'] = '%s ratio'%ratio_name_long 3056 replacement_dic['set_ylabel'] = 'set ylabel "%s"'%ratio_name_short 3057 3058 (ymin, ymax) = HwU.get_y_optimal_range(self[n_histograms:], 3059 labels = wgts_to_consider, scale='LIN',Kratio = True) 3060 3061 # Add a margin on upper and lower bound. 3062 ymax = ymax + 0.2 * (ymax - ymin) 3063 ymin = ymin - 0.2 * (ymax - ymin) 3064 replacement_dic['unset label'] = 'unset label' 3065 replacement_dic['ymin'] = ymin 3066 replacement_dic['ymax'] = ymax 3067 (replacement_dic['origin_x'], replacement_dic['origin_y'], 3068 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 3069 replacement_dic['mytics'] = 2 3070 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/10.0) 3071 replacement_dic['set_ytics'] = 'set ytics auto' 3072 replacement_dic['set_format_x'] = "set format x" 3073 replacement_dic['set_yscale'] = "unset logscale y" 3074 replacement_dic['set_format_y'] = 'unset format' 3075 replacement_dic['set_histo_label'] = \ 3076 'set label "%s" font ",9" at graph 0.03, graph 0.13'%ratio_name_long 3077 # 'set label "NLO/LO (K-factor)" font ",9" at graph 0.82, graph 0.13' 3078 gnuplot_out.append(subhistogram_header%replacement_dic) 3079 3080 uncertainty_plot_lines = [] 3081 plot_lines = [] 3082 3083 # Some crap to get the colors right I suppose... 3084 n=-1 3085 n=n+1 3086 if not mu_var_pos is None: 3087 for j,mu_var in enumerate(mu_var_pos): 3088 if j!=0: n=n+1 3089 if not PDF_var_pos is None: 3090 for j,PDF_var in enumerate(PDF_var_pos): 3091 if j!=0: n=n+1 3092 if not merging_var_pos is None: 3093 for j,merging_var in enumerate(merging_var_pos): 3094 if j!=0: n=n+1 3095 if not alpsfact_var_pos is None: 3096 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3097 if j!=0: n=n+1 3098 3099 for i_histo_ratio, histo_ration in enumerate(self[n_histograms:]): 3100 n=n+1 3101 k=n 3102 block_ratio_pos = block_position+n_histograms+i_histo_ratio 3103 color_index = n%self.number_line_colors_defined+1 3104 # Now add the subhistograms 3105 plot_lines.append( 3106 "'%s' index %d using (($1+$2)/2):3 ls %d title ''"%\ 3107 (HwU_name,block_ratio_pos,color_index)) 3108 if 'statistical' in uncertainties: 3109 plot_lines.append( 3110 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"%\ 3111 (HwU_name,block_ratio_pos,color_index)) 3112 3113 # Then the scale variations 3114 if not mu_var_pos is None: 3115 for j,mu_var in enumerate(mu_var_pos): 3116 uncertainty_plot_lines.append({}) 3117 if j==0: 3118 color_index = k%self.number_line_colors_defined+1 3119 else: 3120 n=n+1 3121 color_index = n%self.number_line_colors_defined+1 3122 # Only print out the additional central value for advanced scale variation 3123 if j>0 or mu[j]!='none': 3124 plot_lines.append( 3125 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3126 %(HwU_name,block_ratio_pos,mu_var+3,color_index)) 3127 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 3128 HwU_name, block_ratio_pos, mu_var+4, color_index+10,'', 3129 band='scale' in use_band) 3130 if not PDF_var_pos is None: 3131 for j,PDF_var in enumerate(PDF_var_pos): 3132 uncertainty_plot_lines.append({}) 3133 if j==0: 3134 color_index = k%self.number_line_colors_defined+1 3135 else: 3136 n=n+1 3137 color_index = n%self.number_line_colors_defined+1 3138 # Only print out the additional central value for advanced pdf variation 3139 if j>0 or pdf[j]!='none': 3140 plot_lines.append( 3141 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3142 %(HwU_name,block_ratio_pos,PDF_var+3,color_index)) 3143 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 3144 HwU_name, block_ratio_pos, PDF_var+4, color_index+20,'', 3145 band='pdf' in use_band) 3146 if not merging_var_pos is None: 3147 for j,merging_var in enumerate(merging_var_pos): 3148 uncertainty_plot_lines.append({}) 3149 if j==0: 3150 color_index = k%self.number_line_colors_defined+1 3151 else: 3152 n=n+1 3153 color_index = n%self.number_line_colors_defined+1 3154 if j>0 or merging[j]!='none': 3155 plot_lines.append( 3156 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3157 %(HwU_name,block_ratio_pos,merging_var+3,color_index)) 3158 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 3159 HwU_name, block_ratio_pos, merging_var+4, color_index+30,'', 3160 band='merging_scale' in use_band) 3161 if not alpsfact_var_pos is None: 3162 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3163 uncertainty_plot_lines.append({}) 3164 if j==0: 3165 color_index = k%self.number_line_colors_defined+1 3166 else: 3167 n=n+1 3168 color_index = n%self.number_line_colors_defined+1 3169 if j>0 or alpsfact[j]!='none': 3170 plot_lines.append( 3171 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3172 %(HwU_name,block_ratio_pos,alpsfact_var+3,color_index)) 3173 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 3174 HwU_name, block_ratio_pos, alpsfact_var+4, color_index+40,'', 3175 band='alpsfact' in use_band) 3176 3177 # Now add the uncertainty lines, those not using a band so that they 3178 # are not covered by those using a band after we reverse plo_lines 3179 for one_plot in uncertainty_plot_lines: 3180 for uncertainty_type, lines in one_plot.items(): 3181 if not uncertainty_type in use_band: 3182 plot_lines.extend(lines) 3183 # then those using a band 3184 for one_plot in uncertainty_plot_lines: 3185 for uncertainty_type, lines in one_plot.items(): 3186 if uncertainty_type in use_band: 3187 plot_lines.extend(lines) 3188 3189 plot_lines.append("1.0 ls 999 title ''") 3190 3191 # Reverse so that bands appear first 3192 plot_lines.reverse() 3193 # Add the plot lines 3194 gnuplot_out.append(',\\\n'.join(plot_lines)) 3195 3196 # Now add the tail for this group 3197 gnuplot_out.extend(['','unset label','', 3198 '################################################################################']) 3199 3200 # Return the starting data_block position for the next histogram group 3201 return block_position+len(self) 3202 3203 if __name__ == "__main__": 3204 main_doc = \ 3205 """ For testing and standalone use. Usage: 3206 python histograms.py <.HwU input_file_path_1> <.HwU input_file_path_2> ... --out=<output_file_path.format> <options> 3207 Where <options> can be a list of the following: 3208 '--help' See this message. 3209 '--gnuplot' or '' output the histograms read to gnuplot 3210 '--HwU' to output the histograms read to the raw HwU source. 3211 '--types=<type1>,<type2>,...' to keep only the type<i> when importing histograms. 3212 '--titles=<title1>,<title2>,...' to keep only the titles which have any of 'title<i>' in them (not necessarily equal to them) 3213 '--n_ratios=<integer>' Specifies how many curves must be considerd for the ratios. 3214 '--no_open' Turn off the automatic processing of the gnuplot output. 3215 '--show_full' to show the complete output of what was read. 3216 '--show_short' to show a summary of what was read. 3217 '--simple_ratios' to turn off correlations and error propagation in the ratio. 3218 '--sum' To sum all identical histograms together 3219 '--average' To average over all identical histograms 3220 '--rebin=<n>' Rebin the plots by merging n-consecutive bins together. 3221 '--assign_types=<type1>,<type2>,...' to assign a type to all histograms of the first, second, etc... files loaded. 3222 '--multiply=<fact1>,<fact2>,...' to multiply all histograms of the first, second, etc... files by the fact1, fact2, etc... 3223 '--no_suffix' Do no add any suffix (like '#1, #2, etc..) to the histograms types. 3224 '--lhapdf-config=<PATH_TO_LHAPDF-CONFIG>' give path to lhapdf-config to compute PDF certainties using LHAPDF (only for lhapdf6) 3225 '--jet_samples=[int1,int2]' Specifies what jet samples to keep. 'None' is the default and keeps them all. 3226 '--central_only' This option specifies to disregard all extra weights, so as to make it possible 3227 to take the ratio of plots with different extra weights specified. 3228 '--keep_all_weights' This option specifies to keep in the HwU produced all the weights, even 3229 those which are not known (i.e. that is scale, PDF or merging variation) 3230 For chosing what kind of variation you want to see on your plot, you can use the following options 3231 '--no_<type>' Turn off the plotting of variations of the chosen type 3232 '--only_<type>' Turn on only the plotting of variations of the chosen type 3233 '--variations=['<type1>',...]' Turn on only the plotting of the variations of the list of chosen types 3234 '--band=['<type1>',...]' Chose for which variations one should use uncertainty bands as opposed to lines 3235 The types can be: pdf, scale, stat, merging or alpsfact 3236 For the last two options one can use ...=all to automatically select all types. 3237 3238 When parsing an XML-formatted plot source output by the Pythia8 driver, the file names can be appended 3239 options as suffixes separated by '|', as follows: 3240 python histograms.py <XML_source_file_name>@<option1>@<option2>@etc.. 3241 These options can be 3242 'run_id=<integer>' Specifies the run_ID from which the plots should be loaded. 3243 By default, the first run is considered and the ones that follow are ignored. 3244 'merging_scale=<float>' This option allows to specify to import only the plots corresponding to a specific 3245 value for the merging scale. 3246 A value of -1 means that only the weights with the same merging scale as the central weight are kept. 3247 By default, all weights are considered. 3248 """ 3249 3250 possible_options=['--help', '--gnuplot', '--HwU', '--types','--n_ratios',\ 3251 '--no_open','--show_full','--show_short','--simple_ratios','--sum','--average','--rebin', \ 3252 '--assign_types','--multiply','--no_suffix', '--out', '--jet_samples', 3253 '--no_scale','--no_pdf','--no_stat','--no_merging','--no_alpsfact', 3254 '--only_scale','--only_pdf','--only_stat','--only_merging','--only_alpsfact', 3255 '--variations','--band','--central_only', '--lhapdf-config','--titles', 3256 '--keep_all_weights'] 3257 n_ratios = -1 3258 uncertainties = ['scale','pdf','statistical','merging_scale','alpsfact'] 3259 # The list of type of uncertainties for which to use bands. None is a 'smart' default 3260 use_band = None 3261 auto_open = True 3262 ratio_correlations = True 3263 consider_reweights = ['pdf','scale','murmuf_scales','merging_scale','alpsfact']
3264 3265 - def log(msg):
3266 print "histograms.py :: %s"%str(msg)
3267 3268 if '--help' in sys.argv or len(sys.argv)==1: 3269 log('\n\n%s'%main_doc) 3270 sys.exit(0) 3271 3272 for arg in sys.argv[1:]: 3273 if arg.startswith('--'): 3274 if arg.split('=')[0] not in possible_options: 3275 log('WARNING: option "%s" not valid. It will be ignored' % arg) 3276 3277 arg_string=' '.join(sys.argv) 3278 3279 OutName = "" 3280 for arg in sys.argv[1:]: 3281 if arg.startswith('--out='): 3282 OutName = arg[6:] 3283 3284 accepted_types = [] 3285 for arg in sys.argv[1:]: 3286 if arg.startswith('--types='): 3287 accepted_types = [(type if type!='None' else None) for type in \ 3288 arg[8:].split(',')] 3289 3290 accepted_titles = [] 3291 for arg in sys.argv[1:]: 3292 if arg.startswith('--titles='): 3293 accepted_titles = [(type if type!='None' else None) for type in \ 3294 arg[9:].split(',')] 3295 3296 assigned_types = [] 3297 for arg in sys.argv[1:]: 3298 if arg.startswith('--assign_types='): 3299 assigned_types = [(type if type!='None' else None) for type in \ 3300 arg[15:].split(',')] 3301 3302 jet_samples_to_keep = None 3303 3304 lhapdfconfig = ['lhapdf-config'] 3305 for arg in sys.argv[1:]: 3306 if arg.startswith('--lhapdf-config='): 3307 lhapdfconfig = arg[16:] 3308 3309 no_suffix = False 3310 if '--no_suffix' in sys.argv: 3311 no_suffix = True 3312 3313 if '--central_only' in sys.argv: 3314 consider_reweights = [] 3315 3316 if '--keep_all_weights' in sys.argv: 3317 consider_reweights = 'ALL' 3318 3319 for arg in sys.argv[1:]: 3320 if arg.startswith('--n_ratios='): 3321 n_ratios = int(arg[11:]) 3322 3323 if '--no_open' in sys.argv: 3324 auto_open = False 3325 3326 variation_type_map={'scale':'scale','merging':'merging_scale','pdf':'pdf', 3327 'stat':'statistical','alpsfact':'alpsfact'} 3328 3329 for arg in sys.argv: 3330 try: 3331 opt, value = arg.split('=') 3332 except ValueError: 3333 continue 3334 if opt=='--jet_samples': 3335 jet_samples_to_keep = eval(value) 3336 if opt=='--variations': 3337 uncertainties=[variation_type_map[type] for type in eval(value, 3338 dict([(key,key) for key in variation_type_map.keys()]+ 3339 [('all',variation_type_map.keys())]))] 3340 if opt=='--band': 3341 use_band=[variation_type_map[type] for type in eval(value, 3342 dict([(key,key) for key in variation_type_map.keys()]+ 3343 [('all',[type for type in variation_type_map.keys() if type!='stat'])]))] 3344 3345 if '--simple_ratios' in sys.argv: 3346 ratio_correlations = False 3347 3348 for arg in sys.argv: 3349 if arg.startswith('--no_') and not arg.startswith('--no_open'): 3350 uncertainties.remove(variation_type_map[arg[5:]]) 3351 if arg.startswith('--only_'): 3352 uncertainties= [variation_type_map[arg[7:]]] 3353 break 3354 3355 # Now remove from the weights considered all those not deemed necessary 3356 # in view of which uncertainties are selected 3357 if isinstance(consider_reweights, list): 3358 naming_map={'pdf':'pdf','scale':'scale', 3359 'merging_scale':'merging_scale','alpsfact':'alpsfact'} 3360 for key in naming_map: 3361 if (not key in uncertainties) and (naming_map[key] in consider_reweights): 3362 consider_reweights.remove(naming_map[key]) 3363 3364 n_files = len([_ for _ in sys.argv[1:] if not _.startswith('--')]) 3365 histo_norm = [1.0]*n_files 3366 3367 for arg in sys.argv[1:]: 3368 if arg.startswith('--multiply='): 3369 histo_norm = [(float(fact) if fact!='' else 1.0) for fact in \ 3370 arg[11:].split(',')] 3371 3372 if '--average' in sys.argv: 3373 histo_norm = [hist/float(n_files) for hist in histo_norm] 3374 3375 log("=======") 3376 histo_list = HwUList([]) 3377 for i, arg in enumerate(sys.argv[1:]): 3378 if arg.startswith('--'): 3379 break 3380 log("Loading histograms from '%s'."%arg) 3381 if OutName=="": 3382 OutName = os.path.basename(arg).split('.')[0]+'_output' 3383 # Make sure to process the potential XML options appended to the filename 3384 file_specification = arg.split('@') 3385 filename = file_specification.pop(0) 3386 file_options = {} 3387 for option in file_specification: 3388 opt, value = option.split('=') 3389 if opt=='run_id': 3390 file_options[opt]=int(value) 3391 if opt=='merging_scale': 3392 file_options[opt]=float(value) 3393 else: 3394 log("Unreckognize file option '%s'."%option) 3395 sys.exit(1) 3396 new_histo_list = HwUList(filename, accepted_types_order=accepted_types, 3397 consider_reweights=consider_reweights, **file_options) 3398 # We filter now the diagrams whose title doesn't match the constraints 3399 if len(accepted_titles)>0: 3400 new_histo_list = HwUList(histo for histo in new_histo_list if 3401 any(t in histo.title for t in accepted_titles)) 3402 for histo in new_histo_list: 3403 if no_suffix or n_files==1: 3404 continue 3405 if not histo.type is None: 3406 histo.type += '|' 3407 else: 3408 histo.type = '' 3409 # Firs option is to give a bit of the name of the source HwU file. 3410 #histo.type += " %s, #%d"%\ 3411 # (os.path.basename(arg).split('.')[0][:3],i+1) 3412 # But it is more elegant to give just the number. 3413 # Overwrite existing number if present. We assume here that one never 3414 # uses the '#' in its custom-defined types, which is a fair assumptions. 3415 try: 3416 suffix = assigned_types[i] 3417 except IndexError: 3418 suffix = "#%d"%(i+1) 3419 try: 3420 histo.type = histo.type[:histo.type.index('#')] + suffix 3421 except ValueError: 3422 histo.type += suffix 3423 3424 if i==0 or all(_ not in ['--sum','--average'] for _ in sys.argv): 3425 for j,hist in enumerate(new_histo_list): 3426 new_histo_list[j]=hist*histo_norm[i] 3427 histo_list.extend(new_histo_list) 3428 continue 3429 3430 if any(_ in sys.argv for _ in ['--sum','--average']): 3431 for j, hist in enumerate(new_histo_list): 3432 # First make sure the plots have the same weight labels and such 3433 hist.test_plot_compability(histo_list[j]) 3434 # Now let the histogram module do the magic and add them. 3435 histo_list[j] += hist*histo_norm[i] 3436 3437 log("A total of %i histograms were found."%len(histo_list)) 3438 log("=======") 3439 3440 n_rebin = 1 3441 for arg in sys.argv[1:]: 3442 if arg.startswith('--rebin='): 3443 n_rebin = int(arg[8:]) 3444 3445 if n_rebin > 1: 3446 for hist in histo_list: 3447 hist.rebin(n_rebin) 3448 3449 if '--gnuplot' in sys.argv or all(arg not in ['--HwU'] for arg in sys.argv): 3450 # Where the magic happens: 3451 histo_list.output(OutName, format='gnuplot', 3452 number_of_ratios = n_ratios, 3453 uncertainties=uncertainties, 3454 ratio_correlations=ratio_correlations, 3455 arg_string=arg_string, 3456 jet_samples_to_keep=jet_samples_to_keep, 3457 use_band=use_band, 3458 auto_open=auto_open, 3459 lhapdfconfig=lhapdfconfig) 3460 # Tell the user that everything went for the best 3461 log("%d histograms have been output in " % len(histo_list)+\ 3462 "the gnuplot format at '%s.[HwU|gnuplot]'." % OutName) 3463 if auto_open: 3464 command = 'gnuplot %s.gnuplot'%OutName 3465 try: 3466 subprocess.call(command,shell=True,stderr=subprocess.PIPE) 3467 except: 3468 log("Automatic processing of the gnuplot card failed. Try the"+\ 3469 " command by hand:\n%s"%command) 3470 else: 3471 sys.exit(0) 3472 3473 if '--HwU' in sys.argv: 3474 log("Histograms data has been output in the HwU format at "+\ 3475 "'%s.HwU'."%OutName) 3476 histo_list.output(OutName, format='HwU') 3477 sys.exit(0) 3478 3479 if '--show_short' in sys.argv or '--show_full' in sys.argv: 3480 for i, histo in enumerate(histo_list): 3481 if i!=0: 3482 log('-------') 3483 log(histo.nice_string(short=(not '--show_full' in sys.argv))) 3484 log("=======")
3485 3486 ######## Routine from https://gist.github.com/thriveth/8352565 3487 ######## To fill for histograms data in matplotlib 3488 -def fill_between_steps(x, y1, y2=0, h_align='right', ax=None, **kwargs):
3489 ''' Fills a hole in matplotlib: fill_between for step plots. 3490 Parameters : 3491 ------------ 3492 x : array-like 3493 Array/vector of index values. These are assumed to be equally-spaced. 3494 If not, the result will probably look weird... 3495 y1 : array-like 3496 Array/vector of values to be filled under. 3497 y2 : array-Like 3498 Array/vector or bottom values for filled area. Default is 0. 3499 **kwargs will be passed to the matplotlib fill_between() function. 3500 ''' 3501 # If no Axes opject given, grab the current one: 3502 if ax is None: 3503 ax = plt.gca() 3504 3505 3506 # First, duplicate the x values 3507 #duplicate the info # xx = numpy.repeat(2)[1:] 3508 xx= []; [(xx.append(d),xx.append(d)) for d in x]; xx = xx[1:] 3509 # Now: the average x binwidth 3510 xstep = x[1] -x[0] 3511 # Now: add one step at end of row. 3512 xx.append(xx[-1] + xstep) 3513 3514 # Make it possible to change step alignment. 3515 if h_align == 'mid': 3516 xx = [X-xstep/2. for X in xx] 3517 elif h_align == 'right': 3518 xx = [X-xstep for X in xx] 3519 3520 # Also, duplicate each y coordinate in both arrays 3521 yy1 = []; [(yy1.append(d),yy1.append(d)) for d in y1] 3522 if isinstance(y1, list): 3523 yy2 = []; [(yy2.append(d),yy2.append(d)) for d in y2] 3524 else: 3525 yy2=y2 3526 3527 # now to the plotting part: 3528 ax.fill_between(xx, yy1, y2=yy2, **kwargs) 3529 3530 return ax
3531 ######## end routine from https://gist.github.com/thriveth/835256 3532