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, **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 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, weight_header, raw_labels=False):
1071 """ Reads *one* histogram from a stream, with the mandatory specification 1072 of the ordered list of weight names. Return True or False depending 1073 on whether the starting definition of a new plot could be found in this 1074 stream.""" 1075 n_bins = 0 1076 # Find the starting point of the stream 1077 for line in stream: 1078 start = HwU.histo_start_re.match(line) 1079 if not start is None: 1080 self.process_histogram_name(start.group('histo_name')) 1081 # We do not want to include auxiliary diagrams which would be 1082 # recreated anyway. 1083 if self.type == 'AUX': 1084 continue 1085 n_bins = int(start.group('n_bins')) 1086 # Make sure to exclude the boundaries from the weight 1087 # specification 1088 self.bins = BinList(weight_labels = [ wgt_label for 1089 wgt_label in weight_header if wgt_label not in 1090 ['boundary_xmin','boundary_xmax']]) 1091 break 1092 1093 # Now look for the bin weights definition 1094 for line_bin in stream: 1095 bin_weights = {} 1096 boundaries = [0.0,0.0] 1097 for j, weight in \ 1098 enumerate(HwU.histo_bin_weight_re.finditer(line_bin)): 1099 if j == len(weight_header): 1100 raise HwU.ParseError, "There is more bin weights"+\ 1101 " specified than expected (%i)"%len(weight_header) 1102 if weight_header[j] == 'boundary_xmin': 1103 boundaries[0] = float(weight.group('weight')) 1104 elif weight_header[j] == 'boundary_xmax': 1105 boundaries[1] = float(weight.group('weight')) 1106 else: 1107 bin_weights[weight_header[j]] = \ 1108 float(weight.group('weight')) 1109 1110 # For the HwU format, we know that exactly two 'weights' 1111 # specified in the weight_header are in fact the boundary 1112 # coordinate, so we must subtract two. 1113 if len(bin_weights)<(len(weight_header)-2): 1114 raise HwU.ParseError, " There are only %i weights"\ 1115 %len(bin_weights)+" specified and %i were expected."%\ 1116 (len(weight_header)-2) 1117 self.bins.append(Bin(tuple(boundaries), bin_weights)) 1118 if len(self.bins)==n_bins: 1119 break 1120 1121 if len(self.bins)!=n_bins: 1122 raise HwU.ParseError, "%i bin specification "%len(self.bins)+\ 1123 "were found and %i were expected."%n_bins 1124 1125 # Now jump to the next <\histo> tag. 1126 for line_end in stream: 1127 if HwU.histo_end_re.match(line_end): 1128 # Finally, remove all the auxiliary weights, but only if not 1129 # asking for raw labels 1130 if not raw_labels: 1131 self.trim_auxiliary_weights() 1132 # End of successful parsing this histogram, so return True. 1133 return True 1134 1135 # Could not find a plot definition starter in this stream, return False 1136 return False
1137
1138 - def trim_auxiliary_weights(self):
1139 """ Remove all weights which are auxiliary (whose name end with '@aux') 1140 so that they are not included (they will be regenerated anyway).""" 1141 1142 for i, wgt_label in enumerate(self.bins.weight_labels): 1143 if isinstance(wgt_label, str) and wgt_label.endswith('@aux'): 1144 for bin in self.bins: 1145 try: 1146 del bin.wgts[wgt_label] 1147 except KeyError: 1148 pass 1149 self.bins.weight_labels = [wgt_label for wgt_label in 1150 self.bins.weight_labels if (not isinstance(wgt_label, str) 1151 or (isinstance(wgt_label, str) and not wgt_label.endswith('@aux')) )]
1152
1153 - def set_uncertainty(self, type='all_scale',lhapdfconfig='lhapdf-config'):
1154 """ Adds a weight to the bins which is the envelope of the scale 1155 uncertainty, for the scale specified which can be either 'mur', 'muf', 1156 'all_scale' or 'PDF'.""" 1157 1158 if type.upper()=='MUR': 1159 new_wgt_label = 'delta_mur' 1160 scale_position = 1 1161 elif type.upper()=='MUF': 1162 new_wgt_label = 'delta_muf' 1163 scale_position = 2 1164 elif type.upper()=='ALL_SCALE': 1165 new_wgt_label = 'delta_mu' 1166 scale_position = -1 1167 elif type.upper()=='PDF': 1168 new_wgt_label = 'delta_pdf' 1169 scale_position = -2 1170 elif type.upper()=='MERGING': 1171 new_wgt_label = 'delta_merging' 1172 elif type.upper()=='ALPSFACT': 1173 new_wgt_label = 'delta_alpsfact' 1174 else: 1175 raise MadGraph5Error, ' The function set_uncertainty can'+\ 1176 " only handle the scales 'mur', 'muf', 'all_scale', 'pdf',"+\ 1177 "'merging' or 'alpsfact'." 1178 1179 wgts_to_consider=[] 1180 label_to_consider=[] 1181 if type.upper() == 'MERGING': 1182 # It is a list of list because we consider only the possibility of 1183 # a single "central value" in this case, so the outtermost list is 1184 # always of length 1. 1185 wgts_to_consider.append([ label for label in self.bins.weight_labels if \ 1186 HwU.get_HwU_wgt_label_type(label)=='merging_scale' ]) 1187 label_to_consider.append('none') 1188 1189 elif type.upper() == 'ALPSFACT': 1190 # It is a list of list because we consider only the possibility of 1191 # a single "central value" in this case, so the outtermost list is 1192 # always of length 1. 1193 wgts_to_consider.append([ label for label in self.bins.weight_labels if \ 1194 HwU.get_HwU_wgt_label_type(label)=='alpsfact' ]) 1195 label_to_consider.append('none') 1196 elif scale_position > -2: 1197 ##########: advanced scale 1198 dyn_scales=[label[1] for label in self.bins.weight_labels if \ 1199 HwU.get_HwU_wgt_label_type(label)=='scale_adv'] 1200 # remove doubles in list but keep the order! 1201 dyn_scales=[scale for n,scale in enumerate(dyn_scales) if scale not in dyn_scales[:n]] 1202 for dyn_scale in dyn_scales: 1203 wgts=[label for label in self.bins.weight_labels if \ 1204 HwU.get_HwU_wgt_label_type(label)=='scale_adv' and label[1]==dyn_scale] 1205 if wgts: 1206 wgts_to_consider.append(wgts) 1207 label_to_consider.append(dyn_scale) 1208 ##########: normal scale 1209 wgts=[label for label in self.bins.weight_labels if \ 1210 HwU.get_HwU_wgt_label_type(label)=='scale'] 1211 ## this is for the 7-point variations (excludes mur/muf = 4, 1/4) 1212 #wgts_to_consider = [ label for label in self.bins.weight_labels if \ 1213 # isinstance(label,tuple) and label[0]=='scale' and \ 1214 # not (0.5 in label and 2.0 in label)] 1215 if wgts: 1216 wgts_to_consider.append(wgts) 1217 label_to_consider.append('none') 1218 ##########: remove renormalisation OR factorisation scale dependence... 1219 1220 if scale_position > -1: 1221 for wgts in wgts_to_consider: 1222 wgts_to_consider.remove(wgts) 1223 wgts = [ label for label in wgts if label[-scale_position]==1.0 ] 1224 wgts_to_consider.append(wgts) 1225 elif scale_position == -2: 1226 ##########: advanced PDF 1227 pdf_sets=[label[2] for label in self.bins.weight_labels if \ 1228 HwU.get_HwU_wgt_label_type(label)=='pdf_adv'] 1229 # remove doubles in list but keep the order! 1230 pdf_sets=[ii for n,ii in enumerate(pdf_sets) if ii not in pdf_sets[:n]] 1231 for pdf_set in pdf_sets: 1232 wgts=[label for label in self.bins.weight_labels if \ 1233 HwU.get_HwU_wgt_label_type(label)=='pdf_adv' and label[2]==pdf_set] 1234 if wgts: 1235 wgts_to_consider.append(wgts) 1236 label_to_consider.append(pdf_set) 1237 ##########: normal PDF 1238 wgts = [ label for label in self.bins.weight_labels if \ 1239 HwU.get_HwU_wgt_label_type(label)=='pdf'] 1240 if wgts: 1241 wgts_to_consider.append(wgts) 1242 label_to_consider.append('none') 1243 1244 if len(wgts_to_consider)==0 or all(len(wgts)==0 for wgts in wgts_to_consider): 1245 # No envelope can be constructed, it is not worth adding the weights 1246 return (None,[None]) 1247 1248 # find and import python version of lhapdf if doing PDF uncertainties 1249 if type=='PDF': 1250 use_lhapdf=False 1251 try: 1252 lhapdf_libdir=subprocess.Popen([lhapdfconfig,'--libdir'],\ 1253 stdout=subprocess.PIPE).stdout.read().strip() 1254 except: 1255 use_lhapdf=False 1256 else: 1257 try: 1258 candidates=[dirname for dirname in os.listdir(lhapdf_libdir) \ 1259 if os.path.isdir(os.path.join(lhapdf_libdir,dirname))] 1260 except OSError: 1261 candidates=[] 1262 for candidate in candidates: 1263 if os.path.isfile(os.path.join(lhapdf_libdir,candidate,'site-packages','lhapdf.so')): 1264 sys.path.insert(0,os.path.join(lhapdf_libdir,candidate,'site-packages')) 1265 try: 1266 import lhapdf 1267 use_lhapdf=True 1268 break 1269 except ImportError: 1270 sys.path.pop(0) 1271 continue 1272 1273 if not use_lhapdf: 1274 try: 1275 candidates=[dirname for dirname in os.listdir(lhapdf_libdir+'64') \ 1276 if os.path.isdir(os.path.join(lhapdf_libdir+'64',dirname))] 1277 except OSError: 1278 candidates=[] 1279 for candidate in candidates: 1280 if os.path.isfile(os.path.join(lhapdf_libdir+'64',candidate,'site-packages','lhapdf.so')): 1281 sys.path.insert(0,os.path.join(lhapdf_libdir+'64',candidate,'site-packages')) 1282 try: 1283 import lhapdf 1284 use_lhapdf=True 1285 break 1286 except ImportError: 1287 sys.path.pop(0) 1288 continue 1289 1290 if not use_lhapdf: 1291 try: 1292 import lhapdf 1293 use_lhapdf=True 1294 except ImportError: 1295 logger.warning("Failed to access python version of LHAPDF: "\ 1296 "cannot compute PDF uncertainty from the "\ 1297 "weights in the histograms. The weights in the HwU data files " \ 1298 "still cover all PDF set members, "\ 1299 "but the automatic computation of the uncertainties from "\ 1300 "those weights might not be correct. \n "\ 1301 "If the python interface to LHAPDF is available on your system, try "\ 1302 "adding its location to the PYTHONPATH environment variable and the"\ 1303 "LHAPDF library location to LD_LIBRARY_PATH (linux) or DYLD_LIBRARY_PATH (mac os x).") 1304 1305 if type=='PDF' and use_lhapdf: 1306 lhapdf.setVerbosity(0) 1307 1308 # Place the new weight label last before the first tuple 1309 position=[] 1310 labels=[] 1311 for i,label in enumerate(label_to_consider): 1312 wgts=wgts_to_consider[i] 1313 if label != 'none': 1314 new_wgt_labels=['%s_cen %s @aux' % (new_wgt_label,label), 1315 '%s_min %s @aux' % (new_wgt_label,label), 1316 '%s_max %s @aux' % (new_wgt_label,label)] 1317 else: 1318 new_wgt_labels=['%s_cen @aux' % new_wgt_label, 1319 '%s_min @aux' % new_wgt_label, 1320 '%s_max @aux' % new_wgt_label] 1321 try: 1322 pos=[(not isinstance(lab, str)) for lab in \ 1323 self.bins.weight_labels].index(True) 1324 position.append(pos) 1325 labels.append(label) 1326 self.bins.weight_labels = self.bins.weight_labels[:pos]+\ 1327 new_wgt_labels + self.bins.weight_labels[pos:] 1328 except ValueError: 1329 pos=len(self.bins.weight_labels) 1330 position.append(pos) 1331 labels.append(label) 1332 self.bins.weight_labels.extend(new_wgt_labels) 1333 1334 if type=='PDF' and use_lhapdf and label != 'none': 1335 p=lhapdf.getPDFSet(label) 1336 1337 # Now add the corresponding weight to all Bins 1338 for bin in self.bins: 1339 if type!='PDF': 1340 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1341 bin.wgts[new_wgt_labels[1]] = min(bin.wgts[label] \ 1342 for label in wgts) 1343 bin.wgts[new_wgt_labels[2]] = max(bin.wgts[label] \ 1344 for label in wgts) 1345 elif type=='PDF' and use_lhapdf and label != 'none' and len(wgts) > 1: 1346 pdfs = [bin.wgts[pdf] for pdf in sorted(wgts)] 1347 ep=p.uncertainty(pdfs,-1) 1348 bin.wgts[new_wgt_labels[0]] = ep.central 1349 bin.wgts[new_wgt_labels[1]] = ep.central-ep.errminus 1350 bin.wgts[new_wgt_labels[2]] = ep.central+ep.errplus 1351 elif type=='PDF' and use_lhapdf and label != 'none' and len(bin.wgts) == 1: 1352 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1353 bin.wgts[new_wgt_labels[1]] = bin.wgts[wgts[0]] 1354 bin.wgts[new_wgt_labels[2]] = bin.wgts[wgts[0]] 1355 else: 1356 pdfs = [bin.wgts[pdf] for pdf in sorted(wgts)] 1357 pdf_up = 0.0 1358 pdf_down = 0.0 1359 cntrl_val = bin.wgts['central'] 1360 if wgts[0] <= 90000: 1361 # use Hessian method (CTEQ & MSTW) 1362 if len(pdfs)>2: 1363 for i in range(int((len(pdfs)-1)/2)): 1364 pdf_up += max(0.0,pdfs[2*i+1]-cntrl_val, 1365 pdfs[2*i+2]-cntrl_val)**2 1366 pdf_down += max(0.0,cntrl_val-pdfs[2*i+1], 1367 cntrl_val-pdfs[2*i+2])**2 1368 pdf_up = cntrl_val + math.sqrt(pdf_up) 1369 pdf_down = cntrl_val - math.sqrt(pdf_down) 1370 else: 1371 pdf_up = bin.wgts[pdfs[0]] 1372 pdf_down = bin.wgts[pdfs[0]] 1373 elif wgts[0] in range(90200, 90303) or \ 1374 wgts[0] in range(90400, 90433) or \ 1375 wgts[0] in range(90700, 90801) or \ 1376 wgts[0] in range(90900, 90931) or \ 1377 wgts[0] in range(91200, 91303) or \ 1378 wgts[0] in range(91400, 91433) or \ 1379 wgts[0] in range(91700, 91801) or \ 1380 wgts[0] in range(91900, 90931): 1381 # PDF4LHC15 Hessian sets 1382 pdf_stdev = 0.0 1383 for pdf in pdfs[1:]: 1384 pdf_stdev += (pdf - cntrl_val)**2 1385 pdf_stdev = math.sqrt(pdf_stdev) 1386 pdf_up = cntrl_val+pdf_stdev 1387 pdf_down = cntrl_val-pdf_stdev 1388 else: 1389 # use Gaussian method (NNPDF) 1390 pdf_stdev = 0.0 1391 for pdf in pdfs[1:]: 1392 pdf_stdev += (pdf - cntrl_val)**2 1393 pdf_stdev = math.sqrt(pdf_stdev/float(len(pdfs)-2)) 1394 pdf_up = cntrl_val+pdf_stdev 1395 pdf_down = cntrl_val-pdf_stdev 1396 # Finally add them to the corresponding new weight 1397 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1398 bin.wgts[new_wgt_labels[1]] = pdf_down 1399 bin.wgts[new_wgt_labels[2]] = pdf_up 1400 1401 # And return the position in self.bins.weight_labels of the first 1402 # of the two new weight label added. 1403 return (position,labels)
1404
1405 - def rebin(self, n_rebin):
1406 """ Rebin the x-axis so as to merge n_rebin consecutive bins into a 1407 single one. """ 1408 1409 if n_rebin < 1 or not isinstance(n_rebin, int): 1410 raise MadGraph5Error, "The argument 'n_rebin' of the HwU function"+\ 1411 " 'rebin' must be larger or equal to 1, not '%s'."%str(n_rebin) 1412 elif n_rebin==1: 1413 return 1414 1415 if self.type and 'NOREBIN' in self.type.upper(): 1416 return 1417 1418 rebinning_list = list(range(0,len(self.bins),n_rebin))+[len(self.bins),] 1419 concat_list = [self.bins[rebinning_list[i]:rebinning_list[i+1]] for \ 1420 i in range(len(rebinning_list)-1)] 1421 1422 new_bins = copy.copy(self.bins) 1423 del new_bins[:] 1424 1425 for bins_to_merge in concat_list: 1426 if len(bins_to_merge)==0: 1427 continue 1428 new_bins.append(Bin(boundaries=(bins_to_merge[0].boundaries[0], 1429 bins_to_merge[-1].boundaries[1]),wgts={'central':0.0})) 1430 for weight in self.bins.weight_labels: 1431 if weight != 'stat_error': 1432 new_bins[-1].wgts[weight] = \ 1433 sum(b.wgts[weight] for b in bins_to_merge) 1434 else: 1435 new_bins[-1].wgts['stat_error'] = \ 1436 math.sqrt(sum(b.wgts['stat_error']**2 for b in\ 1437 bins_to_merge)) 1438 1439 self.bins = new_bins
1440 1441 @classmethod
1442 - def get_x_optimal_range(cls, histo_list, weight_labels=None):
1443 """ Function to determine the optimal x-axis range when plotting 1444 together the histos in histo_list and considering the weights 1445 weight_labels""" 1446 1447 # If no list of weight labels to consider is given, use them all. 1448 if weight_labels is None: 1449 weight_labels = histo_list[0].bins.weight_labels 1450 1451 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1452 for bin in histo.bins if \ 1453 (sum(abs(bin.wgts[label]) for label in weight_labels) > 0.0)] ,[]) 1454 1455 if len(all_boundaries)==0: 1456 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1457 for bin in histo.bins],[]) 1458 if len(all_boundaries)==0: 1459 raise MadGraph5Error, "The histograms with title '%s'"\ 1460 %histo_list[0].title+" seems to have no bins." 1461 1462 x_min = min(all_boundaries) 1463 x_max = max(all_boundaries) 1464 1465 return (x_min, x_max)
1466 1467 @classmethod
1468 - def get_y_optimal_range(cls,histo_list, labels=None, 1469 scale='LOG', Kratio = False):
1470 """ Function to determine the optimal y-axis range when plotting 1471 together the histos in histo_list and considering the weights 1472 weight_labels. The option Kratio is present to allow for the couple of 1473 tweaks necessary for the the K-factor ratio histogram y-range.""" 1474 1475 # If no list of weight labels to consider is given, use them all. 1476 if labels is None: 1477 weight_labels = histo_list[0].bins.weight_labels 1478 else: 1479 weight_labels = labels 1480 1481 all_weights = [] 1482 for histo in histo_list: 1483 for bin in histo.bins: 1484 for label in weight_labels: 1485 # Filter out bin weights at *exactly* because they often 1486 # come from pathological division by zero for empty bins. 1487 if Kratio and bin.wgts[label]==0.0: 1488 continue 1489 if scale!='LOG': 1490 all_weights.append(bin.wgts[label]) 1491 if label == 'stat_error': 1492 all_weights.append(-bin.wgts[label]) 1493 elif bin.wgts[label]>0.0: 1494 all_weights.append(bin.wgts[label]) 1495 1496 1497 sum([ [bin.wgts[label] for label in weight_labels if \ 1498 (scale!='LOG' or bin.wgts[label]!=0.0)] \ 1499 for histo in histo_list for bin in histo.bins], []) 1500 1501 all_weights.sort() 1502 if len(all_weights)!=0: 1503 partial_max = all_weights[int(len(all_weights)*0.95)] 1504 partial_min = all_weights[int(len(all_weights)*0.05)] 1505 max = all_weights[-1] 1506 min = all_weights[0] 1507 else: 1508 if scale!='LOG': 1509 return (0.0,1.0) 1510 else: 1511 return (1.0,10.0) 1512 1513 y_max = 0.0 1514 y_min = 0.0 1515 1516 # If the maximum is too far from the 90% max, then take the partial max 1517 if (max-partial_max)>2.0*(partial_max-partial_min): 1518 y_max = partial_max 1519 else: 1520 y_max = max 1521 1522 # If the maximum is too far from the 90% max, then take the partial max 1523 if (partial_min - min)>2.0*(partial_max-partial_min) and min != 0.0: 1524 y_min = partial_min 1525 else: 1526 y_min = min 1527 1528 if Kratio: 1529 median = all_weights[len(all_weights)//2] 1530 spread = (y_max-y_min) 1531 if abs(y_max-median)<spread*0.05 or abs(median-y_min)<spread*0.05: 1532 y_max = median + spread/2.0 1533 y_min = median - spread/2.0 1534 if y_min != y_max: 1535 return ( y_min , y_max ) 1536 1537 # Enforce the maximum if there is 5 bins or less 1538 if len(histo_list[0].bins) <= 5: 1539 y_min = min 1540 y_max = max 1541 1542 # Finally make sure the range has finite length 1543 if y_min == y_max: 1544 if max == min: 1545 y_min -= 1.0 1546 y_max += 1.0 1547 else: 1548 y_min = min 1549 y_max = max 1550 1551 return ( y_min , y_max )
1552
1553 -class HwUList(histograms_PhysicsObjectList):
1554 """ A class implementing features related to a list of Hwu Histograms. """ 1555 1556 # Define here the number of line color schemes defined. If you need more, 1557 # simply define them in the gnuplot header and increase the number below. 1558 # It must be <= 9. 1559 number_line_colors_defined = 8 1560
1561 - def is_valid_element(self, obj):
1562 """Test wether specified object is of the right type for this list.""" 1563 1564 return isinstance(obj, HwU) or isinstance(obj, HwUList)
1565
1566 - def __init__(self, file_path, weight_header=None, run_id=None, 1567 merging_scale=None, accepted_types_order=[], consider_reweights='ALL', 1568 raw_labels=False, **opts):
1569 """ Read one plot from a file_path or a stream. 1570 This constructor reads all plots specified in target file. 1571 File_path can be a path or a stream in the argument. 1572 The option weight_header specifies an ordered list of weight names 1573 to appear in the file or stream specified. It accepted_types_order is 1574 empty, no filter is applied, otherwise only histograms of the specified 1575 types will be kept, and in this specified order for a given identical 1576 title. The option 'consider_reweights' selects whether one wants to 1577 include all the extra scale/pdf/merging variation weights. Possible values 1578 are 'ALL' or a list of the return types of the function get_HwU_wgt_label_type(). 1579 The option 'raw_labels' specifies that one wants to import the 1580 histogram data with no treatment of the weight labels at all 1581 (this is used for the matplotlib output). 1582 """ 1583 1584 if isinstance(file_path, str): 1585 stream = open(file_path,'r') 1586 elif isinstance(file_path, file): 1587 stream = file_path 1588 else: 1589 return super(HwUList,self).__init__(file_path, **opts) 1590 1591 try: 1592 # Try to read it in XML format 1593 self.parse_histos_from_PY8_XML_stream(stream, run_id, 1594 merging_scale, accepted_types_order, 1595 consider_reweights=consider_reweights, 1596 raw_labels=raw_labels) 1597 except XMLParsingError: 1598 # Rewing the stream 1599 stream.seek(0) 1600 # Attempt to find the weight headers if not specified 1601 if not weight_header: 1602 weight_header = HwU.parse_weight_header(stream,raw_labels=raw_labels) 1603 1604 new_histo = HwU(stream, weight_header,raw_labels=raw_labels) 1605 while not new_histo.bins is None: 1606 if accepted_types_order==[] or \ 1607 new_histo.type in accepted_types_order: 1608 self.append(new_histo) 1609 new_histo = HwU(stream, weight_header, raw_labels=raw_labels) 1610 1611 if not run_id is None: 1612 logger.info("The run_id '%s' was specified, but "%run_id+ 1613 "format of the HwU plot source is the MG5aMC"+ 1614 " so that the run_id information is ignored.") 1615 1616 # Order the histograms according to their type. 1617 titles_order = [h.title for h in self] 1618 def ordering_function(histo): 1619 title_position = titles_order.index(histo.title) 1620 if accepted_types_order==[]: 1621 type_precedence = {'NLO':1,'LO':2,None:3,'AUX':5} 1622 try: 1623 ordering_key = (title_position,type_precedence[histo.type]) 1624 except KeyError: 1625 ordering_key = (title_position,4) 1626 else: 1627 ordering_key = (title_position, 1628 accepted_types_order.index(histo.type)) 1629 return ordering_key
1630 1631 # The command below is to first order them in alphabetical order, but it 1632 # is often better to keep the order of the original HwU source. 1633 # self.sort(key=lambda histo: '%s_%d'%(histo.title, 1634 # type_order.index(histo.type))) 1635 self.sort(key=ordering_function) 1636 1637 # Explicitly close the opened stream for clarity. 1638 if isinstance(file_path, str): 1639 stream.close()
1640
1641 - def get_hist_names(self):
1642 """return a list of all the names of define histograms""" 1643 1644 output = [] 1645 for hist in self: 1646 output.append(hist.get_HwU_histogram_name()) 1647 return output
1648
1649 - def get_wgt_names(self):
1650 """ return the list of all weights define in each histograms""" 1651 1652 return self[0].bins.weight_labels
1653 1654
1655 - def get(self, name):
1656 """return the HWU histograms related to a given name""" 1657 for hist in self: 1658 if hist.get_HwU_histogram_name() == name: 1659 return hist 1660 1661 raise NameError, "no histogram with name: %s" % name
1662
1663 - def parse_histos_from_PY8_XML_stream(self, stream, run_id=None, 1664 merging_scale=None, accepted_types_order=[], 1665 consider_reweights='ALL', raw_labels=False):
1666 """Initialize the HwU histograms from an XML stream. Only one run is 1667 used: the first one if run_id is None or the specified run otherwise. 1668 Accepted type order is a filter to select histograms of only a certain 1669 type. The option 'consider_reweights' selects whether one wants to 1670 include all the extra scale/pdf/merging variation weights. 1671 Possible values are 'ALL' or a list of the return types of the 1672 function get_HwU_wgt_label_type().""" 1673 1674 run_nodes = minidom.parse(stream).getElementsByTagName("run") 1675 all_nodes = dict((int(node.getAttribute('id')),node) for 1676 node in run_nodes) 1677 selected_run_node = None 1678 weight_header = None 1679 if run_id is None: 1680 if len(run_nodes)>0: 1681 selected_run_node = all_nodes[min(all_nodes.keys())] 1682 else: 1683 try: 1684 selected_run_node = all_nodes[int(run_id)] 1685 except: 1686 selected_run_node = None 1687 1688 if selected_run_node is None: 1689 if run_id is None: 1690 raise MadGraph5Error, \ 1691 'No histogram was found in the specified XML source.' 1692 else: 1693 raise MadGraph5Error, \ 1694 "Histogram with run_id '%d' was not found in the "%run_id+\ 1695 "specified XML source." 1696 1697 # If raw weight label are asked for, then simply read the weight_labels 1698 # directly as specified in the XML header 1699 if raw_labels: 1700 # Filter empty weights coming from the split 1701 weight_label_list = [wgt.strip() for wgt in 1702 str(selected_run_node.getAttribute('header')).split(';') if 1703 not re.match('^\s*$',wgt)] 1704 ordered_weight_label_list = [w for w in weight_label_list if w not\ 1705 in ['xmin','xmax']] 1706 # Remove potential repetition of identical weight labels 1707 filtered_ordered_weight_label_list = [] 1708 for wgt_label in ordered_weight_label_list: 1709 if wgt_label not in filtered_ordered_weight_label_list: 1710 filtered_ordered_weight_label_list.append(wgt_label) 1711 1712 selected_weights = dict([ (wgt_pos, 1713 [wgt if wgt not in ['xmin','xmax'] else HwU.mandatory_weights[wgt]]) 1714 for wgt_pos, wgt in enumerate(weight_label_list) if wgt in 1715 filtered_ordered_weight_label_list+['xmin','xmax']]) 1716 1717 return self.retrieve_plots_from_XML_source(selected_run_node, 1718 selected_weights, filtered_ordered_weight_label_list, 1719 raw_labels=True) 1720 1721 # Now retrieve the header and save all weight labels as dictionaries 1722 # with key being properties and their values as value. If the property 1723 # does not defined a value, then put None as a value 1724 all_weights = [] 1725 for wgt_position, wgt_label in \ 1726 enumerate(str(selected_run_node.getAttribute('header')).split(';')): 1727 if not re.match('^\s*$',wgt_label) is None: 1728 continue 1729 all_weights.append({'POSITION':wgt_position}) 1730 for wgt_item in wgt_label.strip().split('_'): 1731 property = wgt_item.strip().split('=') 1732 if len(property) == 2: 1733 all_weights[-1][property[0].strip()] = property[1].strip() 1734 elif len(property)==1: 1735 all_weights[-1][property[0].strip()] = None 1736 else: 1737 raise MadGraph5Error, \ 1738 "The weight label property %s could not be parsed."%wgt_item 1739 1740 # Now make sure that for all weights, there is 'PDF', 'MUF' and 'MUR' 1741 # and 'MERGING' defined. If absent we specify '-1' which implies that 1742 # the 'default' value was used (whatever it was). 1743 # Also cast them in the proper type 1744 for wgt_label in all_weights: 1745 for mandatory_attribute in ['PDF','MUR','MUF','MERGING','ALPSFACT']: 1746 if mandatory_attribute not in wgt_label: 1747 wgt_label[mandatory_attribute] = '-1' 1748 if mandatory_attribute=='PDF': 1749 wgt_label[mandatory_attribute] = int(wgt_label[mandatory_attribute]) 1750 elif mandatory_attribute in ['MUR','MUF','MERGING','ALPSFACT']: 1751 wgt_label[mandatory_attribute] = float(wgt_label[mandatory_attribute]) 1752 1753 # If merging cut is negative, then pick only the one of the central scale 1754 # If not specified, then take them all but use the PDF and scale weight 1755 # of the central merging_scale for the variation. 1756 if merging_scale is None or merging_scale < 0.0: 1757 merging_scale_chosen = all_weights[2]['MERGING'] 1758 else: 1759 merging_scale_chosen = merging_scale 1760 1761 # Central weight parameters are enforced to be those of the third weight 1762 central_PDF = all_weights[2]['PDF'] 1763 # Assume central scale is one, unless specified. 1764 central_MUR = all_weights[2]['MUR'] if all_weights[2]['MUR']!=-1.0 else 1.0 1765 central_MUF = all_weights[2]['MUF'] if all_weights[2]['MUF']!=-1.0 else 1.0 1766 central_alpsfact = all_weights[2]['ALPSFACT'] if all_weights[2]['ALPSFACT']!=-1.0 else 1.0 1767 1768 # Dictionary of selected weights with their position as key and the 1769 # list of weight labels they correspond to. 1770 selected_weights = {} 1771 # Treat the first four weights in a special way: 1772 if 'xmin' not in all_weights[0] or \ 1773 'xmax' not in all_weights[1] or \ 1774 'Weight' not in all_weights[2] or \ 1775 'WeightError' not in all_weights[3]: 1776 raise MadGraph5Error, 'The first weight entries in the XML HwU '+\ 1777 ' source are not the standard expected ones (xmin, xmax, sigmaCentral, errorCentral)' 1778 selected_weights[0] = ['xmin'] 1779 selected_weights[1] = ['xmax'] 1780 1781 # =========== BEGIN HELPER FUNCTIONS =========== 1782 def get_difference_to_central(weight): 1783 """ Return the list of properties which differ from the central weight. 1784 This disregards the merging scale value for which any central value 1785 can be picked anyway.""" 1786 1787 differences = [] 1788 # If the tag 'Weight' is in the weight label, then this is 1789 # automatically considered as the Event weight (central) for which 1790 # only the merging scale can be different 1791 if 'Weight' in weight: 1792 return set([]) 1793 if weight['MUR'] not in [central_MUR, -1.0] or \ 1794 weight['MUF'] not in [central_MUF, -1.0]: 1795 differences.append('mur_muf_scale') 1796 if weight['PDF'] not in [central_PDF,-1]: 1797 differences.append('pdf') 1798 if weight['ALPSFACT'] not in [central_alpsfact, -1]: 1799 differences.append('ALPSFACT') 1800 return set(differences)
1801 1802 def format_weight_label(weight): 1803 """ Print the weight attributes in a nice order.""" 1804 1805 all_properties = weight.keys() 1806 all_properties.pop(all_properties.index('POSITION')) 1807 ordered_properties = [] 1808 # First add the attributes without value 1809 for property in all_properties: 1810 if weight[property] is None: 1811 ordered_properties.append(property) 1812 1813 ordered_properties.sort() 1814 all_properties = [property for property in all_properties if 1815 not weight[property] is None] 1816 1817 # then add PDF, MUR, MUF and MERGING if present 1818 for property in ['PDF','MUR','MUF','ALPSFACT','MERGING']: 1819 all_properties.pop(all_properties.index(property)) 1820 if weight[property]!=-1: 1821 ordered_properties.append(property) 1822 1823 ordered_properties.extend(sorted(all_properties)) 1824 1825 return '_'.join('%s%s'\ 1826 %(key,'' if weight[key] is None else '=%s'%str(weight[key])) for 1827 key in ordered_properties) 1828 # =========== END HELPER FUNCTIONS =========== 1829 1830 1831 # The central value is not necessarily the 3rd one if a different merging 1832 # cut was selected. 1833 if float(all_weights[2]['MERGING']) == merging_scale_chosen: 1834 selected_weights[2]=['central value'] 1835 else: 1836 for weight_position, weight in enumerate(all_weights): 1837 # Check if that weight corresponds to a central weight 1838 # (conventional label for central weight is 'Weight' 1839 if get_difference_to_central(weight)==set([]): 1840 # Check if the merging scale matches this time 1841 if weight['MERGING']==merging_scale_chosen: 1842 selected_weights[weight_position] = ['central value'] 1843 break 1844 # Make sure a central value was found, throw a warning if found 1845 if 'central value' not in sum(selected_weights.values(),[]): 1846 central_merging_scale = all_weights[2]['MERGING'] 1847 logger.warning('Could not find the central weight for the'+\ 1848 ' chosen merging scale (%f).\n'%merging_scale_chosen+\ 1849 'MG5aMC will chose the original central scale provided which '+\ 1850 'correspond to a merging scale of %s'%("'inclusive'" if 1851 central_merging_scale in [0.0,-1.0] else '%f'%central_merging_scale)) 1852 selected_weights[2]=['central value'] 1853 1854 # The error is always the third entry for now. 1855 selected_weights[3]=['dy'] 1856 1857 # Now process all other weights 1858 for weight_position, weight in enumerate(all_weights[4:]): 1859 # Apply special transformation for the weight label: 1860 # scale variation are stored as: 1861 # ('scale', mu_r, mu_f) for scale variation 1862 # ('pdf',PDF) for PDF variation 1863 # ('merging_scale',float) for merging scale 1864 # ('type',value) for all others (e.g. alpsfact) 1865 variations = get_difference_to_central(weight) 1866 # We know select the 'diagonal' variations where each parameter 1867 # is varied one at a time. 1868 1869 # Accept also if both pdf and mur_muf_scale differ because 1870 # the PDF used for the Event weight is often unknown but the 1871 # mu_r and mu_f variational weight specify it. Same story for 1872 # alpsfact. 1873 if variations in [set(['mur_muf_scale']),set(['pdf','mur_muf_scale'])]: 1874 wgt_label = ('scale',weight['MUR'],weight['MUF']) 1875 if variations in [set(['ALPSFACT']),set(['pdf','ALPSFACT'])]: 1876 wgt_label = ('alpsfact',weight['ALPSFACT']) 1877 if variations == set(['pdf']): 1878 wgt_label = ('pdf',weight['PDF']) 1879 if variations == set([]): 1880 # Unknown weight (might turn out to be taken as a merging variation weight below) 1881 wgt_label = format_weight_label(weight) 1882 1883 # Make sure the merging scale matches the chosen one 1884 if weight['MERGING'] != merging_scale_chosen: 1885 # If a merging_scale was specified, then ignore all other weights 1886 if merging_scale: 1887 continue 1888 # Otherwise consider them also, but for now only if it is for 1889 # the central value parameter (central PDF, central mu_R and mu_F) 1890 if variations == set([]): 1891 # We choose to store the merging variation weight labels as floats 1892 wgt_label = ('merging_scale', weight['MERGING']) 1893 # Make sure that the weight label does not already exist. If it does, 1894 # this means that the source has redundant information and that 1895 # there is no need to specify it again. 1896 if wgt_label in sum(selected_weights.values(),[]): 1897 continue 1898 1899 # Now register the selected weight 1900 try: 1901 selected_weights[weight_position+4].append(wgt_label) 1902 except KeyError: 1903 selected_weights[weight_position+4]=[wgt_label,] 1904 1905 if merging_scale and merging_scale > 0.0 and \ 1906 len(sum(selected_weights.values(),[]))==4: 1907 logger.warning('No additional variation weight was found for the '+\ 1908 'chosen merging scale %f.'%merging_scale) 1909 1910 # Make sure to use the predefined keywords for the mandatory weight labels 1911 for wgt_pos in selected_weights: 1912 for i, weight_label in enumerate(selected_weights[wgt_pos]): 1913 try: 1914 selected_weights[wgt_pos][i] = HwU.mandatory_weights[weight_label] 1915 except KeyError: 1916 pass 1917 1918 # Keep only the weights asked for 1919 if consider_reweights!='ALL': 1920 new_selected_weights = {} 1921 for wgt_position, wgt_labels in selected_weights.items(): 1922 for wgt_label in wgt_labels: 1923 if wgt_label in ['central','stat_error','boundary_xmin','boundary_xmax'] or\ 1924 HwU.get_HwU_wgt_label_type(wgt_label) in consider_reweights: 1925 try: 1926 new_selected_weights[wgt_position].append(wgt_label) 1927 except KeyError: 1928 new_selected_weights[wgt_position] = [wgt_label] 1929 selected_weights = new_selected_weights 1930 1931 # Cache the list of selected weights to be defined at each line 1932 weight_label_list = sum(selected_weights.values(),[]) 1933 1934 # The weight_label list to set to self.bins 1935 ordered_weight_label_list = ['central','stat_error'] 1936 for weight_label in weight_label_list: 1937 if not isinstance(weight_label, str): 1938 ordered_weight_label_list.append(weight_label) 1939 for weight_label in weight_label_list: 1940 if weight_label in ['central','stat_error','boundary_xmin','boundary_xmax']: 1941 continue 1942 if isinstance(weight_label, str): 1943 ordered_weight_label_list.append(weight_label) 1944 1945 # Now that we know the desired weights, retrieve all plots from the 1946 # XML source node. 1947 return self.retrieve_plots_from_XML_source(selected_run_node, 1948 selected_weights, ordered_weight_label_list, raw_labels=False) 1949
1950 - def retrieve_plots_from_XML_source(self, xml_node, 1951 selected_weights, ordered_weight_label_list,raw_labels=False):
1952 """Given an XML node and the selected weights and their ordered list, 1953 import all histograms from the specified XML node.""" 1954 1955 # We now start scanning all the plots 1956 for multiplicity_node in xml_node.getElementsByTagName("jethistograms"): 1957 multiplicity = int(multiplicity_node.getAttribute('njet')) 1958 for histogram in multiplicity_node.getElementsByTagName("histogram"): 1959 # We only consider the histograms with all the weight information 1960 if histogram.getAttribute("weight")!='all': 1961 continue 1962 new_histo = HwU() 1963 hist_name = str(histogram.getAttribute('name')) 1964 # prepend the jet multiplicity to the histogram name 1965 new_histo.process_histogram_name('%s |JETSAMPLE@%d'%(hist_name,multiplicity)) 1966 # We do not want to include auxiliary diagrams which would be 1967 # recreated anyway. 1968 if new_histo.type == 'AUX': 1969 continue 1970 # Make sure to exclude the boundaries from the weight 1971 # specification 1972 # Order the weights so that the unreckognized ones go last 1973 new_histo.bins = BinList(weight_labels = ordered_weight_label_list) 1974 hist_data = str(histogram.childNodes[0].data) 1975 for line in hist_data.split('\n'): 1976 if line.strip()=='': 1977 continue 1978 bin_weights = {} 1979 boundaries = [0.0,0.0] 1980 for j, weight in \ 1981 enumerate(HwU.histo_bin_weight_re.finditer(line)): 1982 try: 1983 for wgt_label in selected_weights[j]: 1984 if wgt_label == 'boundary_xmin': 1985 boundaries[0] = float(weight.group('weight')) 1986 elif wgt_label == 'boundary_xmax': 1987 boundaries[1] = float(weight.group('weight')) 1988 else: 1989 if weight.group('weight').upper()=='NAN': 1990 raise MadGraph5Error, \ 1991 "Some weights are found to be 'NAN' in histogram with name '%s'"%hist_name+\ 1992 " and jet sample multiplicity %d."%multiplicity 1993 else: 1994 bin_weights[wgt_label] = \ 1995 float(weight.group('weight')) 1996 except KeyError: 1997 continue 1998 # For this check, we subtract two because of the bin boundaries 1999 if len(bin_weights)!=len(ordered_weight_label_list): 2000 raise MadGraph5Error, \ 2001 'Not all defined weights were found in the XML source.\n'+\ 2002 '%d found / %d expected.'%(len(bin_weights),len(ordered_weight_label_list))+\ 2003 '\nThe missing ones are: %s.'%\ 2004 str(list(set(ordered_weight_label_list)-set(bin_weights.keys())))+\ 2005 "\nIn plot with title '%s' and jet sample multiplicity %d."%\ 2006 (hist_name, multiplicity) 2007 2008 new_histo.bins.append(Bin(tuple(boundaries), bin_weights)) 2009 2010 # if bin_weights['central']!=0.0: 2011 # print '---------' 2012 # print 'multiplicity =',multiplicity 2013 # print 'central =', bin_weights['central'] 2014 # print 'PDF = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,int)] 2015 # 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)) 2016 # print 'scale = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,tuple)] 2017 # 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)) 2018 # print 'merging = ', [(key,bin_weights[key]) for key in bin_weights if isinstance(key,float)] 2019 # 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)) 2020 # print 'alpsfact = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='alpsfact'] 2021 # 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') 2022 # print '---------' 2023 2024 # Finally remove auxiliary weights 2025 if not raw_labels: 2026 new_histo.trim_auxiliary_weights() 2027 2028 # And add it to the list 2029 self.append(new_histo)
2030
2031 - def output(self, path, format='gnuplot',number_of_ratios = -1, 2032 uncertainties=['scale','pdf','statitistical','merging_scale','alpsfact'], 2033 use_band = None, 2034 ratio_correlations=True, arg_string='', 2035 jet_samples_to_keep=None, 2036 auto_open=True, 2037 lhapdfconfig='lhapdf-config'):
2038 """ Ouput this histogram to a file, stream or string if path is kept to 2039 None. The supported format are for now. Chose whether to print the header 2040 or not.""" 2041 2042 if len(self)==0: 2043 return MadGraph5Error, 'No histograms stored in the list yet.' 2044 2045 if not format in HwU.output_formats_implemented: 2046 raise MadGraph5Error, "The specified output format '%s'"%format+\ 2047 " is not yet supported. Supported formats are %s."\ 2048 %HwU.output_formats_implemented 2049 2050 if isinstance(path, str) and '.' not in os.path.basename(path): 2051 output_base_name = os.path.basename(path) 2052 HwU_stream = open(path+'.HwU','w') 2053 else: 2054 raise MadGraph5Error, "The path argument of the output function of"+\ 2055 " the HwUList instance must be file path without its extension." 2056 2057 HwU_output_list = [] 2058 # If the format is just the raw HwU source, then simply write them 2059 # out all in sequence. 2060 if format == 'HwU': 2061 HwU_output_list.extend(self[0].get_HwU_source(print_header=True)) 2062 for histo in self[1:]: 2063 HwU_output_list.extend(histo.get_HwU_source()) 2064 HwU_output_list.extend(['','']) 2065 HwU_stream.write('\n'.join(HwU_output_list)) 2066 HwU_stream.close() 2067 return 2068 2069 # Now we consider that we are attempting a gnuplot output. 2070 if format == 'gnuplot': 2071 gnuplot_stream = open(path+'.gnuplot','w') 2072 2073 # Now group all the identified matching histograms in a list 2074 matching_histo_lists = HwUList([HwUList([self[0]])]) 2075 for histo in self[1:]: 2076 matched = False 2077 for histo_list in matching_histo_lists: 2078 if histo.test_plot_compability(histo_list[0], 2079 consider_type=False, consider_unknown_weight_labels=True): 2080 histo_list.append(histo) 2081 matched = True 2082 break 2083 if not matched: 2084 matching_histo_lists.append(HwUList([histo])) 2085 2086 self[:] = matching_histo_lists 2087 2088 # Write the gnuplot header 2089 gnuplot_output_list_v4 = [ 2090 """ 2091 ################################################################################ 2092 # 2093 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2094 # automatically generates Feynman diagrams and matrix elements for arbitrary 2095 # high-energy processes in the Standard Model and beyond. It also perform the 2096 # integration and/or generate events for these processes, at LO and NLO accuracy. 2097 # 2098 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2099 # 2100 ################################################################################ 2101 # %s 2102 reset 2103 2104 set lmargin 10 2105 set rmargin 0 2106 set terminal postscript portrait enhanced mono dashed lw 1.0 "Helvetica" 9 2107 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2108 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2109 set key font ",9" 2110 set key samplen "2" 2111 set output "%s.ps" 2112 2113 # This is the "PODO" color palette of gnuplot v.5, but with the order 2114 # changed: palette of colors selected to be easily distinguishable by 2115 # color-blind individuals with either protanopia or deuteranopia. Bang 2116 # Wong [2011] Nature Methods 8, 441. 2117 2118 set style line 1 lt 1 lc rgb "#009e73" lw 2.5 2119 set style line 11 lt 2 lc rgb "#009e73" lw 2.5 2120 set style line 21 lt 4 lc rgb "#009e73" lw 2.5 2121 set style line 31 lt 6 lc rgb "#009e73" lw 2.5 2122 set style line 41 lt 8 lc rgb "#009e73" lw 2.5 2123 2124 set style line 2 lt 1 lc rgb "#0072b2" lw 2.5 2125 set style line 12 lt 2 lc rgb "#0072b2" lw 2.5 2126 set style line 22 lt 4 lc rgb "#0072b2" lw 2.5 2127 set style line 32 lt 6 lc rgb "#0072b2" lw 2.5 2128 set style line 42 lt 8 lc rgb "#0072b2" lw 2.5 2129 2130 set style line 3 lt 1 lc rgb "#d55e00" lw 2.5 2131 set style line 13 lt 2 lc rgb "#d55e00" lw 2.5 2132 set style line 23 lt 4 lc rgb "#d55e00" lw 2.5 2133 set style line 33 lt 6 lc rgb "#d55e00" lw 2.5 2134 set style line 43 lt 8 lc rgb "#d55e00" lw 2.5 2135 2136 set style line 4 lt 1 lc rgb "#f0e442" lw 2.5 2137 set style line 14 lt 2 lc rgb "#f0e442" lw 2.5 2138 set style line 24 lt 4 lc rgb "#f0e442" lw 2.5 2139 set style line 34 lt 6 lc rgb "#f0e442" lw 2.5 2140 set style line 44 lt 8 lc rgb "#f0e442" lw 2.5 2141 2142 set style line 5 lt 1 lc rgb "#56b4e9" lw 2.5 2143 set style line 15 lt 2 lc rgb "#56b4e9" lw 2.5 2144 set style line 25 lt 4 lc rgb "#56b4e9" lw 2.5 2145 set style line 35 lt 6 lc rgb "#56b4e9" lw 2.5 2146 set style line 45 lt 8 lc rgb "#56b4e9" lw 2.5 2147 2148 set style line 6 lt 1 lc rgb "#cc79a7" lw 2.5 2149 set style line 16 lt 2 lc rgb "#cc79a7" lw 2.5 2150 set style line 26 lt 4 lc rgb "#cc79a7" lw 2.5 2151 set style line 36 lt 6 lc rgb "#cc79a7" lw 2.5 2152 set style line 46 lt 8 lc rgb "#cc79a7" lw 2.5 2153 2154 set style line 7 lt 1 lc rgb "#e69f00" lw 2.5 2155 set style line 17 lt 2 lc rgb "#e69f00" lw 2.5 2156 set style line 27 lt 4 lc rgb "#e69f00" lw 2.5 2157 set style line 37 lt 6 lc rgb "#e69f00" lw 2.5 2158 set style line 47 lt 8 lc rgb "#e69f00" lw 2.5 2159 2160 set style line 8 lt 1 lc rgb "black" lw 2.5 2161 set style line 18 lt 2 lc rgb "black" lw 2.5 2162 set style line 28 lt 4 lc rgb "black" lw 2.5 2163 set style line 38 lt 6 lc rgb "black" lw 2.5 2164 set style line 48 lt 7 lc rgb "black" lw 2.5 2165 2166 2167 set style line 999 lt 1 lc rgb "gray" lw 2.5 2168 2169 safe(x,y,a) = (y == 0.0 ? a : x/y) 2170 2171 set style data histeps 2172 set key invert 2173 2174 """%(arg_string,output_base_name) 2175 ] 2176 2177 gnuplot_output_list_v5 = [ 2178 """ 2179 ################################################################################ 2180 # 2181 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2182 # automatically generates Feynman diagrams and matrix elements for arbitrary 2183 # high-energy processes in the Standard Model and beyond. It also perform the 2184 # integration and/or generate events for these processes, at LO and NLO accuracy. 2185 # 2186 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2187 # 2188 ################################################################################ 2189 # %s 2190 reset 2191 2192 set lmargin 10 2193 set rmargin 0 2194 set terminal postscript portrait enhanced color "Helvetica" 9 2195 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2196 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2197 set key font ",9" 2198 set key samplen "2" 2199 set output "%s.ps" 2200 2201 # This is the "PODO" color palette of gnuplot v.5, but with the order 2202 # changed: palette of colors selected to be easily distinguishable by 2203 # color-blind individuals with either protanopia or deuteranopia. Bang 2204 # Wong [2011] Nature Methods 8, 441. 2205 2206 set style line 1 lt 1 lc rgb "#009e73" lw 1.3 2207 set style line 11 lt 2 lc rgb "#009e73" lw 1.3 dt (6,3) 2208 set style line 21 lt 4 lc rgb "#009e73" lw 1.3 dt (3,2) 2209 set style line 31 lt 6 lc rgb "#009e73" lw 1.3 dt (2,1) 2210 set style line 41 lt 8 lc rgb "#009e73" lw 1.3 dt (4,3) 2211 2212 set style line 2 lt 1 lc rgb "#0072b2" lw 1.3 2213 set style line 12 lt 2 lc rgb "#0072b2" lw 1.3 dt (6,3) 2214 set style line 22 lt 4 lc rgb "#0072b2" lw 1.3 dt (3,2) 2215 set style line 32 lt 6 lc rgb "#0072b2" lw 1.3 dt (2,1) 2216 set style line 42 lt 8 lc rgb "#0072b2" lw 1.3 dt (4,3) 2217 2218 2219 set style line 3 lt 1 lc rgb "#d55e00" lw 1.3 2220 set style line 13 lt 2 lc rgb "#d55e00" lw 1.3 dt (6,3) 2221 set style line 23 lt 4 lc rgb "#d55e00" lw 1.3 dt (3,2) 2222 set style line 33 lt 6 lc rgb "#d55e00" lw 1.3 dt (2,1) 2223 set style line 43 lt 8 lc rgb "#d55e00" lw 1.3 dt (4,3) 2224 2225 set style line 4 lt 1 lc rgb "#f0e442" lw 1.3 2226 set style line 14 lt 2 lc rgb "#f0e442" lw 1.3 dt (6,3) 2227 set style line 24 lt 4 lc rgb "#f0e442" lw 1.3 dt (3,2) 2228 set style line 34 lt 6 lc rgb "#f0e442" lw 1.3 dt (2,1) 2229 set style line 44 lt 8 lc rgb "#f0e442" lw 1.3 dt (4,3) 2230 2231 set style line 5 lt 1 lc rgb "#56b4e9" lw 1.3 2232 set style line 15 lt 2 lc rgb "#56b4e9" lw 1.3 dt (6,3) 2233 set style line 25 lt 4 lc rgb "#56b4e9" lw 1.3 dt (3,2) 2234 set style line 35 lt 6 lc rgb "#56b4e9" lw 1.3 dt (2,1) 2235 set style line 45 lt 8 lc rgb "#56b4e9" lw 1.3 dt (4,3) 2236 2237 set style line 6 lt 1 lc rgb "#cc79a7" lw 1.3 2238 set style line 16 lt 2 lc rgb "#cc79a7" lw 1.3 dt (6,3) 2239 set style line 26 lt 4 lc rgb "#cc79a7" lw 1.3 dt (3,2) 2240 set style line 36 lt 6 lc rgb "#cc79a7" lw 1.3 dt (2,1) 2241 set style line 46 lt 8 lc rgb "#cc79a7" lw 1.3 dt (4,3) 2242 2243 set style line 7 lt 1 lc rgb "#e69f00" lw 1.3 2244 set style line 17 lt 2 lc rgb "#e69f00" lw 1.3 dt (6,3) 2245 set style line 27 lt 4 lc rgb "#e69f00" lw 1.3 dt (3,2) 2246 set style line 37 lt 6 lc rgb "#e69f00" lw 1.3 dt (2,1) 2247 set style line 47 lt 8 lc rgb "#e69f00" lw 1.3 dt (4,3) 2248 2249 set style line 8 lt 1 lc rgb "black" lw 1.3 2250 set style line 18 lt 2 lc rgb "black" lw 1.3 dt (6,3) 2251 set style line 28 lt 4 lc rgb "black" lw 1.3 dt (3,2) 2252 set style line 38 lt 6 lc rgb "black" lw 1.3 dt (2,1) 2253 set style line 48 lt 8 lc rgb "black" lw 1.3 dt (4,3) 2254 2255 2256 set style line 999 lt 1 lc rgb "gray" lw 1.3 2257 2258 safe(x,y,a) = (y == 0.0 ? a : x/y) 2259 2260 set style data histeps 2261 set key invert 2262 2263 """%(arg_string,output_base_name) 2264 ] 2265 2266 # determine the gnuplot version 2267 try: 2268 p = subprocess.Popen(['gnuplot', '--version'], \ 2269 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 2270 except OSError: 2271 # assume that version 4 of gnuplot is the default if 2272 # gnuplot could not be found 2273 gnuplot_output_list=gnuplot_output_list_v4 2274 else: 2275 output, _ = p.communicate() 2276 if float(output.split()[1]) < 5. : 2277 gnuplot_output_list=gnuplot_output_list_v4 2278 else: 2279 gnuplot_output_list=gnuplot_output_list_v5 2280 2281 2282 # Now output each group one by one 2283 # Block position keeps track of the gnuplot data_block index considered 2284 block_position = 0 2285 for histo_group in self: 2286 # Output this group 2287 block_position = histo_group.output_group(HwU_output_list, 2288 gnuplot_output_list, block_position,output_base_name+'.HwU', 2289 number_of_ratios=number_of_ratios, 2290 uncertainties = uncertainties, 2291 use_band = use_band, 2292 ratio_correlations = ratio_correlations, 2293 jet_samples_to_keep=jet_samples_to_keep, 2294 lhapdfconfig = lhapdfconfig) 2295 2296 # Now write the tail of the gnuplot command file 2297 gnuplot_output_list.extend([ 2298 "unset multiplot", 2299 '!ps2pdf "%s.ps" &> /dev/null'%output_base_name]) 2300 if auto_open: 2301 gnuplot_output_list.append( 2302 '!open "%s.pdf" &> /dev/null'%output_base_name) 2303 2304 # Now write result to stream and close it 2305 gnuplot_stream.write('\n'.join(gnuplot_output_list)) 2306 HwU_stream.write('\n'.join(HwU_output_list)) 2307 gnuplot_stream.close() 2308 HwU_stream.close() 2309 2310 logger.debug("Histograms have been written out at "+\ 2311 "%s.[HwU|gnuplot]' and can "%output_base_name+\ 2312 "now be rendered by invoking gnuplot.")
2313
2314 - def output_group(self, HwU_out, gnuplot_out, block_position, HwU_name, 2315 number_of_ratios = -1, 2316 uncertainties = ['scale','pdf','statitistical','merging_scale','alpsfact'], 2317 use_band = None, 2318 ratio_correlations = True, 2319 jet_samples_to_keep=None, 2320 lhapdfconfig='lhapdf-config'):
2321 2322 """ This functions output a single group of histograms with either one 2323 histograms untyped (i.e. type=None) or two of type 'NLO' and 'LO' 2324 respectively.""" 2325 2326 # This bool can be modified later to decide whether to use uncertainty 2327 # bands or not 2328 # ======== 2329 def get_uncertainty_lines(HwU_name, block_position, 2330 var_pos, color_index,title, ratio=False, band=False): 2331 """ Return a string line corresponding to the plotting of the 2332 uncertainty. Band is to chose wether to display uncertainty with 2333 a band or two lines.""" 2334 2335 # This perl substitution regular expression copies each line of the 2336 # HwU source and swap the x1 and x2 coordinate of the second copy. 2337 # So if input is: 2338 # 2339 # blabla 2340 # +0.01e+01 0.3 4 5 6 2341 # +0.03e+01 0.5 7 8 9 2342 # ... 2343 # 2344 # The output will be 2345 # 2346 # blabla 2347 # +0.01e+01 0.3 4 5 6 2348 # 0.3 +0.01e+01 4 5 6 2349 # +0.03e+01 0.5 7 8 9 2350 # 0.5 +0.03e+01 7 8 9 2351 # ... 2352 # 2353 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'" 2354 # Gnuplot escapes the antislash, so we must esacape then once more O_o. 2355 # Gnuplot doesn't have raw strings, what a shame... 2356 copy_swap_re = copy_swap_re.replace('\\','\\\\') 2357 # For the ratio, we must divide by the central value 2358 position = '(safe($%d,$3,1.0)-1.0)' if ratio else '%d' 2359 if not band: 2360 return ["'%s' index %d using (($1+$2)/2):%s ls %d title '%s'"\ 2361 %(HwU_name,block_position, position%(var_pos),color_index,title), 2362 "'%s' index %d using (($1+$2)/2):%s ls %d title ''"\ 2363 %(HwU_name,block_position, position%(var_pos+1),color_index)] 2364 else: 2365 return [' "<%s %s" index %d using 1:%s:%s with filledcurve ls %d fs transparent solid 0.2 title \'%s\''%\ 2366 (copy_swap_re,HwU_name,block_position, 2367 position%var_pos,position%(var_pos+1),color_index,title)]
2368 # ======== 2369 2370 2371 layout_geometry = [(0.0, 0.5, 1.0, 0.4 ), 2372 (0.0, 0.35, 1.0, 0.15), 2373 (0.0, 0.2, 1.0, 0.15)] 2374 layout_geometry.reverse() 2375 2376 # Group histograms which just differ by jet multiplicity and add their 2377 # sum as first plot 2378 matching_histo_lists = HwUList([HwUList([self[0]])]) 2379 for histo in self[1:]: 2380 matched = False 2381 for histo_list in matching_histo_lists: 2382 if hasattr(histo, 'jetsample') and histo.jetsample >= 0 and \ 2383 histo.type == histo_list[0].type: 2384 matched = True 2385 histo_list.append(histo) 2386 break 2387 if not matched: 2388 matching_histo_lists.append(HwUList([histo])) 2389 2390 # For each group of histograms with different jet multiplicities, we 2391 # define one at the beginning which is the sum. 2392 self[:] = [] 2393 for histo_group in matching_histo_lists: 2394 # First create a plot that sums all jet multiplicities for each type 2395 # (that is, only if jet multiplicities are defined) 2396 if len(histo_group)==1: 2397 self.append(histo_group[0]) 2398 continue 2399 # If there is already a histogram summing them, then don't create 2400 # a copy of it. 2401 if any(hist.jetsample==-1 for hist in histo_group if 2402 hasattr(hist, 'jetsample')): 2403 self.extend(histo_group) 2404 continue 2405 summed_histogram = copy.copy(histo_group[0]) 2406 for histo in histo_group[1:]: 2407 summed_histogram = summed_histogram + histo 2408 summed_histogram.jetsample = -1 2409 self.append(summed_histogram) 2410 self.extend(histo_group) 2411 2412 # Remove the curve of individual jet samples if they are not desired 2413 if not jet_samples_to_keep is None: 2414 self[:] = filter(lambda histo: 2415 (not hasattr(histo,'jetsample')) or (histo.jetsample == -1) or 2416 (histo.jetsample in jet_samples_to_keep), self) 2417 2418 # This function is to create the ratio histograms if the user turned off 2419 # correlations. 2420 def ratio_no_correlations(wgtsA, wgtsB): 2421 new_wgts = {} 2422 for label, wgt in wgtsA.items(): 2423 if wgtsB['central']==0.0 and wgt==0.0: 2424 new_wgts[label] = 0.0 2425 continue 2426 elif wgtsB['central']==0.0: 2427 # It is ok to skip the warning here. 2428 # logger.debug('Warning:: A bin with finite weight '+ 2429 # 'was divided by a bin with zero weight.') 2430 new_wgts[label] = 0.0 2431 continue 2432 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2433 return new_wgts 2434 2435 # First compute the ratio of all the histograms from the second to the 2436 # number_of_ratios+1 ones in the list to the first histogram. 2437 n_histograms = len(self) 2438 ratio_histos = HwUList([]) 2439 # A counter to keep track of the number of ratios included 2440 n_ratios_included = 0 2441 for i, histo in enumerate(self[1:]): 2442 if not hasattr(histo,'jetsample') or histo.jetsample==self[0].jetsample: 2443 n_ratios_included += 1 2444 else: 2445 continue 2446 2447 if number_of_ratios >=0 and n_ratios_included > number_of_ratios: 2448 break 2449 2450 if ratio_correlations: 2451 ratio_histos.append(histo/self[0]) 2452 else: 2453 ratio_histos.append(self[0].__class__.combine(histo, self[0], 2454 ratio_no_correlations)) 2455 if self[0].type=='NLO' and self[1].type=='LO': 2456 ratio_histos[-1].title += '1/K-factor' 2457 elif self[0].type=='LO' and self[1].type=='NLO': 2458 ratio_histos[-1].title += 'K-factor' 2459 else: 2460 ratio_histos[-1].title += ' %s/%s'%( 2461 self[1].type if self[1].type else '(%d)'%(i+2), 2462 self[0].type if self[0].type else '(1)') 2463 # By setting its type to aux, we make sure this histogram will be 2464 # filtered out if the .HwU file output here would be re-loaded later. 2465 ratio_histos[-1].type = 'AUX' 2466 self.extend(ratio_histos) 2467 2468 # Compute scale variation envelope for all diagrams 2469 if 'scale' in uncertainties: 2470 (mu_var_pos,mu) = self[0].set_uncertainty(type='all_scale') 2471 else: 2472 (mu_var_pos,mu) = (None,[None]) 2473 2474 if 'pdf' in uncertainties: 2475 (PDF_var_pos,pdf) = self[0].set_uncertainty(type='PDF',lhapdfconfig=lhapdfconfig) 2476 else: 2477 (PDF_var_pos,pdf) = (None,[None]) 2478 2479 if 'merging_scale' in uncertainties: 2480 (merging_var_pos,merging) = self[0].set_uncertainty(type='merging') 2481 else: 2482 (merging_var_pos,merging) = (None,[None]) 2483 if 'alpsfact' in uncertainties: 2484 (alpsfact_var_pos,alpsfact) = self[0].set_uncertainty(type='alpsfact') 2485 else: 2486 (alpsfact_var_pos,alpsfact) = (None,[None]) 2487 2488 uncertainties_present = list(uncertainties) 2489 if PDF_var_pos is None and 'pdf' in uncertainties_present: 2490 uncertainties_present.remove('pdf') 2491 if mu_var_pos is None and 'scale' in uncertainties_present: 2492 uncertainties_present.remove('scale') 2493 if merging_var_pos is None and 'merging' in uncertainties_present: 2494 uncertainties_present.remove('merging') 2495 if alpsfact_var_pos is None and 'alpsfact' in uncertainties_present: 2496 uncertainties_present.remove('alpsfact') 2497 no_uncertainties = len(uncertainties_present)==0 2498 2499 # If the 'use_band' option is None we should adopt a default which is 2500 try: 2501 uncertainties_present.remove('statistical') 2502 except: 2503 pass 2504 if use_band is None: 2505 # For clarity, it is better to only use bands only for one source 2506 # of uncertainty 2507 if len(uncertainties_present)==0: 2508 use_band = [] 2509 elif len(uncertainties_present)==1: 2510 use_band = uncertainties_present 2511 elif 'scale' in uncertainties_present: 2512 use_band = ['scale'] 2513 else: 2514 use_band = [uncertainties_present[0]] 2515 2516 for histo in self[1:]: 2517 if (not mu_var_pos is None) and \ 2518 mu_var_pos != histo.set_uncertainty(type='all_scale')[0]: 2519 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2520 ' scale uncertainties. It is required to be able to output them'+\ 2521 ' together.' 2522 if (not PDF_var_pos is None) and\ 2523 PDF_var_pos != histo.set_uncertainty(type='PDF',\ 2524 lhapdfconfig=lhapdfconfig)[0]: 2525 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2526 ' PDF uncertainties. It is required to be able to output them'+\ 2527 ' together.' 2528 if (not merging_var_pos is None) and\ 2529 merging_var_pos != histo.set_uncertainty(type='merging')[0]: 2530 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2531 ' merging uncertainties. It is required to be able to output them'+\ 2532 ' together.' 2533 if (not alpsfact_var_pos is None) and\ 2534 alpsfact_var_pos != histo.set_uncertainty(type='alpsfact')[0]: 2535 raise MadGraph5Error, 'Not all histograms in this group specify'+\ 2536 ' alpsfact uncertainties. It is required to be able to output them'+\ 2537 ' together.' 2538 2539 2540 # Now output the corresponding HwU histogram data 2541 for i, histo in enumerate(self): 2542 # Print the header the first time only 2543 HwU_out.extend(histo.get_HwU_source(\ 2544 print_header=(block_position==0 and i==0))) 2545 HwU_out.extend(['','']) 2546 2547 # First the global gnuplot header for this histogram group 2548 global_header =\ 2549 """ 2550 ################################################################################ 2551 ### Rendering of the plot titled '%(title)s' 2552 ################################################################################ 2553 2554 set multiplot 2555 set label "%(title)s" font ",13" at graph 0.04, graph 1.05 2556 set xrange [%(xmin).4e:%(xmax).4e] 2557 set bmargin 0 2558 set tmargin 0 2559 set xtics nomirror 2560 set ytics nomirror 2561 set mytics %(mxtics)d 2562 %(set_xtics)s 2563 set key horizontal noreverse maxcols 1 width -4 2564 set label front 'MadGraph5\_aMC\@NLO' font "Courier,11" rotate by 90 at graph 1.02, graph 0.04 2565 """ 2566 2567 # Now the header for each subhistogram 2568 subhistogram_header = \ 2569 """#-- rendering subhistograms '%(subhistogram_type)s' 2570 %(unset label)s 2571 %(set_format_y)s 2572 set yrange [%(ymin).4e:%(ymax).4e] 2573 set origin %(origin_x).4e, %(origin_y).4e 2574 set size %(size_x).4e, %(size_y).4e 2575 set mytics %(mytics)d 2576 %(set_ytics)s 2577 %(set_format_x)s 2578 %(set_yscale)s 2579 %(set_ylabel)s 2580 %(set_histo_label)s 2581 plot \\""" 2582 replacement_dic = {} 2583 2584 replacement_dic['title'] = self[0].get_HwU_histogram_name(format='human-no_type') 2585 # Determine what weight to consider when computing the optimal 2586 # range for the y-axis. 2587 wgts_to_consider = ['central'] 2588 if not mu_var_pos is None: 2589 for mu_var in mu_var_pos: 2590 wgts_to_consider.append(self[0].bins.weight_labels[mu_var]) 2591 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+1]) 2592 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+2]) 2593 if not PDF_var_pos is None: 2594 for PDF_var in PDF_var_pos: 2595 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var]) 2596 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+1]) 2597 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+2]) 2598 if not merging_var_pos is None: 2599 for merging_var in merging_var_pos: 2600 wgts_to_consider.append(self[0].bins.weight_labels[merging_var]) 2601 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+1]) 2602 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+2]) 2603 if not alpsfact_var_pos is None: 2604 for alpsfact_var in alpsfact_var_pos: 2605 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var]) 2606 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+1]) 2607 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+2]) 2608 2609 (xmin, xmax) = HwU.get_x_optimal_range(self[:2],\ 2610 weight_labels = wgts_to_consider) 2611 replacement_dic['xmin'] = xmin 2612 replacement_dic['xmax'] = xmax 2613 replacement_dic['mxtics'] = 10 2614 replacement_dic['set_xtics'] = 'set xtics auto' 2615 2616 # Add the global header which is now ready 2617 gnuplot_out.append(global_header%replacement_dic) 2618 2619 # Now add the main plot 2620 replacement_dic['subhistogram_type'] = '%s and %s results'%( 2621 str(self[0].type),str(self[1].type)) if len(self)>1 else \ 2622 'single diagram output' 2623 (ymin, ymax) = HwU.get_y_optimal_range(self[:2], 2624 labels = wgts_to_consider, scale=self[0].y_axis_mode) 2625 2626 # Force a linear scale if the detected range is negative 2627 if ymin< 0.0: 2628 self[0].y_axis_mode = 'LIN' 2629 2630 # Already add a margin on upper bound. 2631 if self[0].y_axis_mode=='LOG': 2632 ymax += 10.0 * ymax 2633 ymin -= 0.1 * ymin 2634 else: 2635 ymax += 0.3 * (ymax - ymin) 2636 ymin -= 0.3 * (ymax - ymin) 2637 2638 replacement_dic['ymin'] = ymin 2639 replacement_dic['ymax'] = ymax 2640 replacement_dic['unset label'] = '' 2641 (replacement_dic['origin_x'], replacement_dic['origin_y'], 2642 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 2643 replacement_dic['mytics'] = 10 2644 # Use default choise for the main histogram 2645 replacement_dic['set_ytics'] = 'set ytics auto' 2646 replacement_dic['set_format_x'] = "set format x ''" if \ 2647 (len(self)-n_histograms>0 or not no_uncertainties) else "set format x" 2648 replacement_dic['set_ylabel'] = 'set ylabel "{/Symbol s} per bin [pb]"' 2649 replacement_dic['set_yscale'] = "set logscale y" if \ 2650 self[0].y_axis_mode=='LOG' else 'unset logscale y' 2651 replacement_dic['set_format_y'] = "set format y '10^{%T}'" if \ 2652 self[0].y_axis_mode=='LOG' else 'unset format' 2653 2654 replacement_dic['set_histo_label'] = "" 2655 gnuplot_out.append(subhistogram_header%replacement_dic) 2656 2657 # Now add the main layout 2658 plot_lines = [] 2659 uncertainty_plot_lines = [] 2660 n=-1 2661 2662 for i, histo in enumerate(self[:n_histograms]): 2663 n=n+1 2664 color_index = n%self.number_line_colors_defined+1 2665 # Label to appear for the lower curves 2666 title = [] 2667 if histo.type is None and not hasattr(histo, 'jetsample'): 2668 title.append('%d'%(i+1)) 2669 else: 2670 if histo.type: 2671 title.append('NLO' if \ 2672 histo.type.split()[0]=='NLO' else histo.type) 2673 if hasattr(histo, 'jetsample'): 2674 if histo.jetsample!=-1: 2675 title.append('jet sample %d'%histo.jetsample) 2676 else: 2677 title.append('all jet samples') 2678 2679 title = ', '.join(title) 2680 # Label for the first curve in the upper plot 2681 if histo.type is None and not hasattr(histo, 'jetsample'): 2682 major_title = 'central value for plot (%d)'%(i+1) 2683 else: 2684 major_title = [] 2685 if not histo.type is None: 2686 major_title.append(histo.type) 2687 if hasattr(histo, 'jetsample'): 2688 if histo.jetsample!=-1: 2689 major_title.append('jet sample %d'%histo.jetsample) 2690 else: 2691 major_title.append('all jet samples') 2692 else: 2693 major_title.append('central value') 2694 major_title = ', '.join(major_title) 2695 2696 if not mu[0] in ['none',None]: 2697 major_title += ', dynamical\_scale\_choice=%s'%mu[0] 2698 if not pdf[0] in ['none',None]: 2699 major_title += ', PDF=%s'%pdf[0].replace('_','\_') 2700 2701 # Do not show uncertainties for individual jet samples (unless first 2702 # or specified explicitely and uniquely) 2703 if not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 2704 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 2705 jet_samples_to_keep[0] == histo.jetsample)): 2706 2707 uncertainty_plot_lines.append({}) 2708 2709 # We decide to show uncertainties in the main plot only if they 2710 # are part of a monocolor band. Otherwise, they will only be 2711 # shown in the first subplot. Notice that plotting 'sqrt(-1)' 2712 # is just a trick so as to have only the key printed with no 2713 # line 2714 2715 # Show scale variation for the first central value if available 2716 if not mu_var_pos is None and len(mu_var_pos)>0: 2717 if 'scale' in use_band: 2718 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 2719 HwU_name, block_position+i, mu_var_pos[0]+4, color_index+10, 2720 '%s, scale variation'%title, band='scale' in use_band) 2721 else: 2722 uncertainty_plot_lines[-1]['scale'] = \ 2723 ["sqrt(-1) ls %d title '%s'"%(color_index+10,'%s, scale variation'%title)] 2724 # And now PDF_variation if available 2725 if not PDF_var_pos is None and len(PDF_var_pos)>0: 2726 if 'pdf' in use_band: 2727 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 2728 HwU_name,block_position+i, PDF_var_pos[0]+4, color_index+20, 2729 '%s, PDF variation'%title, band='pdf' in use_band) 2730 else: 2731 uncertainty_plot_lines[-1]['pdf'] = \ 2732 ["sqrt(-1) ls %d title '%s'"%(color_index+20,'%s, PDF variation'%title)] 2733 # And now merging variation if available 2734 if not merging_var_pos is None and len(merging_var_pos)>0: 2735 if 'merging_scale' in use_band: 2736 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 2737 HwU_name,block_position+i, merging_var_pos[0]+4, color_index+30, 2738 '%s, merging scale variation'%title, band='merging_scale' in use_band) 2739 else: 2740 uncertainty_plot_lines[-1]['merging_scale'] = \ 2741 ["sqrt(-1) ls %d title '%s'"%(color_index+30,'%s, merging scale variation'%title)] 2742 # And now alpsfact variation if available 2743 if not alpsfact_var_pos is None and len(alpsfact_var_pos)>0: 2744 if 'alpsfact' in use_band: 2745 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 2746 HwU_name,block_position+i, alpsfact_var_pos[0]+4, color_index+40, 2747 '%s, alpsfact variation'%title, band='alpsfact' in use_band) 2748 else: 2749 uncertainty_plot_lines[-1]['alpsfact'] = \ 2750 ["sqrt(-1) ls %d title '%s'"%(color_index+40,'%s, alpsfact variation'%title)] 2751 2752 plot_lines.append( 2753 "'%s' index %d using (($1+$2)/2):3 ls %d title '%s'"\ 2754 %(HwU_name,block_position+i,color_index, major_title)) 2755 if 'statistical' in uncertainties: 2756 plot_lines.append( 2757 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"\ 2758 %(HwU_name,block_position+i,color_index)) 2759 2760 # Add additional central scale/PDF curves 2761 if not mu_var_pos is None: 2762 for j,mu_var in enumerate(mu_var_pos): 2763 if j!=0: 2764 n=n+1 2765 color_index = n%self.number_line_colors_defined+1 2766 plot_lines.append( 2767 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2768 %(HwU_name,block_position+i,mu_var+3,color_index,\ 2769 '%s dynamical\_scale\_choice=%s' % (title,mu[j]))) 2770 # And now PDF_variation if available 2771 if not PDF_var_pos is None: 2772 for j,PDF_var in enumerate(PDF_var_pos): 2773 if j!=0: 2774 n=n+1 2775 color_index = n%self.number_line_colors_defined+1 2776 plot_lines.append( 2777 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2778 %(HwU_name,block_position+i,PDF_var+3,color_index,\ 2779 '%s PDF=%s' % (title,pdf[j].replace('_','\_')))) 2780 2781 # Now add the uncertainty lines, those not using a band so that they 2782 # are not covered by those using a band after we reverse plo_lines 2783 for one_plot in uncertainty_plot_lines: 2784 for uncertainty_type, lines in one_plot.items(): 2785 if not uncertainty_type in use_band: 2786 plot_lines.extend(lines) 2787 # then those using a band 2788 for one_plot in uncertainty_plot_lines: 2789 for uncertainty_type, lines in one_plot.items(): 2790 if uncertainty_type in use_band: 2791 plot_lines.extend(lines) 2792 2793 # Reverse so that bands appear first 2794 plot_lines.reverse() 2795 2796 # Add the plot lines 2797 gnuplot_out.append(',\\\n'.join(plot_lines)) 2798 2799 # Now we can add the scale variation ratio 2800 replacement_dic['subhistogram_type'] = 'Relative scale and PDF uncertainty' 2801 2802 if 'statistical' in uncertainties: 2803 wgts_to_consider.append('stat_error') 2804 2805 # This function is just to temporarily create the scale ratio histogram with 2806 # the hwu.combine function. 2807 def rel_scale(wgtsA, wgtsB): 2808 new_wgts = {} 2809 for label, wgt in wgtsA.items(): 2810 if label in wgts_to_consider: 2811 if wgtsB['central']==0.0 and wgt==0.0: 2812 new_wgts[label] = 0.0 2813 continue 2814 elif wgtsB['central']==0.0: 2815 # It is ok to skip the warning here. 2816 # logger.debug('Warning:: A bin with finite weight '+ 2817 # 'was divided by a bin with zero weight.') 2818 new_wgts[label] = 0.0 2819 continue 2820 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2821 if label != 'stat_error': 2822 new_wgts[label] -= 1.0 2823 else: 2824 new_wgts[label] = wgtsA[label] 2825 return new_wgts 2826 2827 histos_for_subplots = [(i,histo) for i, histo in enumerate(self[:n_histograms]) if 2828 ( not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 2829 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 2830 jet_samples_to_keep[0] == histo.jetsample)) )] 2831 2832 # Notice even though a ratio histogram is created here, it 2833 # is not actually used to plot the quantity in gnuplot, but just to 2834 # compute the y range. 2835 (ymin, ymax) = HwU.get_y_optimal_range([histo[1].__class__.combine( 2836 histo[1],histo[1],rel_scale) for histo in histos_for_subplots], 2837 labels = wgts_to_consider, scale='LIN') 2838 2839 # Add a margin on upper and lower bound. 2840 ymax = ymax + 0.2 * (ymax - ymin) 2841 ymin = ymin - 0.2 * (ymax - ymin) 2842 replacement_dic['unset label'] = 'unset label' 2843 replacement_dic['ymin'] = ymin 2844 replacement_dic['ymax'] = ymax 2845 if not no_uncertainties: 2846 (replacement_dic['origin_x'], replacement_dic['origin_y'], 2847 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 2848 replacement_dic['mytics'] = 2 2849 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/3.0) 2850 replacement_dic['set_ytics'] = 'set ytics auto' 2851 replacement_dic['set_format_x'] = "set format x ''" if \ 2852 len(self)-n_histograms>0 else "set format x" 2853 replacement_dic['set_ylabel'] = 'set ylabel "%s rel.unc."'\ 2854 %('(1)' if self[0].type==None else '%s'%('NLO' if \ 2855 self[0].type.split()[0]=='NLO' else self[0].type)) 2856 replacement_dic['set_yscale'] = "unset logscale y" 2857 replacement_dic['set_format_y'] = 'unset format' 2858 2859 2860 tit='Relative uncertainties w.r.t. central value' 2861 if n_histograms > 1: 2862 tit=tit+'s' 2863 # if (not mu_var_pos is None and 'scale' not in use_band): 2864 # tit=tit+', scale is dashed' 2865 # if (not PDF_var_pos is None and 'pdf' not in use_band): 2866 # tit=tit+', PDF is dotted' 2867 replacement_dic['set_histo_label'] = \ 2868 'set label "%s" font ",9" front at graph 0.03, graph 0.13' % tit 2869 # Simply don't add these lines if there are no uncertainties. 2870 # This meant uncessary extra work, but I no longer car at this point 2871 if not no_uncertainties: 2872 gnuplot_out.append(subhistogram_header%replacement_dic) 2873 2874 # Now add the first subhistogram 2875 plot_lines = [] 2876 uncertainty_plot_lines = [] 2877 n=-1 2878 for (i,histo) in histos_for_subplots: 2879 n=n+1 2880 k=n 2881 color_index = n%self.number_line_colors_defined+1 2882 # Plot uncertainties 2883 if not mu_var_pos is None: 2884 for j,mu_var in enumerate(mu_var_pos): 2885 uncertainty_plot_lines.append({}) 2886 if j==0: 2887 color_index = k%self.number_line_colors_defined+1 2888 else: 2889 n=n+1 2890 color_index = n%self.number_line_colors_defined+1 2891 # Add the central line only if advanced scale variation 2892 if j>0 or mu[j]!='none': 2893 plot_lines.append( 2894 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2895 %(HwU_name,block_position+i,mu_var+3,color_index)) 2896 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 2897 HwU_name, block_position+i, mu_var+4, color_index+10,'', 2898 ratio=True, band='scale' in use_band) 2899 if not PDF_var_pos is None: 2900 for j,PDF_var in enumerate(PDF_var_pos): 2901 uncertainty_plot_lines.append({}) 2902 if j==0: 2903 color_index = k%self.number_line_colors_defined+1 2904 else: 2905 n=n+1 2906 color_index = n%self.number_line_colors_defined+1 2907 # Add the central line only if advanced pdf variation 2908 if j>0 or pdf[j]!='none': 2909 plot_lines.append( 2910 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2911 %(HwU_name,block_position+i,PDF_var+3,color_index)) 2912 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 2913 HwU_name, block_position+i, PDF_var+4, color_index+20,'', 2914 ratio=True, band='pdf' in use_band) 2915 if not merging_var_pos is None: 2916 for j,merging_var in enumerate(merging_var_pos): 2917 uncertainty_plot_lines.append({}) 2918 if j==0: 2919 color_index = k%self.number_line_colors_defined+1 2920 else: 2921 n=n+1 2922 color_index = n%self.number_line_colors_defined+1 2923 if j>0 or merging[j]!='none': 2924 plot_lines.append( 2925 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2926 %(HwU_name,block_position+i,merging_var+3,color_index)) 2927 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 2928 HwU_name, block_position+i, merging_var+4, color_index+30,'', 2929 ratio=True, band='merging_scale' in use_band) 2930 if not alpsfact_var_pos is None: 2931 for j,alpsfact_var in enumerate(alpsfact_var_pos): 2932 uncertainty_plot_lines.append({}) 2933 if j==0: 2934 color_index = k%self.number_line_colors_defined+1 2935 else: 2936 n=n+1 2937 color_index = n%self.number_line_colors_defined+1 2938 if j>0 or alpsfact[j]!='none': 2939 plot_lines.append( 2940 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 2941 %(HwU_name,block_position+i,alpsfact_var+3,color_index)) 2942 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 2943 HwU_name, block_position+i, alpsfact_var+4, color_index+40,'', 2944 ratio=True, band='alpsfact' in use_band) 2945 2946 if 'statistical' in uncertainties: 2947 plot_lines.append( 2948 "'%s' index %d using (($1+$2)/2):(0.0):(safe($4,$3,0.0)) w yerrorbar ls %d title ''"%\ 2949 (HwU_name,block_position+i,color_index)) 2950 2951 plot_lines.append("0.0 ls 999 title ''") 2952 2953 # Now add the uncertainty lines, those not using a band so that they 2954 # are not covered by those using a band after we reverse plo_lines 2955 for one_plot in uncertainty_plot_lines: 2956 for uncertainty_type, lines in one_plot.items(): 2957 if not uncertainty_type in use_band: 2958 plot_lines.extend(lines) 2959 # then those using a band 2960 for one_plot in uncertainty_plot_lines: 2961 for uncertainty_type, lines in one_plot.items(): 2962 if uncertainty_type in use_band: 2963 plot_lines.extend(lines) 2964 2965 # Reverse so that bands appear first 2966 plot_lines.reverse() 2967 # Add the plot lines 2968 if not no_uncertainties: 2969 gnuplot_out.append(',\\\n'.join(plot_lines)) 2970 2971 # We finish here when no ratio plot are asked for. 2972 if len(self)-n_histograms==0: 2973 # Now add the tail for this group 2974 gnuplot_out.extend(['','unset label','', 2975 '################################################################################']) 2976 # Return the starting data_block position for the next histogram group 2977 return block_position+len(self) 2978 2979 # We can finally add the last subhistograms for the ratios. 2980 ratio_name_long='(' 2981 for i, histo in enumerate(self[:n_histograms]): 2982 if i==0: continue 2983 ratio_name_long+='%d'%(i+1) if histo.type is None else ('NLO' if \ 2984 histo.type.split()[0]=='NLO' else histo.type) 2985 ratio_name_long+=')/' 2986 ratio_name_long+=('(1' if self[0].type==None else '(%s'%('NLO' if \ 2987 self[0].type.split()[0]=='NLO' else self[0].type))+' central value)' 2988 2989 ratio_name_short = 'ratio w.r.t. '+('1' if self[0].type==None else '%s'%('NLO' if \ 2990 self[0].type.split()[0]=='NLO' else self[0].type)) 2991 2992 replacement_dic['subhistogram_type'] = '%s ratio'%ratio_name_long 2993 replacement_dic['set_ylabel'] = 'set ylabel "%s"'%ratio_name_short 2994 2995 (ymin, ymax) = HwU.get_y_optimal_range(self[n_histograms:], 2996 labels = wgts_to_consider, scale='LIN',Kratio = True) 2997 2998 # Add a margin on upper and lower bound. 2999 ymax = ymax + 0.2 * (ymax - ymin) 3000 ymin = ymin - 0.2 * (ymax - ymin) 3001 replacement_dic['unset label'] = 'unset label' 3002 replacement_dic['ymin'] = ymin 3003 replacement_dic['ymax'] = ymax 3004 (replacement_dic['origin_x'], replacement_dic['origin_y'], 3005 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 3006 replacement_dic['mytics'] = 2 3007 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/10.0) 3008 replacement_dic['set_ytics'] = 'set ytics auto' 3009 replacement_dic['set_format_x'] = "set format x" 3010 replacement_dic['set_yscale'] = "unset logscale y" 3011 replacement_dic['set_format_y'] = 'unset format' 3012 replacement_dic['set_histo_label'] = \ 3013 'set label "%s" font ",9" at graph 0.03, graph 0.13'%ratio_name_long 3014 # 'set label "NLO/LO (K-factor)" font ",9" at graph 0.82, graph 0.13' 3015 gnuplot_out.append(subhistogram_header%replacement_dic) 3016 3017 uncertainty_plot_lines = [] 3018 plot_lines = [] 3019 3020 # Some crap to get the colors right I suppose... 3021 n=-1 3022 n=n+1 3023 if not mu_var_pos is None: 3024 for j,mu_var in enumerate(mu_var_pos): 3025 if j!=0: n=n+1 3026 if not PDF_var_pos is None: 3027 for j,PDF_var in enumerate(PDF_var_pos): 3028 if j!=0: n=n+1 3029 if not merging_var_pos is None: 3030 for j,merging_var in enumerate(merging_var_pos): 3031 if j!=0: n=n+1 3032 if not alpsfact_var_pos is None: 3033 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3034 if j!=0: n=n+1 3035 3036 for i_histo_ratio, histo_ration in enumerate(self[n_histograms:]): 3037 n=n+1 3038 k=n 3039 block_ratio_pos = block_position+n_histograms+i_histo_ratio 3040 color_index = n%self.number_line_colors_defined+1 3041 # Now add the subhistograms 3042 plot_lines.append( 3043 "'%s' index %d using (($1+$2)/2):3 ls %d title ''"%\ 3044 (HwU_name,block_ratio_pos,color_index)) 3045 if 'statistical' in uncertainties: 3046 plot_lines.append( 3047 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"%\ 3048 (HwU_name,block_ratio_pos,color_index)) 3049 3050 # Then the scale variations 3051 if not mu_var_pos is None: 3052 for j,mu_var in enumerate(mu_var_pos): 3053 uncertainty_plot_lines.append({}) 3054 if j==0: 3055 color_index = k%self.number_line_colors_defined+1 3056 else: 3057 n=n+1 3058 color_index = n%self.number_line_colors_defined+1 3059 # Only print out the additional central value for advanced scale variation 3060 if j>0 or mu[j]!='none': 3061 plot_lines.append( 3062 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3063 %(HwU_name,block_ratio_pos,mu_var+3,color_index)) 3064 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 3065 HwU_name, block_ratio_pos, mu_var+4, color_index+10,'', 3066 band='scale' in use_band) 3067 if not PDF_var_pos is None: 3068 for j,PDF_var in enumerate(PDF_var_pos): 3069 uncertainty_plot_lines.append({}) 3070 if j==0: 3071 color_index = k%self.number_line_colors_defined+1 3072 else: 3073 n=n+1 3074 color_index = n%self.number_line_colors_defined+1 3075 # Only print out the additional central value for advanced pdf variation 3076 if j>0 or pdf[j]!='none': 3077 plot_lines.append( 3078 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3079 %(HwU_name,block_ratio_pos,PDF_var+3,color_index)) 3080 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 3081 HwU_name, block_ratio_pos, PDF_var+4, color_index+20,'', 3082 band='pdf' in use_band) 3083 if not merging_var_pos is None: 3084 for j,merging_var in enumerate(merging_var_pos): 3085 uncertainty_plot_lines.append({}) 3086 if j==0: 3087 color_index = k%self.number_line_colors_defined+1 3088 else: 3089 n=n+1 3090 color_index = n%self.number_line_colors_defined+1 3091 if j>0 or merging[j]!='none': 3092 plot_lines.append( 3093 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3094 %(HwU_name,block_ratio_pos,merging_var+3,color_index)) 3095 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 3096 HwU_name, block_ratio_pos, merging_var+4, color_index+30,'', 3097 band='merging_scale' in use_band) 3098 if not alpsfact_var_pos is None: 3099 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3100 uncertainty_plot_lines.append({}) 3101 if j==0: 3102 color_index = k%self.number_line_colors_defined+1 3103 else: 3104 n=n+1 3105 color_index = n%self.number_line_colors_defined+1 3106 if j>0 or alpsfact[j]!='none': 3107 plot_lines.append( 3108 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3109 %(HwU_name,block_ratio_pos,alpsfact_var+3,color_index)) 3110 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 3111 HwU_name, block_ratio_pos, alpsfact_var+4, color_index+40,'', 3112 band='alpsfact' in use_band) 3113 3114 # Now add the uncertainty lines, those not using a band so that they 3115 # are not covered by those using a band after we reverse plo_lines 3116 for one_plot in uncertainty_plot_lines: 3117 for uncertainty_type, lines in one_plot.items(): 3118 if not uncertainty_type in use_band: 3119 plot_lines.extend(lines) 3120 # then those using a band 3121 for one_plot in uncertainty_plot_lines: 3122 for uncertainty_type, lines in one_plot.items(): 3123 if uncertainty_type in use_band: 3124 plot_lines.extend(lines) 3125 3126 plot_lines.append("1.0 ls 999 title ''") 3127 3128 # Reverse so that bands appear first 3129 plot_lines.reverse() 3130 # Add the plot lines 3131 gnuplot_out.append(',\\\n'.join(plot_lines)) 3132 3133 # Now add the tail for this group 3134 gnuplot_out.extend(['','unset label','', 3135 '################################################################################']) 3136 3137 # Return the starting data_block position for the next histogram group 3138 return block_position+len(self) 3139 3140 if __name__ == "__main__": 3141 main_doc = \ 3142 """ For testing and standalone use. Usage: 3143 python histograms.py <.HwU input_file_path_1> <.HwU input_file_path_2> ... --out=<output_file_path.format> <options> 3144 Where <options> can be a list of the following: 3145 '--help' See this message. 3146 '--gnuplot' or '' output the histograms read to gnuplot 3147 '--HwU' to output the histograms read to the raw HwU source. 3148 '--types=<type1>,<type2>,...' to keep only the type<i> when importing histograms. 3149 '--n_ratios=<integer>' Specifies how many curves must be considerd for the ratios. 3150 '--no_open' Turn off the automatic processing of the gnuplot output. 3151 '--show_full' to show the complete output of what was read. 3152 '--show_short' to show a summary of what was read. 3153 '--simple_ratios' to turn off correlations and error propagation in the ratio. 3154 '--sum' To sum all identical histograms together 3155 '--average' To average over all identical histograms 3156 '--rebin=<n>' Rebin the plots by merging n-consecutive bins together. 3157 '--assign_types=<type1>,<type2>,...' to assign a type to all histograms of the first, second, etc... files loaded. 3158 '--multiply=<fact1>,<fact2>,...' to multiply all histograms of the first, second, etc... files by the fact1, fact2, etc... 3159 '--no_suffix' Do no add any suffix (like '#1, #2, etc..) to the histograms types. 3160 '--lhapdf-config=<PATH_TO_LHAPDF-CONFIG>' give path to lhapdf-config to compute PDF certainties using LHAPDF (only for lhapdf6) 3161 '--jet_samples=[int1,int2]' Specifies what jet samples to keep. 'None' is the default and keeps them all. 3162 '--central_only' This option specifies to disregard all extra weights, so as to make it possible 3163 to take the ratio of plots with different extra weights specified. 3164 '--keep_all_weights' This option specifies to keep in the HwU produced all the weights, even 3165 those which are not known (i.e. that is scale, PDF or merging variation) 3166 For chosing what kind of variation you want to see on your plot, you can use the following options 3167 '--no_<type>' Turn off the plotting of variations of the chosen type 3168 '--only_<type>' Turn on only the plotting of variations of the chosen type 3169 '--variations=['<type1>',...]' Turn on only the plotting of the variations of the list of chosen types 3170 '--band=['<type1>',...]' Chose for which variations one should use uncertainty bands as opposed to lines 3171 The types can be: pdf, scale, stat, merging or alpsfact 3172 For the last two options one can use ...=all to automatically select all types. 3173 3174 When parsing an XML-formatted plot source output by the Pythia8 driver, the file names can be appended 3175 options as suffixes separated by '|', as follows: 3176 python histograms.py <XML_source_file_name>@<option1>@<option2>@etc.. 3177 These options can be 3178 'run_id=<integer>' Specifies the run_ID from which the plots should be loaded. 3179 By default, the first run is considered and the ones that follow are ignored. 3180 'merging_scale=<float>' This option allows to specify to import only the plots corresponding to a specific 3181 value for the merging scale. 3182 A value of -1 means that only the weights with the same merging scale as the central weight are kept. 3183 By default, all weights are considered. 3184 """ 3185 3186 possible_options=['--help', '--gnuplot', '--HwU', '--types','--n_ratios',\ 3187 '--no_open','--show_full','--show_short','--simple_ratios','--sum','--average','--rebin', \ 3188 '--assign_types','--multiply','--no_suffix', '--out', '--jet_samples', 3189 '--no_scale','--no_pdf','--no_stat','--no_merging','--no_alpsfact', 3190 '--only_scale','--only_pdf','--only_stat','--only_merging','--only_alpsfact', 3191 '--variations','--band','--central_only', '--lhapdf-config'] 3192 n_ratios = -1 3193 uncertainties = ['scale','pdf','statistical','merging_scale','alpsfact'] 3194 # The list of type of uncertainties for which to use bands. None is a 'smart' default 3195 use_band = None 3196 auto_open = True 3197 ratio_correlations = True 3198 consider_reweights = ['pdf','scale','murmuf_scales','merging_scale','alpsfact']
3199 3200 - def log(msg):
3201 print "histograms.py :: %s"%str(msg)
3202 3203 if '--help' in sys.argv or len(sys.argv)==1: 3204 log('\n\n%s'%main_doc) 3205 sys.exit(0) 3206 3207 for arg in sys.argv[1:]: 3208 if arg.startswith('--'): 3209 if arg.split('=')[0] not in possible_options: 3210 log('WARNING: option "%s" not valid. It will be ignored' % arg) 3211 3212 arg_string=' '.join(sys.argv) 3213 3214 OutName = "" 3215 for arg in sys.argv[1:]: 3216 if arg.startswith('--out='): 3217 OutName = arg[6:] 3218 3219 accepted_types = [] 3220 for arg in sys.argv[1:]: 3221 if arg.startswith('--types='): 3222 accepted_types = [(type if type!='None' else None) for type in \ 3223 arg[8:].split(',')] 3224 3225 assigned_types = [] 3226 for arg in sys.argv[1:]: 3227 if arg.startswith('--assign_types='): 3228 assigned_types = [(type if type!='None' else None) for type in \ 3229 arg[15:].split(',')] 3230 3231 jet_samples_to_keep = None 3232 3233 lhapdfconfig = ['lhapdf-config'] 3234 for arg in sys.argv[1:]: 3235 if arg.startswith('--lhapdf-config='): 3236 lhapdfconfig = arg[16:] 3237 3238 no_suffix = False 3239 if '--no_suffix' in sys.argv: 3240 no_suffix = True 3241 3242 if '--central_only' in sys.argv: 3243 consider_reweights = [] 3244 3245 if '--keep_all_weights' in sys.argv: 3246 consider_reweights = 'ALL' 3247 3248 for arg in sys.argv[1:]: 3249 if arg.startswith('--n_ratios='): 3250 n_ratios = int(arg[11:]) 3251 3252 if '--no_open' in sys.argv: 3253 auto_open = False 3254 3255 variation_type_map={'scale':'scale','merging':'merging_scale','pdf':'pdf', 3256 'stat':'statistical','alpsfact':'alpsfact'} 3257 3258 for arg in sys.argv: 3259 try: 3260 opt, value = arg.split('=') 3261 except ValueError: 3262 continue 3263 if opt=='--jet_samples': 3264 jet_samples_to_keep = eval(value) 3265 if opt=='--variations': 3266 uncertainties=[variation_type_map[type] for type in eval(value, 3267 dict([(key,key) for key in variation_type_map.keys()]+ 3268 [('all',variation_type_map.keys())]))] 3269 if opt=='--band': 3270 use_band=[variation_type_map[type] for type in eval(value, 3271 dict([(key,key) for key in variation_type_map.keys()]+ 3272 [('all',[type for type in variation_type_map.keys() if type!='stat'])]))] 3273 3274 if '--simple_ratios' in sys.argv: 3275 ratio_correlations = False 3276 3277 for arg in sys.argv: 3278 if arg.startswith('--no_') and not arg.startswith('--no_open'): 3279 uncertainties.remove(variation_type_map[arg[5:]]) 3280 if arg.startswith('--only_'): 3281 uncertainties= [variation_type_map[arg[7:]]] 3282 break 3283 3284 # Now remove from the weights considered all those not deemed necessary 3285 # in view of which uncertainties are selected 3286 if isinstance(consider_reweights, list): 3287 naming_map={'pdf':'pdf','scale':'scale', 3288 'merging_scale':'merging_scale','alpsfact':'alpsfact'} 3289 for key in naming_map: 3290 if (not key in uncertainties) and (naming_map[key] in consider_reweights): 3291 consider_reweights.remove(naming_map[key]) 3292 3293 n_files = len([_ for _ in sys.argv[1:] if not _.startswith('--')]) 3294 histo_norm = [1.0]*n_files 3295 3296 for arg in sys.argv[1:]: 3297 if arg.startswith('--multiply='): 3298 histo_norm = [(float(fact) if fact!='' else 1.0) for fact in \ 3299 arg[11:].split(',')] 3300 3301 if '--average' in sys.argv: 3302 histo_norm = [hist/float(n_files) for hist in histo_norm] 3303 3304 log("=======") 3305 histo_list = HwUList([]) 3306 for i, arg in enumerate(sys.argv[1:]): 3307 if arg.startswith('--'): 3308 break 3309 log("Loading histograms from '%s'."%arg) 3310 if OutName=="": 3311 OutName = os.path.basename(arg).split('.')[0]+'_output' 3312 # Make sure to process the potential XML options appended to the filename 3313 file_specification = arg.split('@') 3314 filename = file_specification.pop(0) 3315 file_options = {} 3316 for option in file_specification: 3317 opt, value = option.split('=') 3318 if opt=='run_id': 3319 file_options[opt]=int(value) 3320 if opt=='merging_scale': 3321 file_options[opt]=float(value) 3322 else: 3323 log("Unreckognize file option '%s'."%option) 3324 sys.exit(1) 3325 new_histo_list = HwUList(filename, accepted_types_order=accepted_types, 3326 consider_reweights=consider_reweights, **file_options) 3327 for histo in new_histo_list: 3328 if no_suffix or n_files==1: 3329 continue 3330 if not histo.type is None: 3331 histo.type += '|' 3332 else: 3333 histo.type = '' 3334 # Firs option is to give a bit of the name of the source HwU file. 3335 #histo.type += " %s, #%d"%\ 3336 # (os.path.basename(arg).split('.')[0][:3],i+1) 3337 # But it is more elegant to give just the number. 3338 # Overwrite existing number if present. We assume here that one never 3339 # uses the '#' in its custom-defined types, which is a fair assumptions. 3340 try: 3341 suffix = assigned_types[i] 3342 except IndexError: 3343 suffix = "#%d"%(i+1) 3344 try: 3345 histo.type = histo.type[:histo.type.index('#')] + suffix 3346 except ValueError: 3347 histo.type += suffix 3348 3349 if i==0 or all(_ not in ['--sum','--average'] for _ in sys.argv): 3350 for j,hist in enumerate(new_histo_list): 3351 new_histo_list[j]=hist*histo_norm[i] 3352 histo_list.extend(new_histo_list) 3353 continue 3354 3355 if any(_ in sys.argv for _ in ['--sum','--average']): 3356 for j, hist in enumerate(new_histo_list): 3357 # First make sure the plots have the same weight labels and such 3358 hist.test_plot_compability(histo_list[j]) 3359 # Now let the histogram module do the magic and add them. 3360 histo_list[j] += hist*histo_norm[i] 3361 3362 log("A total of %i histograms were found."%len(histo_list)) 3363 log("=======") 3364 3365 n_rebin = 1 3366 for arg in sys.argv[1:]: 3367 if arg.startswith('--rebin='): 3368 n_rebin = int(arg[8:]) 3369 3370 if n_rebin > 1: 3371 for hist in histo_list: 3372 hist.rebin(n_rebin) 3373 3374 if '--gnuplot' in sys.argv or all(arg not in ['--HwU'] for arg in sys.argv): 3375 # Where the magic happens: 3376 histo_list.output(OutName, format='gnuplot', 3377 number_of_ratios = n_ratios, 3378 uncertainties=uncertainties, 3379 ratio_correlations=ratio_correlations, 3380 arg_string=arg_string, 3381 jet_samples_to_keep=jet_samples_to_keep, 3382 use_band=use_band, 3383 auto_open=auto_open, 3384 lhapdfconfig=lhapdfconfig) 3385 # Tell the user that everything went for the best 3386 log("%d histograms have been output in " % len(histo_list)+\ 3387 "the gnuplot format at '%s.[HwU|gnuplot]'." % OutName) 3388 if auto_open: 3389 command = 'gnuplot %s.gnuplot'%OutName 3390 try: 3391 subprocess.call(command,shell=True,stderr=subprocess.PIPE) 3392 except: 3393 log("Automatic processing of the gnuplot card failed. Try the"+\ 3394 " command by hand:\n%s"%command) 3395 else: 3396 sys.exit(0) 3397 3398 if '--HwU' in sys.argv: 3399 log("Histograms data has been output in the HwU format at "+\ 3400 "'%s.HwU'."%OutName) 3401 histo_list.output(OutName, format='HwU') 3402 sys.exit(0) 3403 3404 if '--show_short' in sys.argv or '--show_full' in sys.argv: 3405 for i, histo in enumerate(histo_list): 3406 if i!=0: 3407 log('-------') 3408 log(histo.nice_string(short=(not '--show_full' in sys.argv))) 3409 log("=======")
3410 3411 ######## Routine from https://gist.github.com/thriveth/8352565 3412 ######## To fill for histograms data in matplotlib 3413 -def fill_between_steps(x, y1, y2=0, h_align='right', ax=None, **kwargs):
3414 ''' Fills a hole in matplotlib: fill_between for step plots. 3415 Parameters : 3416 ------------ 3417 x : array-like 3418 Array/vector of index values. These are assumed to be equally-spaced. 3419 If not, the result will probably look weird... 3420 y1 : array-like 3421 Array/vector of values to be filled under. 3422 y2 : array-Like 3423 Array/vector or bottom values for filled area. Default is 0. 3424 **kwargs will be passed to the matplotlib fill_between() function. 3425 ''' 3426 # If no Axes opject given, grab the current one: 3427 if ax is None: 3428 ax = plt.gca() 3429 3430 3431 # First, duplicate the x values 3432 #duplicate the info # xx = numpy.repeat(2)[1:] 3433 xx= []; [(xx.append(d),xx.append(d)) for d in x]; xx = xx[1:] 3434 # Now: the average x binwidth 3435 xstep = x[1] -x[0] 3436 # Now: add one step at end of row. 3437 xx.append(xx[-1] + xstep) 3438 3439 # Make it possible to change step alignment. 3440 if h_align == 'mid': 3441 xx = [X-xstep/2. for X in xx] 3442 elif h_align == 'right': 3443 xx = [X-xstep for X in xx] 3444 3445 # Also, duplicate each y coordinate in both arrays 3446 yy1 = []; [(yy1.append(d),yy1.append(d)) for d in y1] 3447 if isinstance(y1, list): 3448 yy2 = []; [(yy2.append(d),yy2.append(d)) for d in y2] 3449 else: 3450 yy2=y2 3451 3452 # now to the plotting part: 3453 ax.fill_between(xx, yy1, y2=yy2, **kwargs) 3454 3455 return ax
3456 ######## end routine from https://gist.github.com/thriveth/835256 3457