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

Source Code for Module madgraph.various.histograms

   1  #! /usr/bin/env python2 
   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, 90931) or \ 1439 wgts[0] in range(92000, 92031): 1440 # PDF4LHC15 Hessian sets 1441 pdf_stdev = 0.0 1442 for pdf in pdfs[1:]: 1443 pdf_stdev += (pdf - cntrl_val)**2 1444 pdf_stdev = math.sqrt(pdf_stdev) 1445 pdf_up = cntrl_val+pdf_stdev 1446 pdf_down = cntrl_val-pdf_stdev 1447 elif wgts[0] in range(244400, 244501) or \ 1448 wgts[0] in range(244600, 244701) or \ 1449 wgts[0] in range(244800, 244901) or \ 1450 wgts[0] in range(245000, 245101) or \ 1451 wgts[0] in range(245200, 245301) or \ 1452 wgts[0] in range(245400, 245501) or \ 1453 wgts[0] in range(245600, 245701) or \ 1454 wgts[0] in range(245800, 245901) or \ 1455 wgts[0] in range(246000, 246101) or \ 1456 wgts[0] in range(246200, 246301) or \ 1457 wgts[0] in range(246400, 246501) or \ 1458 wgts[0] in range(246600, 246701) or \ 1459 wgts[0] in range(246800, 246901) or \ 1460 wgts[0] in range(247000, 247101) or \ 1461 wgts[0] in range(247200, 247301) or \ 1462 wgts[0] in range(247400, 247501): 1463 # use Gaussian (68%CL) method (NNPDF) 1464 pdf_stdev = 0.0 1465 pdf_diff = sorted([abs(pdf-cntrl_val) for pdf in pdfs[1:]]) 1466 pdf_stdev = pdf_diff[67] 1467 pdf_up = cntrl_val+pdf_stdev 1468 pdf_down = cntrl_val-pdf_stdev 1469 else: 1470 # use Gaussian (one sigma) method (NNPDF) 1471 pdf_stdev = 0.0 1472 for pdf in pdfs[1:]: 1473 pdf_stdev += (pdf - cntrl_val)**2 1474 pdf_stdev = math.sqrt(pdf_stdev/float(len(pdfs)-2)) 1475 pdf_up = cntrl_val+pdf_stdev 1476 pdf_down = cntrl_val-pdf_stdev 1477 # Finally add them to the corresponding new weight 1478 bin.wgts[new_wgt_labels[0]] = bin.wgts[wgts[0]] 1479 bin.wgts[new_wgt_labels[1]] = pdf_down 1480 bin.wgts[new_wgt_labels[2]] = pdf_up 1481 1482 # And return the position in self.bins.weight_labels of the first 1483 # of the two new weight label added. 1484 return (position,labels)
1485
1486 - def select_central_weight(self, selected_label):
1487 """ Select a specific merging scale for the central value of this Histogram. """ 1488 if selected_label not in self.bins.weight_labels: 1489 raise MadGraph5Error("Selected weight label '%s' could not be found in this HwU."%selected_label) 1490 1491 for bin in self.bins: 1492 bin.wgts['central']=bin.wgts[selected_label]
1493
1494 - def rebin(self, n_rebin):
1495 """ Rebin the x-axis so as to merge n_rebin consecutive bins into a 1496 single one. """ 1497 1498 if n_rebin < 1 or not isinstance(n_rebin, int): 1499 raise MadGraph5Error("The argument 'n_rebin' of the HwU function"+\ 1500 " 'rebin' must be larger or equal to 1, not '%s'."%str(n_rebin)) 1501 elif n_rebin==1: 1502 return 1503 1504 if self.type and 'NOREBIN' in self.type.upper(): 1505 return 1506 1507 rebinning_list = list(range(0,len(self.bins),n_rebin))+[len(self.bins),] 1508 concat_list = [self.bins[rebinning_list[i]:rebinning_list[i+1]] for \ 1509 i in range(len(rebinning_list)-1)] 1510 1511 new_bins = copy.copy(self.bins) 1512 del new_bins[:] 1513 1514 for bins_to_merge in concat_list: 1515 if len(bins_to_merge)==0: 1516 continue 1517 new_bins.append(Bin(boundaries=(bins_to_merge[0].boundaries[0], 1518 bins_to_merge[-1].boundaries[1]),wgts={'central':0.0})) 1519 for weight in self.bins.weight_labels: 1520 if weight != 'stat_error': 1521 new_bins[-1].wgts[weight] = \ 1522 sum(b.wgts[weight] for b in bins_to_merge) 1523 else: 1524 new_bins[-1].wgts['stat_error'] = \ 1525 math.sqrt(sum(b.wgts['stat_error']**2 for b in\ 1526 bins_to_merge)) 1527 1528 self.bins = new_bins
1529 1530 @classmethod
1531 - def get_x_optimal_range(cls, histo_list, weight_labels=None):
1532 """ Function to determine the optimal x-axis range when plotting 1533 together the histos in histo_list and considering the weights 1534 weight_labels""" 1535 1536 # If no list of weight labels to consider is given, use them all. 1537 if weight_labels is None: 1538 weight_labels = histo_list[0].bins.weight_labels 1539 1540 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1541 for bin in histo.bins if \ 1542 (sum(abs(bin.wgts[label]) for label in weight_labels) > 0.0)] ,[]) 1543 1544 if len(all_boundaries)==0: 1545 all_boundaries = sum([ list(bin.boundaries) for histo in histo_list \ 1546 for bin in histo.bins],[]) 1547 if len(all_boundaries)==0: 1548 raise MadGraph5Error("The histograms with title '%s'"\ 1549 %histo_list[0].title+" seems to have no bins.") 1550 1551 x_min = min(all_boundaries) 1552 x_max = max(all_boundaries) 1553 1554 return (x_min, x_max)
1555 1556 @classmethod
1557 - def get_y_optimal_range(cls,histo_list, labels=None, 1558 scale='LOG', Kratio = False):
1559 """ Function to determine the optimal y-axis range when plotting 1560 together the histos in histo_list and considering the weights 1561 weight_labels. The option Kratio is present to allow for the couple of 1562 tweaks necessary for the the K-factor ratio histogram y-range.""" 1563 1564 # If no list of weight labels to consider is given, use them all. 1565 if labels is None: 1566 weight_labels = histo_list[0].bins.weight_labels 1567 else: 1568 weight_labels = labels 1569 1570 all_weights = [] 1571 for histo in histo_list: 1572 for bin in histo.bins: 1573 for label in weight_labels: 1574 # Filter out bin weights at *exactly* because they often 1575 # come from pathological division by zero for empty bins. 1576 if Kratio and bin.wgts[label]==0.0: 1577 continue 1578 if scale!='LOG': 1579 all_weights.append(bin.wgts[label]) 1580 if label == 'stat_error': 1581 all_weights.append(-bin.wgts[label]) 1582 elif bin.wgts[label]>0.0: 1583 all_weights.append(bin.wgts[label]) 1584 1585 1586 sum([ [bin.wgts[label] for label in weight_labels if \ 1587 (scale!='LOG' or bin.wgts[label]!=0.0)] \ 1588 for histo in histo_list for bin in histo.bins], []) 1589 1590 all_weights.sort() 1591 if len(all_weights)!=0: 1592 partial_max = all_weights[int(len(all_weights)*0.95)] 1593 partial_min = all_weights[int(len(all_weights)*0.05)] 1594 max = all_weights[-1] 1595 min = all_weights[0] 1596 else: 1597 if scale!='LOG': 1598 return (0.0,1.0) 1599 else: 1600 return (1.0,10.0) 1601 1602 y_max = 0.0 1603 y_min = 0.0 1604 1605 # If the maximum is too far from the 90% max, then take the partial max 1606 if (max-partial_max)>2.0*(partial_max-partial_min): 1607 y_max = partial_max 1608 else: 1609 y_max = max 1610 1611 # If the maximum is too far from the 90% max, then take the partial max 1612 if (partial_min - min)>2.0*(partial_max-partial_min) and min != 0.0: 1613 y_min = partial_min 1614 else: 1615 y_min = min 1616 1617 if Kratio: 1618 median = all_weights[len(all_weights)//2] 1619 spread = (y_max-y_min) 1620 if abs(y_max-median)<spread*0.05 or abs(median-y_min)<spread*0.05: 1621 y_max = median + spread/2.0 1622 y_min = median - spread/2.0 1623 if y_min != y_max: 1624 return ( y_min , y_max ) 1625 1626 # Enforce the maximum if there is 5 bins or less 1627 if len(histo_list[0].bins) <= 5: 1628 y_min = min 1629 y_max = max 1630 1631 # Finally make sure the range has finite length 1632 if y_min == y_max: 1633 if max == min: 1634 y_min -= 1.0 1635 y_max += 1.0 1636 else: 1637 y_min = min 1638 y_max = max 1639 1640 return ( y_min , y_max )
1641
1642 -class HwUList(histograms_PhysicsObjectList):
1643 """ A class implementing features related to a list of Hwu Histograms. """ 1644 1645 # Define here the number of line color schemes defined. If you need more, 1646 # simply define them in the gnuplot header and increase the number below. 1647 # It must be <= 9. 1648 number_line_colors_defined = 8 1649
1650 - def is_valid_element(self, obj):
1651 """Test wether specified object is of the right type for this list.""" 1652 1653 return isinstance(obj, HwU) or isinstance(obj, HwUList)
1654
1655 - def __init__(self, file_path, weight_header=None, run_id=None, 1656 merging_scale=None, accepted_types_order=[], consider_reweights='ALL', 1657 raw_labels=False, **opts):
1658 """ Read one plot from a file_path or a stream. 1659 This constructor reads all plots specified in target file. 1660 File_path can be a path or a stream in the argument. 1661 The option weight_header specifies an ordered list of weight names 1662 to appear in the file or stream specified. It accepted_types_order is 1663 empty, no filter is applied, otherwise only histograms of the specified 1664 types will be kept, and in this specified order for a given identical 1665 title. The option 'consider_reweights' selects whether one wants to 1666 include all the extra scale/pdf/merging variation weights. Possible values 1667 are 'ALL' or a list of the return types of the function get_HwU_wgt_label_type(). 1668 The option 'raw_labels' specifies that one wants to import the 1669 histogram data with no treatment of the weight labels at all 1670 (this is used for the matplotlib output). 1671 """ 1672 1673 if isinstance(file_path, str): 1674 stream = open(file_path,'r') 1675 elif isinstance(file_path, file): 1676 stream = file_path 1677 else: 1678 return super(HwUList,self).__init__(file_path, **opts) 1679 1680 try: 1681 # Try to read it in XML format 1682 self.parse_histos_from_PY8_XML_stream(stream, run_id, 1683 merging_scale, accepted_types_order, 1684 consider_reweights=consider_reweights, 1685 raw_labels=raw_labels) 1686 except XMLParsingError: 1687 # Rewinding the stream 1688 stream.seek(0) 1689 # Attempt to find the weight headers if not specified 1690 if not weight_header: 1691 weight_header = HwU.parse_weight_header(stream,raw_labels=raw_labels) 1692 1693 # Select a specific merging scale if asked for: 1694 selected_label = None 1695 if not merging_scale is None: 1696 for label in weight_header: 1697 if HwU.get_HwU_wgt_label_type(label)=='merging_scale': 1698 if float(label[1])==merging_scale: 1699 selected_label = label 1700 break 1701 if selected_label is None: 1702 raise MadGraph5Error("No weight could be found in the input HwU "+\ 1703 "for the selected merging scale '%4.2f'."%merging_scale) 1704 1705 new_histo = HwU(stream, weight_header,raw_labels=raw_labels, 1706 consider_reweights=consider_reweights, 1707 selected_central_weight=selected_label) 1708 # new_histo.select_central_weight(selected_label) 1709 while not new_histo.bins is None: 1710 if accepted_types_order==[] or \ 1711 new_histo.type in accepted_types_order: 1712 self.append(new_histo) 1713 new_histo = HwU(stream, weight_header, raw_labels=raw_labels, 1714 consider_reweights=consider_reweights, 1715 selected_central_weight=selected_label) 1716 1717 # if not run_id is None: 1718 # logger.debug("The run_id '%s' was specified, but "%run_id+ 1719 # "format of the HwU plot source is the MG5aMC"+ 1720 # " so that the run_id information is ignored.") 1721 1722 # Order the histograms according to their type. 1723 titles_order = [h.title for h in self] 1724 def ordering_function(histo): 1725 title_position = titles_order.index(histo.title) 1726 if accepted_types_order==[]: 1727 type_precedence = {'NLO':1,'LO':2,None:3,'AUX':5} 1728 try: 1729 ordering_key = (title_position,type_precedence[histo.type]) 1730 except KeyError: 1731 ordering_key = (title_position,4) 1732 else: 1733 ordering_key = (title_position, 1734 accepted_types_order.index(histo.type)) 1735 return ordering_key
1736 1737 # The command below is to first order them in alphabetical order, but it 1738 # is often better to keep the order of the original HwU source. 1739 # self.sort(key=lambda histo: '%s_%d'%(histo.title, 1740 # type_order.index(histo.type))) 1741 self.sort(key=ordering_function) 1742 1743 # Explicitly close the opened stream for clarity. 1744 if isinstance(file_path, str): 1745 stream.close()
1746
1747 - def get_hist_names(self):
1748 """return a list of all the names of define histograms""" 1749 1750 output = [] 1751 for hist in self: 1752 output.append(hist.get_HwU_histogram_name()) 1753 return output
1754
1755 - def get_wgt_names(self):
1756 """ return the list of all weights define in each histograms""" 1757 1758 return self[0].bins.weight_labels
1759 1760
1761 - def get(self, name):
1762 """return the HWU histograms related to a given name""" 1763 for hist in self: 1764 if hist.get_HwU_histogram_name() == name: 1765 return hist 1766 1767 raise NameError("no histogram with name: %s" % name)
1768
1769 - def parse_histos_from_PY8_XML_stream(self, stream, run_id=None, 1770 merging_scale=None, accepted_types_order=[], 1771 consider_reweights='ALL', raw_labels=False):
1772 """Initialize the HwU histograms from an XML stream. Only one run is 1773 used: the first one if run_id is None or the specified run otherwise. 1774 Accepted type order is a filter to select histograms of only a certain 1775 type. The option 'consider_reweights' selects whether one wants to 1776 include all the extra scale/pdf/merging variation weights. 1777 Possible values are 'ALL' or a list of the return types of the 1778 function get_HwU_wgt_label_type().""" 1779 1780 run_nodes = minidom.parse(stream).getElementsByTagName("run") 1781 all_nodes = dict((int(node.getAttribute('id')),node) for 1782 node in run_nodes) 1783 selected_run_node = None 1784 weight_header = None 1785 if run_id is None: 1786 if len(run_nodes)>0: 1787 selected_run_node = all_nodes[min(all_nodes.keys())] 1788 else: 1789 try: 1790 selected_run_node = all_nodes[int(run_id)] 1791 except: 1792 selected_run_node = None 1793 1794 if selected_run_node is None: 1795 if run_id is None: 1796 raise MadGraph5Error('No histogram was found in the specified XML source.') 1797 else: 1798 raise MadGraph5Error("Histogram with run_id '%d' was not found in the "%run_id+\ 1799 "specified XML source.") 1800 1801 # If raw weight label are asked for, then simply read the weight_labels 1802 # directly as specified in the XML header 1803 if raw_labels: 1804 # Filter empty weights coming from the split 1805 weight_label_list = [wgt.strip() for wgt in 1806 str(selected_run_node.getAttribute('header')).split(';') if 1807 not re.match('^\s*$',wgt)] 1808 ordered_weight_label_list = [w for w in weight_label_list if w not\ 1809 in ['xmin','xmax']] 1810 # Remove potential repetition of identical weight labels 1811 filtered_ordered_weight_label_list = [] 1812 for wgt_label in ordered_weight_label_list: 1813 if wgt_label not in filtered_ordered_weight_label_list: 1814 filtered_ordered_weight_label_list.append(wgt_label) 1815 1816 selected_weights = dict([ (wgt_pos, 1817 [wgt if wgt not in ['xmin','xmax'] else HwU.mandatory_weights[wgt]]) 1818 for wgt_pos, wgt in enumerate(weight_label_list) if wgt in 1819 filtered_ordered_weight_label_list+['xmin','xmax']]) 1820 1821 return self.retrieve_plots_from_XML_source(selected_run_node, 1822 selected_weights, filtered_ordered_weight_label_list, 1823 raw_labels=True) 1824 1825 # Now retrieve the header and save all weight labels as dictionaries 1826 # with key being properties and their values as value. If the property 1827 # does not defined a value, then put None as a value 1828 all_weights = [] 1829 for wgt_position, wgt_label in \ 1830 enumerate(str(selected_run_node.getAttribute('header')).split(';')): 1831 if not re.match('^\s*$',wgt_label) is None: 1832 continue 1833 all_weights.append({'POSITION':wgt_position}) 1834 for wgt_item in wgt_label.strip().split('_'): 1835 property = wgt_item.strip().split('=') 1836 if len(property) == 2: 1837 all_weights[-1][property[0].strip()] = property[1].strip() 1838 elif len(property)==1: 1839 all_weights[-1][property[0].strip()] = None 1840 #else: 1841 # misc.sprint(all_weights) 1842 # raise MadGraph5Error("The weight label property %s could not be parsed."%wgt_item) 1843 1844 # Now make sure that for all weights, there is 'PDF', 'MUF' and 'MUR' 1845 # and 'MERGING' defined. If absent we specify '-1' which implies that 1846 # the 'default' value was used (whatever it was). 1847 # Also cast them in the proper type 1848 for wgt_label in all_weights: 1849 for mandatory_attribute in ['PDF','MUR','MUF','MERGING','ALPSFACT']: 1850 if mandatory_attribute not in wgt_label: 1851 wgt_label[mandatory_attribute] = '-1' 1852 if mandatory_attribute=='PDF': 1853 wgt_label[mandatory_attribute] = int(wgt_label[mandatory_attribute]) 1854 elif mandatory_attribute in ['MUR','MUF','MERGING','ALPSFACT']: 1855 wgt_label[mandatory_attribute] = float(wgt_label[mandatory_attribute]) 1856 1857 # If merging cut is negative, then pick only the one of the central scale 1858 # If not specified, then take them all but use the PDF and scale weight 1859 # of the central merging_scale for the variation. 1860 if merging_scale is None or merging_scale < 0.0: 1861 merging_scale_chosen = all_weights[2]['MERGING'] 1862 else: 1863 merging_scale_chosen = merging_scale 1864 1865 # Central weight parameters are enforced to be those of the third weight 1866 central_PDF = all_weights[2]['PDF'] 1867 # Assume central scale is one, unless specified. 1868 central_MUR = all_weights[2]['MUR'] if all_weights[2]['MUR']!=-1.0 else 1.0 1869 central_MUF = all_weights[2]['MUF'] if all_weights[2]['MUF']!=-1.0 else 1.0 1870 central_alpsfact = all_weights[2]['ALPSFACT'] if all_weights[2]['ALPSFACT']!=-1.0 else 1.0 1871 1872 # Dictionary of selected weights with their position as key and the 1873 # list of weight labels they correspond to. 1874 selected_weights = {} 1875 # Treat the first four weights in a special way: 1876 if 'xmin' not in all_weights[0] or \ 1877 'xmax' not in all_weights[1] or \ 1878 'Weight' not in all_weights[2] or \ 1879 'WeightError' not in all_weights[3]: 1880 raise MadGraph5Error('The first weight entries in the XML HwU '+\ 1881 ' source are not the standard expected ones (xmin, xmax, sigmaCentral, errorCentral)') 1882 selected_weights[0] = ['xmin'] 1883 selected_weights[1] = ['xmax'] 1884 1885 # =========== BEGIN HELPER FUNCTIONS =========== 1886 def get_difference_to_central(weight): 1887 """ Return the list of properties which differ from the central weight. 1888 This disregards the merging scale value for which any central value 1889 can be picked anyway.""" 1890 1891 differences = [] 1892 # If the tag 'Weight' is in the weight label, then this is 1893 # automatically considered as the Event weight (central) for which 1894 # only the merging scale can be different 1895 if 'Weight' in weight: 1896 return set([]) 1897 if weight['MUR'] not in [central_MUR, -1.0] or \ 1898 weight['MUF'] not in [central_MUF, -1.0]: 1899 differences.append('mur_muf_scale') 1900 if weight['PDF'] not in [central_PDF,-1]: 1901 differences.append('pdf') 1902 if weight['ALPSFACT'] not in [central_alpsfact, -1]: 1903 differences.append('ALPSFACT') 1904 return set(differences)
1905 1906 def format_weight_label(weight): 1907 """ Print the weight attributes in a nice order.""" 1908 1909 all_properties = list(weight.keys()) 1910 all_properties.pop(all_properties.index('POSITION')) 1911 ordered_properties = [] 1912 # First add the attributes without value 1913 for property in all_properties: 1914 if weight[property] is None: 1915 ordered_properties.append(property) 1916 1917 ordered_properties.sort() 1918 all_properties = [property for property in all_properties if 1919 not weight[property] is None] 1920 1921 # then add PDF, MUR, MUF and MERGING if present 1922 for property in ['PDF','MUR','MUF','ALPSFACT','MERGING']: 1923 all_properties.pop(all_properties.index(property)) 1924 if weight[property]!=-1: 1925 ordered_properties.append(property) 1926 1927 ordered_properties.extend(sorted(all_properties)) 1928 1929 return '_'.join('%s%s'\ 1930 %(key,'' if weight[key] is None else '=%s'%str(weight[key])) for 1931 key in ordered_properties) 1932 # =========== END HELPER FUNCTIONS =========== 1933 1934 1935 # The central value is not necessarily the 3rd one if a different merging 1936 # cut was selected. 1937 if float(all_weights[2]['MERGING']) == merging_scale_chosen: 1938 selected_weights[2]=['central value'] 1939 else: 1940 for weight_position, weight in enumerate(all_weights): 1941 # Check if that weight corresponds to a central weight 1942 # (conventional label for central weight is 'Weight' 1943 if get_difference_to_central(weight)==set([]): 1944 # Check if the merging scale matches this time 1945 if weight['MERGING']==merging_scale_chosen: 1946 selected_weights[weight_position] = ['central value'] 1947 break 1948 # Make sure a central value was found, throw a warning if found 1949 if 'central value' not in sum(list(selected_weights.values()),[]): 1950 central_merging_scale = all_weights[2]['MERGING'] 1951 logger.warning('Could not find the central weight for the'+\ 1952 ' chosen merging scale (%f).\n'%merging_scale_chosen+\ 1953 'MG5aMC will chose the original central scale provided which '+\ 1954 'correspond to a merging scale of %s'%("'inclusive'" if 1955 central_merging_scale in [0.0,-1.0] else '%f'%central_merging_scale)) 1956 selected_weights[2]=['central value'] 1957 1958 # The error is always the third entry for now. 1959 selected_weights[3]=['dy'] 1960 1961 # Now process all other weights 1962 for weight_position, weight in enumerate(all_weights[4:]): 1963 # Apply special transformation for the weight label: 1964 # scale variation are stored as: 1965 # ('scale', mu_r, mu_f) for scale variation 1966 # ('pdf',PDF) for PDF variation 1967 # ('merging_scale',float) for merging scale 1968 # ('type',value) for all others (e.g. alpsfact) 1969 variations = get_difference_to_central(weight) 1970 # We know select the 'diagonal' variations where each parameter 1971 # is varied one at a time. 1972 1973 # Accept also if both pdf and mur_muf_scale differ because 1974 # the PDF used for the Event weight is often unknown but the 1975 # mu_r and mu_f variational weight specify it. Same story for 1976 # alpsfact. 1977 if variations in [set(['mur_muf_scale']),set(['pdf','mur_muf_scale'])]: 1978 wgt_label = ('scale',weight['MUR'],weight['MUF']) 1979 if variations in [set(['ALPSFACT']),set(['pdf','ALPSFACT'])]: 1980 wgt_label = ('alpsfact',weight['ALPSFACT']) 1981 if variations == set(['pdf']): 1982 wgt_label = ('pdf',weight['PDF']) 1983 if variations == set([]): 1984 # Unknown weight (might turn out to be taken as a merging variation weight below) 1985 wgt_label = format_weight_label(weight) 1986 1987 # Make sure the merging scale matches the chosen one 1988 if weight['MERGING'] != merging_scale_chosen: 1989 # If a merging_scale was specified, then ignore all other weights 1990 if merging_scale: 1991 continue 1992 # Otherwise consider them also, but for now only if it is for 1993 # the central value parameter (central PDF, central mu_R and mu_F) 1994 if variations == set([]): 1995 # We choose to store the merging variation weight labels as floats 1996 wgt_label = ('merging_scale', weight['MERGING']) 1997 # Make sure that the weight label does not already exist. If it does, 1998 # this means that the source has redundant information and that 1999 # there is no need to specify it again. 2000 if wgt_label in sum(list(selected_weights.values()),[]): 2001 continue 2002 2003 # Now register the selected weight 2004 try: 2005 selected_weights[weight_position+4].append(wgt_label) 2006 except KeyError: 2007 selected_weights[weight_position+4]=[wgt_label,] 2008 2009 if merging_scale and merging_scale > 0.0 and \ 2010 len(sum(list(selected_weights.values()),[]))==4: 2011 logger.warning('No additional variation weight was found for the '+\ 2012 'chosen merging scale %f.'%merging_scale) 2013 2014 # Make sure to use the predefined keywords for the mandatory weight labels 2015 for wgt_pos in selected_weights: 2016 for i, weight_label in enumerate(selected_weights[wgt_pos]): 2017 try: 2018 selected_weights[wgt_pos][i] = HwU.mandatory_weights[weight_label] 2019 except KeyError: 2020 pass 2021 2022 # Keep only the weights asked for 2023 if consider_reweights!='ALL': 2024 new_selected_weights = {} 2025 for wgt_position, wgt_labels in selected_weights.items(): 2026 for wgt_label in wgt_labels: 2027 if wgt_label in ['central','stat_error','boundary_xmin','boundary_xmax'] or\ 2028 HwU.get_HwU_wgt_label_type(wgt_label) in consider_reweights: 2029 try: 2030 new_selected_weights[wgt_position].append(wgt_label) 2031 except KeyError: 2032 new_selected_weights[wgt_position] = [wgt_label] 2033 selected_weights = new_selected_weights 2034 2035 # Cache the list of selected weights to be defined at each line 2036 weight_label_list = sum(list(selected_weights.values()),[]) 2037 2038 # The weight_label list to set to self.bins 2039 ordered_weight_label_list = ['central','stat_error'] 2040 for weight_label in weight_label_list: 2041 if not isinstance(weight_label, str): 2042 ordered_weight_label_list.append(weight_label) 2043 for weight_label in weight_label_list: 2044 if weight_label in ['central','stat_error','boundary_xmin','boundary_xmax']: 2045 continue 2046 if isinstance(weight_label, str): 2047 ordered_weight_label_list.append(weight_label) 2048 2049 # Now that we know the desired weights, retrieve all plots from the 2050 # XML source node. 2051 return self.retrieve_plots_from_XML_source(selected_run_node, 2052 selected_weights, ordered_weight_label_list, raw_labels=False) 2053
2054 - def retrieve_plots_from_XML_source(self, xml_node, 2055 selected_weights, ordered_weight_label_list,raw_labels=False):
2056 """Given an XML node and the selected weights and their ordered list, 2057 import all histograms from the specified XML node.""" 2058 2059 # We now start scanning all the plots 2060 for multiplicity_node in xml_node.getElementsByTagName("jethistograms"): 2061 multiplicity = int(multiplicity_node.getAttribute('njet')) 2062 for histogram in multiplicity_node.getElementsByTagName("histogram"): 2063 # We only consider the histograms with all the weight information 2064 if histogram.getAttribute("weight")!='all': 2065 continue 2066 new_histo = HwU() 2067 hist_name = '%s %s'%(str(histogram.getAttribute('name')), 2068 str(histogram.getAttribute('unit'))) 2069 # prepend the jet multiplicity to the histogram name 2070 new_histo.process_histogram_name('%s |JETSAMPLE@%d'%(hist_name,multiplicity)) 2071 # We do not want to include auxiliary diagrams which would be 2072 # recreated anyway. 2073 if new_histo.type == 'AUX': 2074 continue 2075 # Make sure to exclude the boundaries from the weight 2076 # specification 2077 # Order the weights so that the unreckognized ones go last 2078 new_histo.bins = BinList(weight_labels = ordered_weight_label_list) 2079 hist_data = str(histogram.childNodes[0].data) 2080 for line in hist_data.split('\n'): 2081 if line.strip()=='': 2082 continue 2083 bin_weights = {} 2084 boundaries = [0.0,0.0] 2085 for j, weight in \ 2086 enumerate(HwU.histo_bin_weight_re.finditer(line)): 2087 try: 2088 for wgt_label in selected_weights[j]: 2089 if wgt_label == 'boundary_xmin': 2090 boundaries[0] = float(weight.group('weight')) 2091 elif wgt_label == 'boundary_xmax': 2092 boundaries[1] = float(weight.group('weight')) 2093 else: 2094 if weight.group('weight').upper()=='NAN': 2095 raise MadGraph5Error("Some weights are found to be 'NAN' in histogram with name '%s'"%hist_name+\ 2096 " and jet sample multiplicity %d."%multiplicity) 2097 else: 2098 bin_weights[wgt_label] = \ 2099 float(weight.group('weight')) 2100 except KeyError: 2101 continue 2102 # For this check, we subtract two because of the bin boundaries 2103 if len(bin_weights)!=len(ordered_weight_label_list): 2104 raise MadGraph5Error('Not all defined weights were found in the XML source.\n'+\ 2105 '%d found / %d expected.'%(len(bin_weights),len(ordered_weight_label_list))+\ 2106 '\nThe missing ones are: %s.'%\ 2107 str(list(set(ordered_weight_label_list)-set(bin_weights.keys())))+\ 2108 "\nIn plot with title '%s' and jet sample multiplicity %d."%\ 2109 (hist_name, multiplicity)) 2110 2111 new_histo.bins.append(Bin(tuple(boundaries), bin_weights)) 2112 2113 # if bin_weights['central']!=0.0: 2114 # print '---------' 2115 # print 'multiplicity =',multiplicity 2116 # print 'central =', bin_weights['central'] 2117 # print 'PDF = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='pdf'] 2118 # 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') 2119 # print 'scale = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='scale'] 2120 # 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') 2121 # print 'merging = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='merging_scale'] 2122 # 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') 2123 # print 'alpsfact = ', [(key,bin_weights[key]) for key in bin_weights if HwU.get_HwU_wgt_label_type(key)=='alpsfact'] 2124 # 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') 2125 # print '---------' 2126 # stop 2127 2128 # Finally remove auxiliary weights 2129 if not raw_labels: 2130 new_histo.trim_auxiliary_weights() 2131 2132 # And add it to the list 2133 self.append(new_histo)
2134
2135 - def output(self, path, format='gnuplot',number_of_ratios = -1, 2136 uncertainties=['scale','pdf','statitistical','merging_scale','alpsfact'], 2137 use_band = None, 2138 ratio_correlations=True, arg_string='', 2139 jet_samples_to_keep=None, 2140 auto_open=True, 2141 lhapdfconfig='lhapdf-config', 2142 assigned_colours=None):
2143 """ Ouput this histogram to a file, stream or string if path is kept to 2144 None. The supported format are for now. Chose whether to print the header 2145 or not.""" 2146 2147 if len(self)==0: 2148 return MadGraph5Error, 'No histograms stored in the list yet.' 2149 2150 if not format in HwU.output_formats_implemented: 2151 raise MadGraph5Error("The specified output format '%s'"%format+\ 2152 " is not yet supported. Supported formats are %s."\ 2153 %HwU.output_formats_implemented) 2154 2155 if isinstance(path, str) and not any(ext in os.path.basename(path) \ 2156 for ext in ['.Hwu','.ps','.gnuplot','.pdf']): 2157 output_base_name = os.path.basename(path) 2158 HwU_stream = open(path+'.HwU','w') 2159 else: 2160 raise MadGraph5Error("The path argument of the output function of"+\ 2161 " the HwUList instance must be file path without its extension.") 2162 2163 HwU_output_list = [] 2164 # If the format is just the raw HwU source, then simply write them 2165 # out all in sequence. 2166 if format == 'HwU': 2167 HwU_output_list.extend(self[0].get_HwU_source(print_header=True)) 2168 for histo in self[1:]: 2169 HwU_output_list.extend(histo.get_HwU_source()) 2170 HwU_output_list.extend(['','']) 2171 HwU_stream.write('\n'.join(HwU_output_list)) 2172 HwU_stream.close() 2173 return 2174 2175 # Now we consider that we are attempting a gnuplot output. 2176 if format == 'gnuplot': 2177 gnuplot_stream = open(path+'.gnuplot','w') 2178 2179 # Now group all the identified matching histograms in a list 2180 matching_histo_lists = HwUList([HwUList([self[0]])]) 2181 for histo in self[1:]: 2182 matched = False 2183 for histo_list in matching_histo_lists: 2184 if histo.test_plot_compability(histo_list[0], 2185 consider_type=False, consider_unknown_weight_labels=True): 2186 histo_list.append(histo) 2187 matched = True 2188 break 2189 if not matched: 2190 matching_histo_lists.append(HwUList([histo])) 2191 2192 self[:] = matching_histo_lists 2193 2194 # the histogram colours: 2195 coli=['col1','col2','col3','col4','col5','col6','col7','col8'] 2196 colours={coli[0] : "#009e73", 2197 coli[1] : "#0072b2", 2198 coli[2] : "#d55e00", 2199 coli[3] : "#f0e442", 2200 coli[4] : "#56b4e9", 2201 coli[5] : "#cc79a7", 2202 coli[6] : "#e69f00", 2203 coli[7] : "black"} 2204 if assigned_colours: 2205 for index, item in enumerate(assigned_colours): 2206 if (item != None): colours[coli[index]]=item 2207 2208 replace_dict=colours 2209 replace_dict['arg_string']=arg_string 2210 replace_dict['output_base_name']=output_base_name 2211 2212 # Write the gnuplot header 2213 gnuplot_output_list_v4 = [ 2214 """ 2215 ################################################################################ 2216 # 2217 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2218 # automatically generates Feynman diagrams and matrix elements for arbitrary 2219 # high-energy processes in the Standard Model and beyond. It also perform the 2220 # integration and/or generate events for these processes, at LO and NLO accuracy. 2221 # 2222 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2223 # 2224 ################################################################################ 2225 # %(arg_string)s 2226 reset 2227 2228 set lmargin 10 2229 set rmargin 0 2230 set terminal postscript portrait enhanced mono dashed lw 1.0 "Helvetica" 9 2231 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2232 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2233 set key font ",9" 2234 set key samplen "2" 2235 set output "%(output_base_name)s.ps" 2236 2237 # This is the "PODO" color palette of gnuplot v.5, but with the order 2238 # changed: palette of colors selected to be easily distinguishable by 2239 # color-blind individuals with either protanopia or deuteranopia. Bang 2240 # Wong [2011] Nature Methods 8, 441. 2241 2242 set style line 1 lt 1 lc rgb "%(col1)s" lw 2.5 2243 set style line 11 lt 2 lc rgb "%(col1)s" lw 2.5 2244 set style line 21 lt 4 lc rgb "%(col1)s" lw 2.5 2245 set style line 31 lt 6 lc rgb "%(col1)s" lw 2.5 2246 set style line 41 lt 8 lc rgb "%(col1)s" lw 2.5 2247 2248 set style line 2 lt 1 lc rgb "%(col2)s" lw 2.5 2249 set style line 12 lt 2 lc rgb "%(col2)s" lw 2.5 2250 set style line 22 lt 4 lc rgb "%(col2)s" lw 2.5 2251 set style line 32 lt 6 lc rgb "%(col2)s" lw 2.5 2252 set style line 42 lt 8 lc rgb "%(col2)s" lw 2.5 2253 2254 set style line 3 lt 1 lc rgb "%(col3)s" lw 2.5 2255 set style line 13 lt 2 lc rgb "%(col3)s" lw 2.5 2256 set style line 23 lt 4 lc rgb "%(col3)s" lw 2.5 2257 set style line 33 lt 6 lc rgb "%(col3)s" lw 2.5 2258 set style line 43 lt 8 lc rgb "%(col3)s" lw 2.5 2259 2260 set style line 4 lt 1 lc rgb "%(col4)s" lw 2.5 2261 set style line 14 lt 2 lc rgb "%(col4)s" lw 2.5 2262 set style line 24 lt 4 lc rgb "%(col4)s" lw 2.5 2263 set style line 34 lt 6 lc rgb "%(col4)s" lw 2.5 2264 set style line 44 lt 8 lc rgb "%(col4)s" lw 2.5 2265 2266 set style line 5 lt 1 lc rgb "%(col5)s" lw 2.5 2267 set style line 15 lt 2 lc rgb "%(col5)s" lw 2.5 2268 set style line 25 lt 4 lc rgb "%(col5)s" lw 2.5 2269 set style line 35 lt 6 lc rgb "%(col5)s" lw 2.5 2270 set style line 45 lt 8 lc rgb "%(col5)s" lw 2.5 2271 2272 set style line 6 lt 1 lc rgb "%(col6)s" lw 2.5 2273 set style line 16 lt 2 lc rgb "%(col6)s" lw 2.5 2274 set style line 26 lt 4 lc rgb "%(col6)s" lw 2.5 2275 set style line 36 lt 6 lc rgb "%(col6)s" lw 2.5 2276 set style line 46 lt 8 lc rgb "%(col6)s" lw 2.5 2277 2278 set style line 7 lt 1 lc rgb "%(col7)s" lw 2.5 2279 set style line 17 lt 2 lc rgb "%(col7)s" lw 2.5 2280 set style line 27 lt 4 lc rgb "%(col7)s" lw 2.5 2281 set style line 37 lt 6 lc rgb "%(col7)s" lw 2.5 2282 set style line 47 lt 8 lc rgb "%(col7)s" lw 2.5 2283 2284 set style line 8 lt 1 lc rgb "%(col8)s" lw 2.5 2285 set style line 18 lt 2 lc rgb "%(col8)s" lw 2.5 2286 set style line 28 lt 4 lc rgb "%(col8)s" lw 2.5 2287 set style line 38 lt 6 lc rgb "%(col8)s" lw 2.5 2288 set style line 48 lt 7 lc rgb "%(col8)s" lw 2.5 2289 2290 2291 set style line 999 lt 1 lc rgb "gray" lw 2.5 2292 2293 safe(x,y,a) = (y == 0.0 ? a : x/y) 2294 2295 set style data histeps 2296 set key invert 2297 2298 """%(replace_dict) 2299 ] 2300 2301 gnuplot_output_list_v5 = [ 2302 """ 2303 ################################################################################ 2304 # 2305 # This gnuplot file was generated by MadGraph5_aMC@NLO project, a program which 2306 # automatically generates Feynman diagrams and matrix elements for arbitrary 2307 # high-energy processes in the Standard Model and beyond. It also perform the 2308 # integration and/or generate events for these processes, at LO and NLO accuracy. 2309 # 2310 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 2311 # 2312 ################################################################################ 2313 # %(arg_string)s 2314 reset 2315 2316 set lmargin 10 2317 set rmargin 0 2318 set terminal postscript portrait enhanced color "Helvetica" 9 2319 # The pdf terminal offers transparency support, but you will have to adapt things a bit 2320 #set terminal pdf enhanced font "Helvetica 12" lw 1.0 dashed size 29.7cm, 21cm 2321 set key font ",9" 2322 set key samplen "2" 2323 set output "%(output_base_name)s.ps" 2324 2325 # This is the "PODO" color palette of gnuplot v.5, but with the order 2326 # changed: palette of colors selected to be easily distinguishable by 2327 # color-blind individuals with either protanopia or deuteranopia. Bang 2328 # Wong [2011] Nature Methods 8, 441. 2329 2330 set style line 1 lt 1 lc rgb "%(col1)s" lw 1.3 2331 set style line 101 lt 1 lc rgb "%(col1)s" lw 1.3 dt (6,3) 2332 set style line 11 lt 2 lc rgb "%(col1)s" lw 1.3 dt (6,3) 2333 set style line 21 lt 4 lc rgb "%(col1)s" lw 1.3 dt (3,2) 2334 set style line 31 lt 6 lc rgb "%(col1)s" lw 1.3 dt (2,1) 2335 set style line 41 lt 8 lc rgb "%(col1)s" lw 1.3 dt (4,3) 2336 2337 set style line 2 lt 1 lc rgb "%(col2)s" lw 1.3 2338 set style line 102 lt 1 lc rgb "%(col2)s" lw 1.3 dt (6,3) 2339 set style line 12 lt 2 lc rgb "%(col2)s" lw 1.3 dt (6,3) 2340 set style line 22 lt 4 lc rgb "%(col2)s" lw 1.3 dt (3,2) 2341 set style line 32 lt 6 lc rgb "%(col2)s" lw 1.3 dt (2,1) 2342 set style line 42 lt 8 lc rgb "%(col2)s" lw 1.3 dt (4,3) 2343 2344 set style line 3 lt 1 lc rgb "%(col3)s" lw 1.3 2345 set style line 103 lt 1 lc rgb "%(col3)s" lw 1.3 dt (6,3) 2346 set style line 13 lt 2 lc rgb "%(col3)s" lw 1.3 dt (6,3) 2347 set style line 23 lt 4 lc rgb "%(col3)s" lw 1.3 dt (3,2) 2348 set style line 33 lt 6 lc rgb "%(col3)s" lw 1.3 dt (2,1) 2349 set style line 43 lt 8 lc rgb "%(col3)s" lw 1.3 dt (4,3) 2350 2351 set style line 4 lt 1 lc rgb "%(col4)s" lw 1.3 2352 set style line 104 lt 1 lc rgb "%(col4)s" lw 1.3 dt (6,3) 2353 set style line 14 lt 2 lc rgb "%(col4)s" lw 1.3 dt (6,3) 2354 set style line 24 lt 4 lc rgb "%(col4)s" lw 1.3 dt (3,2) 2355 set style line 34 lt 6 lc rgb "%(col4)s" lw 1.3 dt (2,1) 2356 set style line 44 lt 8 lc rgb "%(col4)s" lw 1.3 dt (4,3) 2357 2358 set style line 5 lt 1 lc rgb "%(col5)s" lw 1.3 2359 set style line 105 lt 1 lc rgb "%(col5)s" lw 1.3 dt (6,3) 2360 set style line 15 lt 2 lc rgb "%(col5)s" lw 1.3 dt (6,3) 2361 set style line 25 lt 4 lc rgb "%(col5)s" lw 1.3 dt (3,2) 2362 set style line 35 lt 6 lc rgb "%(col5)s" lw 1.3 dt (2,1) 2363 set style line 45 lt 8 lc rgb "%(col5)s" lw 1.3 dt (4,3) 2364 2365 set style line 6 lt 1 lc rgb "%(col6)s" lw 1.3 2366 set style line 106 lt 1 lc rgb "%(col6)s" lw 1.3 dt (6,3) 2367 set style line 16 lt 2 lc rgb "%(col6)s" lw 1.3 dt (6,3) 2368 set style line 26 lt 4 lc rgb "%(col6)s" lw 1.3 dt (3,2) 2369 set style line 36 lt 6 lc rgb "%(col6)s" lw 1.3 dt (2,1) 2370 set style line 46 lt 8 lc rgb "%(col6)s" lw 1.3 dt (4,3) 2371 2372 set style line 7 lt 1 lc rgb "%(col7)s" lw 1.3 2373 set style line 107 lt 1 lc rgb "%(col7)s" lw 1.3 dt (6,3) 2374 set style line 17 lt 2 lc rgb "%(col7)s" lw 1.3 dt (6,3) 2375 set style line 27 lt 4 lc rgb "%(col7)s" lw 1.3 dt (3,2) 2376 set style line 37 lt 6 lc rgb "%(col7)s" lw 1.3 dt (2,1) 2377 set style line 47 lt 8 lc rgb "%(col7)s" lw 1.3 dt (4,3) 2378 2379 set style line 8 lt 1 lc rgb "%(col8)s" lw 1.3 2380 set style line 108 lt 1 lc rgb "%(col8)s" lw 1.3 dt (6,3) 2381 set style line 18 lt 2 lc rgb "%(col8)s" lw 1.3 dt (6,3) 2382 set style line 28 lt 4 lc rgb "%(col8)s" lw 1.3 dt (3,2) 2383 set style line 38 lt 6 lc rgb "%(col8)s" lw 1.3 dt (2,1) 2384 set style line 48 lt 8 lc rgb "%(col8)s" lw 1.3 dt (4,3) 2385 2386 2387 set style line 999 lt 1 lc rgb "gray" lw 1.3 2388 2389 safe(x,y,a) = (y == 0.0 ? a : x/y) 2390 2391 set style data histeps 2392 set key invert 2393 2394 """%(replace_dict) 2395 ] 2396 2397 # determine the gnuplot version 2398 try: 2399 p = subprocess.Popen(['gnuplot', '--version'], \ 2400 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 2401 except OSError: 2402 # assume that version 4 of gnuplot is the default if 2403 # gnuplot could not be found 2404 gnuplot_output_list=gnuplot_output_list_v5 2405 else: 2406 output, _ = p.communicate() 2407 output.decode() 2408 try: 2409 version = float(output.split()[1]) 2410 except: 2411 version = 5 2412 if version < 5. : 2413 gnuplot_output_list=gnuplot_output_list_v4 2414 else: 2415 gnuplot_output_list=gnuplot_output_list_v5 2416 2417 2418 # Now output each group one by one 2419 # Block position keeps track of the gnuplot data_block index considered 2420 block_position = 0 2421 for histo_group in self: 2422 # Output this group 2423 block_position = histo_group.output_group(HwU_output_list, 2424 gnuplot_output_list, block_position,output_base_name+'.HwU', 2425 number_of_ratios=number_of_ratios, 2426 uncertainties = uncertainties, 2427 use_band = use_band, 2428 ratio_correlations = ratio_correlations, 2429 jet_samples_to_keep=jet_samples_to_keep, 2430 lhapdfconfig = lhapdfconfig) 2431 2432 # Now write the tail of the gnuplot command file 2433 gnuplot_output_list.extend([ 2434 "unset multiplot", 2435 '!ps2pdf "%s.ps" &> /dev/null'%output_base_name]) 2436 if auto_open: 2437 gnuplot_output_list.append( 2438 '!open "%s.pdf" &> /dev/null'%output_base_name) 2439 2440 # Now write result to stream and close it 2441 gnuplot_stream.write('\n'.join(gnuplot_output_list)) 2442 HwU_stream.write('\n'.join(HwU_output_list)) 2443 gnuplot_stream.close() 2444 HwU_stream.close() 2445 2446 logger.debug("Histograms have been written out at "+\ 2447 "%s.[HwU|gnuplot]' and can "%output_base_name+\ 2448 "now be rendered by invoking gnuplot.")
2449
2450 - def output_group(self, HwU_out, gnuplot_out, block_position, HwU_name, 2451 number_of_ratios = -1, 2452 uncertainties = ['scale','pdf','statitistical','merging_scale','alpsfact'], 2453 use_band = None, 2454 ratio_correlations = True, 2455 jet_samples_to_keep=None, 2456 lhapdfconfig='lhapdf-config'):
2457 2458 """ This functions output a single group of histograms with either one 2459 histograms untyped (i.e. type=None) or two of type 'NLO' and 'LO' 2460 respectively.""" 2461 2462 # This function returns the main central plot line, making sure that 2463 # negative distribution are displayed in dashed style 2464 def get_main_central_plot_lines(HwU_name, block_position, color_index, 2465 title, show_mc_uncertainties): 2466 """ Returns two plot lines, one for the negative contributions in 2467 dashed and one with the positive ones in solid.""" 2468 2469 template = "'%(hwu)s' index %(ind)d using (($1+$2)/2):%(data)s%(stat_col)s%(stat_err)s%(ls)s%(title)s" 2470 template_no_stat = "'%(hwu)s' index %(ind)d using (($1+$2)/2):%(data)s%(ls)s%(title)s" 2471 rep_dic = {'hwu':HwU_name, 2472 'ind':block_position, 2473 'ls':' ls %d'%color_index, 2474 'title':" title '%s'"%title, 2475 'stat_col': ':4', 2476 'stat_err': ' w yerrorbar', 2477 'data':'3', 2478 'linetype':''} 2479 2480 # This would be the original output 2481 # return [template_no_stat%rep_dic]+\ 2482 # ([template%rep_dic] if show_mc_uncertainties else []) 2483 2484 # The use of sqrt(-1) is just a trick to prevent the line to display 2485 res = [] 2486 rep_dic['data'] = '($3 < 0 ? sqrt(-1) : $3)' 2487 res.append(template_no_stat%rep_dic) 2488 rep_dic['title'] = " title ''" 2489 if show_mc_uncertainties: 2490 res.append(template%rep_dic) 2491 rep_dic['data'] = '($3 >= 0 ? sqrt(-1) : abs($3))' 2492 rep_dic['ls'] = ' ls %d'%(100+color_index) 2493 res.append(template_no_stat%rep_dic) 2494 if show_mc_uncertainties: 2495 res.append(template%rep_dic) 2496 return res
2497 2498 # This bool can be modified later to decide whether to use uncertainty 2499 # bands or not 2500 # ======== 2501 def get_uncertainty_lines(HwU_name, block_position, 2502 var_pos, color_index,title, ratio=False, band=False): 2503 """ Return a string line corresponding to the plotting of the 2504 uncertainty. Band is to chose wether to display uncertainty with 2505 a band or two lines.""" 2506 2507 # This perl substitution regular expression copies each line of the 2508 # HwU source and swap the x1 and x2 coordinate of the second copy. 2509 # So if input is: 2510 # 2511 # blabla 2512 # +0.01e+01 0.3 4 5 6 2513 # +0.03e+01 0.5 7 8 9 2514 # ... 2515 # 2516 # The output will be 2517 # 2518 # blabla 2519 # +0.01e+01 0.3 4 5 6 2520 # 0.3 +0.01e+01 4 5 6 2521 # +0.03e+01 0.5 7 8 9 2522 # 0.5 +0.03e+01 7 8 9 2523 # ... 2524 # 2525 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'" 2526 # Gnuplot escapes the antislash, so we must esacape then once more O_o. 2527 # Gnuplot doesn't have raw strings, what a shame... 2528 copy_swap_re = copy_swap_re.replace('\\','\\\\') 2529 # For the ratio, we must divide by the central value 2530 position = '(safe($%d,$3,1.0)-1.0)' if ratio else '%d' 2531 if not band: 2532 return ["'%s' index %d using (($1+$2)/2):%s ls %d title '%s'"\ 2533 %(HwU_name,block_position, position%(var_pos),color_index,title), 2534 "'%s' index %d using (($1+$2)/2):%s ls %d title ''"\ 2535 %(HwU_name,block_position, position%(var_pos+1),color_index)] 2536 else: 2537 return [' "<%s %s" index %d using 1:%s:%s with filledcurve ls %d fs transparent solid 0.2 title \'%s\''%\ 2538 (copy_swap_re,HwU_name,block_position, 2539 position%var_pos,position%(var_pos+1),color_index,title)] 2540 # ======== 2541 2542 2543 layout_geometry = [(0.0, 0.5, 1.0, 0.4 ), 2544 (0.0, 0.35, 1.0, 0.15), 2545 (0.0, 0.2, 1.0, 0.15)] 2546 layout_geometry.reverse() 2547 2548 # Group histograms which just differ by jet multiplicity and add their 2549 # sum as first plot 2550 matching_histo_lists = HwUList([HwUList([self[0]])]) 2551 for histo in self[1:]: 2552 matched = False 2553 for histo_list in matching_histo_lists: 2554 if hasattr(histo, 'jetsample') and histo.jetsample >= 0 and \ 2555 histo.type == histo_list[0].type: 2556 matched = True 2557 histo_list.append(histo) 2558 break 2559 if not matched: 2560 matching_histo_lists.append(HwUList([histo])) 2561 2562 # For each group of histograms with different jet multiplicities, we 2563 # define one at the beginning which is the sum. 2564 self[:] = [] 2565 for histo_group in matching_histo_lists: 2566 # First create a plot that sums all jet multiplicities for each type 2567 # (that is, only if jet multiplicities are defined) 2568 if len(histo_group)==1: 2569 self.append(histo_group[0]) 2570 continue 2571 # If there is already a histogram summing them, then don't create 2572 # a copy of it. 2573 if any(hist.jetsample==-1 for hist in histo_group if 2574 hasattr(hist, 'jetsample')): 2575 self.extend(histo_group) 2576 continue 2577 summed_histogram = copy.copy(histo_group[0]) 2578 for histo in histo_group[1:]: 2579 summed_histogram = summed_histogram + histo 2580 summed_histogram.jetsample = -1 2581 self.append(summed_histogram) 2582 self.extend(histo_group) 2583 2584 # Remove the curve of individual jet samples if they are not desired 2585 if not jet_samples_to_keep is None: 2586 self[:] = [histo for histo in self if (not hasattr(histo,'jetsample')) or (histo.jetsample == -1) or 2587 (histo.jetsample in jet_samples_to_keep)] 2588 2589 # This function is to create the ratio histograms if the user turned off 2590 # correlations. 2591 def ratio_no_correlations(wgtsA, wgtsB): 2592 new_wgts = {} 2593 for label, wgt in wgtsA.items(): 2594 if wgtsB['central']==0.0 and wgt==0.0: 2595 new_wgts[label] = 0.0 2596 continue 2597 elif wgtsB['central']==0.0: 2598 # It is ok to skip the warning here. 2599 # logger.debug('Warning:: A bin with finite weight '+ 2600 # 'was divided by a bin with zero weight.') 2601 new_wgts[label] = 0.0 2602 continue 2603 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2604 return new_wgts 2605 2606 # First compute the ratio of all the histograms from the second to the 2607 # number_of_ratios+1 ones in the list to the first histogram. 2608 n_histograms = len(self) 2609 ratio_histos = HwUList([]) 2610 # A counter to keep track of the number of ratios included 2611 n_ratios_included = 0 2612 for i, histo in enumerate(self[1:]): 2613 if not hasattr(histo,'jetsample') or histo.jetsample==self[0].jetsample: 2614 n_ratios_included += 1 2615 else: 2616 continue 2617 2618 if number_of_ratios >=0 and n_ratios_included > number_of_ratios: 2619 break 2620 2621 if ratio_correlations: 2622 ratio_histos.append(histo/self[0]) 2623 else: 2624 ratio_histos.append(self[0].__class__.combine(histo, self[0], 2625 ratio_no_correlations)) 2626 if self[0].type=='NLO' and self[1].type=='LO': 2627 ratio_histos[-1].title += '1/K-factor' 2628 elif self[0].type=='LO' and self[1].type=='NLO': 2629 ratio_histos[-1].title += 'K-factor' 2630 else: 2631 ratio_histos[-1].title += ' %s/%s'%( 2632 self[1].type if self[1].type else '(%d)'%(i+2), 2633 self[0].type if self[0].type else '(1)') 2634 # By setting its type to aux, we make sure this histogram will be 2635 # filtered out if the .HwU file output here would be re-loaded later. 2636 ratio_histos[-1].type = 'AUX' 2637 self.extend(ratio_histos) 2638 2639 # Compute scale variation envelope for all diagrams 2640 if 'scale' in uncertainties: 2641 (mu_var_pos,mu) = self[0].set_uncertainty(type='all_scale') 2642 else: 2643 (mu_var_pos,mu) = (None,[None]) 2644 2645 if 'pdf' in uncertainties: 2646 (PDF_var_pos,pdf) = self[0].set_uncertainty(type='PDF',lhapdfconfig=lhapdfconfig) 2647 else: 2648 (PDF_var_pos,pdf) = (None,[None]) 2649 2650 if 'merging_scale' in uncertainties: 2651 (merging_var_pos,merging) = self[0].set_uncertainty(type='merging') 2652 else: 2653 (merging_var_pos,merging) = (None,[None]) 2654 if 'alpsfact' in uncertainties: 2655 (alpsfact_var_pos,alpsfact) = self[0].set_uncertainty(type='alpsfact') 2656 else: 2657 (alpsfact_var_pos,alpsfact) = (None,[None]) 2658 2659 uncertainties_present = list(uncertainties) 2660 if PDF_var_pos is None and 'pdf' in uncertainties_present: 2661 uncertainties_present.remove('pdf') 2662 if mu_var_pos is None and 'scale' in uncertainties_present: 2663 uncertainties_present.remove('scale') 2664 if merging_var_pos is None and 'merging' in uncertainties_present: 2665 uncertainties_present.remove('merging') 2666 if alpsfact_var_pos is None and 'alpsfact' in uncertainties_present: 2667 uncertainties_present.remove('alpsfact') 2668 no_uncertainties = len(uncertainties_present)==0 2669 2670 # If the 'use_band' option is None we should adopt a default which is 2671 try: 2672 uncertainties_present.remove('statistical') 2673 except: 2674 pass 2675 if use_band is None: 2676 # For clarity, it is better to only use bands only for one source 2677 # of uncertainty 2678 if len(uncertainties_present)==0: 2679 use_band = [] 2680 elif len(uncertainties_present)==1: 2681 use_band = uncertainties_present 2682 elif 'scale' in uncertainties_present: 2683 use_band = ['scale'] 2684 else: 2685 use_band = [uncertainties_present[0]] 2686 2687 for histo in self[1:]: 2688 if (not mu_var_pos is None) and \ 2689 mu_var_pos != histo.set_uncertainty(type='all_scale')[0]: 2690 raise MadGraph5Error('Not all histograms in this group specify'+\ 2691 ' scale uncertainties. It is required to be able to output them'+\ 2692 ' together.') 2693 if (not PDF_var_pos is None) and\ 2694 PDF_var_pos != histo.set_uncertainty(type='PDF',\ 2695 lhapdfconfig=lhapdfconfig)[0]: 2696 raise MadGraph5Error('Not all histograms in this group specify'+\ 2697 ' PDF uncertainties. It is required to be able to output them'+\ 2698 ' together.') 2699 if (not merging_var_pos is None) and\ 2700 merging_var_pos != histo.set_uncertainty(type='merging')[0]: 2701 raise MadGraph5Error('Not all histograms in this group specify'+\ 2702 ' merging uncertainties. It is required to be able to output them'+\ 2703 ' together.') 2704 if (not alpsfact_var_pos is None) and\ 2705 alpsfact_var_pos != histo.set_uncertainty(type='alpsfact')[0]: 2706 raise MadGraph5Error('Not all histograms in this group specify'+\ 2707 ' alpsfact uncertainties. It is required to be able to output them'+\ 2708 ' together.') 2709 2710 2711 # Now output the corresponding HwU histogram data 2712 for i, histo in enumerate(self): 2713 # Print the header the first time only 2714 HwU_out.extend(histo.get_HwU_source(\ 2715 print_header=(block_position==0 and i==0))) 2716 HwU_out.extend(['','']) 2717 2718 # First the global gnuplot header for this histogram group 2719 global_header =\ 2720 """ 2721 ################################################################################ 2722 ### Rendering of the plot titled '%(title)s' 2723 ################################################################################ 2724 2725 set multiplot 2726 set label "%(title)s" font ",13" at graph 0.04, graph 1.05 2727 set xrange [%(xmin).4e:%(xmax).4e] 2728 set bmargin 0 2729 set tmargin 0 2730 set xtics nomirror 2731 set ytics nomirror 2732 set mytics %(mxtics)d 2733 %(set_xtics)s 2734 set key horizontal noreverse maxcols 1 width -4 2735 set label front 'MadGraph5\_aMC\@NLO' font "Courier,11" rotate by 90 at graph 1.02, graph 0.04 2736 """ 2737 2738 # Now the header for each subhistogram 2739 subhistogram_header = \ 2740 """#-- rendering subhistograms '%(subhistogram_type)s' 2741 %(unset label)s 2742 %(set_format_y)s 2743 set yrange [%(ymin).4e:%(ymax).4e] 2744 set origin %(origin_x).4e, %(origin_y).4e 2745 set size %(size_x).4e, %(size_y).4e 2746 set mytics %(mytics)d 2747 %(set_ytics)s 2748 %(set_format_x)s 2749 %(set_yscale)s 2750 %(set_ylabel)s 2751 %(set_histo_label)s 2752 plot \\""" 2753 replacement_dic = {} 2754 2755 replacement_dic['title'] = self[0].get_HwU_histogram_name(format='human-no_type') 2756 # Determine what weight to consider when computing the optimal 2757 # range for the y-axis. 2758 wgts_to_consider = ['central'] 2759 if not mu_var_pos is None: 2760 for mu_var in mu_var_pos: 2761 wgts_to_consider.append(self[0].bins.weight_labels[mu_var]) 2762 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+1]) 2763 wgts_to_consider.append(self[0].bins.weight_labels[mu_var+2]) 2764 if not PDF_var_pos is None: 2765 for PDF_var in PDF_var_pos: 2766 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var]) 2767 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+1]) 2768 wgts_to_consider.append(self[0].bins.weight_labels[PDF_var+2]) 2769 if not merging_var_pos is None: 2770 for merging_var in merging_var_pos: 2771 wgts_to_consider.append(self[0].bins.weight_labels[merging_var]) 2772 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+1]) 2773 wgts_to_consider.append(self[0].bins.weight_labels[merging_var+2]) 2774 if not alpsfact_var_pos is None: 2775 for alpsfact_var in alpsfact_var_pos: 2776 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var]) 2777 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+1]) 2778 wgts_to_consider.append(self[0].bins.weight_labels[alpsfact_var+2]) 2779 2780 (xmin, xmax) = HwU.get_x_optimal_range(self[:2],\ 2781 weight_labels = wgts_to_consider) 2782 replacement_dic['xmin'] = xmin 2783 replacement_dic['xmax'] = xmax 2784 replacement_dic['mxtics'] = 10 2785 replacement_dic['set_xtics'] = 'set xtics auto' 2786 2787 # Add the global header which is now ready 2788 gnuplot_out.append(global_header%replacement_dic) 2789 2790 # Now add the main plot 2791 replacement_dic['subhistogram_type'] = '%s and %s results'%( 2792 str(self[0].type),str(self[1].type)) if len(self)>1 else \ 2793 'single diagram output' 2794 (ymin, ymax) = HwU.get_y_optimal_range(self[:2], 2795 labels = wgts_to_consider, scale=self[0].y_axis_mode) 2796 2797 # Force a linear scale if the detected range is negative 2798 if ymin< 0.0: 2799 self[0].y_axis_mode = 'LIN' 2800 2801 # Already add a margin on upper bound. 2802 if self[0].y_axis_mode=='LOG': 2803 ymax += 10.0 * ymax 2804 ymin -= 0.1 * ymin 2805 else: 2806 ymax += 0.3 * (ymax - ymin) 2807 ymin -= 0.3 * (ymax - ymin) 2808 2809 replacement_dic['ymin'] = ymin 2810 replacement_dic['ymax'] = ymax 2811 replacement_dic['unset label'] = '' 2812 (replacement_dic['origin_x'], replacement_dic['origin_y'], 2813 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 2814 replacement_dic['mytics'] = 10 2815 # Use default choise for the main histogram 2816 replacement_dic['set_ytics'] = 'set ytics auto' 2817 replacement_dic['set_format_x'] = "set format x ''" if \ 2818 (len(self)-n_histograms>0 or not no_uncertainties) else "set format x" 2819 replacement_dic['set_ylabel'] = 'set ylabel "{/Symbol s} per bin [pb]"' 2820 replacement_dic['set_yscale'] = "set logscale y" if \ 2821 self[0].y_axis_mode=='LOG' else 'unset logscale y' 2822 replacement_dic['set_format_y'] = "set format y '10^{%T}'" if \ 2823 self[0].y_axis_mode=='LOG' else 'unset format' 2824 2825 replacement_dic['set_histo_label'] = "" 2826 gnuplot_out.append(subhistogram_header%replacement_dic) 2827 2828 # Now add the main layout 2829 plot_lines = [] 2830 uncertainty_plot_lines = [] 2831 n=-1 2832 2833 for i, histo in enumerate(self[:n_histograms]): 2834 n=n+1 2835 color_index = n%self.number_line_colors_defined+1 2836 # Label to appear for the lower curves 2837 title = [] 2838 if histo.type is None and not hasattr(histo, 'jetsample'): 2839 title.append('%d'%(i+1)) 2840 else: 2841 if histo.type: 2842 title.append('NLO' if \ 2843 histo.type.split()[0]=='NLO' else histo.type) 2844 if hasattr(histo, 'jetsample'): 2845 if histo.jetsample!=-1: 2846 title.append('jet sample %d'%histo.jetsample) 2847 else: 2848 title.append('all jet samples') 2849 2850 title = ', '.join(title) 2851 # Label for the first curve in the upper plot 2852 if histo.type is None and not hasattr(histo, 'jetsample'): 2853 major_title = 'central value for plot (%d)'%(i+1) 2854 else: 2855 major_title = [] 2856 if not histo.type is None: 2857 major_title.append(histo.type) 2858 if hasattr(histo, 'jetsample'): 2859 if histo.jetsample!=-1: 2860 major_title.append('jet sample %d'%histo.jetsample) 2861 else: 2862 major_title.append('all jet samples') 2863 else: 2864 major_title.append('central value') 2865 major_title = ', '.join(major_title) 2866 2867 if not mu[0] in ['none',None]: 2868 major_title += ', dynamical\_scale\_choice=%s'%mu[0] 2869 if not pdf[0] in ['none',None]: 2870 major_title += ', PDF=%s'%pdf[0].replace('_','\_') 2871 2872 # Do not show uncertainties for individual jet samples (unless first 2873 # or specified explicitely and uniquely) 2874 if not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 2875 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 2876 jet_samples_to_keep[0] == histo.jetsample)): 2877 2878 uncertainty_plot_lines.append({}) 2879 2880 # We decide to show uncertainties in the main plot only if they 2881 # are part of a monocolor band. Otherwise, they will only be 2882 # shown in the first subplot. Notice that plotting 'sqrt(-1)' 2883 # is just a trick so as to have only the key printed with no 2884 # line 2885 2886 # Show scale variation for the first central value if available 2887 if not mu_var_pos is None and len(mu_var_pos)>0: 2888 if 'scale' in use_band: 2889 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 2890 HwU_name, block_position+i, mu_var_pos[0]+4, color_index+10, 2891 '%s, scale variation'%title, band='scale' in use_band) 2892 else: 2893 uncertainty_plot_lines[-1]['scale'] = \ 2894 ["sqrt(-1) ls %d title '%s'"%(color_index+10,'%s, scale variation'%title)] 2895 # And now PDF_variation if available 2896 if not PDF_var_pos is None and len(PDF_var_pos)>0: 2897 if 'pdf' in use_band: 2898 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 2899 HwU_name,block_position+i, PDF_var_pos[0]+4, color_index+20, 2900 '%s, PDF variation'%title, band='pdf' in use_band) 2901 else: 2902 uncertainty_plot_lines[-1]['pdf'] = \ 2903 ["sqrt(-1) ls %d title '%s'"%(color_index+20,'%s, PDF variation'%title)] 2904 # And now merging variation if available 2905 if not merging_var_pos is None and len(merging_var_pos)>0: 2906 if 'merging_scale' in use_band: 2907 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 2908 HwU_name,block_position+i, merging_var_pos[0]+4, color_index+30, 2909 '%s, merging scale variation'%title, band='merging_scale' in use_band) 2910 else: 2911 uncertainty_plot_lines[-1]['merging_scale'] = \ 2912 ["sqrt(-1) ls %d title '%s'"%(color_index+30,'%s, merging scale variation'%title)] 2913 # And now alpsfact variation if available 2914 if not alpsfact_var_pos is None and len(alpsfact_var_pos)>0: 2915 if 'alpsfact' in use_band: 2916 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 2917 HwU_name,block_position+i, alpsfact_var_pos[0]+4, color_index+40, 2918 '%s, alpsfact variation'%title, band='alpsfact' in use_band) 2919 else: 2920 uncertainty_plot_lines[-1]['alpsfact'] = \ 2921 ["sqrt(-1) ls %d title '%s'"%(color_index+40,'%s, alpsfact variation'%title)] 2922 2923 # plot_lines.append( 2924 # "'%s' index %d using (($1+$2)/2):3 ls %d title '%s'"\ 2925 # %(HwU_name,block_position+i,color_index, major_title)) 2926 # if 'statistical' in uncertainties: 2927 # plot_lines.append( 2928 # "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"\ 2929 # %(HwU_name,block_position+i,color_index)) 2930 plot_lines.extend( 2931 get_main_central_plot_lines(HwU_name, block_position+i, 2932 color_index, major_title, 'statistical' in uncertainties)) 2933 2934 # Add additional central scale/PDF curves 2935 if not mu_var_pos is None: 2936 for j,mu_var in enumerate(mu_var_pos): 2937 if j!=0: 2938 n=n+1 2939 color_index = n%self.number_line_colors_defined+1 2940 plot_lines.append( 2941 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2942 %(HwU_name,block_position+i,mu_var+3,color_index,\ 2943 '%s dynamical\_scale\_choice=%s' % (title,mu[j]))) 2944 # And now PDF_variation if available 2945 if not PDF_var_pos is None: 2946 for j,PDF_var in enumerate(PDF_var_pos): 2947 if j!=0: 2948 n=n+1 2949 color_index = n%self.number_line_colors_defined+1 2950 plot_lines.append( 2951 "'%s' index %d using (($1+$2)/2):%d ls %d title '%s'"\ 2952 %(HwU_name,block_position+i,PDF_var+3,color_index,\ 2953 '%s PDF=%s' % (title,pdf[j].replace('_','\_')))) 2954 2955 # Now add the uncertainty lines, those not using a band so that they 2956 # are not covered by those using a band after we reverse plo_lines 2957 for one_plot in uncertainty_plot_lines: 2958 for uncertainty_type, lines in one_plot.items(): 2959 if not uncertainty_type in use_band: 2960 plot_lines.extend(lines) 2961 # then those using a band 2962 for one_plot in uncertainty_plot_lines: 2963 for uncertainty_type, lines in one_plot.items(): 2964 if uncertainty_type in use_band: 2965 plot_lines.extend(lines) 2966 2967 # Reverse so that bands appear first 2968 plot_lines.reverse() 2969 2970 # Add the plot lines 2971 gnuplot_out.append(',\\\n'.join(plot_lines)) 2972 2973 # Now we can add the scale variation ratio 2974 replacement_dic['subhistogram_type'] = 'Relative scale and PDF uncertainty' 2975 2976 if 'statistical' in uncertainties: 2977 wgts_to_consider.append('stat_error') 2978 2979 # This function is just to temporarily create the scale ratio histogram with 2980 # the hwu.combine function. 2981 def rel_scale(wgtsA, wgtsB): 2982 new_wgts = {} 2983 for label, wgt in wgtsA.items(): 2984 if label in wgts_to_consider: 2985 if wgtsB['central']==0.0 and wgt==0.0: 2986 new_wgts[label] = 0.0 2987 continue 2988 elif wgtsB['central']==0.0: 2989 # It is ok to skip the warning here. 2990 # logger.debug('Warning:: A bin with finite weight '+ 2991 # 'was divided by a bin with zero weight.') 2992 new_wgts[label] = 0.0 2993 continue 2994 new_wgts[label] = (wgtsA[label]/wgtsB['central']) 2995 if label != 'stat_error': 2996 new_wgts[label] -= 1.0 2997 else: 2998 new_wgts[label] = wgtsA[label] 2999 return new_wgts 3000 3001 histos_for_subplots = [(i,histo) for i, histo in enumerate(self[:n_histograms]) if 3002 ( not (i!=0 and hasattr(histo,'jetsample') and histo.jetsample!=-1 and \ 3003 not (jet_samples_to_keep and len(jet_samples_to_keep)==1 and 3004 jet_samples_to_keep[0] == histo.jetsample)) )] 3005 3006 # Notice even though a ratio histogram is created here, it 3007 # is not actually used to plot the quantity in gnuplot, but just to 3008 # compute the y range. 3009 (ymin, ymax) = HwU.get_y_optimal_range([histo[1].__class__.combine( 3010 histo[1],histo[1],rel_scale) for histo in histos_for_subplots], 3011 labels = wgts_to_consider, scale='LIN') 3012 3013 # Add a margin on upper and lower bound. 3014 ymax = ymax + 0.2 * (ymax - ymin) 3015 ymin = ymin - 0.2 * (ymax - ymin) 3016 replacement_dic['unset label'] = 'unset label' 3017 replacement_dic['ymin'] = ymin 3018 replacement_dic['ymax'] = ymax 3019 if not no_uncertainties: 3020 (replacement_dic['origin_x'], replacement_dic['origin_y'], 3021 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 3022 replacement_dic['mytics'] = 2 3023 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/3.0) 3024 replacement_dic['set_ytics'] = 'set ytics auto' 3025 replacement_dic['set_format_x'] = "set format x ''" if \ 3026 len(self)-n_histograms>0 else "set format x" 3027 replacement_dic['set_ylabel'] = 'set ylabel "%s rel.unc."'\ 3028 %('(1)' if self[0].type==None else '%s'%('NLO' if \ 3029 self[0].type.split()[0]=='NLO' else self[0].type)) 3030 replacement_dic['set_yscale'] = "unset logscale y" 3031 replacement_dic['set_format_y'] = 'unset format' 3032 3033 3034 tit='Relative uncertainties w.r.t. central value' 3035 if n_histograms > 1: 3036 tit=tit+'s' 3037 # if (not mu_var_pos is None and 'scale' not in use_band): 3038 # tit=tit+', scale is dashed' 3039 # if (not PDF_var_pos is None and 'pdf' not in use_band): 3040 # tit=tit+', PDF is dotted' 3041 replacement_dic['set_histo_label'] = \ 3042 'set label "%s" font ",9" front at graph 0.03, graph 0.13' % tit 3043 # Simply don't add these lines if there are no uncertainties. 3044 # This meant uncessary extra work, but I no longer car at this point 3045 if not no_uncertainties: 3046 gnuplot_out.append(subhistogram_header%replacement_dic) 3047 3048 # Now add the first subhistogram 3049 plot_lines = [] 3050 uncertainty_plot_lines = [] 3051 n=-1 3052 for (i,histo) in histos_for_subplots: 3053 n=n+1 3054 k=n 3055 color_index = n%self.number_line_colors_defined+1 3056 # Plot uncertainties 3057 if not mu_var_pos is None: 3058 for j,mu_var in enumerate(mu_var_pos): 3059 uncertainty_plot_lines.append({}) 3060 if j==0: 3061 color_index = k%self.number_line_colors_defined+1 3062 else: 3063 n=n+1 3064 color_index = n%self.number_line_colors_defined+1 3065 # Add the central line only if advanced scale variation 3066 if j>0 or mu[j]!='none': 3067 plot_lines.append( 3068 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 3069 %(HwU_name,block_position+i,mu_var+3,color_index)) 3070 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 3071 HwU_name, block_position+i, mu_var+4, color_index+10,'', 3072 ratio=True, band='scale' in use_band) 3073 if not PDF_var_pos is None: 3074 for j,PDF_var in enumerate(PDF_var_pos): 3075 uncertainty_plot_lines.append({}) 3076 if j==0: 3077 color_index = k%self.number_line_colors_defined+1 3078 else: 3079 n=n+1 3080 color_index = n%self.number_line_colors_defined+1 3081 # Add the central line only if advanced pdf variation 3082 if j>0 or pdf[j]!='none': 3083 plot_lines.append( 3084 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 3085 %(HwU_name,block_position+i,PDF_var+3,color_index)) 3086 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 3087 HwU_name, block_position+i, PDF_var+4, color_index+20,'', 3088 ratio=True, band='pdf' in use_band) 3089 if not merging_var_pos is None: 3090 for j,merging_var in enumerate(merging_var_pos): 3091 uncertainty_plot_lines.append({}) 3092 if j==0: 3093 color_index = k%self.number_line_colors_defined+1 3094 else: 3095 n=n+1 3096 color_index = n%self.number_line_colors_defined+1 3097 if j>0 or merging[j]!='none': 3098 plot_lines.append( 3099 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 3100 %(HwU_name,block_position+i,merging_var+3,color_index)) 3101 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 3102 HwU_name, block_position+i, merging_var+4, color_index+30,'', 3103 ratio=True, band='merging_scale' in use_band) 3104 if not alpsfact_var_pos is None: 3105 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3106 uncertainty_plot_lines.append({}) 3107 if j==0: 3108 color_index = k%self.number_line_colors_defined+1 3109 else: 3110 n=n+1 3111 color_index = n%self.number_line_colors_defined+1 3112 if j>0 or alpsfact[j]!='none': 3113 plot_lines.append( 3114 "'%s' index %d using (($1+$2)/2):(safe($%d,$3,1.0)-1.0) ls %d title ''"\ 3115 %(HwU_name,block_position+i,alpsfact_var+3,color_index)) 3116 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 3117 HwU_name, block_position+i, alpsfact_var+4, color_index+40,'', 3118 ratio=True, band='alpsfact' in use_band) 3119 3120 if 'statistical' in uncertainties: 3121 plot_lines.append( 3122 "'%s' index %d using (($1+$2)/2):(0.0):(safe($4,$3,0.0)) w yerrorbar ls %d title ''"%\ 3123 (HwU_name,block_position+i,color_index)) 3124 3125 plot_lines.append("0.0 ls 999 title ''") 3126 3127 # Now add the uncertainty lines, those not using a band so that they 3128 # are not covered by those using a band after we reverse plo_lines 3129 for one_plot in uncertainty_plot_lines: 3130 for uncertainty_type, lines in one_plot.items(): 3131 if not uncertainty_type in use_band: 3132 plot_lines.extend(lines) 3133 # then those using a band 3134 for one_plot in uncertainty_plot_lines: 3135 for uncertainty_type, lines in one_plot.items(): 3136 if uncertainty_type in use_band: 3137 plot_lines.extend(lines) 3138 3139 # Reverse so that bands appear first 3140 plot_lines.reverse() 3141 # Add the plot lines 3142 if not no_uncertainties: 3143 gnuplot_out.append(',\\\n'.join(plot_lines)) 3144 3145 # We finish here when no ratio plot are asked for. 3146 if len(self)-n_histograms==0: 3147 # Now add the tail for this group 3148 gnuplot_out.extend(['','unset label','', 3149 '################################################################################']) 3150 # Return the starting data_block position for the next histogram group 3151 return block_position+len(self) 3152 3153 # We can finally add the last subhistograms for the ratios. 3154 ratio_name_long='(' 3155 for i, histo in enumerate(self[:n_histograms]): 3156 if i==0: continue 3157 ratio_name_long+='%d'%(i+1) if histo.type is None else ('NLO' if \ 3158 histo.type.split()[0]=='NLO' else histo.type) 3159 ratio_name_long+=')/' 3160 ratio_name_long+=('(1' if self[0].type==None else '(%s'%('NLO' if \ 3161 self[0].type.split()[0]=='NLO' else self[0].type))+' central value)' 3162 3163 ratio_name_short = 'ratio w.r.t. '+('1' if self[0].type==None else '%s'%('NLO' if \ 3164 self[0].type.split()[0]=='NLO' else self[0].type)) 3165 3166 replacement_dic['subhistogram_type'] = '%s ratio'%ratio_name_long 3167 replacement_dic['set_ylabel'] = 'set ylabel "%s"'%ratio_name_short 3168 3169 (ymin, ymax) = HwU.get_y_optimal_range(self[n_histograms:], 3170 labels = wgts_to_consider, scale='LIN',Kratio = True) 3171 3172 # Add a margin on upper and lower bound. 3173 ymax = ymax + 0.2 * (ymax - ymin) 3174 ymin = ymin - 0.2 * (ymax - ymin) 3175 replacement_dic['unset label'] = 'unset label' 3176 replacement_dic['ymin'] = ymin 3177 replacement_dic['ymax'] = ymax 3178 (replacement_dic['origin_x'], replacement_dic['origin_y'], 3179 replacement_dic['size_x'], replacement_dic['size_y']) = layout_geometry.pop() 3180 replacement_dic['mytics'] = 2 3181 # replacement_dic['set_ytics'] = 'set ytics %f'%((int(10*(ymax-ymin))/10)/10.0) 3182 replacement_dic['set_ytics'] = 'set ytics auto' 3183 replacement_dic['set_format_x'] = "set format x" 3184 replacement_dic['set_yscale'] = "unset logscale y" 3185 replacement_dic['set_format_y'] = 'unset format' 3186 replacement_dic['set_histo_label'] = \ 3187 'set label "%s" font ",9" at graph 0.03, graph 0.13'%ratio_name_long 3188 # 'set label "NLO/LO (K-factor)" font ",9" at graph 0.82, graph 0.13' 3189 gnuplot_out.append(subhistogram_header%replacement_dic) 3190 3191 uncertainty_plot_lines = [] 3192 plot_lines = [] 3193 3194 # Some crap to get the colors right I suppose... 3195 n=-1 3196 n=n+1 3197 if not mu_var_pos is None: 3198 for j,mu_var in enumerate(mu_var_pos): 3199 if j!=0: n=n+1 3200 if not PDF_var_pos is None: 3201 for j,PDF_var in enumerate(PDF_var_pos): 3202 if j!=0: n=n+1 3203 if not merging_var_pos is None: 3204 for j,merging_var in enumerate(merging_var_pos): 3205 if j!=0: n=n+1 3206 if not alpsfact_var_pos is None: 3207 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3208 if j!=0: n=n+1 3209 3210 for i_histo_ratio, histo_ration in enumerate(self[n_histograms:]): 3211 n=n+1 3212 k=n 3213 block_ratio_pos = block_position+n_histograms+i_histo_ratio 3214 color_index = n%self.number_line_colors_defined+1 3215 # Now add the subhistograms 3216 plot_lines.append( 3217 "'%s' index %d using (($1+$2)/2):3 ls %d title ''"%\ 3218 (HwU_name,block_ratio_pos,color_index)) 3219 if 'statistical' in uncertainties: 3220 plot_lines.append( 3221 "'%s' index %d using (($1+$2)/2):3:4 w yerrorbar ls %d title ''"%\ 3222 (HwU_name,block_ratio_pos,color_index)) 3223 3224 # Then the scale variations 3225 if not mu_var_pos is None: 3226 for j,mu_var in enumerate(mu_var_pos): 3227 uncertainty_plot_lines.append({}) 3228 if j==0: 3229 color_index = k%self.number_line_colors_defined+1 3230 else: 3231 n=n+1 3232 color_index = n%self.number_line_colors_defined+1 3233 # Only print out the additional central value for advanced scale variation 3234 if j>0 or mu[j]!='none': 3235 plot_lines.append( 3236 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3237 %(HwU_name,block_ratio_pos,mu_var+3,color_index)) 3238 uncertainty_plot_lines[-1]['scale'] = get_uncertainty_lines( 3239 HwU_name, block_ratio_pos, mu_var+4, color_index+10,'', 3240 band='scale' in use_band) 3241 if not PDF_var_pos is None: 3242 for j,PDF_var in enumerate(PDF_var_pos): 3243 uncertainty_plot_lines.append({}) 3244 if j==0: 3245 color_index = k%self.number_line_colors_defined+1 3246 else: 3247 n=n+1 3248 color_index = n%self.number_line_colors_defined+1 3249 # Only print out the additional central value for advanced pdf variation 3250 if j>0 or pdf[j]!='none': 3251 plot_lines.append( 3252 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3253 %(HwU_name,block_ratio_pos,PDF_var+3,color_index)) 3254 uncertainty_plot_lines[-1]['pdf'] = get_uncertainty_lines( 3255 HwU_name, block_ratio_pos, PDF_var+4, color_index+20,'', 3256 band='pdf' in use_band) 3257 if not merging_var_pos is None: 3258 for j,merging_var in enumerate(merging_var_pos): 3259 uncertainty_plot_lines.append({}) 3260 if j==0: 3261 color_index = k%self.number_line_colors_defined+1 3262 else: 3263 n=n+1 3264 color_index = n%self.number_line_colors_defined+1 3265 if j>0 or merging[j]!='none': 3266 plot_lines.append( 3267 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3268 %(HwU_name,block_ratio_pos,merging_var+3,color_index)) 3269 uncertainty_plot_lines[-1]['merging_scale'] = get_uncertainty_lines( 3270 HwU_name, block_ratio_pos, merging_var+4, color_index+30,'', 3271 band='merging_scale' in use_band) 3272 if not alpsfact_var_pos is None: 3273 for j,alpsfact_var in enumerate(alpsfact_var_pos): 3274 uncertainty_plot_lines.append({}) 3275 if j==0: 3276 color_index = k%self.number_line_colors_defined+1 3277 else: 3278 n=n+1 3279 color_index = n%self.number_line_colors_defined+1 3280 if j>0 or alpsfact[j]!='none': 3281 plot_lines.append( 3282 "'%s' index %d using (($1+$2)/2):%d ls %d title ''"\ 3283 %(HwU_name,block_ratio_pos,alpsfact_var+3,color_index)) 3284 uncertainty_plot_lines[-1]['alpsfact'] = get_uncertainty_lines( 3285 HwU_name, block_ratio_pos, alpsfact_var+4, color_index+40,'', 3286 band='alpsfact' in use_band) 3287 3288 # Now add the uncertainty lines, those not using a band so that they 3289 # are not covered by those using a band after we reverse plo_lines 3290 for one_plot in uncertainty_plot_lines: 3291 for uncertainty_type, lines in one_plot.items(): 3292 if not uncertainty_type in use_band: 3293 plot_lines.extend(lines) 3294 # then those using a band 3295 for one_plot in uncertainty_plot_lines: 3296 for uncertainty_type, lines in one_plot.items(): 3297 if uncertainty_type in use_band: 3298 plot_lines.extend(lines) 3299 3300 plot_lines.append("1.0 ls 999 title ''") 3301 3302 # Reverse so that bands appear first 3303 plot_lines.reverse() 3304 # Add the plot lines 3305 gnuplot_out.append(',\\\n'.join(plot_lines)) 3306 3307 # Now add the tail for this group 3308 gnuplot_out.extend(['','unset label','', 3309 '################################################################################']) 3310 3311 # Return the starting data_block position for the next histogram group 3312 return block_position+len(self) 3313
3314 ################################################################################ 3315 ## matplotlib related function 3316 ################################################################################ 3317 -def plot_ratio_from_HWU(path, ax, hwu_variable, hwu_numerator, hwu_denominator, *args, **opts):
3318 """INPUT: 3319 - path can be a path to HwU or an HwUList instance 3320 - ax is the matplotlib frame where to do the plot 3321 - hwu_variable is the histograms to consider 3322 - hwu_numerator is the numerator of the ratio plot 3323 - hwu_denominator is the denominator of the ratio plot 3324 OUTPUT: 3325 - adding the curves to the plot 3326 - return the HwUList 3327 """ 3328 3329 if isinstance(path, str): 3330 hwu = HwUList(path, raw_labels=True) 3331 else: 3332 hwu = path 3333 3334 if 'hwu_denominator_path' in opts: 3335 print('found second hwu') 3336 if isinstance(opts['hwu_denominator_path'],str): 3337 hwu2 = HwUList(path, raw_labels=True) 3338 else: 3339 hwu2 = opts['hwu_denominator_path'] 3340 del opts['hwu_denominator_path'] 3341 else: 3342 hwu2 = hwu 3343 3344 3345 select_hist = hwu.get(hwu_variable) 3346 select_hist2 = hwu2.get(hwu_variable) 3347 bins = select_hist.get('bins') 3348 num = select_hist.get(hwu_numerator) 3349 denom = select_hist2.get(hwu_denominator) 3350 ratio = [num[i]/denom[i] if denom[i] else 1 for i in range(len(bins))] 3351 if 'drawstyle' not in opts: 3352 opts['drawstyle'] = 'steps' 3353 ax.plot(bins, ratio, *args, **opts) 3354 return hwu
3355
3356 -def plot_from_HWU(path, ax, hwu_variable, hwu_central, *args, **opts):
3357 """INPUT: 3358 - path can be a path to HwU or an HwUList instance 3359 - ax is the matplotlib frame where to do the plot 3360 - hwu_variable is the histograms to consider 3361 - hwu_central is the central curve to consider 3362 - hwu_error is the error band to consider (optional: Default is no band) 3363 - hwu_error_mode is how to compute the error band (optional) 3364 OUTPUT: 3365 - adding the curves to the plot 3366 - return the HwUList 3367 - return the line associated to the central (can be used to get the color) 3368 """ 3369 3370 # Handle optional parameter 3371 if 'hwu_error' in opts: 3372 hwu_error = opts['hwu_error'] 3373 del opts['hwu_error'] 3374 else: 3375 hwu_error = None 3376 3377 if 'hwu_error_mode' in opts: 3378 hwu_error_mode = opts['hwu_error_mode'] 3379 del opts['hwu_error_mode'] 3380 else: 3381 hwu_error_mode = None 3382 3383 if 'hwu_mult' in opts: 3384 hwu_mult = opts['hwu_mult'] 3385 del opts['hwu_mult'] 3386 else: 3387 hwu_mult = 1 3388 3389 if isinstance(path, str): 3390 hwu = HwUList(path, raw_labels=True) 3391 else: 3392 hwu = path 3393 3394 3395 select_hist = hwu.get(hwu_variable) 3396 bins = select_hist.get('bins') 3397 central_value = select_hist.get(hwu_central) 3398 if hwu_mult != 1: 3399 central_value = [hwu_mult*b for b in central_value] 3400 if 'drawstyle' not in opts: 3401 opts['drawstyle'] = 'steps' 3402 H, = ax.plot(bins, central_value, *args, **opts) 3403 3404 # Add error band 3405 if hwu_error: 3406 if not 'hwu_error_mode' in opts: 3407 opts['hwu_error_mode']=None 3408 h_min, h_max = select_hist.get_uncertainty_band(hwu_error, mode=hwu_error_mode) 3409 if hwu_mult != 1: 3410 h_min = [hwu_mult*b for b in h_min] 3411 h_max = [hwu_mult*b for b in h_max] 3412 fill_between_steps(bins, h_min, h_max, ax=ax, facecolor=H.get_color(), 3413 alpha=0.5, edgecolor=H.get_color(),hatch='/') 3414 3415 return hwu, H
3416 3417 3418 3419 3420 3421 3422 if __name__ == "__main__": 3423 main_doc = \ 3424 """ For testing and standalone use. Usage: 3425 python histograms.py <.HwU input_file_path_1> <.HwU input_file_path_2> ... --out=<output_file_path.format> <options> 3426 Where <options> can be a list of the following: 3427 '--help' See this message. 3428 '--gnuplot' or '' output the histograms read to gnuplot 3429 '--HwU' to output the histograms read to the raw HwU source. 3430 '--types=<type1>,<type2>,...' to keep only the type<i> when importing histograms. 3431 '--titles=<title1>,<title2>,...' to keep only the titles which have any of 'title<i>' in them (not necessarily equal to them) 3432 '--n_ratios=<integer>' Specifies how many curves must be considerd for the ratios. 3433 '--no_open' Turn off the automatic processing of the gnuplot output. 3434 '--show_full' to show the complete output of what was read. 3435 '--show_short' to show a summary of what was read. 3436 '--simple_ratios' to turn off correlations and error propagation in the ratio. 3437 '--colours=<colour1>,<colour2>,...' to assign a non-default colour to GnuPlot histograms (max 8 colours) 3438 '--sum' To sum all identical histograms together 3439 '--average' To average over all identical histograms 3440 '--rebin=<n>' Rebin the plots by merging n-consecutive bins together. 3441 '--assign_types=<type1>,<type2>,...' to assign a type to all histograms of the first, second, etc... files loaded. 3442 '--multiply=<fact1>,<fact2>,...' to multiply all histograms of the first, second, etc... files by the fact1, fact2, etc... 3443 '--no_suffix' Do no add any suffix (like '#1, #2, etc..) to the histograms types. 3444 '--lhapdf-config=<PATH_TO_LHAPDF-CONFIG>' give path to lhapdf-config to compute PDF certainties using LHAPDF (only for lhapdf6) 3445 '--jet_samples=[int1,int2]' Specifies what jet samples to keep. 'None' is the default and keeps them all. 3446 '--central_only' This option specifies to disregard all extra weights, so as to make it possible 3447 to take the ratio of plots with different extra weights specified. 3448 '--keep_all_weights' This option specifies to keep in the HwU produced all the weights, even 3449 those which are not known (i.e. that is scale, PDF or merging variation) 3450 For chosing what kind of variation you want to see on your plot, you can use the following options 3451 '--no_<type>' Turn off the plotting of variations of the chosen type 3452 '--only_<type>' Turn on only the plotting of variations of the chosen type 3453 '--variations=['<type1>',...]' Turn on only the plotting of the variations of the list of chosen types 3454 '--band=['<type1>',...]' Chose for which variations one should use uncertainty bands as opposed to lines 3455 The types can be: pdf, scale, stat, merging or alpsfact 3456 For the last two options one can use ...=all to automatically select all types. 3457 3458 When parsing an XML-formatted plot source output by the Pythia8 driver, the file names can be appended 3459 options as suffixes separated by '|', as follows: 3460 python histograms.py <XML_source_file_name>@<option1>@<option2>@etc.. 3461 These options can be 3462 'run_id=<integer>' Specifies the run_ID from which the plots should be loaded. 3463 By default, the first run is considered and the ones that follow are ignored. 3464 'merging_scale=<float>' This option allows to specify to import only the plots corresponding to a specific 3465 value for the merging scale. 3466 A value of -1 means that only the weights with the same merging scale as the central weight are kept. 3467 By default, all weights are considered. 3468 """ 3469 3470 possible_options=['--help', '--gnuplot', '--HwU', '--types','--n_ratios',\ 3471 '--no_open','--show_full','--show_short','--simple_ratios','--sum','--average','--rebin', \ 3472 '--assign_types','--multiply','--no_suffix', '--out', '--jet_samples', 3473 '--no_scale','--no_pdf','--no_stat','--no_merging','--no_alpsfact', 3474 '--only_scale','--only_pdf','--only_stat','--only_merging','--only_alpsfact', 3475 '--variations','--band','--central_only', '--lhapdf-config','--titles', 3476 '--keep_all_weights','--colours'] 3477 n_ratios = -1 3478 uncertainties = ['scale','pdf','statistical','merging_scale','alpsfact'] 3479 # The list of type of uncertainties for which to use bands. None is a 'smart' default 3480 use_band = None 3481 auto_open = True 3482 ratio_correlations = True 3483 consider_reweights = ['pdf','scale','murmuf_scales','merging_scale','alpsfact']
3484 3485 - def log(msg):
3486 print("histograms.py :: %s"%str(msg))
3487 3488 if '--help' in sys.argv or len(sys.argv)==1: 3489 log('\n\n%s'%main_doc) 3490 sys.exit(0) 3491 3492 for arg in sys.argv[1:]: 3493 if arg.startswith('--'): 3494 if arg.split('=')[0] not in possible_options: 3495 log('WARNING: option "%s" not valid. It will be ignored' % arg) 3496 3497 arg_string=' '.join(sys.argv) 3498 3499 OutName = "" 3500 for arg in sys.argv[1:]: 3501 if arg.startswith('--out='): 3502 OutName = arg[6:] 3503 3504 accepted_types = [] 3505 for arg in sys.argv[1:]: 3506 if arg.startswith('--types='): 3507 accepted_types = [(type if type!='None' else None) for type in \ 3508 arg[8:].split(',')] 3509 3510 accepted_titles = [] 3511 for arg in sys.argv[1:]: 3512 if arg.startswith('--titles='): 3513 accepted_titles = [(type if type!='None' else None) for type in \ 3514 arg[9:].split(',')] 3515 3516 assigned_types = [] 3517 for arg in sys.argv[1:]: 3518 if arg.startswith('--assign_types='): 3519 assigned_types = [(type if type!='None' else None) for type in \ 3520 arg[15:].split(',')] 3521 3522 assigned_colours = [] 3523 for arg in sys.argv[1:]: 3524 if arg.startswith('--colours='): 3525 assigned_colours = [(colour if colour!='None' else None) for colour in \ 3526 arg[10:].split(',')] 3527 3528 jet_samples_to_keep = None 3529 3530 lhapdfconfig = ['lhapdf-config'] 3531 for arg in sys.argv[1:]: 3532 if arg.startswith('--lhapdf-config='): 3533 lhapdfconfig = arg[16:] 3534 3535 no_suffix = False 3536 if '--no_suffix' in sys.argv: 3537 no_suffix = True 3538 3539 if '--central_only' in sys.argv: 3540 consider_reweights = [] 3541 3542 if '--keep_all_weights' in sys.argv: 3543 consider_reweights = 'ALL' 3544 3545 for arg in sys.argv[1:]: 3546 if arg.startswith('--n_ratios='): 3547 n_ratios = int(arg[11:]) 3548 3549 if '--no_open' in sys.argv: 3550 auto_open = False 3551 3552 variation_type_map={'scale':'scale','merging':'merging_scale','pdf':'pdf', 3553 'stat':'statistical','alpsfact':'alpsfact'} 3554 3555 for arg in sys.argv: 3556 try: 3557 opt, value = arg.split('=') 3558 except ValueError: 3559 continue 3560 if opt=='--jet_samples': 3561 jet_samples_to_keep = eval(value) 3562 if opt=='--variations': 3563 uncertainties=[variation_type_map[type] for type in eval(value, 3564 dict([(key,key) for key in variation_type_map.keys()]+ 3565 [('all',list(variation_type_map.keys()))]))] 3566 if opt=='--band': 3567 use_band=[variation_type_map[type] for type in eval(value, 3568 dict([(key,key) for key in variation_type_map.keys()]+ 3569 [('all',[type for type in variation_type_map.keys() if type!='stat'])]))] 3570 3571 if '--simple_ratios' in sys.argv: 3572 ratio_correlations = False 3573 3574 for arg in sys.argv: 3575 if arg.startswith('--no_') and not arg.startswith('--no_open'): 3576 uncertainties.remove(variation_type_map[arg[5:]]) 3577 if arg.startswith('--only_'): 3578 uncertainties= [variation_type_map[arg[7:]]] 3579 break 3580 3581 # Now remove from the weights considered all those not deemed necessary 3582 # in view of which uncertainties are selected 3583 if isinstance(consider_reweights, list): 3584 naming_map={'pdf':'pdf','scale':'scale', 3585 'merging_scale':'merging_scale','alpsfact':'alpsfact'} 3586 for key in naming_map: 3587 if (not key in uncertainties) and (naming_map[key] in consider_reweights): 3588 consider_reweights.remove(naming_map[key]) 3589 3590 n_files = len([_ for _ in sys.argv[1:] if not _.startswith('--')]) 3591 histo_norm = [1.0]*n_files 3592 3593 for arg in sys.argv[1:]: 3594 if arg.startswith('--multiply='): 3595 histo_norm = [(float(fact) if fact!='' else 1.0) for fact in \ 3596 arg[11:].split(',')] 3597 3598 if '--average' in sys.argv: 3599 histo_norm = [hist/float(n_files) for hist in histo_norm] 3600 3601 log("=======") 3602 histo_list = HwUList([]) 3603 for i, arg in enumerate(sys.argv[1:]): 3604 if arg.startswith('--'): 3605 break 3606 log("Loading histograms from '%s'."%arg) 3607 if OutName=="": 3608 OutName = os.path.basename(arg).split('.')[0]+'_output' 3609 # Make sure to process the potential XML options appended to the filename 3610 file_specification = arg.split('@') 3611 filename = file_specification.pop(0) 3612 file_options = {} 3613 for option in file_specification: 3614 opt, value = option.split('=') 3615 if opt=='run_id': 3616 file_options[opt]=int(value) 3617 if opt=='merging_scale': 3618 file_options[opt]=float(value) 3619 else: 3620 log("Unreckognize file option '%s'."%option) 3621 sys.exit(1) 3622 new_histo_list = HwUList(filename, accepted_types_order=accepted_types, 3623 consider_reweights=consider_reweights, **file_options) 3624 # We filter now the diagrams whose title doesn't match the constraints 3625 if len(accepted_titles)>0: 3626 new_histo_list = HwUList(histo for histo in new_histo_list if 3627 any(t in histo.title for t in accepted_titles)) 3628 for histo in new_histo_list: 3629 if no_suffix or n_files==1: 3630 continue 3631 if not histo.type is None: 3632 histo.type += '|' 3633 else: 3634 histo.type = '' 3635 # Firs option is to give a bit of the name of the source HwU file. 3636 #histo.type += " %s, #%d"%\ 3637 # (os.path.basename(arg).split('.')[0][:3],i+1) 3638 # But it is more elegant to give just the number. 3639 # Overwrite existing number if present. We assume here that one never 3640 # uses the '#' in its custom-defined types, which is a fair assumptions. 3641 try: 3642 suffix = assigned_types[i] 3643 except IndexError: 3644 suffix = "#%d"%(i+1) 3645 try: 3646 histo.type = histo.type[:histo.type.index('#')] + suffix 3647 except ValueError: 3648 histo.type += suffix 3649 3650 if i==0 or all(_ not in ['--sum','--average'] for _ in sys.argv): 3651 for j,hist in enumerate(new_histo_list): 3652 new_histo_list[j]=hist*histo_norm[i] 3653 histo_list.extend(new_histo_list) 3654 continue 3655 3656 if any(_ in sys.argv for _ in ['--sum','--average']): 3657 for j, hist in enumerate(new_histo_list): 3658 # First make sure the plots have the same weight labels and such 3659 hist.test_plot_compability(histo_list[j]) 3660 # Now let the histogram module do the magic and add them. 3661 histo_list[j] += hist*histo_norm[i] 3662 3663 log("A total of %i histograms were found."%len(histo_list)) 3664 log("=======") 3665 3666 n_rebin = 1 3667 for arg in sys.argv[1:]: 3668 if arg.startswith('--rebin='): 3669 n_rebin = int(arg[8:]) 3670 3671 if n_rebin > 1: 3672 for hist in histo_list: 3673 hist.rebin(n_rebin) 3674 3675 if '--gnuplot' in sys.argv or all(arg not in ['--HwU'] for arg in sys.argv): 3676 # Where the magic happens: 3677 histo_list.output(OutName, format='gnuplot', 3678 number_of_ratios = n_ratios, 3679 uncertainties=uncertainties, 3680 ratio_correlations=ratio_correlations, 3681 arg_string=arg_string, 3682 jet_samples_to_keep=jet_samples_to_keep, 3683 use_band=use_band, 3684 auto_open=auto_open, 3685 lhapdfconfig=lhapdfconfig, 3686 assigned_colours=assigned_colours) 3687 # Tell the user that everything went for the best 3688 log("%d histograms have been output in " % len(histo_list)+\ 3689 "the gnuplot format at '%s.[HwU|gnuplot]'." % OutName) 3690 if auto_open: 3691 command = 'gnuplot %s.gnuplot'%OutName 3692 try: 3693 subprocess.call(command,shell=True,stderr=subprocess.PIPE) 3694 except: 3695 log("Automatic processing of the gnuplot card failed. Try the"+\ 3696 " command by hand:\n%s"%command) 3697 else: 3698 sys.exit(0) 3699 3700 if '--HwU' in sys.argv: 3701 log("Histograms data has been output in the HwU format at "+\ 3702 "'%s.HwU'."%OutName) 3703 histo_list.output(OutName, format='HwU') 3704 sys.exit(0) 3705 3706 if '--show_short' in sys.argv or '--show_full' in sys.argv: 3707 for i, histo in enumerate(histo_list): 3708 if i!=0: 3709 log('-------') 3710 log(histo.nice_string(short=(not '--show_full' in sys.argv))) 3711 log("=======")
3712 3713 ######## Routine from https://gist.github.com/thriveth/8352565 3714 ######## To fill for histograms data in matplotlib 3715 -def fill_between_steps(x, y1, y2=0, h_align='right', ax=None, **kwargs):
3716 ''' Fills a hole in matplotlib: fill_between for step plots. 3717 Parameters : 3718 ------------ 3719 x : array-like 3720 Array/vector of index values. These are assumed to be equally-spaced. 3721 If not, the result will probably look weird... 3722 y1 : array-like 3723 Array/vector of values to be filled under. 3724 y2 : array-Like 3725 Array/vector or bottom values for filled area. Default is 0. 3726 **kwargs will be passed to the matplotlib fill_between() function. 3727 ''' 3728 # If no Axes opject given, grab the current one: 3729 if ax is None: 3730 ax = plt.gca() 3731 3732 3733 # First, duplicate the x values 3734 #duplicate the info # xx = numpy.repeat(2)[1:] 3735 xx= []; [(xx.append(d),xx.append(d)) for d in x]; xx = xx[1:] 3736 # Now: the average x binwidth 3737 xstep = x[1] -x[0] 3738 # Now: add one step at end of row. 3739 xx.append(xx[-1] + xstep) 3740 3741 # Make it possible to change step alignment. 3742 if h_align == 'mid': 3743 xx = [X-xstep/2. for X in xx] 3744 elif h_align == 'right': 3745 xx = [X-xstep for X in xx] 3746 3747 # Also, duplicate each y coordinate in both arrays 3748 yy1 = []; [(yy1.append(d),yy1.append(d)) for d in y1] 3749 if isinstance(y1, list): 3750 yy2 = []; [(yy2.append(d),yy2.append(d)) for d in y2] 3751 else: 3752 yy2=y2 3753 if len(yy2) != len(yy1): 3754 yy2 = []; [(yy2.append(d),yy2.append(d)) for d in y2] 3755 3756 # now to the plotting part: 3757 ax.fill_between(xx, yy1, y2=yy2, **kwargs) 3758 3759 return ax
3760 ######## end routine from https://gist.github.com/thriveth/835256 3761