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