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

Source Code for Module madgraph.various.lhe_parser

   1  from __future__ import division 
   2  from __future__ import absolute_import 
   3  from __future__ import print_function 
   4  import collections 
   5  import random 
   6  import re 
   7  import operator 
   8  import numbers 
   9  import math 
  10  import time 
  11  import os 
  12  import shutil 
  13  import sys 
  14  import six 
  15  from six.moves import filter 
  16  from six.moves import range 
  17  from six.moves import zip 
  18  from functools import reduce 
  19   
  20  pjoin = os.path.join 
  21   
  22  if '__main__' == __name__: 
  23      import sys 
  24      import os  
  25      sys.path.append('../../') 
  26      root = os.path.dirname(__file__) 
  27      if os.path.basename(root) == 'internal': 
  28              __package__ = "internal" 
  29              sys.path.append(os.path.dirname(root)) 
  30              import internal 
  31      else: 
  32          __package__ = "madgraph.various" 
  33   
  34  try: 
  35      import madgraph 
  36  except ImportError: 
  37      from . import misc 
  38      from . import banner as banner_mod 
  39  else: 
  40      import madgraph.various.misc as misc 
  41      import madgraph.various.banner as banner_mod 
  42  import logging 
  43  import gzip 
  44   
  45   
  46  try: 
  47      import madgraph.various.hepmc_parser as hepmc_parser 
  48  except Exception as error: 
  49      hepmc_parser = False 
  50      misc.sprint("No hepmc reader since", error) 
  51      pass 
  52   
  53   
  54  logger = logging.getLogger("madgraph.lhe_parser") 
  55   
  56  if six.PY3: 
  57      six.text_type = str 
58 59 -class Particle(object):
60 """ """ 61 # regular expression not use anymore to speed up the computation 62 #pattern=re.compile(r'''^\s* 63 # (?P<pid>-?\d+)\s+ #PID 64 # (?P<status>-?\d+)\s+ #status (1 for output particle) 65 # (?P<mother1>-?\d+)\s+ #mother 66 # (?P<mother2>-?\d+)\s+ #mother 67 # (?P<color1>[+-e.\d]*)\s+ #color1 68 # (?P<color2>[+-e.\d]*)\s+ #color2 69 # (?P<px>[+-e.\d]*)\s+ #px 70 # (?P<py>[+-e.\d]*)\s+ #py 71 # (?P<pz>[+-e.\d]*)\s+ #pz 72 # (?P<E>[+-e.\d]*)\s+ #E 73 # (?P<mass>[+-e.\d]*)\s+ #mass 74 # (?P<vtim>[+-e.\d]*)\s+ #displace vertex 75 # (?P<helicity>[+-e.\d]*)\s* #helicity 76 # ($|(?P<comment>\#[\d|D]*)) #comment/end of string 77 # ''',66) #verbose+ignore case 78 79 80
81 - def __init__(self, line=None, event=None):
82 """ """ 83 84 if isinstance(line, Particle): 85 for key in line.__dict__: 86 setattr(self, key, getattr(line, key)) 87 if event: 88 self.event = event 89 return 90 elif hepmc_parser and isinstance(line, hepmc_parser.HEPMC_Particle): 91 self.event = event 92 self.event_id = len(event) #not yet in the event 93 for key in ['pid', 'status', 'E','px','py','pz','mass']: 94 setattr(self, key, getattr(line, key)) 95 self.mother1 = 1 96 self.mother2 = 1 97 self.color1 = 0 98 self.color2 = 0 99 self.vtim = 0 100 self.comment = '' 101 self.helicity = 9 102 self.rwgt = 0 103 return 104 105 106 self.event = event 107 self.event_id = len(event) #not yet in the event 108 # LHE information 109 self.pid = 0 110 self.status = 0 # -1:initial. 1:final. 2: propagator 111 self.mother1 = None 112 self.mother2 = None 113 self.color1 = 0 114 self.color2 = None 115 self.px = 0 116 self.py = 0 117 self.pz = 0 118 self.E = 0 119 self.mass = 0 120 self.vtim = 0 121 self.helicity = 9 122 self.rwgt = 0 123 self.comment = '' 124 125 if line: 126 self.parse(line)
127 128 @property
129 - def pdg(self):
130 "convenient alias" 131 return self.pid
132
133 - def parse(self, line):
134 """parse the line""" 135 136 args = line.split() 137 keys = ['pid', 'status','mother1','mother2','color1', 'color2', 'px','py','pz','E', 138 'mass','vtim','helicity'] 139 140 for key,value in zip(keys,args): 141 setattr(self, key, float(value)) 142 self.pid = int(self.pid) 143 144 self.comment = ' '.join(args[len(keys):]) 145 if self.comment.startswith(('|','#')): 146 self.comment = self.comment[1:]
147 148 # Note that mother1/mother2 will be modified by the Event parse function to replace the 149 # integer by a pointer to the actual particle object. 150
151 - def __str__(self):
152 """string representing the particles""" 153 return " %8d %2d %4d %4d %4d %4d %+13.10e %+13.10e %+13.10e %14.10e %14.10e %10.4e %10.4e" \ 154 % (self.pid, 155 self.status, 156 (self.mother1 if isinstance(self.mother1, numbers.Number) else self.mother1.event_id+1) if self.mother1 else 0, 157 (self.mother2 if isinstance(self.mother2, numbers.Number) else self.mother2.event_id+1) if self.mother2 else 0, 158 self.color1, 159 self.color2, 160 self.px, 161 self.py, 162 self.pz, 163 self.E, 164 self.mass, 165 self.vtim, 166 self.helicity)
167
168 - def __eq__(self, other):
169 170 if not isinstance(other, Particle): 171 return False 172 if self.pid == other.pid and \ 173 self.status == other.status and \ 174 self.mother1 == other.mother1 and \ 175 self.mother2 == other.mother2 and \ 176 self.color1 == other.color1 and \ 177 self.color2 == other.color2 and \ 178 self.px == other.px and \ 179 self.py == other.py and \ 180 self.pz == other.pz and \ 181 self.E == other.E and \ 182 self.mass == other.mass and \ 183 self.vtim == other.vtim and \ 184 self.helicity == other.helicity: 185 return True 186 return False
187
188 - def set_momentum(self, momentum):
189 190 self.E = momentum.E 191 self.px = momentum.px 192 self.py = momentum.py 193 self.pz = momentum.pz
194
195 - def add_decay(self, decay_event):
196 """associate to this particle the decay in the associate event""" 197 198 return self.event.add_decay_to_particle(self.event_id, decay_event)
199 200
201 - def __repr__(self):
202 return 'Particle("%s", event=%s)' % (str(self), self.event)
203
204 205 -class EventFile(object):
206 """A class to allow to read both gzip and not gzip file""" 207 208 allow_empty_event = False 209 encoding = 'UTF-8' 210
211 - def __init__(self, path, mode='r', *args, **opt):
212 """open file and read the banner [if in read mode]""" 213 214 if mode in ['r','rb']: 215 mode ='r' 216 self.mode = mode 217 218 self.to_zip = False 219 self.zip_mode = False 220 221 if not path.endswith(".gz"): 222 self.file = open(path, mode, *args, **opt) 223 elif mode == 'r' and not os.path.exists(path) and os.path.exists(path[:-3]): 224 self.file = open(path[:-3], mode, *args, **opt) 225 path = path[:-3] 226 else: 227 try: 228 self.file = gzip.GzipFile(path, mode, *args, **opt) 229 self.zip_mode =True 230 except IOError as error: 231 raise 232 except Exception as error: 233 misc.sprint(error) 234 if mode == 'r': 235 misc.gunzip(path) 236 else: 237 self.to_zip = True 238 self.file = open(path[:-3], mode, *args, **opt) 239 path = path[:-3] 240 241 242 self.path = path 243 244 245 self.parsing = True # check if/when we need to parse the event. 246 self.eventgroup = False 247 248 self.banner = '' 249 if mode in ['r', 'rb']: 250 line = '' 251 while '</init>' not in line.lower(): 252 line = self.file.readline() 253 if not line: 254 self.seek(0) 255 self.banner = '' 256 break 257 if 'b' in mode or self.zip_mode: 258 line = str(line.decode(self.encoding)) 259 if '<event' in line.lower(): 260 self.seek(0) 261 self.banner = '' 262 break 263 264 self.banner += line
265
266 - def get_banner(self):
267 """return a banner object""" 268 import madgraph.various.banner as banner 269 if isinstance(self.banner, banner.Banner): 270 return self.banner 271 272 output = banner.Banner() 273 output.read_banner(self.banner) 274 return output
275 276 @property
277 - def name(self):
278 return self.file.name
279 280 @property
281 - def closed(self):
282 return self.file.closed
283 284 @property
285 - def cross(self):
286 """return the cross-section of the file #from the banner""" 287 try: 288 return self._cross 289 except Exception: 290 pass 291 292 onebanner = self.get_banner() 293 self._cross = onebanner.get_cross() 294 return self._cross
295
296 - def __len__(self):
297 if self.file.closed: 298 return 0 299 if hasattr(self,"len"): 300 return self.len 301 self.seek(0) 302 nb_event=0 303 with misc.TMP_variable(self, 'parsing', False): 304 for _ in self: 305 nb_event +=1 306 self.len = nb_event 307 self.seek(0) 308 return self.len
309
310 - def __iter__(self):
311 return self
312
313 - def next(self):
314 315 if not self.eventgroup: 316 return self.next_event() 317 else: 318 return self.next_eventgroup()
319 320 __next__ = next 321
322 - def next_event(self):
323 324 text = '' 325 line = '' 326 mode = 0 327 328 while True: 329 # reading the next line of the file 330 line = self.file.readline() 331 if not line: 332 raise StopIteration 333 if 'b' in self.mode or self.zip_mode: 334 line = line.decode(self.encoding) 335 336 if '<event' in line: 337 mode = 1 338 text = [] 339 if mode: 340 text.append(line) 341 342 if '</event>' in line: 343 if self.parsing: 344 out = Event(text) 345 if len(out) == 0 and not self.allow_empty_event: 346 raise Exception 347 return out 348 else: 349 return text
350 351
352 - def next_eventgroup(self):
353 events = [] 354 text = '' 355 line = '' 356 mode = 0 357 while '</eventgroup>' not in line: 358 359 # reading the next line of the file 360 line = self.file.readline() 361 if not line: 362 raise StopIteration 363 if 'b' in self.mode: 364 line = line.decode(self.encoding) 365 366 if '<eventgroup' in line: 367 events=[] 368 text = '' 369 elif '<event' in line: 370 text = [] 371 mode=1 372 elif '</event>' in line: 373 if self.parsing: 374 events.append(Event(text)) 375 else: 376 events.append('\n'.join(text)) 377 text = '' 378 mode = 0 379 if mode: 380 text += line 381 if len(events) == 0: 382 return self.next_eventgroup() 383 384 return events
385 386
387 - def initialize_unweighting(self, get_wgt, trunc_error):
388 """ scan once the file to return 389 - the list of the hightest weight (of size trunc_error*NB_EVENT 390 - the cross-section by type of process 391 - the total number of events in the file 392 """ 393 394 # We need to loop over the event file to get some information about the 395 # new cross-section/ wgt of event. 396 self.seek(0) 397 all_wgt = [] 398 cross = collections.defaultdict(int) 399 nb_event = 0 400 for event in self: 401 nb_event +=1 402 wgt = get_wgt(event) 403 cross['all'] += wgt 404 cross['abs'] += abs(wgt) 405 cross[event.ievent] += wgt 406 all_wgt.append(abs(wgt)) 407 # avoid all_wgt to be too large 408 if nb_event % 20000 == 0: 409 all_wgt.sort() 410 # drop the lowest weight 411 nb_keep = max(20, int(nb_event*trunc_error*15)) 412 all_wgt = all_wgt[-nb_keep:] 413 414 #final selection of the interesting weight to keep 415 all_wgt.sort() 416 # drop the lowest weight 417 nb_keep = max(20, int(nb_event*trunc_error*10)) 418 all_wgt = all_wgt[-nb_keep:] 419 self.seek(0) 420 return all_wgt, cross, nb_event
421
422 - def write_events(self, event):
423 """ write a single events or a list of event 424 if self.eventgroup is ON, then add <eventgroup> around the lists of events 425 """ 426 if isinstance(event, Event): 427 if self.eventgroup: 428 tmp = '<eventgroup>\n%s\n</eventgroup>\n' % event 429 self.write(tmp) 430 else: 431 self.write(str(event)) 432 elif isinstance(event, list): 433 if self.eventgroup: 434 self.write('<eventgroup>\n') 435 for evt in event: 436 self.write(str(evt)) 437 if self.eventgroup: 438 self.write('</eventgroup>\n')
439
440 - def unweight(self, outputpath, get_wgt=None, max_wgt=0, trunc_error=0, 441 event_target=0, log_level=logging.INFO, normalization='average'):
442 """unweight the current file according to wgt information wgt. 443 which can either be a fct of the event or a tag in the rwgt list. 444 max_wgt allow to do partial unweighting. 445 trunc_error allow for dynamical partial unweighting 446 event_target reweight for that many event with maximal trunc_error. 447 (stop to write event when target is reached) 448 """ 449 if not get_wgt: 450 def weight(event): 451 return event.wgt
452 get_wgt = weight 453 unwgt_name = "central weight" 454 elif isinstance(get_wgt, (str,six.text_type)): 455 unwgt_name =get_wgt 456 def get_wgt(event): 457 event.parse_reweight() 458 return event.reweight_data[unwgt_name] 459 else: 460 unwgt_name = get_wgt.__name__ 461 462 # check which weight to write 463 if hasattr(self, "written_weight"): 464 written_weight = lambda x: math.copysign(self.written_weight,float(x)) 465 else: 466 written_weight = lambda x: x 467 468 all_wgt, cross, nb_event = self.initialize_unweighting(get_wgt, trunc_error) 469 470 # function that need to be define on the flight 471 def max_wgt_for_trunc(trunc): 472 """find the weight with the maximal truncation.""" 473 474 xsum = 0 475 i=1 476 while (xsum - all_wgt[-i] * (i-1) <= cross['abs'] * trunc): 477 max_wgt = all_wgt[-i] 478 xsum += all_wgt[-i] 479 i +=1 480 if i == len(all_wgt): 481 break 482 483 return max_wgt 484 # end of the function 485 486 # choose the max_weight 487 if not max_wgt: 488 if trunc_error == 0 or len(all_wgt)<2 or event_target: 489 max_wgt = all_wgt[-1] 490 else: 491 max_wgt = max_wgt_for_trunc(trunc_error) 492 493 # need to modify the banner so load it to an object 494 if self.banner: 495 try: 496 import internal 497 except: 498 import madgraph.various.banner as banner_module 499 else: 500 import internal.banner as banner_module 501 if not isinstance(self.banner, banner_module.Banner): 502 banner = self.get_banner() 503 # 1. modify the cross-section 504 banner.modify_init_cross(cross) 505 # 3. add information about change in weight 506 banner["unweight"] = "unweighted by %s" % unwgt_name 507 else: 508 banner = self.banner 509 # modify the lha strategy 510 curr_strategy = banner.get_lha_strategy() 511 if normalization in ['unit', 'sum']: 512 strategy = 3 513 else: 514 strategy = 4 515 if curr_strategy >0: 516 banner.set_lha_strategy(abs(strategy)) 517 else: 518 banner.set_lha_strategy(-1*abs(strategy)) 519 520 # Do the reweighting (up to 20 times if we have target_event) 521 nb_try = 20 522 nb_keep = 0 523 for i in range(nb_try): 524 self.seek(0) 525 if event_target: 526 if i==0: 527 max_wgt = max_wgt_for_trunc(0) 528 else: 529 #guess the correct max_wgt based on last iteration 530 efficiency = nb_keep/nb_event 531 needed_efficiency = event_target/nb_event 532 last_max_wgt = max_wgt 533 needed_max_wgt = last_max_wgt * efficiency / needed_efficiency 534 535 min_max_wgt = max_wgt_for_trunc(trunc_error) 536 max_wgt = max(min_max_wgt, needed_max_wgt) 537 max_wgt = min(max_wgt, all_wgt[-1]) 538 if max_wgt == last_max_wgt: 539 if nb_keep < event_target and log_level>=10: 540 logger.log(log_level+10,"fail to reach target %s", event_target) 541 break 542 else: 543 break 544 545 #create output file (here since we are sure that we have to rewrite it) 546 if outputpath: 547 outfile = EventFile(outputpath, "w") 548 # need to write banner information 549 # need to see what to do with rwgt information! 550 if self.banner and outputpath: 551 banner.write(outfile, close_tag=False) 552 553 # scan the file 554 nb_keep = 0 555 trunc_cross = 0 556 for event in self: 557 r = random.random() 558 wgt = get_wgt(event) 559 if abs(wgt) < r * max_wgt: 560 continue 561 elif wgt > 0: 562 nb_keep += 1 563 event.wgt = written_weight(max(wgt, max_wgt)) 564 if abs(wgt) > max_wgt: 565 trunc_cross += abs(wgt) - max_wgt 566 if event_target ==0 or nb_keep <= event_target: 567 if outputpath: 568 outfile.write(str(event)) 569 570 elif wgt < 0: 571 nb_keep += 1 572 event.wgt = -1* written_weight(max(abs(wgt), max_wgt)) 573 if abs(wgt) > max_wgt: 574 trunc_cross += abs(wgt) - max_wgt 575 if outputpath and (event_target ==0 or nb_keep <= event_target): 576 outfile.write(str(event)) 577 578 if event_target and nb_keep > event_target: 579 if not outputpath: 580 #no outputpath define -> wants only the nb of unweighted events 581 continue 582 elif event_target and i != nb_try-1 and nb_keep >= event_target *1.05: 583 outfile.write("</LesHouchesEvents>\n") 584 outfile.close() 585 #logger.log(log_level, "Found Too much event %s. Try to reduce truncation" % nb_keep) 586 continue 587 else: 588 outfile.write("</LesHouchesEvents>\n") 589 outfile.close() 590 break 591 elif event_target == 0: 592 if outputpath: 593 outfile.write("</LesHouchesEvents>\n") 594 outfile.close() 595 break 596 elif outputpath: 597 outfile.write("</LesHouchesEvents>\n") 598 outfile.close() 599 # logger.log(log_level, "Found only %s event. Reduce max_wgt" % nb_keep) 600 601 else: 602 # pass here if event_target > 0 and all the attempt fail. 603 logger.log(log_level+10,"fail to reach target event %s (iteration=%s)", event_target,i) 604 605 # logger.log(log_level, "Final maximum weight used for final "+\ 606 # "unweighting is %s yielding %s events." % (max_wgt,nb_keep)) 607 608 if event_target: 609 nb_events_unweighted = nb_keep 610 nb_keep = min( event_target, nb_keep) 611 else: 612 nb_events_unweighted = nb_keep 613 614 logger.log(log_level, "write %i event (efficiency %.2g %%, truncation %.2g %%) after %i iteration(s)", 615 nb_keep, nb_events_unweighted/nb_event*100, trunc_cross/cross['abs']*100, i) 616 617 #correct the weight in the file if not the correct number of event 618 if nb_keep != event_target and hasattr(self, "written_weight") and strategy !=4: 619 written_weight = lambda x: math.copysign(self.written_weight*event_target/nb_keep, float(x)) 620 startfile = EventFile(outputpath) 621 tmpname = pjoin(os.path.dirname(outputpath), "wgtcorrected_"+ os.path.basename(outputpath)) 622 outfile = EventFile(tmpname, "w") 623 outfile.write(startfile.banner) 624 for event in startfile: 625 event.wgt = written_weight(event.wgt) 626 outfile.write(str(event)) 627 outfile.write("</LesHouchesEvents>\n") 628 startfile.close() 629 outfile.close() 630 shutil.move(tmpname, outputpath) 631 632 633 634 635 self.max_wgt = max_wgt 636 return nb_keep 637
638 - def apply_fct_on_event(self, *fcts, **opts):
639 """ apply one or more fct on all event. """ 640 641 opt= {"print_step": 5000, "maxevent":float("inf"),'no_output':False} 642 opt.update(opts) 643 start = time.time() 644 nb_fct = len(fcts) 645 out = [] 646 for i in range(nb_fct): 647 out.append([]) 648 self.seek(0) 649 nb_event = 0 650 for event in self: 651 nb_event += 1 652 if opt["print_step"] and (nb_event % opt["print_step"]) == 0: 653 if hasattr(self,"len"): 654 print(("currently at %s/%s event [%is]" % (nb_event, self.len, time.time()-start))) 655 else: 656 print(("currently at %s event [%is]" % (nb_event, time.time()-start))) 657 for i in range(nb_fct): 658 value = fcts[i](event) 659 if not opt['no_output']: 660 out[i].append(value) 661 if nb_event > opt['maxevent']: 662 break 663 if nb_fct == 1: 664 return out[0] 665 else: 666 return out
667
668 - def split(self, nb_event=0, partition=None, cwd=os.path.curdir, zip=False):
669 """split the file in multiple file. Do not change the weight!""" 670 671 nb_file = -1 672 for i, event in enumerate(self): 673 if (not (partition is None) and i==sum(partition[:nb_file+1])) or \ 674 (partition is None and i % nb_event == 0): 675 if i: 676 #close previous file 677 current.write('</LesHouchesEvent>\n') 678 current.close() 679 # create the new file 680 nb_file +=1 681 # If end of partition then finish writing events here. 682 if not partition is None and (nb_file+1>len(partition)): 683 return nb_file 684 if zip: 685 current = EventFile(pjoin(cwd,'%s_%s.lhe.gz' % (self.path, nb_file)),'w') 686 else: 687 current = open(pjoin(cwd,'%s_%s.lhe' % (self.path, nb_file)),'w') 688 current.write(self.banner) 689 current.write(str(event)) 690 if i!=0: 691 current.write('</LesHouchesEvent>\n') 692 current.close() 693 694 return nb_file +1
695
696 - def update_HwU(self, hwu, fct, name='lhe', keep_wgt=False, maxevents=sys.maxsize):
697 """take a HwU and add this event file for the function fct""" 698 699 if not isinstance(hwu, list): 700 hwu = [hwu] 701 702 class HwUUpdater(object): 703 704 def __init__(self, fct, keep_wgt): 705 706 self.fct = fct 707 self.first = True 708 self.keep_wgt = keep_wgt
709 710 def add(self, event): 711 712 value = self.fct(event) 713 # initialise the curve for the first call 714 if self.first: 715 for h in hwu: 716 # register the variables 717 if isinstance(value, dict): 718 h.add_line(list(value.keys())) 719 else: 720 721 h.add_line(name) 722 if self.keep_wgt is True: 723 event.parse_reweight() 724 h.add_line(['%s_%s' % (name, key) 725 for key in event.reweight_data]) 726 elif self.keep_wgt: 727 h.add_line(list(self.keep_wgt.values())) 728 self.first = False 729 # Fill the histograms 730 for h in hwu: 731 if isinstance(value, tuple): 732 h.addEvent(value[0], value[1]) 733 else: 734 h.addEvent(value,{name:event.wgt}) 735 if self.keep_wgt: 736 event.parse_reweight() 737 if self.keep_wgt is True: 738 data = dict(('%s_%s' % (name, key),event.reweight_data[key]) 739 for key in event.reweight_data) 740 h.addEvent(value, data) 741 else: 742 data = dict(( value,event.reweight_data[key]) 743 for key,value in self.keep_wgt.items()) 744 h.addEvent(value, data) 745 746 747 748 self.apply_fct_on_event(HwUUpdater(fct,keep_wgt).add, no_output=True,maxevent=maxevents) 749 return hwu 750
751 - def create_syscalc_data(self, out_path, pythia_input=None):
752 """take the lhe file and add the matchscale from the pythia_input file""" 753 754 if pythia_input: 755 def next_data(): 756 for line in open(pythia_input): 757 if line.startswith('#'): 758 continue 759 data = line.split() 760 print((int(data[0]), data[-3], data[-2], data[-1])) 761 yield (int(data[0]), data[-3], data[-2], data[-1])
762 else: 763 def next_data(): 764 i=0 765 while 1: 766 yield [i,0,0,0] 767 i+=1 768 sys_iterator = next_data() 769 #ensure that we are at the beginning of the file 770 self.seek(0) 771 out = open(out_path,'w') 772 773 pdf_pattern = re.compile(r'''<init>(.*)</init>''', re.M+re.S) 774 init = pdf_pattern.findall(self.banner)[0].split('\n',2)[1] 775 id1, id2, _, _, _, _, pdf1,pdf2,_,_ = init.split() 776 id = [int(id1), int(id2)] 777 type = [] 778 for i in range(2): 779 if abs(id[i]) == 2212: 780 if i > 0: 781 type.append(1) 782 else: 783 type.append(-1) 784 else: 785 type.append(0) 786 pdf = max(int(pdf1),int(pdf2)) 787 788 out.write("<header>\n" + \ 789 "<orgpdf>%i</orgpdf>\n" % pdf + \ 790 "<beams> %s %s</beams>\n" % tuple(type) + \ 791 "</header>\n") 792 793 794 nevt, smin, smax, scomp = next(sys_iterator) 795 for i, orig_event in enumerate(self): 796 if i < nevt: 797 continue 798 new_event = Event() 799 sys = orig_event.parse_syscalc_info() 800 new_event.syscalc_data = sys 801 if smin: 802 new_event.syscalc_data['matchscale'] = "%s %s %s" % (smin, scomp, smax) 803 out.write(str(new_event), nevt) 804 try: 805 nevt, smin, smax, scomp = next(sys_iterator) 806 except StopIteration: 807 break 808
809 - def get_alphas(self, scale, lhapdf_config='lhapdf-config'):
810 """return the alphas value associated to a given scale""" 811 812 if hasattr(self, 'alpsrunner'): 813 return self.alpsrunner(scale) 814 815 # 816 banner = banner_mod.Banner(self.banner) 817 run_card = banner.charge_card('run_card') 818 use_runner = False 819 if abs(run_card['lpp1']) != 1 and abs(run_card['lpp2']) != 1: 820 # no pdf use. -> use Runner 821 use_runner = True 822 else: 823 # try to use lhapdf 824 lhapdf = misc.import_python_lhapdf(lhapdf_config) 825 if not lhapdf: 826 logger.warning('fail to link to lhapdf for the alphas-running. Use Two loop computation') 827 use_runner = True 828 try: 829 self.pdf = lhapdf.mkPDF(int(self.banner.run_card.get_lhapdf_id())) 830 except Exception: 831 logger.warning('fail to link to lhapdf for the alphas-running. Use Two loop computation') 832 use_runner = True 833 834 if not use_runner: 835 self.alpsrunner = lambda scale: self.pdf.alphasQ(scale) 836 else: 837 try: 838 from models.model_reader import Alphas_Runner 839 except ImportError: 840 root = os.path.dirname(__file__) 841 root_path = pjoin(root, os.pardir, os.pardir) 842 try: 843 import internal.madevent_interface as me_int 844 cmd = me_int.MadEventCmd(root_path,force_run=True) 845 except ImportError: 846 import internal.amcnlo_run_interface as me_int 847 cmd = me_int.Cmd(root_path,force_run=True) 848 if 'mg5_path' in cmd.options and cmd.options['mg5_path']: 849 sys.path.append(cmd.options['mg5_path']) 850 from models.model_reader import Alphas_Runner 851 852 if not hasattr(banner, 'param_card'): 853 param_card = banner.charge_card('param_card') 854 else: 855 param_card = banner.param_card 856 857 asmz = param_card.get_value('sminputs', 3, 0.13) 858 nloop =2 859 zmass = param_card.get_value('mass', 23, 91.188) 860 cmass = param_card.get_value('mass', 4, 1.4) 861 if cmass == 0: 862 cmass = 1.4 863 bmass = param_card.get_value('mass', 5, 4.7) 864 if bmass == 0: 865 bmass = 4.7 866 self.alpsrunner = Alphas_Runner(asmz, nloop, zmass, cmass, bmass) 867 868 869 870 return self.alpsrunner(scale)
871
872 - def seek(self, *args, **opts):
873 return self.file.seek(*args, **opts)
874
875 - def tell(self):
876 if self.zipmode: 877 currpos = self.file.tell() 878 if not currpos: 879 currpos = self.size 880 return currpos 881 else: 882 self.file.tell()
883
884 - def write(self, text):
885 886 if self.zip_mode or 'b' in self.mode: 887 self.file.write(text.encode()) 888 else: 889 self.file.write(text)
890
891 - def close(self,*args, **opts):
892 893 out = self.file.close(*args, **opts) 894 if self.to_zip: 895 misc.gzip(self.path)
896
897 - def __del__(self):
898 try: 899 self.file.close() 900 except Exception: 901 pass
902
903 904 -class MultiEventFile(EventFile):
905 """a class to read simultaneously multiple file and read them in mixing them. 906 Unweighting can be done at the same time. 907 The number of events in each file need to be provide in advance 908 (if not provide the file is first read to find that number""" 909
910 - def __new__(cls, start_list=[],parse=True):
911 return object.__new__(MultiEventFile)
912
913 - def __init__(self, start_list=[], parse=True):
914 """if trunc_error is define here then this allow 915 to only read all the files twice and not three times.""" 916 self.eventgroup = False 917 self.files = [] 918 #self.filesiter = [] 919 self.parsefile = parse #if self.files is formatted or just the path 920 self.banner = '' 921 self.initial_nb_events = [] 922 self.total_event_in_files = 0 923 self.curr_nb_events = [] 924 self.allcross = [] 925 self.error = [] 926 self.across = [] 927 self.scales = [] 928 if start_list: 929 if parse: 930 for p in start_list: 931 self.add(p) 932 else: 933 self.files = start_list 934 #self.filesiter = [f.__iter__() for f in self.files] 935 self._configure = False
936
937 - def close(self,*args,**opts):
938 for f in self.files: 939 f.close(*args, **opts)
940
941 - def add(self, path, cross, error, across, nb_event=0, scale=1):
942 """ add a file to the pool, across allow to reweight the sum of weight 943 in the file to the given cross-section 944 """ 945 946 if across == 0: 947 # No event linked to this channel -> so no need to include it 948 return 949 obj = EventFile(path) 950 obj.eventgroup = self.eventgroup 951 if len(self.files) == 0 and not self.banner: 952 self.banner = obj.banner 953 self.curr_nb_events.append(0) 954 self.initial_nb_events.append(0) 955 self.allcross.append(cross) 956 self.across.append(across) 957 self.error.append(error) 958 self.scales.append(scale) 959 self.files.append(obj) 960 #self.filesiter.append(obj.__iter__()) 961 if nb_event: 962 obj.len = nb_event 963 self._configure = False 964 return obj
965
966 - def __iter__(self):
967 968 if not self._configure: 969 self.configure() 970 return self
971
972 - def next(self):
973 if not self._configure: 974 self.configure() 975 remaining_event = self.total_event_in_files - sum(self.curr_nb_events) 976 if remaining_event == 0: 977 raise StopIteration 978 # determine which file need to be read 979 nb_event = random.randint(1, remaining_event) 980 sum_nb=0 981 for i, obj in enumerate(self.files): 982 sum_nb += self.initial_nb_events[i] - self.curr_nb_events[i] 983 if nb_event <= sum_nb: 984 self.curr_nb_events[i] += 1 985 event = next(obj) 986 if not self.eventgroup: 987 event.sample_scale = self.scales[i] # for file reweighting 988 else: 989 for evt in event: 990 evt.sample_scale = self.scales[i] 991 return event 992 else: 993 raise StopIteration
994 995 996 __next__ = next 997
998 - def define_init_banner(self, wgt, lha_strategy, proc_charac=None):
999 """define the part of the init_banner""" 1000 1001 if not self.banner: 1002 return 1003 1004 # compute the cross-section of each splitted channel 1005 grouped_cross = {} 1006 grouped_error = {} 1007 for i,ff in enumerate(self.files): 1008 filename = ff.path 1009 from_init = False 1010 Pdir = [P for P in filename.split(os.path.sep) if P.startswith('P')] 1011 if Pdir: 1012 Pdir = Pdir[-1] 1013 group = Pdir.split("_")[0][1:] 1014 if not group.isdigit(): 1015 from_init = True 1016 else: 1017 from_init = True 1018 1019 if not from_init: 1020 if group in grouped_cross: 1021 grouped_cross[group] += self.allcross[i] 1022 grouped_error[group] += self.error[i]**2 1023 else: 1024 grouped_cross[group] = self.allcross[i] 1025 grouped_error[group] = self.error[i]**2 1026 else: 1027 ban = banner_mod.Banner(ff.banner) 1028 for line in ban['init'].split('\n'): 1029 splitline = line.split() 1030 if len(splitline)==4: 1031 cross, error, _, group = splitline 1032 if int(group) in grouped_cross: 1033 grouped_cross[group] += float(cross) 1034 grouped_error[group] += float(error)**2 1035 else: 1036 grouped_cross[group] = float(cross) 1037 grouped_error[group] = float(error)**2 1038 nb_group = len(grouped_cross) 1039 1040 # compute the information for the first line 1041 try: 1042 run_card = self.banner.run_card 1043 except: 1044 run_card = self.banner.charge_card("run_card") 1045 1046 init_information = run_card.get_banner_init_information() 1047 #correct for special case 1048 if proc_charac and proc_charac['ninitial'] == 1: 1049 #special case for 1>N 1050 init_information = run_card.get_banner_init_information() 1051 event = next(self) 1052 init_information["idbmup1"] = event[0].pdg 1053 init_information["ebmup1"] = event[0].mass 1054 init_information["idbmup2"] = 0 1055 init_information["ebmup2"] = 0 1056 self.seek(0) 1057 else: 1058 # check special case without PDF for one (or both) beam 1059 if init_information["idbmup1"] in [0,9]: 1060 event = next(self) 1061 init_information["idbmup1"]= event[0].pdg 1062 if init_information["idbmup2"] == 0: 1063 init_information["idbmup2"]= event[1].pdg 1064 self.seek(0) 1065 if init_information["idbmup2"] in [0,9]: 1066 event = next(self) 1067 init_information["idbmup2"] = event[1].pdg 1068 self.seek(0) 1069 1070 init_information["nprup"] = nb_group 1071 1072 if run_card["lhe_version"] < 3: 1073 init_information["generator_info"] = "" 1074 else: 1075 init_information["generator_info"] = "<generator name='MadGraph5_aMC@NLO' version='%s'>please cite 1405.0301 </generator>\n" \ 1076 % misc.get_pkg_info()['version'] 1077 1078 # cross_information: 1079 cross_info = "%(cross)e %(error)e %(wgt)e %(id)i" 1080 init_information["cross_info"] = [] 1081 for id in grouped_cross: 1082 conv = {"id": int(id), "cross": grouped_cross[id], "error": math.sqrt(grouped_error[id]), 1083 "wgt": wgt} 1084 init_information["cross_info"].append( cross_info % conv) 1085 init_information["cross_info"] = '\n'.join(init_information["cross_info"]) 1086 init_information['lha_stra'] = -1 * abs(lha_strategy) 1087 1088 template_init =\ 1089 """ %(idbmup1)i %(idbmup2)i %(ebmup1)e %(ebmup2)e %(pdfgup1)i %(pdfgup2)i %(pdfsup1)i %(pdfsup2)i %(lha_stra)i %(nprup)i 1090 %(cross_info)s 1091 %(generator_info)s 1092 """ 1093 1094 self.banner["init"] = template_init % init_information
1095 1096 1097
1098 - def initialize_unweighting(self, getwgt, trunc_error):
1099 """ scan once the file to return 1100 - the list of the hightest weight (of size trunc_error*NB_EVENT 1101 - the cross-section by type of process 1102 - the total number of events in the files 1103 In top of that it initialise the information for the next routine 1104 to determine how to choose which file to read 1105 """ 1106 self.seek(0) 1107 all_wgt = [] 1108 total_event = 0 1109 sum_cross = collections.defaultdict(int) 1110 for i,f in enumerate(self.files): 1111 nb_event = 0 1112 # We need to loop over the event file to get some information about the 1113 # new cross-section/ wgt of event. 1114 cross = collections.defaultdict(int) 1115 new_wgt =[] 1116 for event in f: 1117 nb_event += 1 1118 total_event += 1 1119 event.sample_scale = 1 1120 wgt = getwgt(event) 1121 cross['all'] += wgt 1122 cross['abs'] += abs(wgt) 1123 cross[event.ievent] += wgt 1124 new_wgt.append(abs(wgt)) 1125 # avoid all_wgt to be too large 1126 if nb_event % 20000 == 0: 1127 new_wgt.sort() 1128 # drop the lowest weight 1129 nb_keep = max(20, int(nb_event*trunc_error*15)) 1130 new_wgt = new_wgt[-nb_keep:] 1131 if nb_event == 0: 1132 raise Exception 1133 # store the information 1134 self.initial_nb_events[i] = nb_event 1135 self.scales[i] = self.across[i]/cross['abs'] if self.across[i] else 1 1136 #misc.sprint("sum of wgt in event %s is %s. Should be %s => scale %s (nb_event: %s)" 1137 # % (i, cross['all'], self.allcross[i], self.scales[i], nb_event)) 1138 for key in cross: 1139 sum_cross[key] += cross[key]* self.scales[i] 1140 all_wgt +=[self.scales[i] * w for w in new_wgt] 1141 all_wgt.sort() 1142 nb_keep = max(20, int(total_event*trunc_error*10)) 1143 all_wgt = all_wgt[-nb_keep:] 1144 1145 self.total_event_in_files = total_event 1146 #final selection of the interesting weight to keep 1147 all_wgt.sort() 1148 # drop the lowest weight 1149 nb_keep = max(20, int(total_event*trunc_error*10)) 1150 all_wgt = all_wgt[-nb_keep:] 1151 self.seek(0) 1152 self._configure = True 1153 return all_wgt, sum_cross, total_event
1154
1155 - def configure(self):
1156 1157 self._configure = True 1158 for i,f in enumerate(self.files): 1159 self.initial_nb_events[i] = len(f) 1160 self.total_event_in_files = sum(self.initial_nb_events)
1161
1162 - def __len__(self):
1163 1164 return len(self.files)
1165
1166 - def seek(self, pos):
1167 """ """ 1168 1169 if pos !=0: 1170 raise Exception 1171 for i in range(len(self)): 1172 self.curr_nb_events[i] = 0 1173 for f in self.files: 1174 f.seek(pos)
1175
1176 - def unweight(self, outputpath, get_wgt, **opts):
1177 """unweight the current file according to wgt information wgt. 1178 which can either be a fct of the event or a tag in the rwgt list. 1179 max_wgt allow to do partial unweighting. 1180 trunc_error allow for dynamical partial unweighting 1181 event_target reweight for that many event with maximal trunc_error. 1182 (stop to write event when target is reached) 1183 """ 1184 1185 1186 if isinstance(get_wgt, (str,six.text_type)): 1187 unwgt_name =get_wgt 1188 def get_wgt_multi(event): 1189 event.parse_reweight() 1190 return event.reweight_data[unwgt_name] * event.sample_scale
1191 else: 1192 unwgt_name = get_wgt.__name__ 1193 get_wgt_multi = lambda event: get_wgt(event) * event.sample_scale 1194 #define the weighting such that we have built-in the scaling 1195 1196 if 'proc_charac' in opts: 1197 if opts['proc_charac']: 1198 proc_charac = opts['proc_charac'] 1199 else: 1200 proc_charac=None 1201 del opts['proc_charac'] 1202 else: 1203 proc_charac = None 1204 1205 if 'event_target' in opts and opts['event_target']: 1206 if 'normalization' in opts: 1207 if opts['normalization'] == 'sum': 1208 new_wgt = sum(self.across)/opts['event_target'] 1209 strategy = 3 1210 elif opts['normalization'] == 'average': 1211 strategy = 4 1212 new_wgt = sum(self.across) 1213 elif opts['normalization'] == 'unit': 1214 strategy =3 1215 new_wgt = 1. 1216 else: 1217 strategy = 4 1218 new_wgt = sum(self.across) 1219 self.define_init_banner(new_wgt, strategy, proc_charac=proc_charac) 1220 self.written_weight = new_wgt 1221 elif 'write_init' in opts and opts['write_init']: 1222 self.define_init_banner(0,0, proc_charac=proc_charac) 1223 del opts['write_init'] 1224 return super(MultiEventFile, self).unweight(outputpath, get_wgt_multi, **opts)
1225
1226 - def write(self, path, random=False, banner=None, get_info=False):
1227 """ """ 1228 1229 try: 1230 str_type = (str,six.text_type) 1231 except NameError: 1232 str_type = (str) 1233 1234 if isinstance(path, str_type): 1235 out = EventFile(path, 'w') 1236 if self.parsefile and not banner: 1237 banner = self.files[0].banner 1238 elif not banner: 1239 firstlhe = EventFile(self.files[0]) 1240 banner = firstlhe.banner 1241 else: 1242 out = path 1243 if banner: 1244 out.write(banner) 1245 nb_event = 0 1246 info = collections.defaultdict(float) 1247 if random and self.open: 1248 for event in self: 1249 nb_event +=1 1250 out.write(event) 1251 if get_info: 1252 event.parse_reweight() 1253 for key, value in event.reweight_data.items(): 1254 info[key] += value 1255 info['central'] += event.wgt 1256 elif not random: 1257 for i,f in enumerate(self.files): 1258 #check if we need to parse the file or not 1259 if not self.parsefile: 1260 if i==0: 1261 try: 1262 lhe = firstlhe 1263 except: 1264 lhe = EventFile(f) 1265 else: 1266 lhe = EventFile(f) 1267 else: 1268 lhe = f 1269 for event in lhe: 1270 nb_event +=1 1271 if get_info: 1272 event.parse_reweight() 1273 for key, value in event.reweight_data.items(): 1274 info[key] += value 1275 info['central'] += event.wgt 1276 out.write(str(event)) 1277 lhe.close() 1278 out.write("</LesHouchesEvents>\n") 1279 return nb_event, info
1280
1281 - def remove(self):
1282 """ """ 1283 if self.parsefile: 1284 for f in self.files: 1285 os.remove(f.path) 1286 else: 1287 for f in self.files: 1288 os.remove(f)
1289
1290 1291 1292 -class Event(list):
1293 """Class storing a single event information (list of particles + global information)""" 1294 1295 warning_order = True # raise a warning if the order of the particle are not in accordance of child/mother 1296
1297 - def __init__(self, text=None):
1298 """The initialization of an empty Event (or one associate to a text file)""" 1299 list.__init__(self) 1300 1301 # First line information 1302 self.nexternal = 0 1303 self.ievent = 0 1304 self.wgt = 0 1305 self.aqcd = 0 1306 self.scale = 0 1307 self.aqed = 0 1308 self.aqcd = 0 1309 # Weight information 1310 self.tag = '' 1311 self.eventflag = {} # for information in <event > 1312 self.comment = '' 1313 self.reweight_data = {} 1314 self.matched_scale_data = None 1315 self.syscalc_data = {} 1316 if text: 1317 self.parse(text)
1318 1319 1320
1321 - def parse(self, text):
1322 """Take the input file and create the structured information""" 1323 #text = re.sub(r'</?event>', '', text) # remove pointless tag 1324 status = 'first' 1325 1326 if not isinstance(text, list): 1327 text = text.split('\n') 1328 1329 for line in text: 1330 line = line.strip() 1331 if not line: 1332 continue 1333 elif line[0] == '#': 1334 self.comment += '%s\n' % line 1335 continue 1336 elif line.startswith('<event'): 1337 if '=' in line: 1338 found = re.findall(r"""(\w*)=(?:(?:['"])([^'"]*)(?=['"])|(\S*))""",line) 1339 #for '<event line=4 value=\'3\' error="5" test=" 1 and 2">\n' 1340 #return [('line', '', '4'), ('value', '3', ''), ('error', '5', ''), ('test', ' 1 and 2', '')] 1341 self.eventflag = dict((n, a1) if a1 else (n,a2) for n,a1,a2 in found) 1342 # return {'test': ' 1 and 2', 'line': '4', 'value': '3', 'error': '5'} 1343 continue 1344 1345 elif 'first' == status: 1346 if '<rwgt>' in line: 1347 status = 'tag' 1348 else: 1349 self.assign_scale_line(line) 1350 status = 'part' 1351 continue 1352 if '<' in line: 1353 status = 'tag' 1354 1355 if 'part' == status: 1356 part = Particle(line, event=self) 1357 if part.E != 0 or part.status==-1: 1358 self.append(part) 1359 elif self.nexternal: 1360 self.nexternal-=1 1361 else: 1362 if '</event>' in line: 1363 line = line.replace('</event>','',1) 1364 self.tag += '%s\n' % line 1365 1366 self.assign_mother()
1367 1368
1369 - def assign_mother(self):
1370 """convert the number in actual particle""" 1371 #Security if not incoming particle. Define a fake particle 1372 if all(p.status != -1 for p in self): 1373 if not self.nexternal: 1374 return 1375 if self.warning_order: 1376 Event.warning_order = False 1377 logger.warning("Weird format for lhe format: no incoming particle... adding a fake one") 1378 raise Exception 1379 mother = Particle(event=self) 1380 mother.status = -1 1381 mother.pid = 0 1382 self.insert(0,mother) 1383 mother.color2 = 0 1384 mother.event_id = 0 1385 self.nexternal += 1 1386 for p in self[1:]: 1387 if isinstance(p.mother1, int) and p.mother1 > 1: 1388 p.mother1 += 1 1389 if isinstance(p.mother2, int) and p.mother2 > 1: 1390 p.mother2 += 1 1391 p.event_id += 1 1392 1393 1394 # assign the mother: 1395 for i,particle in enumerate(self): 1396 if i < particle.mother1 or i < particle.mother2: 1397 if self.warning_order: 1398 logger.warning("Order of particle in the event did not agree with parent/child order. This might be problematic for some code.") 1399 Event.warning_order = False 1400 self.reorder_mother_child() 1401 return self.assign_mother() 1402 1403 if particle.mother1: 1404 try: 1405 particle.mother1 = self[int(particle.mother1) -1] 1406 except Exception: 1407 logger.warning("WRONG MOTHER INFO %s", self) 1408 particle.mother1 = 0 1409 if particle.mother2: 1410 try: 1411 particle.mother2 = self[int(particle.mother2) -1] 1412 except Exception: 1413 logger.warning("WRONG MOTHER INFO %s", self) 1414 particle.mother2 = 0
1415
1416 - def rescale_weights(self, ratio):
1417 """change all the weights by a given ratio""" 1418 1419 self.wgt *= ratio 1420 self.parse_reweight() 1421 for key in self.reweight_data: 1422 self.reweight_data[key] *= ratio 1423 return self.wgt
1424
1425 - def reorder_mother_child(self):
1426 """check and correct the mother/child position. 1427 only correct one order by call (but this is a recursive call)""" 1428 1429 tomove, position = None, None 1430 for i,particle in enumerate(self): 1431 if i < particle.mother1: 1432 # move i after particle.mother1 1433 tomove, position = i, particle.mother1-1 1434 break 1435 if i < particle.mother2: 1436 tomove, position = i, particle.mother2-1 1437 1438 # nothing to change -> we are done 1439 if not tomove: 1440 return 1441 1442 # move the particles: 1443 particle = self.pop(tomove) 1444 self.insert(int(position), particle) 1445 1446 #change the mother id/ event_id in the event. 1447 for i, particle in enumerate(self): 1448 particle.event_id = i 1449 #misc.sprint( i, particle.event_id) 1450 m1, m2 = particle.mother1, particle.mother2 1451 if m1 == tomove +1: 1452 particle.mother1 = position+1 1453 elif tomove < m1 <= position +1: 1454 particle.mother1 -= 1 1455 if m2 == tomove +1: 1456 particle.mother2 = position+1 1457 elif tomove < m2 <= position +1: 1458 particle.mother2 -= 1 1459 # re-call the function for the next potential change 1460 return self.reorder_mother_child()
1461 1462 1463 1464 1465 1466
1467 - def parse_reweight(self):
1468 """Parse the re-weight information in order to return a dictionary 1469 {key: value}. If no group is define group should be '' """ 1470 if self.reweight_data: 1471 return self.reweight_data 1472 self.reweight_data = {} 1473 self.reweight_order = [] 1474 start, stop = self.tag.find('<rwgt>'), self.tag.find('</rwgt>') 1475 if start != -1 != stop : 1476 pattern = re.compile(r'''<\s*wgt id=(?:\'|\")(?P<id>[^\'\"]+)(?:\'|\")\s*>\s*(?P<val>[\ded+-.]*)\s*</wgt>''',re.I) 1477 data = pattern.findall(self.tag[start:stop]) 1478 try: 1479 self.reweight_data = dict([(pid, float(value)) for (pid, value) in data 1480 if not self.reweight_order.append(pid)]) 1481 # the if is to create the order file on the flight 1482 except ValueError as error: 1483 raise Exception('Event File has unvalid weight. %s' % error) 1484 self.tag = self.tag[:start] + self.tag[stop+7:] 1485 return self.reweight_data
1486
1487 - def parse_nlo_weight(self, real_type=(1,11), threshold=None):
1488 """ """ 1489 if hasattr(self, 'nloweight'): 1490 return self.nloweight 1491 1492 start, stop = self.tag.find('<mgrwgt>'), self.tag.find('</mgrwgt>') 1493 if start != -1 != stop : 1494 1495 text = self.tag[start+8:stop] 1496 self.nloweight = NLO_PARTIALWEIGHT(text, self, real_type=real_type, 1497 threshold=threshold) 1498 return self.nloweight
1499
1500 - def rewrite_nlo_weight(self, wgt=None):
1501 """get the string associate to the weight""" 1502 1503 text="""<mgrwgt> 1504 %(total_wgt).10e %(nb_wgt)i %(nb_event)i 0 1505 %(event)s 1506 %(wgt)s 1507 </mgrwgt>""" 1508 1509 1510 if not wgt: 1511 if not hasattr(self, 'nloweight'): 1512 return 1513 wgt = self.nloweight 1514 1515 data = {'total_wgt': wgt.total_wgt, #need to check name and meaning, 1516 'nb_wgt': wgt.nb_wgt, 1517 'nb_event': wgt.nb_event, 1518 'event': '\n'.join(p.__str__(mode='fortran') for p in wgt.momenta), 1519 'wgt':'\n'.join(w.__str__(mode='formatted') 1520 for e in wgt.cevents for w in e.wgts)} 1521 1522 data['total_wgt'] = sum([w.ref_wgt for e in wgt.cevents for w in e.wgts]) 1523 start, stop = self.tag.find('<mgrwgt>'), self.tag.find('</mgrwgt>') 1524 1525 self.tag = self.tag[:start] + text % data + self.tag[stop+9:]
1526 1527
1528 - def parse_lo_weight(self):
1529 """ """ 1530 1531 1532 if hasattr(self, 'loweight'): 1533 return self.loweight 1534 1535 if not hasattr(Event, 'loweight_pattern'): 1536 Event.loweight_pattern = re.compile('''<rscale>\s*(?P<nqcd>\d+)\s+(?P<ren_scale>[\d.e+-]+)\s*</rscale>\s*\n\s* 1537 <asrwt>\s*(?P<asrwt>[\s\d.+-e]+)\s*</asrwt>\s*\n\s* 1538 <pdfrwt\s+beam=["']?(?P<idb1>1|2)["']?\>\s*(?P<beam1>[\s\d.e+-]*)\s*</pdfrwt>\s*\n\s* 1539 <pdfrwt\s+beam=["']?(?P<idb2>1|2)["']?\>\s*(?P<beam2>[\s\d.e+-]*)\s*</pdfrwt>\s*\n\s* 1540 <totfact>\s*(?P<totfact>[\d.e+-]*)\s*</totfact> 1541 ''',re.X+re.I+re.M) 1542 1543 start, stop = self.tag.find('<mgrwt>'), self.tag.find('</mgrwt>') 1544 1545 if start != -1 != stop : 1546 text = self.tag[start+8:stop] 1547 1548 info = Event.loweight_pattern.search(text) 1549 if not info: 1550 raise Exception('%s not parsed'% text) 1551 self.loweight={} 1552 self.loweight['n_qcd'] = int(info.group('nqcd')) 1553 self.loweight['ren_scale'] = float(info.group('ren_scale')) 1554 self.loweight['asrwt'] =[float(x) for x in info.group('asrwt').split()[1:]] 1555 self.loweight['tot_fact'] = float(info.group('totfact')) 1556 1557 if info.group('idb1') == info.group('idb2'): 1558 raise Exception('%s not parsed'% text) 1559 1560 if info.group('idb1') =="1": 1561 args = info.group('beam1').split() 1562 else: 1563 args = info.group('beam2').split() 1564 npdf = int(args[0]) 1565 self.loweight['n_pdfrw1'] = npdf 1566 self.loweight['pdf_pdg_code1'] = [int(i) for i in args[1:1+npdf]] 1567 self.loweight['pdf_x1'] = [float(i) for i in args[1+npdf:1+2*npdf]] 1568 self.loweight['pdf_q1'] = [float(i) for i in args[1+2*npdf:1+3*npdf]] 1569 if info.group('idb2') =="2": 1570 args = info.group('beam2').split() 1571 else: 1572 args = info.group('beam1').split() 1573 npdf = int(args[0]) 1574 self.loweight['n_pdfrw2'] = npdf 1575 self.loweight['pdf_pdg_code2'] = [int(i) for i in args[1:1+npdf]] 1576 self.loweight['pdf_x2'] = [float(i) for i in args[1+npdf:1+2*npdf]] 1577 self.loweight['pdf_q2'] = [float(i) for i in args[1+2*npdf:1+3*npdf]] 1578 1579 else: 1580 return None 1581 return self.loweight
1582 1583
1584 - def parse_matching_scale(self):
1585 """Parse the line containing the starting scale for the shower""" 1586 1587 if self.matched_scale_data is not None: 1588 return self.matched_scale_data 1589 1590 self.matched_scale_data = [] 1591 1592 1593 pattern = re.compile("<scales\s|</scales>") 1594 data = re.split(pattern,self.tag) 1595 if len(data) == 1: 1596 return [] 1597 else: 1598 tmp = {} 1599 start,content, end = data 1600 self.tag = "%s%s" % (start, end) 1601 pattern = re.compile("pt_clust_(\d*)=\"([\de+-.]*)\"") 1602 for id,value in pattern.findall(content): 1603 tmp[int(id)] = float(value) 1604 for i in range(1, len(self)+1): 1605 if i in tmp: 1606 self.matched_scale_data.append(tmp[i]) 1607 else: 1608 self.matched_scale_data.append(-1) 1609 return self.matched_scale_data
1610
1611 - def parse_syscalc_info(self):
1612 """ parse the flag for syscalc between <mgrwt></mgrwt> 1613 <mgrwt> 1614 <rscale> 3 0.26552898E+03</rscale> 1615 <asrwt>0</asrwt> 1616 <pdfrwt beam="1"> 1 21 0.14527945E+00 0.26552898E+03</pdfrwt> 1617 <pdfrwt beam="2"> 1 21 0.15249110E-01 0.26552898E+03</pdfrwt> 1618 <totfact> 0.10344054E+04</totfact> 1619 </mgrwt> 1620 """ 1621 if self.syscalc_data: 1622 return self.syscalc_data 1623 1624 pattern = re.compile("<mgrwt>|</mgrwt>") 1625 pattern2 = re.compile("<(?P<tag>[\w]*)(?:\s*(\w*)=[\"'](.*)[\"']\s*|\s*)>(.*)</(?P=tag)>") 1626 data = re.split(pattern,self.tag) 1627 if len(data) == 1: 1628 return [] 1629 else: 1630 tmp = {} 1631 start,content, end = data 1632 self.tag = "%s%s" % (start, end) 1633 for tag, key, keyval, tagval in pattern2.findall(content): 1634 if key: 1635 self.syscalc_data[(tag, key, keyval)] = tagval 1636 else: 1637 self.syscalc_data[tag] = tagval 1638 return self.syscalc_data
1639 1640
1641 - def add_decay_to_particle(self, position, decay_event):
1642 """define the decay of the particle id by the event pass in argument""" 1643 1644 this_particle = self[position] 1645 #change the status to internal particle 1646 this_particle.status = 2 1647 this_particle.helicity = 0 1648 1649 # some usefull information 1650 decay_particle = decay_event[0] 1651 this_4mom = FourMomentum(this_particle) 1652 nb_part = len(self) #original number of particle 1653 1654 thres = decay_particle.E*1e-10 1655 assert max(decay_particle.px, decay_particle.py, decay_particle.pz) < thres,\ 1656 "not on rest particle %s %s %s %s" % (decay_particle.E, decay_particle.px,decay_particle.py,decay_particle.pz) 1657 1658 self.nexternal += decay_event.nexternal -1 1659 old_scales = list(self.parse_matching_scale()) 1660 if old_scales: 1661 jet_position = sum(1 for i in range(position) if self[i].status==1) 1662 initial_pos = sum(1 for i in range(position) if self[i].status==-1) 1663 self.matched_scale_data.pop(initial_pos+jet_position) 1664 # add the particle with only handling the 4-momenta/mother 1665 # color information will be corrected later. 1666 for particle in decay_event[1:]: 1667 # duplicate particle to avoid border effect 1668 new_particle = Particle(particle, self) 1669 new_particle.event_id = len(self) 1670 self.append(new_particle) 1671 if old_scales: 1672 self.matched_scale_data.append(old_scales[initial_pos+jet_position]) 1673 # compute and assign the new four_momenta 1674 new_momentum = FourMomentum(new_particle).boost(this_4mom) 1675 new_particle.set_momentum(new_momentum) 1676 # compute the new mother 1677 for tag in ['mother1', 'mother2']: 1678 mother = getattr(particle, tag) 1679 if isinstance(mother, Particle): 1680 mother_id = getattr(particle, tag).event_id 1681 if mother_id == 0: 1682 setattr(new_particle, tag, this_particle) 1683 else: 1684 try: 1685 setattr(new_particle, tag, self[nb_part + mother_id -1]) 1686 except Exception as error: 1687 print(error) 1688 misc.sprint( self) 1689 misc.sprint(nb_part + mother_id -1) 1690 misc.sprint(tag) 1691 misc.sprint(position, decay_event) 1692 misc.sprint(particle) 1693 misc.sprint(len(self), nb_part + mother_id -1) 1694 raise 1695 elif tag == "mother2" and isinstance(particle.mother1, Particle): 1696 new_particle.mother2 = this_particle 1697 else: 1698 raise Exception("Something weird happens. Please report it for investigation") 1699 # Need to correct the color information of the particle 1700 # first find the first available color index 1701 max_color=501 1702 for particle in self[:nb_part]: 1703 max_color=max(max_color, particle.color1, particle.color2) 1704 1705 # define a color mapping and assign it: 1706 color_mapping = {} 1707 color_mapping[decay_particle.color1] = this_particle.color1 1708 color_mapping[decay_particle.color2] = this_particle.color2 1709 for particle in self[nb_part:]: 1710 if particle.color1: 1711 if particle.color1 not in color_mapping: 1712 max_color +=1 1713 color_mapping[particle.color1] = max_color 1714 particle.color1 = max_color 1715 else: 1716 particle.color1 = color_mapping[particle.color1] 1717 if particle.color2: 1718 if particle.color2 not in color_mapping: 1719 max_color +=1 1720 color_mapping[particle.color2] = max_color 1721 particle.color2 = max_color 1722 else: 1723 particle.color2 = color_mapping[particle.color2]
1724
1725 - def add_decays(self, pdg_to_decay):
1726 """use auto-recursion""" 1727 1728 pdg_to_decay = dict(pdg_to_decay) 1729 1730 for i,particle in enumerate(self): 1731 if particle.status != 1: 1732 continue 1733 if particle.pdg in pdg_to_decay and pdg_to_decay[particle.pdg]: 1734 one_decay = pdg_to_decay[particle.pdg].pop() 1735 self.add_decay_to_particle(i, one_decay) 1736 return self.add_decays(pdg_to_decay) 1737 return self
1738 1739 1740
1741 - def remove_decay(self, pdg_code=0, event_id=None):
1742 1743 to_remove = [] 1744 if event_id is not None: 1745 to_remove.append(self[event_id]) 1746 1747 if pdg_code: 1748 for particle in self: 1749 if particle.pid == pdg_code: 1750 to_remove.append(particle) 1751 1752 new_event = Event() 1753 # copy first line information + ... 1754 for tag in ['nexternal', 'ievent', 'wgt', 'aqcd', 'scale', 'aqed','tag','comment']: 1755 setattr(new_event, tag, getattr(self, tag)) 1756 1757 for particle in self: 1758 if isinstance(particle.mother1, Particle) and particle.mother1 in to_remove: 1759 to_remove.append(particle) 1760 if particle.status == 1: 1761 new_event.nexternal -= 1 1762 continue 1763 elif isinstance(particle.mother2, Particle) and particle.mother2 in to_remove: 1764 to_remove.append(particle) 1765 if particle.status == 1: 1766 new_event.nexternal -= 1 1767 continue 1768 else: 1769 new_event.append(Particle(particle)) 1770 1771 #ensure that the event_id is correct for all_particle 1772 # and put the status to 1 for removed particle 1773 for pos, particle in enumerate(new_event): 1774 particle.event_id = pos 1775 if particle in to_remove: 1776 particle.status = 1 1777 return new_event
1778
1779 - def get_decay(self, pdg_code=0, event_id=None):
1780 1781 to_start = [] 1782 if event_id is not None: 1783 to_start.append(self[event_id]) 1784 1785 elif pdg_code: 1786 for particle in self: 1787 if particle.pid == pdg_code: 1788 to_start.append(particle) 1789 break 1790 1791 new_event = Event() 1792 # copy first line information + ... 1793 for tag in ['ievent', 'wgt', 'aqcd', 'scale', 'aqed','tag','comment']: 1794 setattr(new_event, tag, getattr(self, tag)) 1795 1796 # Add the decaying particle 1797 old2new = {} 1798 new_decay_part = Particle(to_start[0]) 1799 new_decay_part.mother1 = None 1800 new_decay_part.mother2 = None 1801 new_decay_part.status = -1 1802 old2new[new_decay_part.event_id] = len(old2new) 1803 new_event.append(new_decay_part) 1804 1805 1806 # add the other particle 1807 for particle in self: 1808 if isinstance(particle.mother1, Particle) and particle.mother1.event_id in old2new\ 1809 or isinstance(particle.mother2, Particle) and particle.mother2.event_id in old2new: 1810 old2new[particle.event_id] = len(old2new) 1811 new_event.append(Particle(particle)) 1812 1813 #ensure that the event_id is correct for all_particle 1814 # and correct the mother1/mother2 by the new reference 1815 nexternal = 0 1816 for pos, particle in enumerate(new_event): 1817 particle.event_id = pos 1818 if particle.mother1: 1819 particle.mother1 = new_event[old2new[particle.mother1.event_id]] 1820 if particle.mother2: 1821 particle.mother2 = new_event[old2new[particle.mother2.event_id]] 1822 if particle.status in [-1,1]: 1823 nexternal +=1 1824 new_event.nexternal = nexternal 1825 1826 return new_event
1827
1828 - def boost(self, filter=None):
1829 """modify the current event to boost it according to the current filter""" 1830 if filter is None: 1831 filter = lambda p: p.status==-1 1832 1833 if not isinstance(filter, FourMomentum): 1834 pboost = FourMomentum() 1835 for p in self: 1836 if list(filter(p)): 1837 pboost += p 1838 else: 1839 pboost = FourMomentum(pboost) 1840 1841 # change sign of three-component due to helas convention 1842 pboost.px *=-1 1843 pboost.py *=-1 1844 pboost.pz *=-1 1845 for p in self: 1846 b= FourMomentum(p).boost(pboost) 1847 p.E, p.px, p.py, p.pz = b.E, b.px, b.py, b.pz 1848 1849 return self
1850
1851 - def check(self):
1852 """check various property of the events""" 1853 1854 # check that relative error is under control 1855 threshold = 1e-6 1856 1857 #1. Check that the 4-momenta are conserved 1858 E, px, py, pz = 0,0,0,0 1859 absE, abspx, abspy, abspz = 0,0,0,0 1860 for particle in self: 1861 coeff = 1 1862 if particle.status == -1: 1863 coeff = -1 1864 elif particle.status != 1: 1865 continue 1866 E += coeff * particle.E 1867 absE += abs(particle.E) 1868 px += coeff * particle.px 1869 py += coeff * particle.py 1870 pz += coeff * particle.pz 1871 abspx += abs(particle.px) 1872 abspy += abs(particle.py) 1873 abspz += abs(particle.pz) 1874 # check mass 1875 fourmass = FourMomentum(particle).mass 1876 1877 if particle.mass and (abs(particle.mass) - fourmass)/ abs(particle.mass) > threshold: 1878 raise Exception( "Do not have correct mass lhe: %s momentum: %s (error at %s" % (particle.mass, fourmass, (abs(particle.mass) - fourmass)/ abs(particle.mass))) 1879 1880 1881 if E/absE > threshold: 1882 logger.critical(self) 1883 raise Exception("Do not conserve Energy %s, %s" % (E/absE, E)) 1884 if px/abspx > threshold: 1885 logger.critical(self) 1886 raise Exception("Do not conserve Px %s, %s" % (px/abspx, px)) 1887 if py/abspy > threshold: 1888 logger.critical(self) 1889 raise Exception("Do not conserve Py %s, %s" % (py/abspy, py)) 1890 if pz/abspz > threshold: 1891 logger.critical(self) 1892 raise Exception("Do not conserve Pz %s, %s" % (pz/abspz, pz)) 1893 1894 #2. check the color of the event 1895 self.check_color_structure()
1896 1897 #3. check mass 1898 1899
1900 - def assign_scale_line(self, line):
1901 """read the line corresponding to global event line 1902 format of the line is: 1903 Nexternal IEVENT WEIGHT SCALE AEW AS 1904 """ 1905 inputs = line.split() 1906 assert len(inputs) == 6 1907 self.nexternal=int(inputs[0]) 1908 self.ievent=int(inputs[1]) 1909 self.wgt=float(inputs[2]) 1910 self.scale=float(inputs[3]) 1911 self.aqed=float(inputs[4]) 1912 self.aqcd=float(inputs[5])
1913
1914 - def get_tag_and_order(self):
1915 """Return the unique tag identifying the SubProcesses for the generation. 1916 Usefull for program like MadSpin and Reweight module.""" 1917 1918 initial, final, order = [], [], [[], []] 1919 for particle in self: 1920 if particle.status == -1: 1921 initial.append(particle.pid) 1922 order[0].append(particle.pid) 1923 elif particle.status == 1: 1924 final.append(particle.pid) 1925 order[1].append(particle.pid) 1926 initial.sort(), final.sort() 1927 tag = (tuple(initial), tuple(final)) 1928 return tag, order
1929 1930 @staticmethod
1931 - def mass_shuffle(momenta, sqrts, new_mass, new_sqrts=None):
1932 """use the RAMBO method to shuffle the PS. initial sqrts is preserved.""" 1933 1934 if not new_sqrts: 1935 new_sqrts = sqrts 1936 1937 oldm = [p.mass_sqr for p in momenta] 1938 newm = [m**2 for m in new_mass] 1939 tot_mom = sum(momenta, FourMomentum()) 1940 if tot_mom.pt2 > 1e-5: 1941 boost_back = FourMomentum(tot_mom.mass,0,0,0).boost_to_restframe(tot_mom) 1942 for i,m in enumerate(momenta): 1943 momenta[i] = m.boost_to_restframe(tot_mom) 1944 1945 # this is the equation 4.3 of RAMBO paper 1946 f = lambda chi: new_sqrts - sum(math.sqrt(max(0, M + chi**2*(p.E**2-m))) 1947 for M,p,m in zip(newm, momenta,oldm)) 1948 # this is the derivation of the function 1949 df = lambda chi: -1* sum(chi*(p.E**2-m)/math.sqrt(max(0,(p.E**2-m)*chi**2+M)) 1950 for M,p,m in zip(newm, momenta,oldm)) 1951 1952 if sum(new_mass) > new_sqrts: 1953 return momenta, 0 1954 try: 1955 chi = misc.newtonmethod(f, df, 1.0, error=1e-7,maxiter=1000) 1956 except: 1957 return momenta, 0 1958 # create the new set of momenta # eq. (4.2) 1959 new_momenta = [] 1960 for i,p in enumerate(momenta): 1961 new_momenta.append( 1962 FourMomentum(math.sqrt(newm[i]+chi**2*(p.E**2-oldm[i])), 1963 chi*p.px, chi*p.py, chi*p.pz)) 1964 1965 #if __debug__: 1966 # for i,p in enumerate(new_momenta): 1967 # misc.sprint(p.mass_sqr, new_mass[i]**2, i,p, momenta[i]) 1968 # assert p.mass_sqr == new_mass[i]**2 1969 1970 # compute the jacobian factor (eq. 4.9) 1971 jac = chi**(3*len(momenta)-3) 1972 jac *= reduce(operator.mul,[p.E/k.E for p,k in zip(momenta, new_momenta)],1) 1973 jac *= sum(p.norm_sq/p.E for p in momenta) 1974 jac /= sum(k.norm_sq/k.E for k in new_momenta) 1975 1976 # boost back the events in the lab-frame 1977 if tot_mom.pt2 > 1e-5: 1978 for i,m in enumerate(new_momenta): 1979 new_momenta[i] = m.boost_to_restframe(boost_back) 1980 return new_momenta, jac
1981 1982 1983 1984
1985 - def change_ext_mass(self, new_param_card):
1986 """routine to rescale the mass via RAMBO method. no internal mass preserve. 1987 sqrts is preserve (RAMBO algo) 1988 """ 1989 1990 old_momenta = [] 1991 new_masses = [] 1992 change_mass = False # check if we need to change the mass 1993 for part in self: 1994 if part.status == 1: 1995 old_momenta.append(FourMomentum(part)) 1996 new_masses.append(new_param_card.get_value('mass', abs(part.pid))) 1997 if not misc.equal(part.mass, new_masses[-1], 4, zero_limit=10): 1998 change_mass = True 1999 2000 if not change_mass: 2001 return 1 2002 2003 sqrts = self.sqrts 2004 2005 # apply the RAMBO algo 2006 new_mom, jac = self.mass_shuffle(old_momenta, sqrts, new_masses) 2007 2008 #modify the momenta of the particles: 2009 ind =0 2010 for part in self: 2011 if part.status==1: 2012 part.E, part.px, part.py, part.pz, part.mass = \ 2013 new_mom[ind].E, new_mom[ind].px, new_mom[ind].py, new_mom[ind].pz,new_mom[ind].mass 2014 ind+=1 2015 return jac
2016
2017 - def change_sqrts(self, new_sqrts):
2018 """routine to rescale the momenta to change the invariant mass""" 2019 2020 old_momenta = [] 2021 incoming = [] 2022 masses = [] 2023 for part in self: 2024 if part.status == -1: 2025 incoming.append(FourMomentum(part)) 2026 if part.status == 1: 2027 old_momenta.append(FourMomentum(part)) 2028 masses.append(part.mass) 2029 2030 p_init = FourMomentum() 2031 p_inits = [] 2032 n_init = 0 2033 for p in incoming: 2034 n_init +=1 2035 p_init += p 2036 p_inits.append(p) 2037 old_sqrts = p_init.mass 2038 2039 new_mom, jac = self.mass_shuffle(old_momenta, old_sqrts, masses, new_sqrts=new_sqrts) 2040 2041 #modify the momenta of the particles: 2042 ind =0 2043 for part in self: 2044 if part.status==1: 2045 part.E, part.px, part.py, part.pz, part.mass = \ 2046 new_mom[ind].E, new_mom[ind].px, new_mom[ind].py, new_mom[ind].pz,new_mom[ind].mass 2047 ind+=1 2048 2049 #change the initial state 2050 p_init = FourMomentum() 2051 for part in self: 2052 if part.status==1: 2053 p_init += part 2054 if n_init == 1: 2055 for part in self: 2056 if part.status == -1: 2057 part.E, part.px, part.py, part.pz = \ 2058 p_init.E, p_init.px, p_init.py, p_init.pz 2059 elif n_init ==2: 2060 if not misc.equal(p_init.px, 0) or not misc.equal(p_init.py, 0): 2061 raise Exception 2062 if not misc.equal(p_inits[0].px, 0) or not misc.equal(p_inits[0].py, 0): 2063 raise Exception 2064 #assume that initial energy is written as 2065 # p1 = (sqrts/2*exp(eta), 0, 0 , E1) 2066 # p2 = (sqrts/2*exp(-eta), 0, 0 , -E2) 2067 # keep eta fix 2068 eta = math.log(2*p_inits[0].E/old_sqrts) 2069 new_p = [[new_sqrts/2*math.exp(eta), 0., 0., new_sqrts/2*math.exp(eta)], 2070 [new_sqrts/2*math.exp(-eta), 0., 0., -new_sqrts/2*math.exp(-eta)]] 2071 2072 ind=0 2073 for part in self: 2074 if part.status == -1: 2075 part.E, part.px, part.py, part.pz = new_p[ind] 2076 ind+=1 2077 if ind ==2: 2078 break 2079 else: 2080 raise Exception 2081 2082 return jac
2083 2084
2085 - def get_helicity(self, get_order, allow_reversed=True):
2086 """return a list with the helicities in the order asked for""" 2087 2088 #avoid to modify the input 2089 order = [list(get_order[0]), list(get_order[1])] 2090 out = [9] *(len(order[0])+len(order[1])) 2091 for i, part in enumerate(self): 2092 if part.status == 1: #final 2093 try: 2094 ind = order[1].index(part.pid) 2095 except ValueError as error: 2096 if not allow_reversed: 2097 raise error 2098 else: 2099 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2100 try: 2101 return self.get_helicity(order, False) 2102 except ValueError: 2103 raise error 2104 position = len(order[0]) + ind 2105 order[1][ind] = 0 2106 elif part.status == -1: 2107 try: 2108 ind = order[0].index(part.pid) 2109 except ValueError as error: 2110 if not allow_reversed: 2111 raise error 2112 else: 2113 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2114 try: 2115 return self.get_helicity(order, False) 2116 except ValueError: 2117 raise error 2118 2119 position = ind 2120 order[0][ind] = 0 2121 else: #intermediate 2122 continue 2123 out[position] = int(part.helicity) 2124 return out
2125 2126
2127 - def check_color_structure(self):
2128 """check the validity of the color structure""" 2129 2130 #1. check that each color is raised only once. 2131 color_index = collections.defaultdict(int) 2132 for particle in self: 2133 if particle.status in [-1,1]: 2134 if particle.color1: 2135 color_index[particle.color1] +=1 2136 if -7 < particle.pdg < 0: 2137 raise Exception("anti-quark with color tag") 2138 if particle.color2: 2139 color_index[particle.color2] +=1 2140 if 7 > particle.pdg > 0: 2141 raise Exception("quark with anti-color tag") 2142 2143 2144 for key,value in color_index.items(): 2145 if value > 2: 2146 print(self) 2147 print(key, value) 2148 raise Exception('Wrong color_flow') 2149 2150 2151 #2. check that each parent present have coherent color-structure 2152 check = [] 2153 popup_index = [] #check that the popup index are created in a unique way 2154 for particle in self: 2155 mothers = [] 2156 childs = [] 2157 if particle.mother1: 2158 mothers.append(particle.mother1) 2159 if particle.mother2 and particle.mother2 is not particle.mother1: 2160 mothers.append(particle.mother2) 2161 if not mothers: 2162 continue 2163 if (particle.mother1.event_id, particle.mother2.event_id) in check: 2164 continue 2165 check.append((particle.mother1.event_id, particle.mother2.event_id)) 2166 2167 childs = [p for p in self if p.mother1 is particle.mother1 and \ 2168 p.mother2 is particle.mother2] 2169 2170 mcolors = [] 2171 manticolors = [] 2172 for m in mothers: 2173 if m.color1: 2174 if m.color1 in manticolors: 2175 manticolors.remove(m.color1) 2176 else: 2177 mcolors.append(m.color1) 2178 if m.color2: 2179 if m.color2 in mcolors: 2180 mcolors.remove(m.color2) 2181 else: 2182 manticolors.append(m.color2) 2183 ccolors = [] 2184 canticolors = [] 2185 for m in childs: 2186 if m.color1: 2187 if m.color1 in canticolors: 2188 canticolors.remove(m.color1) 2189 else: 2190 ccolors.append(m.color1) 2191 if m.color2: 2192 if m.color2 in ccolors: 2193 ccolors.remove(m.color2) 2194 else: 2195 canticolors.append(m.color2) 2196 for index in mcolors[:]: 2197 if index in ccolors: 2198 mcolors.remove(index) 2199 ccolors.remove(index) 2200 for index in manticolors[:]: 2201 if index in canticolors: 2202 manticolors.remove(index) 2203 canticolors.remove(index) 2204 2205 if mcolors != []: 2206 #only case is a epsilon_ijk structure. 2207 if len(canticolors) + len(mcolors) != 3: 2208 logger.critical(str(self)) 2209 raise Exception("Wrong color flow for %s -> %s" % ([m.pid for m in mothers], [c.pid for c in childs])) 2210 else: 2211 popup_index += canticolors 2212 elif manticolors != []: 2213 #only case is a epsilon_ijk structure. 2214 if len(ccolors) + len(manticolors) != 3: 2215 logger.critical(str(self)) 2216 raise Exception("Wrong color flow for %s -> %s" % ([m.pid for m in mothers], [c.pid for c in childs])) 2217 else: 2218 popup_index += ccolors 2219 2220 # Check that color popup (from epsilon_ijk) are raised only once 2221 if len(popup_index) != len(set(popup_index)): 2222 logger.critical(self) 2223 raise Exception("Wrong color flow: identical poping-up index, %s" % (popup_index))
2224
2225 - def __eq__(self, other):
2226 """two event are the same if they have the same momentum. other info are ignored""" 2227 2228 if other is None: 2229 return False 2230 if len(self) != len(other): 2231 return False 2232 2233 for i,p in enumerate(self): 2234 if p.E != other[i].E: 2235 return False 2236 elif p.pz != other[i].pz: 2237 return False 2238 elif p.px != other[i].px: 2239 return False 2240 elif p.py != other[i].py: 2241 return False 2242 return True
2243 2244
2245 - def __str__(self, event_id=''):
2246 """return a correctly formatted LHE event""" 2247 2248 out="""<event%(event_flag)s> 2249 %(scale)s 2250 %(particles)s 2251 %(comments)s 2252 %(tag)s 2253 %(reweight)s 2254 </event> 2255 """ 2256 if event_id not in ['', None]: 2257 self.eventflag['event'] = str(event_id) 2258 2259 if self.eventflag: 2260 event_flag = ' %s' % ' '.join('%s="%s"' % (k,v) for (k,v) in self.eventflag.items()) 2261 else: 2262 event_flag = '' 2263 2264 scale_str = "%2d %6d %+13.7e %14.8e %14.8e %14.8e" % \ 2265 (self.nexternal,self.ievent,self.wgt,self.scale,self.aqed,self.aqcd) 2266 2267 2268 if self.reweight_data: 2269 # check that all key have an order if not add them at the end 2270 if set(self.reweight_data.keys()) != set(self.reweight_order): 2271 self.reweight_order += [k for k in self.reweight_data.keys() \ 2272 if k not in self.reweight_order] 2273 2274 reweight_str = '<rwgt>\n%s\n</rwgt>' % '\n'.join( 2275 '<wgt id=\'%s\'> %+13.7e </wgt>' % (i, float(self.reweight_data[i])) 2276 for i in self.reweight_order if i in self.reweight_data) 2277 else: 2278 reweight_str = '' 2279 2280 tag_str = self.tag 2281 if hasattr(self, 'nloweight') and self.nloweight.modified: 2282 self.rewrite_nlo_weight() 2283 tag_str = self.tag 2284 2285 if self.matched_scale_data: 2286 tmp_scale = ' '.join(['pt_clust_%i=\"%s\"' % (i+1,v) 2287 for i,v in enumerate(self.matched_scale_data) 2288 if v!=-1]) 2289 if tmp_scale: 2290 tag_str = "<scales %s></scales>%s" % (tmp_scale, self.tag) 2291 2292 if self.syscalc_data: 2293 keys= ['rscale', 'asrwt', ('pdfrwt', 'beam', '1'), ('pdfrwt', 'beam', '2'), 2294 'matchscale', 'totfact'] 2295 sys_str = "<mgrwt>\n" 2296 template = """<%(key)s%(opts)s>%(values)s</%(key)s>\n""" 2297 for k in keys: 2298 if k not in self.syscalc_data: 2299 continue 2300 replace = {} 2301 replace['values'] = self.syscalc_data[k] 2302 if isinstance(k, (str, six.text_type)): 2303 replace['key'] = k 2304 replace['opts'] = '' 2305 else: 2306 replace['key'] = k[0] 2307 replace['opts'] = ' %s=\"%s\"' % (k[1],k[2]) 2308 sys_str += template % replace 2309 sys_str += "</mgrwt>\n" 2310 reweight_str = sys_str + reweight_str 2311 2312 out = out % {'event_flag': event_flag, 2313 'scale': scale_str, 2314 'particles': '\n'.join([str(p) for p in self]), 2315 'tag': tag_str, 2316 'comments': self.comment, 2317 'reweight': reweight_str} 2318 2319 return re.sub('[\n]+', '\n', out)
2320
2321 - def get_momenta(self, get_order, allow_reversed=True):
2322 """return the momenta vector in the order asked for""" 2323 2324 #avoid to modify the input 2325 order = [list(get_order[0]), list(get_order[1])] 2326 out = [''] *(len(order[0])+len(order[1])) 2327 for i, part in enumerate(self): 2328 if part.status == 1: #final 2329 try: 2330 ind = order[1].index(part.pid) 2331 except ValueError as error: 2332 if not allow_reversed: 2333 raise error 2334 else: 2335 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2336 try: 2337 return self.get_momenta_str(order, False) 2338 except ValueError: 2339 raise error 2340 position = len(order[0]) + ind 2341 order[1][ind] = 0 2342 elif part.status == -1: 2343 try: 2344 ind = order[0].index(part.pid) 2345 except ValueError as error: 2346 if not allow_reversed: 2347 raise error 2348 else: 2349 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2350 try: 2351 return self.get_momenta_str(order, False) 2352 except ValueError: 2353 raise error 2354 2355 position = ind 2356 order[0][ind] = 0 2357 else: #intermediate 2358 continue 2359 2360 out[position] = (part.E, part.px, part.py, part.pz) 2361 2362 return out
2363 2364
2365 - def get_scale(self,type):
2366 2367 if type == 1: 2368 return self.get_et_scale() 2369 elif type == 2: 2370 return self.get_ht_scale() 2371 elif type == 3: 2372 return self.get_ht_scale(prefactor=0.5) 2373 elif type == 4: 2374 return self.get_sqrts_scale() 2375 elif type == -1: 2376 return self.get_ht_scale(prefactor=0.5)
2377 2378
2379 - def get_ht_scale(self, prefactor=1):
2380 2381 scale = 0 2382 for particle in self: 2383 if particle.status != 1: 2384 continue 2385 p=FourMomentum(particle) 2386 scale += math.sqrt(p.mass_sqr + p.pt**2) 2387 2388 return prefactor * scale
2389 2390
2391 - def get_et_scale(self, prefactor=1):
2392 2393 scale = 0 2394 for particle in self: 2395 if particle.status != 1: 2396 continue 2397 p = FourMomentum(particle) 2398 pt = p.pt 2399 if (pt>0): 2400 scale += p.E*pt/math.sqrt(pt**2+p.pz**2) 2401 2402 return prefactor * scale
2403 2404 @property
2405 - def sqrts(self):
2406 return self.get_sqrts_scale(1)
2407
2408 - def get_sqrts_scale(self, prefactor=1):
2409 2410 scale = 0 2411 init = [] 2412 for particle in self: 2413 if particle.status == -1: 2414 init.append(FourMomentum(particle)) 2415 if len(init) == 1: 2416 return init[0].mass 2417 elif len(init)==2: 2418 return math.sqrt((init[0]+init[1])**2)
2419 2420 2421 2422
2423 - def get_momenta_str(self, get_order, allow_reversed=True):
2424 """return the momenta str in the order asked for""" 2425 2426 out = self.get_momenta(get_order, allow_reversed) 2427 #format 2428 format = '%.12f' 2429 format_line = ' '.join([format]*4) + ' \n' 2430 out = [format_line % one for one in out] 2431 out = ''.join(out).replace('e','d') 2432 return out
2433
2434 -class FourMomentum(object):
2435 """a convenient object for 4-momenta operation""" 2436
2437 - def __init__(self, obj=0, px=0, py=0, pz=0, E=0):
2438 """initialize the four momenta""" 2439 2440 if obj == 0 and E: 2441 obj = E 2442 2443 if isinstance(obj, (FourMomentum, Particle)): 2444 px = obj.px 2445 py = obj.py 2446 pz = obj.pz 2447 E = obj.E 2448 elif isinstance(obj, (list, tuple)): 2449 assert len(obj) ==4 2450 E = obj[0] 2451 px = obj[1] 2452 py = obj[2] 2453 pz = obj[3] 2454 elif isinstance(obj, (str, six.text_type)): 2455 obj = [float(i) for i in obj.split()] 2456 assert len(obj) ==4 2457 E = obj[0] 2458 px = obj[1] 2459 py = obj[2] 2460 pz = obj[3] 2461 else: 2462 E =obj 2463 2464 2465 self.E = float(E) 2466 self.px = float(px) 2467 self.py = float(py) 2468 self.pz = float(pz)
2469 2470 @property
2471 - def mass(self):
2472 """return the mass""" 2473 return math.sqrt(max(self.E**2 - self.px**2 - self.py**2 - self.pz**2,0))
2474 2475 @property
2476 - def mass_sqr(self):
2477 """return the mass square""" 2478 return self.E**2 - self.px**2 - self.py**2 - self.pz**2
2479 2480 @property
2481 - def pt(self):
2482 return math.sqrt(max(0, self.pt2))
2483 2484 @property
2485 - def pseudorapidity(self):
2486 norm = math.sqrt(self.px**2 + self.py**2+self.pz**2) 2487 return 0.5* math.log((norm - self.pz) / (norm + self.pz))
2488 2489 @property
2490 - def rapidity(self):
2491 return 0.5* math.log((self.E +self.pz) / (self.E - self.pz))
2492 2493 2494 @property
2495 - def pt2(self):
2496 """ return the pt square """ 2497 2498 return self.px**2 + self.py**2
2499 2500 @property
2501 - def norm(self):
2502 """ return |\vec p| """ 2503 return math.sqrt(self.px**2 + self.py**2 + self.pz**2)
2504 2505 @property
2506 - def norm_sq(self):
2507 """ return |\vec p|^2 """ 2508 return self.px**2 + self.py**2 + self.pz**2
2509 2510 @property
2511 - def theta(self):
2512 """return the mass square""" 2513 import math 2514 return math.atan(math.sqrt((self.px**2+self.py**2)/self.pz**2))
2515 2516
2517 - def __add__(self, obj):
2518 2519 assert isinstance(obj, FourMomentum) 2520 new = FourMomentum(self.E+obj.E, 2521 self.px + obj.px, 2522 self.py + obj.py, 2523 self.pz + obj.pz) 2524 return new
2525
2526 - def __iadd__(self, obj):
2527 """update the object with the sum""" 2528 self.E += obj.E 2529 self.px += obj.px 2530 self.py += obj.py 2531 self.pz += obj.pz 2532 return self
2533
2534 - def __sub__(self, obj):
2535 2536 assert isinstance(obj, FourMomentum) 2537 new = FourMomentum(self.E-obj.E, 2538 self.px - obj.px, 2539 self.py - obj.py, 2540 self.pz - obj.pz) 2541 return new
2542
2543 - def __isub__(self, obj):
2544 """update the object with the sum""" 2545 self.E -= obj.E 2546 self.px -= obj.px 2547 self.py -= obj.py 2548 self.pz -= obj.pz 2549 return self
2550
2551 - def __mul__(self, obj):
2552 if isinstance(obj, FourMomentum): 2553 return self.E*obj.E - self.px *obj.px - self.py * obj.py - self.pz * obj.pz 2554 elif isinstance(obj, (float, int)): 2555 return FourMomentum(obj*self.E,obj*self.px,obj*self.py,obj*self.pz ) 2556 else: 2557 raise NotImplemented
2558 __rmul__ = __mul__ 2559
2560 - def __pow__(self, power):
2561 assert power in [1,2] 2562 2563 if power == 1: 2564 return FourMomentum(self) 2565 elif power == 2: 2566 return self.mass_sqr
2567
2568 - def __repr__(self):
2569 return 'FourMomentum(%s,%s,%s,%s)' % (self.E, self.px, self.py,self.pz)
2570
2571 - def __str__(self, mode='python'):
2572 if mode == 'python': 2573 return self.__repr__() 2574 elif mode == 'fortran': 2575 return '%.10e %.10e %.10e %.10e' % self.get_tuple()
2576
2577 - def get_tuple(self):
2578 return (self.E, self.px, self.py,self.pz)
2579
2580 - def boost(self, mom):
2581 """mom 4-momenta is suppose to be given in the rest frame of this 4-momenta. 2582 the output is the 4-momenta in the frame of this 4-momenta 2583 function copied from HELAS routine. 2584 if the current momenta is (E,\vec{p}), in order to go to the rest frame 2585 of the current particle, mom should be (E, -\vec{p}) 2586 """ 2587 2588 pnorm = mom.px**2 + mom.py**2 + mom.pz**2 2589 if pnorm: 2590 s3product = self.px * mom.px + self.py * mom.py + self.pz * mom.pz 2591 mass = mom.mass 2592 lf = (self.E + (mom.E - mass) * s3product / pnorm ) / mass 2593 return FourMomentum(E=(self.E*mom.E+s3product)/mass, 2594 px=self.px + mom.px * lf, 2595 py=self.py + mom.py * lf, 2596 pz=self.pz + mom.pz * lf) 2597 else: 2598 return FourMomentum(mom)
2599
2600 - def zboost(self, pboost=None, E=0, pz=0):
2601 """Both momenta should be in the same frame. 2602 The boost perform correspond to the boost required to set pboost at 2603 rest (only z boost applied). 2604 """ 2605 if isinstance(pboost, FourMomentum): 2606 E = pboost.E 2607 pz = pboost.pz 2608 2609 #beta = pz/E 2610 gamma = E / math.sqrt(E**2-pz**2) 2611 gammabeta = pz / math.sqrt(E**2-pz**2) 2612 2613 out = FourMomentum([gamma*self.E - gammabeta*self.pz, 2614 self.px, 2615 self.py, 2616 gamma*self.pz - gammabeta*self.E]) 2617 2618 if abs(out.pz) < 1e-6 * out.E: 2619 out.pz = 0 2620 return out
2621
2622 - def boost_to_restframe(self, pboost):
2623 """apply the boost transformation such that pboost is at rest in the new frame. 2624 First apply a rotation to allign the pboost to the z axis and then use 2625 zboost routine (see above) 2626 """ 2627 2628 if pboost.px == 0 == pboost.py: 2629 out = self.zboost(E=pboost.E,pz=pboost.pz) 2630 return out 2631 2632 2633 # write pboost as (E, p cosT sinF, p sinT sinF, p cosF) 2634 # rotation such that it become (E, 0 , 0 , p ) is 2635 # cosT sinF , -sinT , cosT sinF 2636 # sinT cosF , cosT , sinT sinF 2637 # -sinT , 0 , cosF 2638 p = math.sqrt( pboost.px**2 + pboost.py**2+ pboost.pz**2) 2639 cosF = pboost.pz / p 2640 sinF = math.sqrt(1-cosF**2) 2641 sinT = pboost.py/p/sinF 2642 cosT = pboost.px/p/sinF 2643 2644 out=FourMomentum([self.E, 2645 self.px*cosT*cosF + self.py*sinT*cosF-self.pz*sinF, 2646 -self.px*sinT+ self.py*cosT, 2647 self.px*cosT*sinF + self.py*sinT*sinF + self.pz*cosF 2648 ]) 2649 out = out.zboost(E=pboost.E,pz=p) 2650 return out
2651
2652 2653 2654 2655 -class OneNLOWeight(object):
2656
2657 - def __init__(self, input, real_type=(1,11)):
2658 """ """ 2659 2660 self.real_type = real_type 2661 if isinstance(input, (str, six.text_type)): 2662 self.parse(input)
2663
2664 - def __str__(self, mode='display'):
2665 2666 if mode == 'display': 2667 out = """ pwgt: %(pwgt)s 2668 born, real : %(born)s %(real)s 2669 pdgs : %(pdgs)s 2670 bjks : %(bjks)s 2671 scales**2, gs: %(scales2)s %(gs)s 2672 born/real related : %(born_related)s %(real_related)s 2673 type / nfks : %(type)s %(nfks)s 2674 to merge : %(to_merge_pdg)s in %(merge_new_pdg)s 2675 ref_wgt : %(ref_wgt)s""" % self.__dict__ 2676 return out 2677 elif mode == 'formatted': 2678 format_var = [] 2679 variable = [] 2680 2681 def to_add_full(f, v, format_var, variable): 2682 """ function to add to the formatted output""" 2683 if isinstance(v, list): 2684 format_var += [f]*len(v) 2685 variable += v 2686 else: 2687 format_var.append(f) 2688 variable.append(v)
2689 to_add = lambda x,y: to_add_full(x,y, format_var, variable) 2690 #set the formatting 2691 to_add('%.10e', [p*self.bias_wgt for p in self.pwgt]) 2692 to_add('%.10e', self.born) 2693 to_add('%.10e', self.real) 2694 to_add('%i', self.nexternal) 2695 to_add('%i', self.pdgs) 2696 to_add('%i', self.qcdpower) 2697 to_add('%.10e', self.bjks) 2698 to_add('%.10e', self.scales2) 2699 to_add('%.10e', self.gs) 2700 to_add('%i', [self.born_related, self.real_related]) 2701 to_add('%i' , [self.type, self.nfks]) 2702 to_add('%i' , self.to_merge_pdg) 2703 to_add('%i', self.merge_new_pdg) 2704 to_add('%.10e', self.ref_wgt*self.bias_wgt) 2705 to_add('%.10e', self.bias_wgt) 2706 return ' '.join(format_var) % tuple(variable)
2707 2708
2709 - def parse(self, text, keep_bias=False):
2710 """parse the line and create the related object. 2711 keep bias allow to not systematically correct for the bias in the written information""" 2712 #0.546601845792D+00 0.000000000000D+00 0.000000000000D+00 0.119210435309D+02 0.000000000000D+00 5 -1 2 -11 12 21 0 0.24546101D-01 0.15706890D-02 0.12586055D+04 0.12586055D+04 0.12586055D+04 1 2 2 2 5 2 2 0.539995789976D+04 2713 #0.274922677249D+01 0.000000000000D+00 0.000000000000D+00 0.770516514633D+01 0.113763730192D+00 5 21 2 -11 12 1 2 0.52500539D-02 0.30205908D+00 0.45444066D+04 0.45444066D+04 0.45444066D+04 0.12520062D+01 1 2 1 3 5 1 -1 0.110944218997D+05 2714 # below comment are from Rik description email 2715 data = text.split() 2716 # 1. The first three doubles are, as before, the 'wgt', i.e., the overall event of this 2717 # contribution, and the ones multiplying the log[mu_R/QES] and the log[mu_F/QES] 2718 # stripped of alpha_s and the PDFs. 2719 # from example: 0.274922677249D+01 0.000000000000D+00 0.000000000000D+00 2720 self.pwgt = [float(f) for f in data[:3]] 2721 # 2. The next two doubles are the values of the (corresponding) Born and 2722 # real-emission matrix elements. You can either use these values to check 2723 # that the newly computed original matrix element weights are correct, 2724 # or directly use these so that you don't have to recompute the original weights. 2725 # For contributions for which the real-emission matrix elements were 2726 # not computed, the 2nd of these numbers is zero. The opposite is not true, 2727 # because each real-emission phase-space configuration has an underlying Born one 2728 # (this is not unique, but on our code we made a specific choice here). 2729 # This latter information is useful if the real-emission matrix elements 2730 # are unstable; you can then reweight with the Born instead. 2731 # (see also point 9 below, where the momentum configurations are assigned). 2732 # I don't think this instability is real problem when reweighting the real-emission 2733 # with tree-level matrix elements (as we generally would do), but is important 2734 # when reweighting with loop-squared contributions as we have been doing for gg->H. 2735 # (I'm not sure that reweighting tree-level with loop^2 is something that 2736 # we can do in general, because we don't really know what to do with the 2737 # virtual matrix elements because we cannot generate 2-loop diagrams.) 2738 # from example: 0.770516514633D+01 0.113763730192D+00 2739 self.born = float(data[3]) 2740 self.real = float(data[4]) 2741 # 3. integer: number of external particles of the real-emission configuration (as before) 2742 # from example: 5 2743 self.nexternal = int(data[5]) 2744 # 4. PDG codes corresponding to the real-emission configuration (as before) 2745 # from example: 21 2 -11 12 1 2 2746 self.pdgs = [int(i) for i in data[6:6+self.nexternal]] 2747 flag = 6+self.nexternal # new starting point for the position 2748 # 5. next integer is the power of g_strong in the matrix elements (as before) 2749 # from example: 2 2750 self.qcdpower = int(data[flag]) 2751 # 6. 2 doubles: The bjorken x's used for this contribution (as before) 2752 # from example: 0.52500539D-02 0.30205908D+00 2753 self.bjks = [float(f) for f in data[flag+1:flag+3]] 2754 # 7. 3 doubles: The Ellis-sexton scale, the renormalisation scale and the factorisation scale, all squared, used for this contribution (as before) 2755 # from example: 0.45444066D+04 0.45444066D+04 0.45444066D+04 2756 self.scales2 = [float(f) for f in data[flag+3:flag+6]] 2757 # 8.the value of g_strong 2758 # from example: 0.12520062D+01 2759 self.gs = float(data[flag+6]) 2760 # 9. 2 integers: the corresponding Born and real-emission type kinematics. (in the list of momenta) 2761 # Note that also the Born-kinematics has n+1 particles, with, in general, 2762 # one particle with zero momentum (this is not ALWAYS the case, 2763 # there could also be 2 particles with perfectly collinear momentum). 2764 # To convert this from n+1 to a n particles, you have to sum the momenta 2765 # of the two particles that 'merge', see point 12 below. 2766 # from example: 1 2 2767 self.born_related = int(data[flag+7]) 2768 self.real_related = int(data[flag+8]) 2769 # 10. 1 integer: the 'type'. This is the information you should use to determine 2770 # if to reweight with Born, virtual or real-emission matrix elements. 2771 # (Apart from the possible problems with complicated real-emission matrix elements 2772 # that need to be computed very close to the soft/collinear limits, see point 2 above. 2773 # I guess that for tree-level this is always okay, but when reweighting 2774 # a tree-level contribution with a one-loop squared one, as we do 2775 # for gg->Higgs, this is important). 2776 # type=1 : real-emission: 2777 # type=2 : Born: 2778 # type=3 : integrated counter terms: 2779 # type=4 : soft counter-term: 2780 # type=5 : collinear counter-term: 2781 # type=6 : soft-collinear counter-term: 2782 # type=7 : O(alphaS) expansion of Sudakov factor for NNLL+NLO: 2783 # type=8 : soft counter-term (with n+1-body kin.): 2784 # type=9 : collinear counter-term (with n+1-body kin.): 2785 # type=10: soft-collinear counter-term (with n+1-body kin.): 2786 # type=11: real-emission (with n-body kin.): 2787 # type=12: MC subtraction with n-body kin.: 2788 # type=13: MC subtraction with n+1-body kin.: 2789 # type=14: virtual corrections minus approximate virtual 2790 # type=15: approximate virtual corrections: 2791 # from example: 1 2792 self.type = int(data[flag+9]) 2793 # 11. 1 integer: The FKS configuration for this contribution (not really 2794 # relevant for anything, but is used in checking the reweighting to 2795 # get scale & PDF uncertainties). 2796 # from example: 3 2797 self.nfks = int(data[flag+10]) 2798 # 12. 2 integers: the two particles that should be merged to form the 2799 # born contribution from the real-emission one. Remove these two particles 2800 # from the (ordered) list of PDG codes, and insert a newly created particle 2801 # at the location of the minimum of the two particles removed. 2802 # I.e., if you merge particles 2 and 4, you have to insert the new particle 2803 # as the 2nd particle. And particle 5 and above will be shifted down by one. 2804 # from example: 5 1 2805 self.to_merge_pdg = [int (f) for f in data[flag+11:flag+13]] 2806 # 13. 1 integer: the PDG code of the particle that is created after merging the two particles at point 12. 2807 # from example -1 2808 self.merge_new_pdg = int(data[flag+13]) 2809 # 14. 1 double: the reference number that one should be able to reconstruct 2810 # form the weights (point 1 above) and the rest of the information of this line. 2811 # This is really the contribution to this event as computed by the code 2812 # (and is passed to the integrator). It contains everything. 2813 # from example: 0.110944218997D+05 2814 self.ref_wgt = float(data[flag+14]) 2815 # 15. The bias weight. This weight is included in the self.ref_wgt, as well as in 2816 # the self.pwgt. However, it is already removed from the XWGTUP (and 2817 # scale/pdf weights). That means that in practice this weight is not used. 2818 try: 2819 self.bias_wgt = float(data[flag+15]) 2820 except IndexError: 2821 self.bias_wgt = 1.0 2822 2823 if not keep_bias: 2824 self.ref_wgt /= self.bias_wgt 2825 self.pwgt = [p/self.bias_wgt for p in self.pwgt] 2826 2827 #check the momenta configuration linked to the event 2828 if self.type in self.real_type: 2829 self.momenta_config = self.real_related 2830 else: 2831 self.momenta_config = self.born_related
2832
2833 2834 -class NLO_PARTIALWEIGHT(object):
2835
2836 - class BasicEvent(list):
2837 2838
2839 - def __init__(self, momenta, wgts, event, real_type=(1,11)):
2840 2841 list.__init__(self, momenta) 2842 assert self 2843 self.soft = False 2844 self.wgts = wgts 2845 self.pdgs = list(wgts[0].pdgs) 2846 self.event = event 2847 self.real_type = real_type 2848 2849 if wgts[0].momenta_config == wgts[0].born_related: 2850 # need to remove one momenta. 2851 ind1, ind2 = [ind-1 for ind in wgts[0].to_merge_pdg] 2852 if ind1> ind2: 2853 ind1, ind2 = ind2, ind1 2854 if ind1 >= sum(1 for p in event if p.status==-1): 2855 new_p = self[ind1] + self[ind2] 2856 else: 2857 new_p = self[ind1] - self[ind2] 2858 self.pop(ind1) 2859 self.insert(ind1, new_p) 2860 self.pop(ind2) 2861 self.pdgs.pop(ind1) 2862 self.pdgs.insert(ind1, wgts[0].merge_new_pdg ) 2863 self.pdgs.pop(ind2) 2864 # DO NOT update the pdgs of the partial weight! 2865 2866 elif any(w.type in self.real_type for w in wgts): 2867 if any(w.type not in self.real_type for w in wgts): 2868 raise Exception 2869 # Do nothing !!! 2870 # previously (commented we were checking here if the particle 2871 # were too soft this is done later now 2872 # The comment line below allow to convert this event 2873 # to a born one (old method) 2874 # self.pop(ind1) 2875 # self.insert(ind1, new_p) 2876 # self.pop(ind2) 2877 # self.pdgs.pop(ind1) 2878 # self.pdgs.insert(ind1, wgts[0].merge_new_pdg ) 2879 # self.pdgs.pop(ind2) 2880 # # DO NOT update the pdgs of the partial weight! 2881 else: 2882 raise Exception
2883
2884 - def check_fks_singularity(self, ind1, ind2, nb_init=2, threshold=None):
2885 """check that the propagator associated to ij is not too light 2886 [related to soft-collinear singularity]""" 2887 2888 if threshold is None: 2889 threshold = 1e-8 2890 2891 if ind1> ind2: 2892 ind1, ind2 = ind2, ind1 2893 if ind1 >= nb_init: 2894 new_p = self[ind1] + self[ind2] 2895 else: 2896 new_p = self[ind1] - self[ind2] 2897 2898 inv_mass = new_p.mass_sqr 2899 if nb_init == 2: 2900 shat = (self[0]+self[1]).mass_sqr 2901 else: 2902 shat = self[0].mass_sqr 2903 2904 2905 if (abs(inv_mass)/shat < threshold): 2906 return True 2907 else: 2908 return False
2909 2910
2911 - def get_pdg_code(self):
2912 return self.pdgs
2913
2914 - def get_tag_and_order(self):
2915 """ return the tag and order for this basic event""" 2916 (initial, _), _ = self.event.get_tag_and_order() 2917 order = self.get_pdg_code() 2918 2919 2920 initial, out = order[:len(initial)], order[len(initial):] 2921 initial.sort() 2922 out.sort() 2923 return (tuple(initial), tuple(out)), order
2924
2925 - def get_momenta(self, get_order, allow_reversed=True):
2926 """return the momenta vector in the order asked for""" 2927 2928 #avoid to modify the input 2929 order = [list(get_order[0]), list(get_order[1])] 2930 out = [''] *(len(order[0])+len(order[1])) 2931 pdgs = self.get_pdg_code() 2932 for pos, part in enumerate(self): 2933 if pos < len(get_order[0]): #initial 2934 try: 2935 ind = order[0].index(pdgs[pos]) 2936 except ValueError as error: 2937 if not allow_reversed: 2938 raise error 2939 else: 2940 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2941 try: 2942 return self.get_momenta(order, False) 2943 except ValueError: 2944 raise error 2945 2946 2947 position = ind 2948 order[0][ind] = 0 2949 else: #final 2950 try: 2951 ind = order[1].index(pdgs[pos]) 2952 except ValueError as error: 2953 if not allow_reversed: 2954 raise error 2955 else: 2956 order = [[-i for i in get_order[0]],[-i for i in get_order[1]]] 2957 try: 2958 return self.get_momenta(order, False) 2959 except ValueError: 2960 raise error 2961 position = len(order[0]) + ind 2962 order[1][ind] = 0 2963 2964 out[position] = (part.E, part.px, part.py, part.pz) 2965 2966 return out
2967 2968
2969 - def get_helicity(self, *args):
2970 return [9] * len(self)
2971 2972 @property
2973 - def aqcd(self):
2974 return self.event.aqcd
2975
2976 - def get_ht_scale(self, prefactor=1):
2977 2978 scale = 0 2979 for particle in self: 2980 p = particle 2981 scale += math.sqrt(max(0, p.mass_sqr + p.pt**2)) 2982 2983 return prefactor * scale
2984
2985 - def get_et_scale(self, prefactor=1):
2986 2987 scale = 0 2988 for particle in self: 2989 p = particle 2990 pt = p.pt 2991 if (pt>0): 2992 scale += p.E*pt/math.sqrt(pt**2+p.pz**2) 2993 2994 return prefactor * scale
2995 2996
2997 - def get_sqrts_scale(self, event,prefactor=1):
2998 2999 scale = 0 3000 nb_init = 0 3001 for particle in event: 3002 if particle.status == -1: 3003 nb_init+=1 3004 if nb_init == 1: 3005 return self[0].mass 3006 elif nb_init==2: 3007 return math.sqrt((self[0]+self[1])**2)
3008 3009 3010 3011
3012 - def __init__(self, input, event, real_type=(1,11), threshold=None):
3013 3014 self.real_type = real_type 3015 self.event = event 3016 self.total_wgt = 0. 3017 self.nb_event = 0 3018 self.nb_wgts = 0 3019 self.threshold = threshold 3020 self.modified = False #set on True if we decide to change internal infor 3021 # that need to be written in the event file. 3022 #need to be set manually when this is the case 3023 if isinstance(input, (str,six.text_type)): 3024 self.parse(input)
3025 3026 3027
3028 - def parse(self, text):
3029 """create the object from the string information (see example below)""" 3030 #0.2344688900d+00 8 2 0 3031 #0.4676614699d+02 0.0000000000d+00 0.0000000000d+00 0.4676614699d+02 3032 #0.4676614699d+02 0.0000000000d+00 0.0000000000d+00 -.4676614699d+02 3033 #0.4676614699d+02 0.2256794794d+02 0.4332148227d+01 0.4073073437d+02 3034 #0.4676614699d+02 -.2256794794d+02 -.4332148227d+01 -.4073073437d+02 3035 #0.0000000000d+00 -.0000000000d+00 -.0000000000d+00 -.0000000000d+00 3036 #0.4780341163d+02 0.0000000000d+00 0.0000000000d+00 0.4780341163d+02 3037 #0.4822581633d+02 0.0000000000d+00 0.0000000000d+00 -.4822581633d+02 3038 #0.4729127470d+02 0.2347155377d+02 0.5153455534d+01 0.4073073437d+02 3039 #0.4627255267d+02 -.2167412893d+02 -.3519736379d+01 -.4073073437d+02 3040 #0.2465400591d+01 -.1797424844d+01 -.1633719155d+01 -.4224046944d+00 3041 #0.473706252575d-01 0.000000000000d+00 0.000000000000d+00 5 -3 3 -11 11 21 0 0.11849903d-02 0.43683926d-01 0.52807978d+03 0.52807978d+03 0.52807978d+03 1 2 1 0.106660059627d+03 3042 #-.101626389492d-02 0.000000000000d+00 -.181915673961d-03 5 -3 3 -11 11 21 2 0.11849903d-02 0.43683926d-01 0.52807978d+03 0.52807978d+03 0.52807978d+03 1 3 1 -.433615206719d+01 3043 #0.219583436285d-02 0.000000000000d+00 0.000000000000d+00 5 -3 3 -11 11 21 2 0.11849903d-02 0.43683926d-01 0.52807978d+03 0.52807978d+03 0.52807978d+03 1 15 1 0.936909375537d+01 3044 #0.290043597283d-03 0.000000000000d+00 0.000000000000d+00 5 -3 3 -11 11 21 2 0.12292838d-02 0.43683926d-01 0.58606724d+03 0.58606724d+03 0.58606724d+03 1 12 1 0.118841547979d+01 3045 #-.856330613460d-01 0.000000000000d+00 0.000000000000d+00 5 -3 3 -11 11 21 2 0.11849903d-02 0.43683926d-01 0.52807978d+03 0.52807978d+03 0.52807978d+03 1 4 1 -.365375546483d+03 3046 #0.854918237609d-01 0.000000000000d+00 0.000000000000d+00 5 -3 3 -11 11 21 2 0.12112732d-02 0.45047393d-01 0.58606724d+03 0.58606724d+03 0.58606724d+03 2 11 1 0.337816057347d+03 3047 #0.359257891118d-05 0.000000000000d+00 0.000000000000d+00 5 21 3 -11 11 3 2 0.12292838d-02 0.43683926d-01 0.58606724d+03 0.58606724d+03 0.58606724d+03 1 12 3 0.334254554762d+00 3048 #0.929944817736d-03 0.000000000000d+00 0.000000000000d+00 5 21 3 -11 11 3 2 0.12112732d-02 0.45047393d-01 0.58606724d+03 0.58606724d+03 0.58606724d+03 2 11 3 0.835109616010d+02 3049 3050 3051 text = text.lower().replace('d','e') 3052 all_line = text.split('\n') 3053 #get global information 3054 first_line ='' 3055 while not first_line.strip(): 3056 first_line = all_line.pop(0) 3057 3058 wgt, nb_wgt, nb_event, _ = first_line.split() 3059 self.total_wgt = float(wgt.replace('d','e')) 3060 nb_wgt, nb_event = int(nb_wgt), int(nb_event) 3061 self.nb_wgt, self.nb_event = nb_wgt, nb_event 3062 3063 momenta = [] 3064 self.momenta = momenta #keep the original list of momenta to be able to rewrite the events 3065 wgts = [] 3066 for line in all_line: 3067 data = line.split() 3068 if len(data) == 4: 3069 p = FourMomentum(data) 3070 momenta.append(p) 3071 elif len(data)>0: 3072 wgt = OneNLOWeight(line, real_type=self.real_type) 3073 wgts.append(wgt) 3074 3075 assert len(wgts) == int(nb_wgt) 3076 3077 get_weights_for_momenta = dict( (i,[]) for i in range(1,nb_event+1) ) 3078 size_momenta = 0 3079 for wgt in wgts: 3080 if wgt.momenta_config in get_weights_for_momenta: 3081 get_weights_for_momenta[wgt.momenta_config].append(wgt) 3082 else: 3083 if size_momenta == 0: size_momenta = wgt.nexternal 3084 assert size_momenta == wgt.nexternal 3085 get_weights_for_momenta[wgt.momenta_config] = [wgt] 3086 3087 assert sum(len(c) for c in get_weights_for_momenta.values()) == int(nb_wgt), "%s != %s" % (sum(len(c) for c in get_weights_for_momenta.values()), nb_wgt) 3088 3089 # check singular behavior 3090 for key in range(1, nb_event+1): 3091 wgts = get_weights_for_momenta[key] 3092 if not wgts: 3093 continue 3094 if size_momenta == 0: size_momenta = wgts[0].nexternal 3095 p = momenta[size_momenta*(key-1):key*size_momenta] 3096 evt = self.BasicEvent(p, wgts, self.event, self.real_type) 3097 if len(evt) == size_momenta: #real type 3098 for wgt in wgts: 3099 if not wgt.type in self.real_type: 3100 continue 3101 if evt.check_fks_singularity(wgt.to_merge_pdg[0]-1, 3102 wgt.to_merge_pdg[1]-1, 3103 nb_init=sum(1 for p in self.event if p.status==-1), 3104 threshold=self.threshold): 3105 get_weights_for_momenta[wgt.momenta_config].remove(wgt) 3106 get_weights_for_momenta[wgt.born_related].append(wgt) 3107 wgt.momenta_config = wgt.born_related 3108 3109 assert sum(len(c) for c in get_weights_for_momenta.values()) == int(nb_wgt), "%s != %s" % (sum(len(c) for c in get_weights_for_momenta.values()), nb_wgt) 3110 3111 self.cevents = [] 3112 for key in range(1, nb_event+1): 3113 if key in get_weights_for_momenta: 3114 wgt = get_weights_for_momenta[key] 3115 if not wgt: 3116 continue 3117 pdg_to_event = {} 3118 for w in wgt: 3119 pdgs = w.pdgs 3120 if w.momenta_config == w.born_related: 3121 pdgs = list(pdgs) 3122 ind1, ind2 = [ind-1 for ind in w.to_merge_pdg] 3123 if ind1> ind2: 3124 ind1, ind2 = ind2, ind1 3125 pdgs.pop(ind1) 3126 pdgs.insert(ind1, w.merge_new_pdg ) 3127 pdgs.pop(ind2) 3128 pdgs = tuple(pdgs) 3129 if pdgs not in pdg_to_event: 3130 p = momenta[size_momenta*(key-1):key*size_momenta] 3131 evt = self.BasicEvent(p, [w], self.event, self.real_type) 3132 self.cevents.append(evt) 3133 pdg_to_event[pdgs] = evt 3134 else: 3135 pdg_to_event[pdgs].wgts.append(w) 3136 3137 if __debug__: 3138 nb_wgt_check = 0 3139 for cevt in self.cevents: 3140 nb_wgt_check += len(cevt.wgts) 3141 assert nb_wgt_check == int(nb_wgt)
3142 3143 3144 3145 if '__main__' == __name__: 3146 3147 if False: 3148 lhe = EventFile('unweighted_events.lhe') 3149 #lhe.parsing = False 3150 start = time.time() 3151 for event in lhe: 3152 pass 3153 s = time.time() 3154 print(s-start) 3155 # event.parse_lo_weight() 3156 # print('old method -> ', time.time()-start) 3157 # lhe = EventFile('unweighted_events.lhe.gz') 3158 #lhe.parsing = False 3159 # start = time.time() 3160 # for event in lhe: 3161 # event.parse_lo_weight_test() 3162 # print('new method -> ', time.time()-start) 3163 3164 3165 # Example 1: adding some missing information to the event (here distance travelled) 3166 if False: 3167 start = time 3168 lhe = EventFile('unweighted_events.lhe.gz') 3169 output = open('output_events.lhe', 'w') 3170 #write the banner to the output file 3171 output.write(lhe.banner) 3172 # Loop over all events 3173 for event in lhe: 3174 for particle in event: 3175 # modify particle attribute: here remove the mass 3176 particle.mass = 0 3177 particle.vtim = 2 # The one associate to distance travelled by the particle. 3178 3179 #write this modify event 3180 output.write(str(event)) 3181 output.write('</LesHouchesEvent>\n') 3182 3183 # Example 3: Plotting some variable 3184 if True: 3185 lhe = EventFile('/Users/omattelaer/Documents/eclipse/2.7.2_alternate/PROC_TEST_TT2/SubProcesses/P1_mupmum_ttxmupmum/G10/it4.lhe') 3186 import matplotlib.pyplot as plt 3187 import matplotlib.gridspec as gridspec 3188 nbins = 100 3189 3190 nb_pass = 0 3191 data_t1 = [] 3192 data_t2 = [] 3193 wgts = [] 3194 colors = [] 3195 for event in lhe: 3196 p = [FourMomentum(particle) for particle in event] 3197 t1 = - (p[1] -p[5])**2/13000**2 3198 data_t1.append(t1) 3199 t2 = - (p[0] -p[2]-p[3])**2/13000**2 3200 data_t2.append(t2) 3201 wgts.append(event.wgt) 3202 if event.wgt > 0.2335320e-005: 3203 colors.append('red') 3204 else: 3205 colors.append('blue') 3206 lhe = EventFile('/Users/omattelaer/Documents/eclipse/2.7.2_alternate/PROC_TEST_TT2/SubProcesses/P1_mupmum_ttxmupmum/G10/unweighted.lhe') 3207 import numpy as np 3208 import matplotlib.pyplot as plt 3209 data2_t1 = [] 3210 data2_t2 = [] 3211 wgts = [] 3212 colors2 = [] 3213 for event in lhe: 3214 p = [FourMomentum(particle) for particle in event] 3215 t1 = - (p[1] -p[5])**2/13000**2 3216 data2_t1.append(t1) 3217 t2 = - (p[0] -p[2]-p[3])**2/13000**2 3218 data2_t2.append(t2) 3219 wgts.append(event.wgt) 3220 if event.wgt > 0.2335320e-005: 3221 colors2.append('black') 3222 else: 3223 colors2.append('green') 3224 3225 3226 3227 # colors = (0,0,0) 3228 area = np.pi*3 3229 3230 # Plot 3231 # ax.set_xlim([10^-20,13000**2]) 3232 plt.xscale('log') 3233 plt.yscale('log') 3234 plt.xlabel('pa') 3235 plt.ylabel('pmu') 3236 plt.scatter(data_t1, data_t2, c=colors, label='weighted')#, s=area, c=colors, alpha=0.5) 3237 plt.scatter(data2_t1, data2_t2, c=colors2, label='unweighted')#, s=area, c=colors, alpha=0.5) 3238 plt.legend() 3239 3240 plt.show() 3241 3242 3243 print(nb_pass) 3244 gs1 = gridspec.GridSpec(2, 1, height_ratios=[5,1]) 3245 gs1.update(wspace=0, hspace=0) # set the spacing between axes. 3246 ax = plt.subplot(gs1[0]) 3247 3248 n, bins, patches = ax.hist(data, nbins, histtype='step', label='original') 3249 ax_c = ax.twinx() 3250 ax_c.set_ylabel('MadGraph5_aMC@NLO') 3251 ax_c.yaxis.set_label_coords(1.01, 0.25) 3252 ax_c.set_yticks(ax.get_yticks()) 3253 ax_c.set_yticklabels([]) 3254 print("bin value:", n) 3255 print("start/end point of bins", bins) 3256 plt.axis('on') 3257 plt.xlabel('weight ratio') 3258 plt.show() 3259 3260 3261 # Example 4: More complex plotting example (with ratio plot) 3262 if False: 3263 lhe = EventFile('unweighted_events.lhe') 3264 import matplotlib.pyplot as plt 3265 import matplotlib.gridspec as gridspec 3266 nbins = 100 3267 3268 #mtau, wtau = 45, 5.1785e-06 3269 mtau, wtau = 1.777, 4.027000e-13 3270 nb_pass = 0 3271 data, data2, data3 = [], [], [] 3272 for event in lhe: 3273 nb_pass +=1 3274 if nb_pass > 10000: 3275 break 3276 tau1 = FourMomentum() 3277 tau2 = FourMomentum() 3278 for part in event: 3279 if part.pid in [-12,11,16]: 3280 momenta = FourMomentum(part) 3281 tau1 += momenta 3282 elif part.pid == 15: 3283 tau2 += FourMomentum(part) 3284 3285 if abs((mtau-tau2.mass())/wtau)<1e6 and tau2.mass() >1: 3286 data.append((tau1.mass()-mtau)/wtau) 3287 data2.append((tau2.mass()-mtau)/wtau) 3288 gs1 = gridspec.GridSpec(2, 1, height_ratios=[5,1]) 3289 gs1.update(wspace=0, hspace=0) # set the spacing between axes. 3290 ax = plt.subplot(gs1[0]) 3291 3292 n, bins, patches = ax.hist(data2, nbins, histtype='step', label='original') 3293 n2, bins2, patches2 = ax.hist(data, bins=bins, histtype='step',label='reconstructed') 3294 import cmath 3295 3296 breit = lambda m : math.sqrt(4*math.pi)*1/(((m)**2-mtau**2)**2+(mtau*wtau)**2)*wtau 3297 3298 data3 = [breit(mtau + x*wtau)*wtau*16867622.6624*50 for x in bins] 3299 3300 ax.plot(bins, data3,label='breit-wigner') 3301 # add the legend 3302 ax.legend() 3303 # add on the right program tag 3304 ax_c = ax.twinx() 3305 ax_c.set_ylabel('MadGraph5_aMC@NLO') 3306 ax_c.yaxis.set_label_coords(1.01, 0.25) 3307 ax_c.set_yticks(ax.get_yticks()) 3308 ax_c.set_yticklabels([]) 3309 3310 plt.title('invariant mass of tau LHE/reconstructed') 3311 plt.axis('on') 3312 ax.set_xticklabels([]) 3313 # ratio plot 3314 ax = plt.subplot(gs1[1]) 3315 data4 = [n[i]/(data3[i]) for i in range(nbins)] 3316 ax.plot(bins, data4 + [0] , 'b') 3317 data4 = [n2[i]/(data3[i]) for i in range(nbins)] 3318 ax.plot(bins, data4 + [0] , 'g') 3319 ax.set_ylim([0,2]) 3320 #remove last y tick to avoid overlap with above plot: 3321 tick = ax.get_yticks() 3322 ax.set_yticks(tick[:-1]) 3323 3324 3325 plt.axis('on') 3326 plt.xlabel('(M - Mtau)/Wtau') 3327 plt.show() 3328