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