#!/usr/bin/env python from __future__ import division ################################################################################ # # Copyright (c) 2009 The MadGraph5_aMC@NLO Development team and Contributors # # This file is a part of the MadGraph5_aMC@NLO project, an application which # automatically generates Feynman diagrams and matrix elements for arbitrary # high-energy processes in the Standard Model and beyond. # # It is subject to the MadGraph5_aMC@NLO license which should accompany this # distribution. # # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch # ############################################################################### """ #################################################################### # # Routine to decay prodution events in a generic way, # including spin correlation effects # # Ref: S. Frixione, E. Laenen, P. Motylinski, B. R. Webber # JHEP 04 (2007) 081 # # ##################################################################### """ import collections import re import os import shutil import logging import time import cmath import copy pjoin = os.path.join from subprocess import Popen, PIPE, STDOUT os.sys.path.append("../.") import string import itertools #import madgraph.core.base_objects as base_objects #import madgraph.core.helas_objects as helas_objects #import madgraph.core.diagram_generation as diagram_generation import models.import_ufo as import_ufo #import madgraph.various.process_checks as process_checks #from time import strftime #from madgraph.interface.madgraph_interface import MadGraphCmd import madgraph.interface.master_interface as Cmd import madgraph.interface.madevent_interface as me_interface import madgraph.iolibs.save_load_object as save_load_object import madgraph.iolibs.files as files import madgraph.fks.fks_common as fks_common import aloha logger = logging.getLogger('decay.stdout') # -> stdout logger_stderr = logging.getLogger('decay.stderr') # ->stderr import random import math from madgraph import MG5DIR, MadGraph5Error import madgraph.various.misc as misc #import time class MadSpinError(MadGraph5Error): pass class Event: """ class to read an event, record the information, write down the event in the lhe format. This class is used both for production and decayed events""" def __init__(self, inputfile=None, banner=None): """Store the name of the event file """ self.inputfile=inputfile self.particle={} self.banner = banner def give_momenta(self, map_event=None): """ return the set of external momenta of the event, in two different formats: p is list momenta, with each momentum a list [E,px,py,pz] string is a sting """ if not map_event: map_event = {} for part in range(len(self.particle)): map_event[part] = part p=[] string="" for id in xrange(len(self.particle)): particle = self.particle[map_event[id] + 1] if particle["istup"] < 2: mom = particle["momentum"] p.append(mom) string+= '%s %s %s %s \n' % (mom.E, mom.px, mom.py, mom.pz) return p, string def change_wgt(self, value=None, factor=None): if value: self.wgt = value elif factor: self.wgt *= factor # change the wgt associate to the additional weight start, stop = self.rwgt.find(''), self.rwgt.find('') if start != -1 != stop : pattern = re.compile(r'''<\s*wgt id=[\'\"](?P[^\'\"]+)[\'\"]\s*>\s*(?P[\ded+-.]*)\s*''') data = pattern.findall(self.rwgt) if len(data)==0: print self.rwgt try: text = ''.join(' %+15.7e \n' % (pid, float(value) * factor) for (pid,value) in data) except ValueError, error: raise Exception, 'Event File has unvalid weight. %s' % error self.rwgt = self.rwgt[:start] + '\n'+ text + self.rwgt[stop:] def string_event_compact(self): """ return a string with the momenta of the event written in an easy readable way """ line="" for part in range(1,len(self.particle)+1): line+=str(self.particle[part]["pid"])+" " line+=str(self.particle[part]["momentum"].px)+" " line+=str(self.particle[part]["momentum"].py)+" " line+=str(self.particle[part]["momentum"].pz)+" " line+=str(self.particle[part]["momentum"].E)+" " line+=str(self.particle[part]["momentum"].m)+" " line+="\n" return line def get_tag(self): initial = [] final = [] order = [[],[]] for part in self.particle.values(): pid = part['pid'] mother1 = part['mothup1'] mother2 = part['mothup2'] if 0 == mother1 == mother2: initial.append(pid) order[0].append(pid) else: final.append(pid) order[1].append(pid) initial.sort() final.sort() return (tuple(initial), tuple(final)), order def string_event(self): """ return a string with the information of the event written in the lhe format. """ line=self.event_init_line # This is the line line1=' %2d %6d %+13.7e %14.8e %14.8e %14.8e' % \ (self.nexternal,self.ievent,self.wgt,self.scale,self.aqed,self.aqcd) line+=line1+"\n" scales= [] for item in range(1,len(self.event2mg.keys())+1): part=self.event2mg[item] if part>0: particle_line=self.get_particle_line(self.particle[part]) if abs(self.particle[part]["istup"]) == 1: if "pt_scale" in self.particle[part]: scales.append(self.particle[part]["pt_scale"]) else: scales.append(None) else: particle_line=self.get_particle_line(self.resonance[part]) line+=particle_line if any(scales): sqrts = self.particle[1]["pt_scale"] line += "\n" % ' '.join(['pt_clust_%i=\"%s\"' %(i-1,s if s else sqrts) for i,s in enumerate(scales) if i>1]) if self.diese: line += self.diese if self.rwgt: line += self.rwgt line+=" \n" return line def get_particle_line(self,leg): line=" %8d %2d %4d %4d %4d %4d %+18.11e %+18.11e %+18.11e %18.11e %18.11e %10.4e %10.4e" \ % (leg["pid"], leg["istup"],leg["mothup1"],leg["mothup2"],\ leg["colup1"],leg["colup2"],leg["momentum"].px,leg["momentum"].py,\ leg["momentum"].pz,leg["momentum"].E, leg["mass"],\ 0.0,float(leg["helicity"]) ) line+="\n" return line def reshuffle_resonances(self,mother): """ reset the momentum of each resonance in the production event to the sum of momenta of the daughters """ daughters=[] for part in self.event2mg.keys(): index=self.event2mg[part] if index>0: if self.particle[index]["mothup1"]==mother: daughters.append(index) if index<0: if self.resonance[index]["mothup1"]==mother: daughters.append(index) if len(daughters)!=2: logger.info("Got more than 2 (%s) daughters for one particles" % len(daughters)) logger.info("in one production event (before decay)") if daughters[0]>0: momentum_mother=self.particle[daughters[0]]["momentum"].copy() else: momentum_mother=self.resonance[daughters[0]]["momentum"].copy() # there might be more than 2 daughters, add all their momentum to get the momentum of the mother for index in range(1,len(daughters)): if daughters[index]>0: momentum_mother=momentum_mother.add(self.particle[daughters[index]]["momentum"]) else: momentum_mother=momentum_mother.add(self.resonance[daughters[index]]["momentum"]) res=self.event2mg[mother] del self.resonance[res]["momentum"] self.resonance[res]["momentum"]=momentum_mother.copy() # recurrence: if self.resonance[res]["mothup1"]>2: self.reshuffle_resonances(self.resonance[res]["mothup1"]) def reset_resonances(self): """ re-evaluate the momentum of each resonance, based on the momenta of the external particles """ mothers=[] for part in self.particle.keys(): if self.particle[part]["mothup1"]>2 and \ self.particle[part]["mothup1"] not in mothers : mothers.append(self.particle[part]["mothup1"]) self.reshuffle_resonances(self.particle[part]["mothup1"]) def assign_scale_line(self, line): """read the line corresponding to global event line format of the line is: Nexternal IEVENT WEIGHT SCALE AEW AS """ line = line.replace('d','e').replace('D','e') inputs = line.split() assert len(inputs) == 6 self.nexternal=int(inputs[0]) self.ievent=int(inputs[1]) self.wgt=float(inputs[2]) self.scale=float(inputs[3]) self.aqed=float(inputs[4]) self.aqcd=float(inputs[5]) def get_next_event(self): """ read next event in the lhe event file """ line_type = 'none' # support type: init / event / rwgt self.diese = '' for line in self.inputfile: line = line.lower() if line=="": continue # Find special tag in the line if line[0]=="#": self.diese+=line continue if ') self.event_init_line=line.lstrip().replace('nplo','npLO').replace('npnlo','npNLO') line_type = 'init' continue elif '' in line: #re-weighting information block line_type = 'rwgt' #No Continue! need to keep track of this line elif '' in line: if not self.particle: continue self.shat=self.particle[1]["momentum"].dot(self.particle[2]["momentum"]) return 1 elif line_type == 'rwgt' and 'wgt' in line: # force to continue to be in rwgt line up to line_type = 'rwgt' elif "pt_clust_" in line: line_type="clusteringv3" elif '<' in line: line_type = 'other_block' if line_type == 'none': continue elif line_type == 'other_block': self.diese += line # read the line and assign the date accordingly elif line_type == 'init': line_type = 'event' self.assign_scale_line(line) # initialize some local variable index_prod=0 index_external=0 index_resonance=0 self.particle={} self.resonance={} self.max_col=500 self.diese="" self.rwgt="" self.event2mg={} # dict. event-like label <-> "madgraph-like" label # in "madgraph-like", resonances are labeled -1, -2, ... elif line_type == 'rwgt': #special aMC@NLO information self.rwgt += line if '' in line: line_type = 'event' elif line_type == 'event': index_prod+=1 line=line.replace("\n","") line = line.replace('d','e').replace('D','e') inputs=line.split() pid=int(inputs[0]) istup=int(inputs[1]) mothup1=int(inputs[2]) mothup2=int(inputs[3]) colup1=int(inputs[4]) if colup1>self.max_col: self.max_col=colup1 colup2=int(inputs[5]) if colup2>self.max_col: self.max_col=colup2 mom=momentum(float(inputs[9]),float(inputs[6]),float(inputs[7]),float(inputs[8])) mass=float(inputs[10]) helicity=float(inputs[12]) if abs(istup)==1: index_external+=1 self.event2mg[index_prod]=index_external self.particle[index_external]={"pid":pid,"istup":istup,"mothup1":mothup1,\ "mothup2":mothup2,"colup1":colup1,"colup2":colup2,"momentum":mom,"mass":mass,"helicity":helicity} elif istup==2: index_resonance=index_resonance-1 self.event2mg[index_prod]=index_resonance self.resonance[index_resonance]={"pid":pid,"istup":istup,"mothup1":mothup1,\ "mothup2":mothup2,"colup1":colup1,"colup2":colup2,"momentum":mom,"mass":mass,"helicity":helicity} else: logger.warning('unknown status in lhe file') elif line_type == "clusteringv3": scales = re.findall(r"""pt_clust_(\d+)=\"([e\+\-.\d]+)\"""", line) scales = sorted(scales, key= lambda x: -1*int(x[0])) for index in range(1,len(self.particle)+1): if self.particle[index]["istup"] == 1: self.particle[index]["pt_scale"] = scales.pop()[1] if not self.banner: self.particle[1]["pt_scale"] = self.particle[1]["momentum"].E + self.particle[2]["momentum"].E else: self.particle[1]["pt_scale"] = float(self.banner.get('run_card', 'ebeam1'))+float(self.banner.get('run_card', 'ebeam2')) return "no_event" class pid2label(dict): """ dico pid:label for a given model""" def __init__(self,model): for particle in model["particles"]: self[particle["pdg_code"]]=particle["name"] self[-particle["pdg_code"]]=particle["antiname"] class pid2color(dict): """ dico pid:color rep. for a given model (ex: 5:-3 )""" def __init__(self,model): for particle in model["particles"]: self[particle["pdg_code"]]=particle["color"] if particle["color"] not in [1,8]: self[-particle["pdg_code"]]=-particle["color"] else: self[-particle["pdg_code"]]=particle["color"] class label2pid(dict): """ dico label:pid for a given model""" def __init__(self,model): for particle in model["particles"]: self[particle["name"]]=particle.get_pdg_code() self[particle["antiname"]]=-particle.get_pdg_code() if particle['self_antipart']: self[particle["name"]]=abs(self[particle["name"]]) self[particle["antiname"]]=abs(self[particle["antiname"]]) class dc_branch_from_me(dict): """ A dictionary to record information necessary to decay particles { -1 : {"d1": { "label": XX , "nb": YY }, "d2": { "label": XX , "nb": YY } }, -2 : {"d1": { "label": XX , "nb": YY }, "d2": { "label": XX , "nb": YY } }, .... } """ def __init__(self, process): """ """ self.model = process.get('model') self["tree"]={} self.nexternal = 0 self.nb_decays = 1 #define a function to allow recursion. def add_decay(proc, propa_id=-1): # see what need to be decayed to_decay = {} for dec in proc.get('decay_chains'): pid = dec.get('legs')[0].get('id') if pid in to_decay: to_decay[pid].append(dec) else: to_decay[pid] = [dec] #done self['tree'][propa_id] = {'nbody': len(proc.get('legs'))-1,\ 'label':proc.get('legs')[0].get('id')} # loop over the child child_propa_id = propa_id for c_nb,leg in enumerate(proc.get('legs')): if c_nb == 0: continue self["tree"][propa_id]["d%s" % c_nb] = {} c_pid = leg.get('id') self["tree"][propa_id]["d%s" % c_nb]["label"] = c_pid self["tree"][propa_id]["d%s" % c_nb]["labels"] = [c_pid] if c_pid in to_decay: child_propa_id -= 1 self["tree"][propa_id]["d%s" % c_nb]["index"] = child_propa_id self.nb_decays += 1 child_propa_id = add_decay(to_decay[c_pid].pop(), child_propa_id) else: self.nexternal += 1 self["tree"][propa_id]["d%s" % c_nb]["index"] = self.nexternal return child_propa_id # launch the recursive loop add_decay(process) def generate_momenta(self,mom_init,ran, pid2width,pid2mass,BW_cut,E_collider, sol_nb=None): """Generate the momenta in each decay branch If ran=1: the generation is random, with a. p^2 of each resonance generated according to a BW distribution b. cos(theta) and phi (angles in the rest frame of the decaying particle) are generated according to a flat distribution (no grid) the phase-space weight is also return (up to an overall normalization) since it is needed in the unweighting procedure If ran=0: evaluate the momenta based on the previously-generated p^2, cos(theta) and phi in each splitting. This is used in the reshuffling phase (e.g. when we give a mass to gluons in the decay chain ) """ index2mom={} # pid2mom={} # a dict { pid : {"status":status, "momentum":momentum} } assert isinstance(mom_init, momentum) index2mom[-1] = {} index2mom[-1]["momentum"] = mom_init if index2mom[-1]['momentum'].m < 1e-3: logger.warning('Decaying particle with m< 1e-3 GeV in generate_momenta') index2mom[-1]["pid"] = self['tree'][-1]["label"] index2mom[-1]["status"] = 2 weight=1.0 for res in range(-1,-self.nb_decays-1,-1): tree = self["tree"][res] # Here mA^2 has to be set to p^2: # # IF res=-1: # p^2 has been either fixed to the value in the # production lhe event, or generated according to a Breit-Wigner distr. # during the reshuffling phase of the production event # -> we just need to read the value here # IF res<-1: # p^2 has been generated during the previous iteration of this loop # -> we just need to read the value here mA=index2mom[res]["momentum"].m if mA < 1.0e-3: logger.debug('Warning: decaying parting with m<1 MeV in generate_momenta ') mass_sum = mA all_mass = [] for i in range(tree["nbody"]): tag = "d%s" % (i+1) d = tree[tag]["index"] # For the daughters, the mass is either generate (intermediate leg + BW mode on) # or set to the pole mass (external leg or BW mode off) # If ran=0, just read the value from the previous generation of momenta #(this is used for reshuffling purposes) if d>0 or not BW_cut : m = pid2mass(tree[tag]["label"]) elif ran==0: # reshuffling phase m= tree[tag]["mass"] else: pid=tree[tag]["label"] # NOTE: here pole and width are normalized by 4.0*mB**2, # Just a convention pole=0.25 #pid2mass[pid]**2/mA**2 w=pid2width(pid) mpole=pid2mass(pid) width=pid2width(pid)*pid2mass(pid)/(4.0*pid2mass(pid)**2) #/mA**2 m_min=max(mpole-BW_cut*w, 0.5) m_max=mpole+BW_cut*w if E_collider>0: m_max=min(m_max,0.99*E_collider) zmin=math.atan(m_min**2/w/mpole-mpole/w)/width zmax=math.atan(m_max**2/w/mpole-mpole/w)/width m, jac=self.transpole(pole,width, zmin,zmax) m = math.sqrt(m * 4.0 * mpole**2) # record the mass for the reshuffling phase, # in case the point passes the reweighting creteria tree[tag]["mass"] = m #update the weigth of the phase-space point weight=weight*jac # for checking conservation of energy mass_sum -= m all_mass.append(m) if mass_sum < 0: logger.debug('mA 2: raise Exception, 'Phase Space generator not yet ready for 3 body decay' if ran==1: decay_mom=generate_2body_decay(index2mom[res]["momentum"],mA, all_mass[0],all_mass[1]) # record the angles for the reshuffling phase, # in case the point passes the reweighting creteria tree["costh"]=decay_mom.costh tree["sinth"]=decay_mom.sinth tree["cosphi"]=decay_mom.cosphi tree["sinphi"]=decay_mom.sinphi else: # we are in the reshuffling phase, # so we read the angles that have been stored from the # previous phase-space point generation costh=self["tree"][res]["costh"] sinth=self["tree"][res]["sinth"] cosphi=self["tree"][res]["cosphi"] sinphi=self["tree"][res]["sinphi"] decay_mom=generate_2body_decay(index2mom[res]["momentum"],mA, all_mass[0],all_mass[1],\ costh_val=costh, sinth_val=sinth, cosphi_val=cosphi, \ sinphi_val=sinphi) # record the momenta for later use index2mom[self["tree"][res]["d1"]["index"]]={} index2mom[self["tree"][res]["d1"]["index"]]["momentum"]=decay_mom.momd1 if sol_nb is None: sol_nb = random.randint(0,len(self["tree"][res]["d1"]["labels"])-1) # print self["tree"][res]["d1"]["labels"] # print '584 get sol_nb', sol_nb,'=>',self["tree"][res]["d1"]["labels"][sol_nb],self["tree"][res]["d2"]["labels"][sol_nb] # else: # print sol_nb, sol_nb is None, # print 'take back', sol_nb,'=>',self["tree"][res]["d1"]["labels"][sol_nb],self["tree"][res]["d2"]["labels"][sol_nb] index2mom[self["tree"][res]["d1"]["index"]]["pid"]=self["tree"]\ [res]["d1"]["labels"][sol_nb] index2mom[self["tree"][res]["d2"]["index"]]={} index2mom[self["tree"][res]["d2"]["index"]]["momentum"]=decay_mom.momd2 index2mom[self["tree"][res]["d2"]["index"]]["pid"]=self["tree"]\ [res]["d2"]["labels"][sol_nb] if (self["tree"][res]["d1"]["index"]>0): index2mom[self["tree"][res]["d1"]["index"]]["status"]=1 else: index2mom[self["tree"][res]["d1"]["index"]]["status"]=2 if (self["tree"][res]["d2"]["index"]>0): index2mom[self["tree"][res]["d2"]["index"]]["status"]=1 else: index2mom[self["tree"][res]["d2"]["index"]]["status"]=2 return index2mom, weight, sol_nb def transpole(self,pole,width, zmin, zmax): """ routine for the generation of a p^2 according to a Breit Wigner distribution the generation window is [ M_pole^2 - 30*M_pole*Gamma , M_pole^2 + 30*M_pole*Gamma ] """ z=zmin+(zmax-zmin)*random.random() y = pole+width*math.tan(width*z) jac=(width/math.cos(width*z))**2*(zmax-zmin) return y, jac def add_decay_ids(self, proc_list): """ """ #define a function to allow recursion. def add_decay(proc, propa_id=-1): # see what need to be decayed to_decay = {} for dec in proc.get('decay_chains'): pid = dec.get('legs')[0].get('id') if pid in to_decay: to_decay[pid].append(dec) else: to_decay[pid] = [dec] # loop over the child for c_nb,leg in enumerate(proc.get('legs')): if c_nb == 0: continue # self["tree"][propa_id]["d%s" % c_nb] = {} c_pid = leg.get('id') self["tree"][propa_id]["d%s" % c_nb]["labels"].append(c_pid) if c_pid in to_decay: add_decay(to_decay[c_pid].pop(), propa_id-1) # launch the recursive loop for proc in proc_list: add_decay(proc) class momentum: """A class to handel 4-vectors and the associated operations """ def __init__(self,E,px,py,pz): self.px=px self.py=py self.pz=pz self.E=E self.mod2=px**2+py**2+pz**2 self.sq=E**2-self.mod2 control = E**2+self.mod2 if not control: self.m = 0 elif self.sq/control < 1e-8: self.m=0.0 else: self.m=math.sqrt(self.sq) def dot3(self,q): """ return |p|^2 (spatial components only) """ return self.px*q.px+self.py*q.py+self.pz*q.pz def dot(self,q): """ Minkowski inner product """ return self.E*q.E-self.px*q.px-self.py*q.py-self.pz*q.pz def subtract(self,q): tot=momentum(self.E-q.E,self.px-q.px,self.py-q.py,self.pz-q.pz) return tot def add(self,q): tot=momentum(self.E+q.E,self.px+q.px,self.py+q.py,self.pz+q.pz) return tot __add__ = add def nice_string(self): return str(self.E)+" "+str(self.px)+" "+str(self.py)+" "+str(self.pz) __str__ = nice_string def boost(self, q): """ boost a vector from a frame where q is at rest to a frame where q is given This routine has been taken from HELAS """ qq = q.mod2 if (qq > 1E-10*abs(q.E)): pq=self.dot3(q) m=q.m #if (abs(m-self.mA)>1e-6): print "warning: wrong mass" lf=((q.E-m)*pq/qq+self.E)/m pboost=momentum((self.E*q.E+pq)/m, self.px+q.px*lf,\ self.py+q.py*lf,self.pz+q.pz*lf) else: pboost=momentum(self.E,self.px,self.py,self.pz) return pboost def copy(self): copy_mom=momentum(self.E,self.px,self.py,self.pz) return copy_mom def invrot(self,q): # inverse of the "rot" operation ppE=self.E qt2 = (q.px)**2 + (q.py)**2 if(qt2==0.0): if ( q.pz>0 ): ppx = self.px ppy = self.py ppz = self.pz else: ppx = -self.px ppy = -self.py ppz = -self.pz else: qq = math.sqrt(qt2+q.pz**2) qt=math.sqrt(qt2) ppy=-q.py/qt*self.px+q.px/qt*self.py if (q.pz==0): ppx=-qq/qt*self.pz if (q.py!=0): ppz=(self.py-q.py*q.pz/qq/qt-q.px/qt*ppy)*qq/q.py else: ppz=(self.px-q.px*q.pz/qq/qt*ppx+q.py/qt*ppy)*qq/q.px else: if (q.py!=0): ppz=(qt**2*self.py+q.py*q.pz*self.pz-q.px*qt*ppy)/qq/q.py else: ppz=(q.px*self.px+q.pz*self.pz)/qq ppx=(-self.pz+q.pz/qq*ppz)*qq/qt pp=momentum(ppE,ppx,ppy,ppz) return pp def rot(self, q): """ rotate the spatial components of the vector from a frame where q is aligned with the z axis to a frame where the direction of q is specified as an argument Taken from HELAS """ protE =self.E qt2 = (q.px)**2 + (q.py)**2 if(qt2==0.0): if ( q.pz>0 ): protx = self.px proty = self.py protz = self.pz else: protx = -self.px proty = -self.py protz = -self.pz else: qq = math.sqrt(qt2+q.pz**2) qt = math.sqrt(qt2) protx = q.px*q.pz/qq/qt*self.px -q.py/qt*self.py +q.px/qq*self.pz proty = q.py*q.pz/qq/qt*self.px +q.px/qt*self.py +q.py/qq*self.pz protz = -qt/qq*self.px + q.pz/qq*self.pz prot=momentum(protE,protx,proty,protz) return prot class generate_2body_decay: """generate momenta for a generic A > B + C decay """ def __init__(self,p,mA,mB,mC, costh_val=None, sinth_val=None, cosphi_val=None, sinphi_val=None): """ Generate the momentum of B and C in the decay A -> B+C If the angles are given, use them to reconstruct the momenta of B, C in the rest fram of A. If the routine is called without (costh_val, ...), then generate cos(theta) and phi randomly (flat distr.) in the rest frame of A Finally, boost the momenta of B and C in the frame where A has momentum p """ self.mA=mA self.mB=mB self.mC=mC pmod=self.lambda_fct()/(2.0 * self.mA) if not costh_val: costh=2.0*random.random()-1.0 sinth=math.sqrt(1-costh**2) else: costh=costh_val sinth=sinth_val if not cosphi_val: phi=random.random()*2.0*math.pi sinphi=math.sin(phi) cosphi=math.cos(phi) else: sinphi=sinphi_val cosphi=cosphi_val energyB=math.sqrt(pmod**2+mB**2) energyC=math.sqrt(pmod**2+mC**2) pBrest=momentum(energyB, pmod*cosphi*sinth,pmod*sinphi*sinth, pmod*costh) pCrest=momentum(energyC,-pmod*cosphi*sinth,-pmod*sinphi*sinth, -pmod*costh) self.momd1=pBrest.boost(p) self.momd2=pCrest.boost(p) # record costh and phi for later use self.costh=costh self.sinth=sinth self.cosphi=cosphi self.sinphi=sinphi def lambda_fct(self): """ The usual lambda function involved in 2-body decay """ lam=self.mA**4+self.mB**4+self.mC**4 lam=lam-2.0*self.mA**2*self.mB**2-2.0*self.mA**2*self.mC**2\ -2.0*self.mC**2*self.mB**2 # if lam<0: #print self.mA #print self.mB #print self.mC return math.sqrt(lam) class production_topo(dict): """ A dictionnary to record information about a given topology of a production event self["branchings"] is a list of the branchings defining the topology (see class branching) self["get_mass2"] is a dictionnary {index -> mass**2 of the corresponding particle} self["get_momentum"] is a dictionnary {index -> momentum of the corresponding particle} self["get_id"] is a dictionnary {index -> pid the corresponding particle} Note: index= "madgraph-like" numerotation of the particles """ def __init__(self, production, options): """ Initialise the dictionaries+list used later on to record the information about the topology of a production event. Note that self["branchings"] is a list, as it makes it easier to scan the topology in the ascendent order """ self["branchings"]=[] self["get_mass2"]={} self["get_momentum"]={} self["get_id"]={} self.production = production self.options = options def add_one_branching(self,index_propa, index_d1,index_d2,type_propa): """ add the information of one splitting in the topology """ branch=branching(index_propa, index_d1,index_d2,type_propa) self["branchings"].append(branch) def print_topo(self): """Print the structure of the topology """ for branch in self["branchings"]: d1=branch["index_d1"] d2=branch["index_d2"] propa=branch["index_propa"] line=str(propa)+" > " line+=str(d1)+" + " line+=str(d2)+" , type=" line+=branch["type"] print line class AllMatrixElement(dict): """Object containing all the production topologies required for event to decay. This contains the routine to add a production topologies if needed. """ def __init__(self, banner, options, decay_ids, model): dict.__init__(self) self.banner = banner self.options = options self.decay_ids = set([abs(id) for id in decay_ids]) self.has_particles_ambiguity = False self.model = model def add(self, topologies, keys): """Adding one element to the list of production_topo""" for key in keys: self[key] = topologies def get_br(self, proc): # get the branching ratio associated to a process br = 1 ids = collections.defaultdict(list) #check for identical decay for decay in proc.get('decay_chains'): init, final = decay.get_initial_final_ids() lhaid = tuple([len(final)] + [x for x in final]) ids[init[0]].append(decay) if init[0] in self.banner.param_card['decay'].decay_table: br *= self.banner.param_card['decay'].decay_table[init[0]].get(lhaid).value br *= self.get_br(decay) elif -init[0] in self.banner.param_card['decay'].decay_table: init = -init[0] lhaid=[x if self.model.get_particle(x)['self_antipart'] else -x for x in final] lhaid.sort() lhaid = tuple([len(final)] + lhaid) br *= self.banner.param_card['decay'].decay_table[init].get(lhaid).value br *= self.get_br(decay) elif init[0] not in self.decay_ids and -init[0] not in self.decay_ids: logger.warning("No Branching ratio applied for %s. Please check if this is expected" % init[0]) br *= self.get_br(decay) else: raise MadGraph5Error,"No valid decay for %s. No 2 body decay for that particle. (three body are not supported by MadSpin)" % init[0] for decays in ids.values(): if len(decays) == 1: continue br *= math.factorial(len(decays)) while decays: nb=1 curr = decays.pop() while 1: try: decays.remove(curr) nb+=1 except ValueError: break br /= math.factorial(nb) return br def add_decay(self, proc_list, me_path): """ adding a decay to the possibility me_path is the path of the fortran directory br is the associate branching ratio finals is the list of final states equivalent to this ME matrix_element is the MG5 matrix element """ # Usefull debug code tell the status of the various imported decay text = '' data = [] for key, prod in self.items(): if prod in data: continue data.append(prod) br = prod['total_br'] if br: text += '%s %s %s\n' %(key, os.path.basename(prod['path']), br) if not isinstance(proc_list, list): proc_list = proc_list.get('processes') tag = proc_list[0].get_initial_final_ids() # process with decay not compatible with tag [process under consideration] # or with process splitting different for process and decay. to_postpose = [proc for proc in proc_list if id(self[tag]) != id(self[proc.get_initial_final_ids()])] finals = [] for proc in proc_list: succeed = True # check if the decay is compatible with the process #under consideration. tmp = [] for dproc in proc.get('decay_chains'): pid = dproc.get('legs')[0].get('id') # check that the pid correspond -> if not postpone the process # to be added in a second step (happen due to symmetry) if pid not in tag[1]: to_postpose.append(proc) succeed= False break tmp.append((pid,dproc.get_final_ids_after_decay())) if succeed and tmp not in finals: finals.append(tmp) # Treat Not compatible decay. if to_postpose: self.add_decay(to_postpose, me_path) # # Now that the various final states are computed, we can add the # associated decay. First computing the branching ratio and then # decaying topology me = proc_list[0] # all the other are symmetric -> no need to keep those decay_tags = [d.shell_string(pdg_order=True) for d in me['decay_chains']] #avoid duplicate if any(tuple(decay_tags)==t['decay_tag'] for t in self[tag]['decays']): return # the decay: out = {'path': me_path, 'matrix_element': me, 'br': len(finals) * self.get_br(proc), 'finals': finals, 'base_order':[l.get('id') for l in me.get_legs_with_decays()] , 'decay_struct':self.get_full_process_structure(proc_list), 'decay_tag': tuple(decay_tags)} # adding it to the current object self[tag]['decays'].append(out) self[tag]['total_br'] += out['br'] # update the particle decaying in the process decaying = [m.get('legs')[0].get('id') for m in me.get('decay_chains')] decaying.sort() self[tag]['decaying'] = tuple(decaying) # sanity check assert self[tag]['total_br'] <= 1.01, "wrong BR for %s: %s " % (tag,self[tag]['total_br']) def get_full_process_structure(self, me_list): """ return a string with the definition of the process fully decayed and also a list of dc_branch objects with all infomation about the topology of each decay branch """ me = me_list[0] decay_struct = {} to_decay = collections.defaultdict(list) for i, proc in enumerate(me.get('decay_chains')): pid = proc.get('legs')[0].get('id') to_decay[pid].append((i,proc)) for leg in me.get('legs'): pid = leg.get('id') nb = leg.get('number') if pid in to_decay: i, proc = to_decay[pid].pop() decay_struct[nb] = dc_branch_from_me(proc) identical = [me.get('decay_chains')[i] for me in me_list[1:]] decay_struct[nb].add_decay_ids(identical) return decay_struct def get_topologies(self, matrix_element): """Extraction of the phase-space self.topologies from mg5 matrix elements This is used for the production matrix element only. the routine is essentially equivalent to write_configs_file_from_diagrams except that I don't write the topology in a file, I record it in an object production_topo (the class is defined above in this file) """ # Extract number of external particles ( nexternal, ninitial) = matrix_element.get_nexternal_ninitial() del nexternal preconfigs = [(i+1, d) for i,d in enumerate(matrix_element.get('diagrams'))] mapconfigs = [c[0] for c in preconfigs] configs=[[c[1]] for c in preconfigs] model = matrix_element.get('processes')[0].get('model') topologies ={} # dictionnary {mapconfig number -> production_topology} # this is the object to be returned at the end of this routine s_and_t_channels = [] vert_list = [max([d for d in config if d][0].get_vertex_leg_numbers()) \ for config in configs if [d for d in config if d][0].\ get_vertex_leg_numbers()!=[]] minvert = min(vert_list) if vert_list!=[] else 0 # Number of subprocesses # nsubprocs = len(configs[0]) nconfigs = 0 new_pdg = model.get_first_non_pdg() for iconfig, helas_diags in enumerate(configs): if any([vert > minvert for vert in [d for d in helas_diags if d][0].get_vertex_leg_numbers()]): # Only 3-vertices allowed in configs.inc continue nconfigs += 1 # Need s- and t-channels for all subprocesses, including # those that don't contribute to this config empty_verts = [] stchannels = [] for h in helas_diags: if h: # get_s_and_t_channels gives vertices starting from # final state external particles and working inwards stchannels.append(h.get('amplitudes')[0].\ get_s_and_t_channels(ninitial, model, new_pdg)) else: stchannels.append((empty_verts, None)) # For t-channels, just need the first non-empty one tchannels = [t for s,t in stchannels if t != None][0] # For s_and_t_channels (to be used later) use only first config s_and_t_channels.append([[s for s,t in stchannels if t != None][0], tchannels]) # Make sure empty_verts is same length as real vertices if any([s for s,t in stchannels]): empty_verts[:] = [None]*max([len(s) for s,t in stchannels]) # Reorganize s-channel vertices to get a list of all # subprocesses for each vertex schannels = zip(*[s for s,t in stchannels]) else: schannels = [] allchannels = schannels if len(tchannels) > 1: # Write out tchannels only if there are any non-trivial ones allchannels = schannels + tchannels # Write out propagators for s-channel and t-channel vertices # use the AMP2 index to label the self.topologies tag_topo=mapconfigs[iconfig] topologies[tag_topo]=production_topo(topologies, self.options) for verts in allchannels: if verts in schannels: vert = [v for v in verts if v][0] else: vert = verts daughters = [leg.get('number') for leg in vert.get('legs')[:-1]] last_leg = vert.get('legs')[-1] if verts in schannels: type_propa="s" elif verts in tchannels[:-1]: type_propa="t" if (type_propa): topologies[tag_topo].add_one_branching(last_leg.get('number'),\ daughters[0],daughters[1],type_propa) return topologies def get_decay_from_tag(self, production_tag, decay_tag): for decay in self[production_tag]['decays']: if decay['decay_tag']==decay_tag: return decay msg = 'Unable to retrieve decay from decay_tag\n%s\n%s' %(production_tag, decay_tag) raise Exception, msg def get_random_decay(self, production_tag,first=[]): """select randomly a decay channel""" a = random.random() * self[production_tag]['total_br'] #print 'total', self[production_tag]['total_br'] if __debug__: if not first: sum = 0 for decay in self[production_tag]['decays']: sum += decay['br'] assert sum == self[production_tag]['total_br'] first.append(1) sum = 0 for decay in self[production_tag]['decays']: sum += decay['br'] if a < sum: return decay def adding_me(self, matrix_elements, path): """Adding one element to the list based on the matrix element""" for me in matrix_elements: skip = [] # due to particles/anti-particles some me need to be add # as a separate matrix element in the instance. topo = self.get_topologies(me) # get the orignal order: initial = [] final = [l.get('id') for l in me.get('processes')[0].get('legs')\ if l.get('state') or initial.append(l.get('id'))] decaying_base = [id for id in final if abs(id) in self.decay_ids] decaying_base.sort() topo['base_order'] = (initial , final) # topo['matrix_element'] = me tags = [] topo['tag2order'] = {} for proc in me.get('processes'): # set of processes accounted by the me initial = [] final = [l.get('id') for l in proc.get('legs')\ if l.get('state') or initial.append(l.get('id'))] decaying = [id for id in final if abs(id) in self.decay_ids] decaying.sort() if decaying != decaying_base: skip.append(proc) continue topo['decaying'] = () tags.append(proc.get_initial_final_ids()) topo['tag2order'][tags[-1]] = (initial , final) if tags[0] not in self: self.add(topo, tags) # mens self[tag]=topo for each tag in tags topo['path'] = pjoin(path, 'SubProcesses', 'P%s' % me.get('processes')[0].shell_string()) topo['decays'] = [] topo['total_br'] = 0 topo['Pid'] = proc.get('id') if skip: self.add_me_symmetric(skip, topo) def add_me_symmetric(self, process_list, topo): """ """ self.has_particles_ambiguity = True skip = [] # due to particles/anti-particles some may need to be add # as a separate matrix element in the instance. old_topo = topo topo = dict(topo) #change the main pointer topo['decays'] = [] # unlink information which need to be different. topo['total_br'] = 0 # topo['tag2order'] = {} topo['decaying'] = () for key in topo.keys(): if isinstance(key, int): topo[key] = copy.copy(topo[key]) assert id(old_topo) != id(topo) assert id(topo['decays']) != id(old_topo['decays']) tags = [] for i, proc in enumerate(process_list): initial = [] final = [l.get('id') for l in proc.get('legs')\ if l.get('state') or initial.append(l.get('id'))] decaying = [pid for pid in final if abs(pid) in self.decay_ids] decaying.sort() if i == 0: decaying_base = decaying if decaying != decaying_base: skip.append(proc) continue tags.append(proc.get_initial_final_ids()) topo['tag2order'][tags[-1]] = (initial , final) if tags[0] not in self: self.add(topo, tags) for key in topo.keys(): if isinstance(key, int): topo[key].production = self[tags[0]] if skip: self.add_me_symmetric(skip, topo) class branching(dict): """ A dictionnary to record information about a given branching in a production event self["type"] is the type of the branching , either "s" for s-channel or "t" for t-channel self["invariant"] is a real number with the value of p^2 associated with the branching self["index_d1"] is the mg index of the first daughter self["index_d2"] is the mg index of the second daughter self["index_propa"] is the mg index of the propagator """ def __init__(self, index_propa, index_d1,index_d2, s_or_t): self["index_d1"]=index_d1 self["index_d2"]=index_d2 self["index_propa"]=index_propa self["type"]=s_or_t class width_estimate(object): """All methods used to calculate branching fractions""" def __init__(self,resonances,path_me, banner, model, pid2label): self.resonances=resonances self.path_me=path_me self.pid2label = pid2label self.label2pid = self.pid2label self.model = model #print self.model self.banner = banner #self.model= def update_branch(self,branches,to_add): """ complete the definition of the branch by appending each element of to_add""" newbranches={} for item1 in branches.keys(): for item2 in to_add.keys(): tag=item1+item2 newbranches[tag]={} newbranches[tag]['config']=branches[item1]['config']+to_add[item2]['config'] newbranches[tag]['br']=branches[item1]['br']*to_add[item2]['br'] return newbranches def extract_br(self, decay_processes, mgcmd): """Find a way to get the branching ratio. (depending of the model/cards)""" # calculate which br are interesting to compute. to_decay = set(decay_processes.keys()) for decays in decay_processes.values(): for decay in decays: if ',' in decay: to_decay.update(set([l.split('>')[0].strip() for l in decay.replace('(','').replace(')','').split(',')])) # Maybe the branching fractions are already given in the banner: self.extract_br_from_banner(self.banner) to_decay = list(to_decay) for part in to_decay[:]: if part in mgcmd._multiparticles: to_decay += [self.pid2label[id] for id in mgcmd._multiparticles[part]] to_decay.remove(part) to_decay = list(set([p for p in to_decay if not p in self.br])) if to_decay: logger.info('We need to recalculate the branching fractions for %s' % ','.join(to_decay)) if hasattr(self.model.get('particles')[0], 'partial_widths'): logger.info('using the FeynRules formula present in the model (arXiv:1402.1178)') else: logger.info('Using MadWidth (arXiv:1402.1178)') #self.extract_br_from_width_evaluation(to_decay) self.launch_width_evaluation(to_decay, mgcmd) return self.br def get_BR_for_each_decay(self, decay_processes, multiparticles): """ get the list for possible decays & the associated branching fraction """ model = self.model base_model = self.model pid2label = self.pid2label ponctuation=[',','>',')','('] new_decay_processes={} for part in decay_processes.keys(): pos_symbol=-1 branch_list=decay_processes[part].split() new_decay_processes[part]={} new_decay_processes[part]['']={} new_decay_processes[part]['']['config']="" new_decay_processes[part]['']['br']=1.0 initial="" final=[] for index, item in enumerate(branch_list): # First get the symbol at the next position if index': # case1: we have a particle initiating a branching initial=item if item not in [ particle['name'] for particle in base_model['particles'] ] \ and item not in [ particle['antiname'] for particle in base_model['particles'] ]: raise Exception, "No particle "+item+ " in the model "+model continue elif item=='>': continue # case 2: we have the > symbole elif item not in ponctuation : # case 3: we have a particle originating from a branching final.append(item) if next_symbol=='' or next_symbol in ponctuation: #end of a splitting, verify that it exists if initial not in self.br.keys(): logger.debug('Branching fractions of particle '+initial+' are unknown') return 0 if len(final)>2: raise Exception, 'splittings different from A > B +C are currently not implemented ' if final[0] in multiparticles.keys(): set_B=[pid2label[pid] for pid in multiparticles[final[0]]] else: if final[0] not in [ particle['name'] for particle in base_model['particles'] ] \ and final[0] not in [ particle['antiname'] for particle in base_model['particles'] ]: raise Exception, "No particle "+item+ " in the model " set_B=[final[0]] if final[1] in multiparticles.keys(): set_C=[pid2label[pid] for pid in multiparticles[final[1]]] else: if final[1] not in [ particle['name'] for particle in base_model['particles'] ] \ and final[1] not in [ particle['antiname'] for particle in base_model['particles'] ]: raise Exception, "No particle "+item+ " in the model "+model set_C=[final[1]] splittings={} counter=0 for chan in range(len(self.br[initial])): # loop over all channels got_it=0 for d1 in set_B: for d2 in set_C: if (d1==self.br[initial][chan]['daughters'][0] and \ d2==self.br[initial][chan]['daughters'][1]) or \ (d2==self.br[initial][chan]['daughters'][0] and \ d1==self.br[initial][chan]['daughters'][1]): split=" "+initial+" > "+d1+" "+d2+" " # For the tag we need to order d1 d2, so that equivalent tags can be correctly idetified list_daughters=sorted([d1,d2]) tag_split="|"+initial+">"+list_daughters[0]+list_daughters[1] counter+=1 splittings[tag_split]={} splittings[tag_split]['config']=split splittings[tag_split]['br']=self.br[initial][chan]['br'] got_it=1 break # to avoid double counting in cases such as w+ > j j if got_it: break if len(splittings)==0: logger.info('Branching '+initial+' > '+final[0]+' '+final[1]) logger.info('is currently unknown') return 0 else: new_decay_processes[part]=self.update_branch(new_decay_processes[part],splittings) inital="" final=[] else: # case 4: ponctuation symbol outside a splitting # just append it to all the current branches fake_splitting={} fake_splitting['']={} fake_splitting['']['br']=1.0 fake_splitting['']['config']=item new_decay_processes[part]=self.update_branch(new_decay_processes[part],fake_splitting) return new_decay_processes def print_branching_fractions(self): """ print a list of all known branching fractions""" for res in self.br.keys(): logger.info(' ') logger.info('decay channels for '+res+' : ( width = ' +str(self.width_value[res])+' GeV )') logger.info(' BR d1 d2' ) for decay in self.br[res]: bran = decay['br'] d1 = decay['daughters'][0] d2 = decay['daughters'][1] logger.info(' %e %s %s ' % (bran, d1, d2) ) logger.info(' ') def print_partial_widths(self): """ print a list of all known partial widths""" for res in self.br.keys(): logger.info(' ') logger.info('decay channels for '+res+' :') logger.info(' width d1 d2' ) for chan, decay in enumerate(self.br[res]): width=self.br[res][chan]['width'] d1=self.br[res][chan]['daughters'][0] d2=self.br[res][chan]['daughters'][1] logger.info(' %e %s %s ' % (width, d1, d2) ) logger.info(' ') def extract_br_from_width_evaluation(self, to_decay): """ use MadGraph5_aMC@NLO to generate me's for res > all all """ raise DeprecationWarning if os.path.isdir(pjoin(self.path_me,"width_calculator")): shutil.rmtree(pjoin(self.path_me,"width_calculator")) assert not os.path.exists(pjoin(self.path_me, "width_calculator")) path_me = self.path_me label2pid = self.label2pid # first build a set resonances with pid>0 #particle_set= list(to_decay) pids = set([abs(label2pid[name]) for name in to_decay]) particle_set = [label2pid[pid] for pid in pids] modelpath = self.model.get('modelpath') if os.path.basename(modelpath) != self.model['name']: name, restrict = self.model['name'].rsplit('-',1) if os.path.exists(pjoin(os.path.dirname(modelpath),name, 'restrict_%s.dat' % restrict)): modelpath = pjoin(os.path.dirname(modelpath), self.model['name']) commandline="import model %s\n" % modelpath commandline+="generate %s > all all \n" % particle_set[0] commandline+= "set automatic_html_opening False --no_save\n" if len(particle_set)>1: for index in range(1,len(particle_set)): commandline+="add process %s > all all \n" % particle_set[index] commandline += "output %s/width_calculator -f \n" % path_me aloha.loop_mode = False aloha.unitary_gauge = False cmd = Cmd.MasterCmd() for line in commandline.split('\n'): cmd.run_cmd(line) # WRONG Needs to takes the param_card from the input files. ff = open(pjoin(path_me, 'width_calculator', 'Cards', 'param_card.dat'),'w') ff.write(self.banner['slha']) ff.close() lhapdf = False if os.environ.has_key('lhapdf'): lhapdf = os.environ['lhapdf'] del os.environ['lhapdf'] # run but remove the pdf dependencies cmd.import_command_file(['launch', 'set lpp1 0', 'set lpp2 0', 'done']) if lhapdf: os.environ['lhapdf'] = lhapdf #me_cmd = me_interface.MadEventCmd(pjoin(path_me,'width_calculator')) #me_cmd.exec_cmd('set automatic_html_opening False --no_save') filename=pjoin(path_me,'width_calculator','Events','run_01','param_card.dat') self.extract_br_from_card(filename) def extract_br_for_antiparticle(self): ''' for each channel with a specific br value, set the branching fraction of the complex conjugated channel to the same br value ''' label2pid = self.label2pid pid2label = self.label2pid for res in self.br.keys(): particle=self.model.get_particle(label2pid[res]) if particle['self_antipart']: continue anti_res=pid2label[-label2pid[res]] self.br[anti_res] = [] if res in self.width_value: self.width_value[anti_res]=self.width_value[res] elif anti_res in self.width_value: self.width_value[res]=self.width_value[anti_res] res, anti_res = anti_res, res for chan, decay in enumerate(self.br[res]): self.br[anti_res].append({}) bran=decay['br'] d1=decay['daughters'][0] d2=decay['daughters'][1] d1bar=pid2label[-label2pid[d1]] d2bar=pid2label[-label2pid[d2]] self.br[anti_res][chan]['br']=bran self.br[anti_res][chan]['daughters']=[] self.br[anti_res][chan]['daughters'].append(d1bar) self.br[anti_res][chan]['daughters'].append(d2bar) if decay.has_key('width'): self.br[anti_res][chan]['width']=decay['width'] def launch_width_evaluation(self,resonances, mgcmd): """ launch the calculation of the partial widths """ label2pid = self.label2pid pid2label = self.label2pid model = self.model # first build a set resonances with pid>0 # since compute_width cannot be used for particle with pid<0 particle_set = set() for part in resonances: if part in mgcmd._multiparticles: for pid in mgcmd._multiparticles[part]: particle_set.add(abs(pid)) continue pid_part = abs(label2pid[part]) particle_set.add(abs(pid_part)) particle_set = list(particle_set) argument = {'particles': particle_set, 'path': pjoin(self.path_me, 'param_card.dat'), 'output': pjoin(self.path_me, 'param_card.dat'), 'body_decay': 2} self.compute_widths(model, argument) self.extract_br_from_card(pjoin(self.path_me, 'param_card.dat')) self.banner['slha'] = open(pjoin(self.path_me, 'param_card.dat')).read() if hasattr(self.banner,'param_card'): del self.banner.param_card self.banner.charge_card('param_card') return def compute_widths(self, model, opts): from madgraph.interface.master_interface import MasterCmd import madgraph.iolibs.helas_call_writers as helas_call_writers cmd = MasterCmd() #self.define_child_cmd_interface(cmd, interface=False) cmd.exec_cmd('set automatic_html_opening False --no_save') if not opts['path']: opts['path'] = pjoin(self.me_dir, 'Cards', 'param_card.dat') if not opts['force'] : self.ask_edit_cards(['param_card'],[], plot=False) line = 'compute_widths %s %s' % \ (' '.join([str(i) for i in opts['particles']]), ' '.join('--%s=%s' % (key,value) for (key,value) in opts.items() if key not in ['model', 'force', 'particles'] and value)) cmd.exec_cmd('import model %s' % model.get('modelpath+restriction')) #pattern for checking complex mass scheme. has_cms = re.compile(r'''set\s+complex_mass_scheme\s*(True|T|1|true|$|;)''', re.M) force_CMS = has_cms.search(self.banner['mg5proccard']) if force_CMS: cmd.exec_cmd('set complex_mass_scheme') # cmd._curr_model = model # cmd._curr_fortran_model = helas_call_writers.FortranUFOHelasCallWriter(model) cmd.exec_cmd(line) #self.child = None del cmd def extract_br_from_banner(self, banner): """get the branching ratio from the banner object: for each resonance with label 'res', and for each channel with index i, returns a dictionary branching_fractions[res][i] with keys 'daughters' : label of the daughters (only 2 body) 'br' : value of the branching fraction""" self.br = {} # read the param_card internally to the banner if not hasattr(banner, 'param_card'): banner.charge_card('param_card') param_card = banner.param_card return self.extract_br_from_card(param_card) def extract_br_from_card(self, param_card): """get the branching ratio from the banner object: for each resonance with label 'res', and for each channel with index i, returns a dictionary branching_fractions[res][i] with keys 'daughters' : label of the daughters (only 2 body) 'br' : value of the branching fraction""" if isinstance(param_card, str): import models.check_param_card as check_param_card param_card = check_param_card.ParamCard(param_card) if 'decay' not in param_card or not hasattr(param_card['decay'], 'decay_table'): return self.br self.width_value={} for id, data in param_card['decay'].decay_table.items(): # check is the new width is close to the one originally in the banner recalculated_width=param_card['decay'].param_dict[(id,)].value width_in_the_banner=self.banner.get('param_card', 'decay', abs(id)).value relative_diff=abs(recalculated_width-width_in_the_banner)/recalculated_width if (relative_diff > 0.05): logger.warning('The LO estimate for the width of particle %s ' % id) logger.warning('differs from the one in the banner by %d percent ' % (relative_diff*100)) label = self.pid2label[id] current = [] # tmp name for self.br[label] for parameter in data: if parameter.lhacode[0] == 2: d = [self.pid2label[pid] for pid in parameter.lhacode[1:]] current.append({'daughters':d, 'br': parameter.value}) self.br[label] = current self.width_value[label]=recalculated_width #update the banner: self.banner['slha'] = param_card.write(None) self.banner.param_card = param_card self.extract_br_for_antiparticle() return self.br class decay_misc: """class with various methods for the decay""" @staticmethod def get_all_resonances(banner, mgcmd, allowed): """ return a list of labels of each resonance involved in the decay chain """ allowed = list(allowed) found = set() alias = {} # if an allowed particles is inside a multiparticle # look at the content of the multiparticles in order to know which one # we need to track. multiparticles = mgcmd._multiparticles model = mgcmd._curr_model for name, content in multiparticles.items(): curr = [model.get_particle(id).get('name') \ for id in content if model.get_particle(id).get('name') in allowed ] if found: alias[name] = set(curr) # Now look which of the possible decay that we need to look at are indeed # present in the final state of one process. for line in banner.proc_card: line.strip() if line.startswith('generate') or line.startswith('add process'): final_process = re.split(r'>.*>|>|[\$/,@\[]', line)[1] for search in allowed: if search in final_process: found.add(search) allowed.remove(search) for search, data in alias.items(): if search in final_process: found.update(data) del alias[search] # treat multiparticles finalfound = set() for name in found: if name in mgcmd._multiparticles: finalfound.update([model.get_particle(id).get('name') for id in mgcmd._multiparticles[name]]) finalfound.discard(name) else: finalfound.add(name) return finalfound def reorder_branch(self,branch): """ branch is a string with the definition of a decay chain If branch contains " A > B C , B > ... " reorder into " A > C B , B > ... " """ branch=branch.replace(',', ' , ') branch=branch.replace(')', ' ) ') branch=branch.replace('(', ' ( ') list_branch=branch.split(" ") for index in range(len(list_branch)-1,-1,-1): if list_branch[index]==' ' or list_branch[index]=='': del list_branch[index] #print list_branch for index, item in enumerate(list_branch): if item[-1] =="," and list_branch[index+1]!="(": # search pos of B and C counter=1 while 1: if list_branch[index-counter].find("=")<0: break counter+=1 if list_branch[index-counter-1]==list_branch[index+1]: # swap the two particles before the comma: temp=list_branch[index-counter-1] list_branch[index-counter-1]=list_branch[index-counter] list_branch[index-counter]=temp if item[-1] =="," and list_branch[index+1]=="(": # search pos of B and C counter=1 while 1: if list_branch[index-counter].find("=")<0: break counter+=1 if list_branch[index-counter -1]==list_branch[index+2]: # swap the two particles before the comma: temp=list_branch[index-counter-1] list_branch[index-counter-1]=list_branch[index-counter] list_branch[index-counter]=temp new_branch="" for item in list_branch: new_branch+=item+" " return new_branch, list_branch[0] def set_light_parton_massless(self,topo): """ masses of light partons are set to zero for the evaluation of the matrix elements """ light_partons=[21,1,2,3] for part in topo["get_id"].keys(): if abs(topo["get_id"][part]) in light_partons : topo["get_mass2"][part]=0.0 # need to check if last branch is a t-branching. If it is, # we need to update the value of branch["m2"] # since this will be used in the reshuffling procedure if len(topo["branchings"])>0: # Exclude 2>1 self.topologies if topo["branchings"][-1]["type"]=="t": if topo["branchings"][-2]["type"]!="t": logger.info('last branching is t-channel') logger.info('but last-but-one branching is not t-channel') else: part=topo["branchings"][-1]["index_d2"] if part >0: # reset the mass only if "part" is an external particle topo["branchings"][-2]["m2"]=math.sqrt(topo["get_mass2"][part]) @staticmethod def modify_param_card(pid2widths, path_me): """Modify the param_card w/r to what is read from the banner: if the value of a width is set to zero in the banner, it is automatically set to its default value in this code """ param_card=open(pjoin(path_me,'param_card.dat'), 'r') new_param_card="" while 1: line=param_card.readline() if line =="": break list_line=line.split() if len(list_line)>2: if list_line[0]=="DECAY" and int(list_line[1]) in pid2widths.keys(): list_line[2]=str(pid2widths[int(list_line[1])]) line="" for item in list_line: line+=item+ " " line+="\n" new_param_card+=line param_card.close() param_card=open(pjoin(path_me, 'param_card.dat'), 'w') param_card.write(new_param_card) param_card.close() def select_one_topo(self,prod_values): # # prod_values[0] is the value of |M_prod|^2 # prod_values[1], prod_values[2], ... are the values of individual diagrams # (called AMP2 in mg) # # first evaluate the sum of all single diagram values total=0.0 cumul=[0.0] for i in range(1,len(prod_values)): cumul.append(cumul[i-1]+float(prod_values[i])) total+=float(prod_values[i]) for i in range(len(cumul)): cumul[i]=cumul[i]/total #print "Cumulative AMP2 values: " #print cumul select_topo=random.random() for i in range(1,len(cumul)): if select_topo>cumul[i-1] and select_topo0: branch_list=final_state_full.split(",") del branch_list[0] list_obj=final_state_full.split() final_state_compact="" to_be_deleted=[] for index, obj in enumerate(list_obj): if obj==">": to_be_deleted.append(list_obj[index-1]) for obj in list_obj: if obj!=">" and obj!="," and obj not in to_be_deleted: final_state_compact+=obj+" " branches={} for branch in branch_list: list_part=branch.split() branches[list_part[0]]={"finalstate":list_part[2:]} branches[list_part[0]]["branch"], dummy= self.reorder_branch(branch) else: final_state_compact=final_state_full branches={} return final_state_compact, branches def get_mean_sd(self,list_obj): """ return the mean value and the standard deviation of a list of reals """ sum=0.0 N=float(len(list_obj)) for item in list_obj: sum+=item mean=sum/N sd=0.0 for item in list_obj: sd+=(item-mean)**2 if N > 1: sd=math.sqrt(sd/(N-1.0)) else: sd = mean/5. return mean, sd class decay_all_events(object): def __init__(self, ms_interface, banner, inputfile, options): """Store all the component and organize special variable""" # input self.options = options #max_weight_arg = options['max_weight'] self.path_me = os.path.realpath(options['curr_dir']) if options['ms_dir']: self.path_me = os.path.realpath(options['ms_dir']) if not os.path.exists(self.path_me): os.mkdir(self.path_me) self.mgcmd = ms_interface.mg5cmd self.mscmd = ms_interface self.model = ms_interface.model self.banner = banner self.evtfile = inputfile self.curr_event = Event(self.evtfile, banner) self.inverted_decay_mapping={} self.width_estimator = None self.curr_dir = os.getcwd() # dictionary to fortan evaluator self.calculator = {} self.calculator_nbcall = {} # need to unbuffer all I/O in fortran, otherwise # the values of matrix elements are not passed to the Python script os.environ['GFORTRAN_UNBUFFERED_ALL']='y' # Remove old stuff from previous runs # so that the current run is not confused # Don't have to do that for gridpack / or if asked. if not (options["ms_dir"] or options["use_old_dir"]): if os.path.isdir(pjoin(self.path_me,"production_me")): shutil.rmtree(pjoin(self.path_me,"production_me")) if os.path.isdir(pjoin(self.path_me,"full_me")): shutil.rmtree(pjoin(self.path_me,"full_me")) if os.path.isdir(pjoin(self.path_me,"decay_me")): shutil.rmtree(pjoin(self.path_me,"decay_me")) # Prepare some dict usefull for optimize model imformation # pid -> label and label -> pid self.pid2label=pid2label(self.model) self.banner.check_pid(self.pid2label) self.pid2label.update(label2pid(self.model)) self.pid2massvar={} self.pid2widthvar={} for part in self.model['particles']: self.pid2massvar[int(part['pdg_code'])]=part['mass'] self.pid2widthvar[int(part['pdg_code'])]=part['width'] # load the Monte Carlo masses self.MC_masses=self.get_MC_masses() # logger.info('Value of the Monte Carlo masses: ') # logger.info(self.MC_masses) # dictionary pid > color_rep self.pid2color = pid2color(self.model) # energy of the collider self.Ecollider=float(self.banner.get('run_card', 'ebeam1'))\ +float(self.banner.get('run_card', 'ebeam2')) # write down the seed: seedfile=open(pjoin(self.path_me, 'seeds.dat'),'w') seedfile.write(' %s \n' % self.options['seed']) seedfile.close() # width and mass information will be filled up later self.pid2width = lambda pid: self.banner.get('param_card', 'decay', abs(pid)).value self.pid2mass = lambda pid: self.banner.get('param_card', 'mass', abs(pid)).value if os.path.isfile(pjoin(self.path_me,"param_card.dat")): os.remove(pjoin(self.path_me,"param_card.dat")) # now overwrite the param_card.dat in Cards: param_card=self.banner['slha'] #param_card=decay_tools.check_param_card( param_card) # now we can write the param_card.dat: # Note that the width of each resonance in the # decay chain should be >0 , we will check that later on model_name = os.path.basename(self.model.get('name')) param=open(pjoin(self.path_me,'param_card.dat'),"w") param.write(param_card) param.close() if model_name == 'mssm' or model_name.startswith('mssm-'): #need to convert to SLHA2 format import models.check_param_card as check_param_card check_param_card.convert_to_mg5card(pjoin(self.path_me,'param_card.dat')) self.list_branches = ms_interface.list_branches decay_ids = [self.pid2label[key] for key in self.list_branches \ if key in self.pid2label] for multi in self.mgcmd._multiparticles: if multi in self.list_branches: decay_ids += self.mgcmd._multiparticles[multi] self.all_ME = AllMatrixElement(banner, self.options, decay_ids, self.model) self.all_decay = {} # generate BR and all the square matrix element based on the banner. pickle_info = pjoin(self.path_me,"production_me", "all_ME.pkl") if not options["use_old_dir"] or not os.path.exists(pickle_info): self.generate_all_matrix_element() save_load_object.save_to_file(pickle_info, (self.all_ME,self.all_decay,self.width_estimator)) else: try: self.all_ME, self.all_decay,self.width_estimator = save_load_object.load_from_file(pjoin(self.path_me,"production_me", "all_ME.pkl")) except Exception,error: logger.debug(str(error)) self.generate_all_matrix_element() save_load_object.save_to_file(pickle_info, (self.all_ME,self.all_decay,self.width_estimator)) if not self.options["onlyhelicity"] and self.options['spinmode'] != 'onshell': resonances = self.width_estimator.resonances logger.debug('List of resonances: %s' % resonances) self.extract_resonances_mass_width(resonances) self.compile() def get_MC_masses(self): MC_masses={} pid_heavyquarks=[4,5] if 'montecarlomasses' in self.banner: MC_masses_lines=self.banner['montecarlomasses'].split('\n') for line in MC_masses_lines: pidvalue=line.split() if len(pidvalue)<2: continue # skip blank lines pid=abs(int(pidvalue[0])) value=float(pidvalue[1]) MC_masses[pid]=value if pid in pid_heavyquarks: value_ME=self.banner.get('param_card','mass', pid).value if value_ME>1E-10: if pid==5: logger.warning('set the mass of the b-quark to its value in the param_card.dat: %s GeV ' % value_ME) if pid==4: logger.warning('set the mass of the c-quark to its value in the param_card.dat: %s GeV ' % value_ME) MC_masses[pid]=value_ME return MC_masses def run(self): """Running the full code""" max_weight_arg = self.options['max_weight'] decay_tools=decay_misc() #Next step: we need to determine which matrix elements are really necessary #========================================================================== decay_mapping = self.get_identical_decay() # also compute the inverted map, which will be used in the decay procedure for tag in decay_mapping: for equiv_decay in decay_mapping[tag]: self.inverted_decay_mapping[equiv_decay[0]]=tag self.mscmd.update_status('MadSpin: Estimate the maximum weight') # Estimation of the maximum weight #================================= if max_weight_arg>0: for key in self.all_ME: for mode in self.all_ME['decays']: mode['max_weight'] = max_weight_arg elif self.options["onlyhelicity"]: logger.info("not needed in helicity mode") else: #self.get_max_weight_from_1toN() self.get_max_weight_from_event(decay_mapping) # add probability of not writting events (for multi production with # different decay self.add_loose_decay() # Store this object with all the associate number for gridpack: if self.options['ms_dir']: self.save_status_to_pickle() self.ending_run() def ending_run(self): """launch the unweighting and deal with final information""" # launch the decay and reweighting self.mscmd.update_status('MadSpin: Decaying Events') efficiency = self.decaying_events(self.inverted_decay_mapping) self.efficiency = efficiency if efficiency != 1 and any(v==-1 for v in self.br_per_id.values()): # need to change the banner information [nb_event/cross section] files.cp(self.outputfile.name, '%s_tmp' % self.outputfile.name) self.outputfile = open(self.outputfile.name, 'w') self.write_banner_information(efficiency) pos = self.outputfile.tell() old = open('%s_tmp' % self.outputfile.name) line='' while '' not in line: line = old.readline() self.outputfile.write(old.read()) files.rm('%s_tmp' % self.outputfile.name) # Closing all run self.terminate_fortran_executables() if not self.options['ms_dir']: shutil.rmtree(pjoin(self.path_me,'production_me')) shutil.rmtree(pjoin(self.path_me,'full_me')) if not self.options["onlyhelicity"]: shutil.rmtree(pjoin(self.path_me,'decay_me')) # set the environment variable GFORTRAN_UNBUFFERED_ALL # to its original value #os.environ['GFORTRAN_UNBUFFERED_ALL']='n' def save_status_to_pickle(self): import madgraph.iolibs.save_load_object as save_load_object #don't store the event file in the pkl evt_file, self.evtfile = self.evtfile, None curr_event, self.curr_event = self.curr_event , None mgcmd, self.mgcmd = self.mgcmd, None mscmd, self.mscmd = self.mscmd , None pid2mass, self.pid2mass = self.pid2mass, None pid2width, self.pid2width = self.pid2width, None #banner, self.banner = self.banner, None #self.all_ME.banner = None name = pjoin(self.options['ms_dir'], 'madspin.pkl') save_load_object.save_to_file(name, self) #restore the event file self.evtfile = evt_file self.curr_event = curr_event self.mgcmd = mgcmd self.mscmd = mscmd self.pid2mass = pid2mass self.pid2width = pid2width #self.banner = banner #self.all_ME.banner = banner def decaying_events(self,inverted_decay_mapping): """perform the decay of each events""" decay_tools = decay_misc() # tools for checking if max_weight is too often broken. report = collections.defaultdict(int,{'over_weight': 0}) logger.info(' ' ) logger.info('Decaying the events... ') self.outputfile = open(pjoin(self.path_me,'decayed_events.lhe'), 'w') self.write_banner_information() event_nb, fail_nb = 0, 0 nb_skip = 0 trial_nb_all_events=0 starttime = time.time() nb_fail_mc_mass=0 while 1: # loop on event file production_tag, event_map = self.load_event() if production_tag == 0 == event_map: #end of file break if event_nb and \ (event_nb % max(int(10**int(math.log10(float(event_nb)))),1000)==0): running_time = misc.format_timer(time.time()-starttime) logger.info('Event nb %s %s' % (event_nb, running_time)) self.mscmd.update_status(('$events',1,event_nb, 'decaying events'), force=False, print_log=False) if (event_nb==10001): logger.info('reducing number of print status. Next status update in 10000 events') if self.options["onlyhelicity"]: trial_nb, fail = self.adding_only_helicity(event_map, production_tag) trial_nb_all_events+=trial_nb fail_nb += fail event_nb += 1 continue # Here we need to select a decay configuration on a random basis: decay = self.all_ME.get_random_decay(production_tag) if not decay['decay_tag']: #Not writting events due to global reweighting nb_skip +=1 continue else: # for the matrix element, identify the master decay channel to which 'decay' is equivalent: decay_tag_me=inverted_decay_mapping[decay['decay_tag']] try: decay_me=self.all_ME.get_decay_from_tag(production_tag, decay_tag_me) except Exception: #if the master didn't exsit try the original one. decay_me=self.all_ME.get_decay_from_tag(production_tag, decay['decay_tag']) event_nb+=1 report[decay['decay_tag']] += 1 indices_for_mc_masses, values_for_mc_masses=self.get_montecarlo_masses_from_event(decay['decay_struct'], event_map, decay['prod2full']) nb_mc_masses=len(indices_for_mc_masses) p, p_str=self.curr_event.give_momenta(event_map) stdin_text=' %s %s %s %s \n' % ('2', self.options['BW_cut'], self.Ecollider, decay_me['max_weight']) stdin_text+=p_str # here I also need to specify the Monte Carlo Masses stdin_text+=" %s \n" % nb_mc_masses if nb_mc_masses>0: stdin_text+='%s \n' % str(indices_for_mc_masses).strip('[]').replace(',', ' ') stdin_text+='%s \n' % str(values_for_mc_masses).strip('[]').replace(',', ' ') # here apply the reweighting procedure in fortran output = self.loadfortran( 'unweighting', decay_me['path'], stdin_text) if not output: fail_nb +=1 continue trial_nb, BWvalue, weight, momenta, failed, use_mc_masses, helicities = output # next: need to fill all intermediate momenta if nb_mc_masses>0 and use_mc_masses==0:nb_fail_mc_mass+=1 ext_mom=self.get_mom(momenta) # fill all momenta in the decay branches momenta_in_decay=self.get_int_mom_in_decay(decay['decay_struct'],ext_mom) # reset extrenal momenta in the production event self.reset_mom_in_prod_event(decay['decay_struct'],decay['prod2full'],\ event_map,momenta_in_decay,ext_mom, use_mc_masses, helicities) # reset intermediate momenta in prod event self.curr_event.reset_resonances() # decayed_event = self.decay_one_event_new(self.curr_event,decay['decay_struct'],\ event_map, momenta_in_decay,use_mc_masses, helicities) # Treat the case we get too many failures for the PS generation. if failed > 500 : logger.debug('Got a production event with %s failures for the phase-space generation generation ' % failed) # Treat the case that we ge too many overweight. if weight > decay_me['max_weight']: report['over_weight'] += 1 report['%s_f' % (decay['decay_tag'],)] +=1 if __debug__: misc.sprint('''over_weight: %s %s, occurence: %s%%, occurence_channel: %s%% production_tag:%s [%s], decay:%s [%s], BW_cut: %1g\n ''' %\ (weight/decay['max_weight'], decay['decay_tag'], 100 * report['over_weight']/event_nb, 100 * report['%s_f' % (decay['decay_tag'],)] / report[decay['decay_tag']], os.path.basename(self.all_ME[production_tag]['path']), production_tag, os.path.basename(decay['path']), decay['decay_tag'],BWvalue)) if weight > 10.0 * decay['max_weight']: error = """Found a weight MUCH larger than the computed max_weight (ratio: %s). This usually means that the Narrow width approximation reaches it's limit on part of the Phase-Space. Do not trust too much the tale of the distribution and/or relaunch the code with smaller BW_cut. This is for channel %s with current BW_value at : %g'""" \ % (weight/decay['max_weight'], decay['decay_tag'], BWvalue) logger.error(error) elif report['over_weight'] > max(0.005*event_nb,3): error = """Found too many weight larger than the computed max_weight (%s/%s = %s%%). Please relaunch MS with more events/PS point by event in the computation of the maximum_weight. """ % (report['over_weight'], event_nb, 100 * report['over_weight']/event_nb ) raise MadSpinError, error error = True elif report['%s_f' % (decay['decay_tag'],)] > max(0.01*report[decay['decay_tag']],3): error = """Found too many weight larger than the computed max_weight (%s/%s = %s%%), for channel %s. Please relaunch MS with more events/PS point by event in the computation of the maximum_weight. """ % (report['%s_f' % (decay['decay_tag'],)],\ report['%s' % (decay['decay_tag'],)],\ 100 * report['%s_f' % (decay['decay_tag'],)] / report[ decay['decay_tag']] ,\ decay['decay_tag']) raise MadSpinError, error decayed_event.change_wgt(factor= self.branching_ratio) #decayed_event.wgt = decayed_event.wgt * self.branching_ratio self.outputfile.write(decayed_event.string_event()) #print "number of trials: "+str(trial_nb) trial_nb_all_events+=trial_nb self.outputfile.write('\n') self.evtfile.close() self.outputfile.close() if report['over_weight'] > max(0.15*math.sqrt(event_nb),1): error = """Found many weight larger than the computed max_weight (%s/%s = %s%%). """ % (report['over_weight'], event_nb, 100 * report['over_weight']/event_nb ) logger.warning(error) for decay_tag in self.all_decay.keys(): if report['%s_f' % (decay_tag,)] > max(0.2*report[decay_tag],1): error = """Found many weight larger than the computed max_weight (%s/%s = %s%%), for channel %s.""" % (report['%s_f' % (decay_tag,)],\ report['%s' % (decay_tag,)],\ 100 * report['%s_f' % (decay_tag,)] / report[decay_tag] ,\ decay_tag) logger.warning(error) logger.info('Total number of events written: %s/%s ' % (event_nb, event_nb+nb_skip)) logger.info('Average number of trial points per production event: '\ +str(float(trial_nb_all_events)/float(event_nb))) logger.info('Branching ratio to allowed decays: %g' % self.branching_ratio) logger.info('Number of events with weights larger than max_weight: %s' % report['over_weight']) logger.info('Number of subprocesses '+str(len(self.calculator))) logger.info('Number of failures when restoring the Monte Carlo masses: %s ' % nb_fail_mc_mass) if fail_nb: logger.info('Number of failures in reshuffling (event skipped): %s ' % fail_nb) return event_nb/(event_nb+nb_skip) def adding_only_helicity(self, event_map, production_tag): """no decays for this production mode, run in passthrough mode, only adding the helicities to the events """ #no decays for this production mode, run in passthrough mode, only adding the helicities to the events nb_mc_masses=0 p, p_str=self.curr_event.give_momenta(event_map) stdin_text=' %s %s %s %s \n' % ('2', self.options['BW_cut'], self.Ecollider, 1.0) stdin_text+=p_str # here I also need to specify the Monte Carlo Masses stdin_text+=" %s \n" % nb_mc_masses mepath = self.all_ME[production_tag]['path'] decay = self.all_ME[production_tag]['decays'][0] decay_me=self.all_ME.get_decay_from_tag(production_tag, decay['decay_tag']) mepath = decay_me['path'] output = self.loadfortran( 'unweighting', mepath, stdin_text) if not output: # Event fail return 0, 1 trial_nb, BWvalue, weight, momenta, failed, use_mc_masses, helicities = output self.reset_helicityonly_in_prod_event(event_map, helicities) decayed_event = self.curr_event self.outputfile.write(decayed_event.string_event()) #print "number of trials: "+str(trial_nb) return trial_nb, 0 def get_int_mom_in_decay(self,decay_struct,ext_mom): """ fill """ momenta_in_decay={} for part in decay_struct.keys(): branch=decay_struct[part]['mg_tree'] nb_splitting=len(branch) for split in range(nb_splitting-1,-1,-1): mother=branch[split][0] d1=branch[split][1] d2=branch[split][2] if d1>0: momenta_in_decay[d1]=ext_mom[d1-1] # list_momenta is ordered according to ME if d2>0: momenta_in_decay[d2]=ext_mom[d2-1] # list_momenta is ordered according to ME momenta_in_decay[mother]=momenta_in_decay[d1].add(momenta_in_decay[d2]) return momenta_in_decay def reset_mom_in_prod_event(self, decay_struct,prod2full, event_map, momenta_in_decay,ext_mom,use_mc_masses,helicities): """ Reset the external momenta in the production event, since the virtuality of decaying particles has slightly changed the kinematics """ for index in self.curr_event.event2mg.keys(): if self.curr_event.event2mg[index]>0: part=self.curr_event.event2mg[index] # index for production ME part_for_curr_evt=event_map[part-1]+1 # index for curr event pid=self.curr_event.particle[part_for_curr_evt]['pid'] if part in decay_struct: id_res=decay_struct[part]['mg_tree'][0][0] self.curr_event.particle[part_for_curr_evt]['momentum']=momenta_in_decay[id_res].copy() self.curr_event.particle[part_for_curr_evt]['mass']=self.curr_event.particle[part_for_curr_evt]['momentum'].m else: self.curr_event.particle[part_for_curr_evt]['momentum']=ext_mom[prod2full[part-1]-1] self.curr_event.particle[part_for_curr_evt]['helicity']=helicities[prod2full[part-1]-1] if not use_mc_masses or abs(pid) not in self.MC_masses: try: self.curr_event.particle[part_for_curr_evt]['mass']=self.banner.get('param_card','mass', abs(pid)).value except KeyError: if self.model.get_particle(abs(pid)).get('mass').lower() == 'zero': self.curr_event.particle[part_for_curr_evt]['mass'] = 0 else: raise else: self.curr_event.particle[part_for_curr_evt]['mass']=self.MC_masses[abs(pid)] def reset_helicityonly_in_prod_event(self, event_map, helicities): """ Reset the external momenta in the production event, since the virtuality of decaying particles has slightly changed the kinematics """ for index in self.curr_event.event2mg.keys(): if self.curr_event.event2mg[index]>0: part=self.curr_event.event2mg[index] # index for production ME part_for_curr_evt=event_map[part-1]+1 # index for curr event pid=self.curr_event.particle[part_for_curr_evt]['pid'] self.curr_event.particle[part_for_curr_evt]['helicity']=helicities[part-1] def get_mom(self,momenta): """ input: list of momenta in a string format output: list of momenta in a 'momentum' format """ output=[] for item in momenta: comps=item.split() mom=momentum(float(comps[0]),float(comps[1]),float(comps[2]),float(comps[3])) output.append(mom) return output def get_identical_decay(self): """identify the various decay which are identical to each other""" logger.info('detect independant decays') start = time.time() # Possbilitiy to Bypass this step if len(self.all_decay) == 1: relation = {} base_tag = None for prod in self.all_ME.values(): for decay in prod['decays']: tags = decay['decay_tag'] for tag in tags: if not base_tag: relation[tag] = (tag, 1) base_tag = tag elif (tag,1) not in relation[base_tag]: relation[tag] = (base_tag,1) decay_mapping = self.get_process_identical_ratio(relation) return decay_mapping BW_cut = self.options['BW_cut'] #class the decay by class (nbody/pid) nbody_to_decay = collections.defaultdict(list) for decay in self.all_decay.values(): id = decay['dc_branch']['tree'][-1]['label'] id_final = decay['processes'][0].get_final_ids_after_decay() cut = 0.0 mass_final = tuple([m if m> cut else 0 for m in map(self.pid2mass, id_final)]) nbody_to_decay[(decay['nbody'], abs(id), mass_final)].append(decay) relation = {} # {tag: {(tag2, ratio)}} # Loop over the class and create the relation information about the 1 for ((nbody, pid, finals),decays) in nbody_to_decay.items(): if len(decays) == 1: continue mom_init = momentum(self.pid2mass(pid), 0, 0, 0) # create an object for the validation, keeping the ratio between # MEM i and MEM j. this is set at zero when the ratio is not found #constant valid = dict([ ((i, j), True) for j in range(len(decays)) for i in range(len(decays)) if i != j]) for nb in range(125): tree, jac, nb_sol = decays[0]['dc_branch'].generate_momenta(mom_init,\ True, self.pid2width, self.pid2mass, BW_cut,self.Ecollider) if not tree: continue p_str = '%s\n%s\n'% (tree[-1]['momentum'], '\n'.join(str(tree[i]['momentum']) for i in range(1, len(tree)) if i in tree)) values = {} for i in range(len(decays)): if any([valid[(i,j)] for j in range(len(decays)) if i !=j]): values[i] = self.calculate_matrix_element('decay', decays[i]['path'], p_str) else: #skip computation if all possibility are ruled out. values[i] = 0 #check if the ratio is constant for all possibilities for i in range(len(decays)): for j in range(i+1, len(decays)): if values[i] == 0 or values[j] == 0 or valid[(i,j)] == 0: continue # already not valid elif valid[(i,j)] is True: valid[(i,j)] = values[j]/values[i] valid[(j,i)] = valid[(i,j)] elif (valid[(i,j)] - values[j]/values[i]) < 1e-6 * (valid[(i,j)] + values[j]/values[i]): pass else: valid[(i, j)] = 0 valid[(j, i)] = 0 if __debug__: for i in range(len(decays)): comment= "| " for j in range(len(decays)): if i == j: comment+= "%4e " % 1 continue comment+= "%4e " % valid[(i,j)] comment+= "|"+ os.path.basename(decays[i]['path']) logger.debug(comment) # store the result in the relation object. (using tag as key) for i in range(len(decays)): tag_i = decays[i]['tag'][2:] for j in range(i+1, len(decays)): tag_j = decays[j]['tag'][2:] if valid[(i,j)] and tag_j not in relation: relation[tag_j] = (tag_i, valid[(i,j)]) # fullfill the object with the already identify to one decay. #and add those who doesn't have any relations. for decay in self.all_decay.values(): tags = [m.shell_string(pdg_order=True)[2:] for m in decay['processes']] init_tag = tags[0] if init_tag not in relation: out = (init_tag, 1) else: out = relation[init_tag] for tag in tags[1:]: relation[tag] = out decay_mapping = self.get_process_identical_ratio(relation) logger.info('Done in %ss' % (time.time()-start)) return decay_mapping def get_process_identical_ratio(self, relation): # Now that we have ratio relation between each tag, we need to say #what is the relation between the decay of the production process. #This is not only the product since some decay can be equivalent. decay_mapping = {} # final output: {first_process: [(equiv_proc, ratio), ...] tag2real = {} # basic tag [the one related via relation] -> first process # basic tag ratio doesn't have any identical factor (this simplify calculation) nb=0 for prod in self.all_ME.values(): for decay in prod['decays']: tag = decay['decay_tag'] nb+=1 # build the basic tag (all equiv process are related to this tag) basic_tag = [] ratio = 1 for t in tag: if t in relation: basic_tag.append(relation[t][0]) ratio *= relation[t][1] else: basic_tag.append(t) basic_tag = tuple(basic_tag) # compute identical factor ratio compare to a fully diffent decay #that we have assume for the basic tag if len(set(tag)) != len(tag): for t in set(tag): ratio /= math.factorial(tag.count(t)) # Now build the output if basic_tag not in tag2real: tag2real[basic_tag] = (tag, ratio) decay_mapping[tag] = set([(tag, 1)]) ratio2=1 else: real_tag, ratio2 = tag2real[basic_tag] if real_tag != tag: decay_mapping[real_tag].add((tag, ratio/ratio2)) return decay_mapping @misc.mute_logger() @misc.set_global() def generate_all_matrix_element(self): """generate the full series of matrix element needed by Madspin. i.e. the undecayed and the decay one. And associate those to the madspin production_topo object""" # 1. compute the partial width # 2. compute the production matrix element # 3. create the all_topology object # 4. compute the full matrix element (use the partial to throw away # pointless decay. # 5. add the decay information to the all_topology object (with branching # ratio) # 0. clean previous run ------------------------------------------------ path_me = self.path_me try: shutil.rmtree(pjoin(path_me,'full_me')) except Exception: pass try: shutil.rmtree(pjoin(path_me,'production_me')) except Exception, error: pass path_me = self.path_me # 1. compute the partial width------------------------------------------ if not self.options["onlyhelicity"]: self.get_branching_ratio() # 2. compute the production matrix element ----------------------------- processes = [line[9:].strip() for line in self.banner.proc_card if line.startswith('generate')] processes += [' '.join(line.split()[2:]) for line in self.banner.proc_card if re.search('^\s*add\s+process', line)] mgcmd = self.mgcmd modelpath = self.model.get('modelpath+restriction') commandline="import model %s" % modelpath if not self.model.mg5_name: commandline += ' --modelname' mgcmd.exec_cmd(commandline) # Handle the multiparticle of the banner #for name, definition in self.mscmd.multiparticles: if hasattr(self.mscmd, 'multiparticles_ms'): for name, pdgs in self.mscmd.multiparticles_ms.items(): if name == 'all': continue #self.banner.get('proc_card').get('multiparticles'): mgcmd.do_define("%s = %s" % (name, ' '.join(`i` for i in pdgs))) mgcmd.exec_cmd("set group_subprocesses False") logger.info('generating the production square matrix element') start = time.time() commandline='' for proc in processes: # deal with @ syntax need to move it after the decay specification if '@' in proc: proc, proc_nb = proc.split('@') try: proc_nb = int(proc_nb) except ValueError: raise MadSpinError, 'MadSpin didn\'t allow order restriction after the @ comment: \"%s\" not valid' % proc_nb proc_nb = '@ %i' % proc_nb else: proc_nb = '' if ',' in proc: raise MadSpinError, 'MadSpin can not decay event which comes from a decay chain.'+\ '\n The full decay chain should either be handle by MadGraph or by Masdspin.' if '[' not in proc: commandline+="add process %s %s --no_warning=duplicate;" % (proc, proc_nb) else: process, order, final = re.split('\[\s*(.*)\s*\]', proc) commandline+="add process %s %s --no_warning=duplicate;" % (process, proc_nb) if not order: continue elif not order.startswith('virt='): if '=' in order: order = order.split('=',1)[1] # define the list of particles that are needed for the radiateion pert = fks_common.find_pert_particles_interactions( mgcmd._curr_model,pert_order = order)['soft_particles'] commandline += "define pert_%s = %s;" % (order, ' '.join(map(str,pert)) ) # check if we have to increase by one the born order if '%s=' % order in process or '%s<=' % order in process: result=re.split(' ',process) process='' for r in result: if '%s=' % order in r: ior=re.split('=',r) r='%s<=%i' % (order,int(ior[1])+1) elif '%s<=' % order in r: ior=re.split('=',r) r='%s<=%i' % (order,int(ior[1])+1) process=process+r+' ' #handle special tag $ | / @ result = re.split('([/$@]|\w+1): del self.list_branches[key] elif not self.options["onlyhelicity"]: raise Exception, " No decay define for process." logger.info('keeping dummy decay for passthrough mode') # 4. compute the full matrix element ----------------------------------- if not self.options["onlyhelicity"]: logger.info('generating the full matrix element squared (with decay)') start = time.time() to_decay = self.mscmd.list_branches.keys() decay_text = [] for decays in self.mscmd.list_branches.values(): for decay in decays: if '=' not in decay: decay += ' QCD=99' if ',' in decay: decay_text.append('(%s)' % decay) else: decay_text.append(decay) decay_text = ', '.join(decay_text) commandline = '' for proc in processes: # deal with @ syntax need to move it after the decay specification if '@' in proc: proc, proc_nb = proc.split('@') try: proc_nb = int(proc_nb) except ValueError: raise MadSpinError, 'MadSpin didn\'t allow order restriction after the @ comment: \"%s\" not valid' % proc_nb proc_nb = '@ %i' % proc_nb else: proc_nb = '' if '[' not in proc: nb_comma = proc.count(',') if nb_comma == 0: commandline+="add process %s, %s %s --no_warning=duplicate;" % (proc, decay_text, proc_nb) elif nb_comma == 1: before, after = proc.split(',') commandline+="add process %s, %s, (%s, %s) %s --no_warning=duplicate;" % (before, decay_text, after, decay_text, proc_nb) else: raise Exception, 'too much decay at MG level. this can not be done for the moment)' else: process, order, final = re.split('\[\s*(.*)\s*\]', proc) commandline+="add process %s, %s %s --no_warning=duplicate;" % (process, decay_text, proc_nb) if not order: continue elif not order.startswith('virt='): if '=' in order: order = order.split('=',1)[1] # define the list of particles that are needed for the radiateion pert = fks_common.find_pert_particles_interactions( mgcmd._curr_model,pert_order = order)['soft_particles'] commandline += "define pert_%s = %s;" % (order, ' '.join(map(str,pert)) ) # check if we have to increase by one the born order if '%s=' % order in process or '%s<=' % order in process: result=re.split(' ',process) process='' for r in result: if '%s=' % order in r: ior=re.split('=',r) r='%s=%i' % (order,int(ior[1])+1) elif '%s<=' % order in r: ior=re.split('=',r) r='%s<=%i' % (order,int(ior[1])+1) process=process+r+' ' #handle special tag $ | / @ result = re.split('([/$@]|\w+ N).') start = time.time() commandline = '' i=0 for processes in self.list_branches.values(): for proc in processes: commandline+="add process %s @%i --no_warning=duplicate;" % (proc,i) i+=1 commandline = commandline.replace('add process', 'generate',1) mgcmd.exec_cmd(commandline, precmd=True) # remove decay with 0 branching ratio. mgcmd.remove_pointless_decay(self.banner.param_card) # commandline = 'output standalone_msF %s' % pjoin(path_me,'decay_me') logger.info(commandline) mgcmd.exec_cmd(commandline, precmd=True) logger.info('Done %.4g' % (time.time()-start)) # self.all_decay = {} for matrix_element in mgcmd._curr_matrix_elements.get_matrix_elements(): me = matrix_element.get('processes')[0] me_string = me.shell_string() dirpath = pjoin(path_me,'decay_me', 'SubProcesses', "P%s" % me_string) # self.all_decay[me_string] = {'path': dirpath, 'dc_branch':dc_branch_from_me(me), 'nbody': len(me.get_final_ids_after_decay()), 'processes': matrix_element.get('processes'), 'tag': me.shell_string(pdg_order=True)} # # if __debug__: # #check that all decay matrix element correspond to a decay only # for prod in self.all_ME.values(): # for decay in prod['matrix_element']['base_amplitude']['process']['decay_chains']: # assert decay.shell_string() in self.all_decay def get_branching_ratio(self): """compute the branching ratio of all the decaying particles""" # Compute the width branching ratio. Doing this at this point allows #to remove potential pointless decay in the diagram generation. resonances = decay_misc.get_all_resonances(self.banner, self.mgcmd, self.mscmd.list_branches.keys()) logger.debug('List of resonances:%s' % resonances) path_me = os.path.realpath(self.path_me) width = width_estimate(resonances, path_me, self.banner, self.model, self.pid2label) width.extract_br(self.list_branches, self.mgcmd) width.print_branching_fractions() #self.channel_br = width.get_BR_for_each_decay(self.decay_processes, # self.mgcmd._multiparticles) self.width_estimator = width self.banner.param_card = width.banner.param_card return width def compile(self): logger.info('Compiling code') self.compile_fortran(self.path_me, mode="full_me") if not self.options["onlyhelicity"]: self.compile_fortran(self.path_me, mode="production_me") self.compile_fortran(self.path_me, mode="decay_me") def compile_fortran(self, path_me, mode='production_me'): """ Compile the fortran executables associated with the evalutation of the matrix elements (production process) Returns the path to the fortran executable """ base_dir = pjoin(path_me, mode,"SubProcesses") list_prod=os.listdir(base_dir) logger.debug("""Finalizing %s's """% mode) # COMPILATION OF LIBRARY misc.compile( cwd=pjoin(path_me, mode,"Source","DHELAS"), mode='fortran') file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'lha_read_ms.f') shutil.copyfile(file_madspin, pjoin(path_me, mode,"Source","MODEL","lha_read.f" )) if not self.options["use_old_dir"]: misc.compile(arg=['clean'], cwd=pjoin(path_me, mode,"Source","MODEL"), mode='fortran') misc.compile( cwd=pjoin(path_me, mode,"Source","MODEL"), mode='fortran') file=pjoin(path_me, 'param_card.dat') shutil.copyfile(file,pjoin(path_me,mode,"Cards","param_card.dat")) # get all paths to matix elements list_prod=[] if mode == 'full_me': for tag in self.all_ME: for dico in self.all_ME[tag]['decays']: full_path=dico['path'] if full_path not in list_prod: list_prod.append(full_path) elif mode == 'production_me': for tag in self.all_ME: prod_path=self.all_ME[tag]['path'] if prod_path not in list_prod: list_prod.append(prod_path) elif mode == 'decay_me': for dir in os.listdir(base_dir): if dir[0] == 'P': list_prod.append(pjoin(base_dir, dir)) for i,me_path in enumerate(list_prod): # if direc[0] == "P" and os.path.isdir(pjoin(base_dir, direc)): # new_path = pjoin(base_dir, direc) new_path = me_path if mode == 'full_me': file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'driver.f') shutil.copyfile(file_madspin, pjoin(new_path,"driver.f")) elif mode == 'production_me': file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'driver_prod.f') shutil.copyfile(file_madspin, pjoin(new_path,"check_sa.f")) else: file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'driver_decay.f') shutil.copyfile(file_madspin, pjoin(new_path,"check_sa.f")) if mode=='full_me': file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'ranmar.f') shutil.copyfile(file_madspin, pjoin(new_path,"ranmar.f")) file_madspin=pjoin(path_me, 'seeds.dat') files.ln(file_madspin, new_path) file_madspin=pjoin(new_path, 'offset.dat') open(file_madspin,'w').write('%i\n' % i) if mode == 'full_me': file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'makefile_full') elif mode == 'production_me': file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'makefile_prod') else: file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'makefile_decay') shutil.copyfile(file_madspin, pjoin(new_path,"makefile") ) # files to produce the parameters: file_madspin=pjoin(MG5DIR, 'MadSpin', 'src', 'initialize.f') shutil.copyfile(file_madspin,pjoin(new_path,"initialize.f")) shutil.copyfile(pjoin(path_me, mode,'Source','MODEL','input.inc'), pjoin(new_path,'input.inc')) if not os.path.exists(pjoin(new_path,os.path.pardir, 'parameters.inc')): if not self.options["use_old_dir"]: misc.compile(arg=['clean'], cwd=new_path, mode='fortran') misc.compile(arg=['init'],cwd=new_path,mode='fortran') misc.call('./init', cwd=new_path) shutil.copyfile(pjoin(new_path,'parameters.inc'), pjoin(new_path,os.path.pardir, 'parameters.inc')) if mode == 'production_me': misc.compile(cwd=new_path, mode='fortran') else: misc.compile(cwd=new_path, mode='fortran') misc.compile(arg=['check'], cwd=new_path, mode='fortran') if __debug__: if(os.path.getsize(pjoin(path_me, mode,'SubProcesses', 'parameters.inc'))<10): print pjoin(path_me, mode,'SubProcesses', 'parameters.inc') raise Exception, "Parameters of the model were not written correctly ! %s " %\ os.path.getsize(pjoin(path_me, mode,'SubProcesses', 'parameters.inc')) def extract_resonances_mass_width(self, resonances): """ """ label2width = {} label2mass = {} pid2width = {} #pid2mass = self.pid2mass need_param_card_modif = False # now extract the width of the resonances: for particle_label in resonances: try: part=abs(self.pid2label[particle_label]) #mass = self.banner.get('param_card','mass', abs(part)) width = self.banner.get('param_card','decay', abs(part)) except ValueError, error: continue else: if (width.value > 0.001): label2width[particle_label]=float(width.value) else: # the width is less than 1 MeV, need to use an effective width !! # this is useful to handle cases like tau decays label2width[particle_label]=0.001 need_param_card_modif = True logger.warning('ATTENTION') logger.warning('Found a very small width in the param_card for particle '\ +str(particle_label)) logger.warning('Use instead an effective width of 1 MeV ' ) #label2mass[particle_label]=float(mass.value) #pid2mass[part]=label2mass[particle_label] pid2width[abs(part)]=label2width[particle_label] if label2width[particle_label]==0.0: need_param_card_modif = True for param in self.model["parameters"][('external',)]: if param.lhablock=="DECAY" and param.lhacode==[abs(part)]: label2width[particle_label]=max(param.value,0.001) pid2width[abs(part)]=label2width[particle_label] logger.warning('ATTENTION') logger.warning('Found a zero width in the param_card for particle '\ +str(particle_label)) logger.warning('Use instead the default/effective value '\ +str(label2width[particle_label])) # now we need to modify the values of the width # in param_card.dat, since this is where the input # parameters will be read when evaluating matrix elements if need_param_card_modif: decay_misc.modify_param_card(pid2width, self.path_me) def get_max_weight_from_event(self, decay_mapping): """ """ decay_tools = decay_misc() # check all set of decay that need to be done: decay_set = set() for production in self.all_ME.values(): decay_set.add(production['decaying']) numberev = self.options['Nevents_for_max_weigth'] # number of events numberps = self.options['max_weight_ps_point'] # number of phase pace points per event logger.info(' ') logger.info(' Estimating the maximum weight ') logger.info(' ***************************** ') logger.info(' Probing the first '+str(numberev)+' events') logger.info(' with '+str(numberps)+' phase space points') if len(decay_set) > 1: logger.info(' For %s decaying particle type in the final states' % len(decay_set)) logger.info(' ') probe_weight = [] starttime = time.time() ev = -1 nb_decay = dict( (key,0) for key in decay_set) probe_weight = dict( (key,[]) for key in decay_set) while ev+1 < len(decay_set) * numberev: production_tag, event_map = self.load_event() if production_tag == 0 == event_map: #end of file logger.info('Not enough events for at least one production mode.') logger.info('This is ok as long as you don\'t reuse the max weight for other generations.') break #check if this event is usefull or not decaying = self.all_ME[production_tag]['decaying'] if nb_decay[decaying] >= numberev: continue ev += 1 nb_decay[decaying] += 1 # mg5_me_prod, prod_values = self.evaluate_me_production(production_tag, event_map) logger.debug('Event %s/%s: ' % (ev+1, len(decay_set)*numberev)) if (len(decay_set)*numberev -(ev+2)) >0: self.mscmd.update_status((len(decay_set)*numberev -(ev+2),1,ev+1, 'MadSpin: Maximum weight'), force=False, print_log=False) #logger.debug('Selected topology : '+str(tag_topo)) max_decay = {} mean_decay= {} std_decay = {} atleastonedecay=False for decay in self.all_ME[production_tag]['decays']: tag = decay['decay_tag'] if decay_mapping and not tag in decay_mapping: continue if not tag: continue # No decay for this process atleastonedecay = True weight = self.get_max_weight_from_fortran(decay['path'], event_map,numberps,self.options['BW_cut']) #weight=mg5_me_full*BW_weight_prod*BW_weight_decay/mg5_me_prod if tag in max_decay: max_decay[tag] = max([max_decay[tag], weight]) else: max_decay[tag] = weight #print weight, max_decay[name] #raise Exception if not atleastonedecay: # NO decay [one possibility is all decay are identical to their particle] logger.info('No independent decay for one type of final states -> skip those events for the maximum weight computation') nb_decay[decaying] = numberev ev += numberev -1 continue probe_weight[decaying].append(max_decay) self.terminate_fortran_executables() self.calculator = {} self.calculator_nbcall = {} if ev % 5 == 0: running_time = misc.format_timer(time.time()-starttime) info_text = 'Event %s/%s : %s \n' % (ev + 1, len(decay_set)*numberev, running_time) #for index,tag_decay in enumerate(max_decay): # info_text += ' decay_config %s [%s] : %s\n' % \ # (index+1, ','.join(tag_decay), probe_weight[decaying][nb_decay[decaying]-1][tag_decay]) logger.info(info_text[:-1]) # Computation of the maximum weight used in the unweighting procedure for decaying in probe_weight: if not probe_weight[decaying]: continue #me_linked = [me for me in self.all_ME.values() if me['decaying'] == decaying] for decay_tag in probe_weight[decaying][0].keys(): weights=[] for ev in range(numberev): try: weights.append(probe_weight[decaying][ev][decay_tag]) except: continue if not weights: logger.warning( 'no events for %s' % decay_tag) continue weights.sort(reverse=True) assert len(weights) == 1 or weights[0] >= weights[1] ave_weight, std_weight = decay_tools.get_mean_sd(weights) base_max_weight = 1.05 * (ave_weight+self.options['nb_sigma']*std_weight) for i in [20, 30, 40, 50]: if len(weights) < i: break ave_weight, std_weight = decay_tools.get_mean_sd(weights[:i]) base_max_weight = max(base_max_weight, 1.05 * (ave_weight+self.options['nb_sigma']*std_weight)) if weights[0] > base_max_weight: base_max_weight = 1.05 * weights[0] for associated_decay, ratio in decay_mapping[decay_tag]: max_weight= ratio * base_max_weight if ratio != 1: max_weight *= 1.1 #security br = 0 #assign the value to the associated decays for k,m in self.all_ME.items(): for mi in m['decays']: if mi['decay_tag'] == associated_decay: mi['max_weight'] = max_weight br = mi['br'] nb_finals = len(mi['finals']) if decay_tag == associated_decay: logger.debug('Decay channel %s :Using maximum weight %s [%s] (BR: %s)' % \ (','.join(decay_tag), base_max_weight, max(weights), br/nb_finals)) else: logger.debug('Decay channel %s :Using maximum weight %s (BR: %s)' % \ (','.join(associated_decay), max_weight, br/nb_finals)) # if __debug__: # check that all decay have a max_weight and fix it if not the case. for prod in self.all_ME.values(): for dec in prod['decays']: if dec['decay_tag'] and not 'max_weight' in dec: dec['max_weight'] = 0. # assert 'max_weight' in dec and dec['max_weight'] ,\ # 'fail for %s (%s)' % (str(dec['decay_tag']), \ # os.path.basename(prod['path'])) self.evtfile.seek(0) return def load_event(self): """Load the next event and ensure that the ME is define""" #decay_tools = decay_misc() if self.curr_event.get_next_event() == 'no_event': return 0, 0 production_tag, order = self.curr_event.get_tag() P_order = self.all_ME[production_tag]['tag2order'][production_tag] event_map = {} evt_order = list(order[0])+list(order[1]) for i, id in enumerate(P_order[0] + P_order[1]): in_event = [pos for pos, label in enumerate(evt_order) \ if label == id] if i < len(order[0]): in_event = [pos for pos in in_event if pos < len(order[0])] else: in_event = [pos for pos in in_event if pos >= len(order[0])] if len(in_event) == 1: in_event = in_event[0] else: config = random.randint(0, len(in_event)-1) in_event = in_event[config] evt_order[in_event] = 0 event_map[i] = in_event if __debug__ and len(order[0]) == 2: assert event_map[0] in [0,1], 'wrong event mapping %s' % event_map assert event_map[1] in [0,1], 'wrong event mapping %s' % event_map assert production_tag in self.all_ME return production_tag, event_map def get_max_weight_from_fortran(self, path, event_map,nbpoints,BWcut): """return the max. weight associated with me decay['path']""" p, p_str=self.curr_event.give_momenta(event_map) std_in=" %s %s %s %s \n" % ("1",BWcut, self.Ecollider, nbpoints) std_in+=p_str max_weight = self.loadfortran('maxweight', path, std_in) return max_weight def loadfortran(self, mode, path, stdin_text, first=True): """ call the fortran executable """ tmpdir = '' if ('full',path) in self.calculator: external = self.calculator[('full',path)] self.calculator_nbcall[('full',path)] += 1 else: logger.debug('we have %s calculator ready' % len(self.calculator)) tmpdir = path executable_prod="./check" external = Popen(executable_prod, stdout=PIPE, stdin=PIPE, stderr=STDOUT, cwd=tmpdir) self.calculator[('full',path,)] = external self.calculator_nbcall[('full',path)] = 1 try: external.stdin.write(stdin_text) except IOError: if not first: raise try: external.terminate() except: pass del self.calculator[('full',path,)] return self.loadfortran(mode, path, stdin_text, first=False) if mode == 'maxweight': maxweight=float(external.stdout.readline()) output = maxweight elif mode == 'full_me': me_value=float(external.stdout.readline()) output = me_value elif mode == 'unweighting': firstline=external.stdout.readline().split() try: nexternal=int(firstline[0]) trials= int(firstline[1]) BWvalue= float(firstline[2]) weight= float(firstline[3]) failed= float(firstline[4]) use_mc_masses=int(firstline[5]) except ValueError: logger.debug(firstline) return momenta=[external.stdout.readline() for i in range(nexternal)] lastline=external.stdout.readline().split() helicities=[lastline[i] for i in range(len(lastline))] output = trials, BWvalue, weight, momenta, failed, use_mc_masses, helicities if len(self.calculator) > self.options['max_running_process']: logger.debug('more than %s calculators. Perform cleaning' % self.options['max_running_process']) nb_calls = self.calculator_nbcall.values() nb_calls.sort() cut = max([nb_calls[len(nb_calls)//2], 0.001 * nb_calls[-1]]) for key, external in list(self.calculator.items()): nb = self.calculator_nbcall[key] if nb < cut: if key[0]=='full': path=key[1] end_signal="5 0 0 0 \n" # before closing, write down the seed external.stdin.write(end_signal) ranmar_state=external.stdout.readline() ranmar_file=pjoin(path,'ranmar_state.dat') ranmar=open(ranmar_file, 'w') ranmar.write(ranmar_state) ranmar.close() external.stdin.close() external.stdout.close() external.terminate() del self.calculator[key] del self.calculator_nbcall[key] else: self.calculator_nbcall[key] = self.calculator_nbcall[key] //10 return output def calculate_matrix_element(self, mode, production, stdin_text): """routine to return the matrix element""" if mode != "decay": raise Exception, "This function is only secure in mode decay." tmpdir = '' if (mode, production) in self.calculator: external = self.calculator[(mode, production)] self.calculator_nbcall[(mode, production)] += 1 else: logger.debug('we have %s calculator ready' % len(self.calculator)) if mode == 'prod': tmpdir = pjoin(self.path_me,'production_me', 'SubProcesses', production) elif mode in ['full','decay']: tmpdir = pjoin(self.path_me,'%s_me' % mode, 'SubProcesses', production) executable_prod="./check" external = Popen(executable_prod, stdout=PIPE, stdin=PIPE, stderr=STDOUT, cwd=tmpdir) self.calculator[(mode, production)] = external self.calculator_nbcall[(mode, production)] = 1 external.stdin.write(stdin_text) if mode == 'prod': info = int(external.stdout.readline()) nb_output = abs(info)+1 else: info = 1 nb_output = 1 prod_values = ' '.join([external.stdout.readline() for i in range(nb_output)]) if info < 0: print 'ZERO DETECTED' print prod_values print stdin_text os.system('lsof -p %s' % external.pid) return ' '.join(prod_values.split()[-1*(nb_output-1):]) if len(self.calculator) > self.options['max_running_process']: logger.debug('more than 100 calculator. Perform cleaning') nb_calls = self.calculator_nbcall.values() nb_calls.sort() cut = max([nb_calls[len(nb_calls)//2], 0.001 * nb_calls[-1]]) for key, external in list(self.calculator.items()): nb = self.calculator_nbcall[key] if nb < cut: external.stdin.close() external.stdout.close() external.terminate() del self.calculator[key] del self.calculator_nbcall[key] else: self.calculator_nbcall[key] = self.calculator_nbcall[key] //10 if mode == 'prod': return prod_values else: return float(prod_values) def generate_configs_file(self,nfinal,decay, path): """ write the file configs_decay.inc also record the itree information in a python variable, this will be needed to write down the event decay_struct['mg_tree'] = [(d1,d2, mother), (d1,d2,mother), ...] with - BACKWARD ORDER, - me indices """ decay_struct=decay['decay_struct'] me_index=2 # should match the particle index in the full matrix element count_res=0 # count number of resonances iforest=[] pmasswidth=[] # data (map_external2res(i), i=1,4)/1,2,-2,-4/ decay['prod2full']=[1,2] map_external=' data (map_external2res(i), i=1,%s)/1,2,' %(nfinal+2) for part in range(3,nfinal+3): if part in decay_struct: # particle in the prod. event to be decayed #print part decay_struct[part]['mg_tree']=[] nb_res=len(decay_struct[part]["tree"].keys()) for res in range(-1,-nb_res-1,-1): label=abs(decay_struct[part]["tree"][res]['label']) mass=self.pid2massvar[label] width=self.pid2widthvar[label] me_res=-nb_res-res-count_res-1 indexd1=decay_struct[part]["tree"][res]["d1"]["index"] if indexd1>0: me_index+=1 me_d1=me_index else: # need to label resonances backward me_d1 = -nb_res-indexd1-count_res-1 indexd2=decay_struct[part]["tree"][res]["d2"]["index"] if indexd2>0: me_index+=1 me_d2=me_index else: # need to label resonances backward me_d2 = -nb_res-indexd2-count_res-1 iforest.append(" DATA (IDECAY(I, %s ),I=1,2)/ %s , %s / \n" % (me_res, me_d1, me_d2)) decay_struct[part]['mg_tree'].append((me_res,me_d1,me_d2)) pmasswidth.append(" PRMASS(%s)=%s \n" %(me_res,mass) ) pmasswidth.append(" PRWIDTH(%s)=%s \n" %(me_res,width) ) count_res=count_res+nb_res map_external+='%s ,' % (-count_res) decay['prod2full'].append(-count_res) else: me_index+=1 map_external+='%s ,' % me_index decay['prod2full'].append(me_index) map_external=map_external[:-1]+'/ \n' trappe=open(pjoin(path,'configs_decay.inc'),'w') trappe.write(map_external) for item in iforest: trappe.write(item) trappe.write(' ns_channel_decay= %s \n' % count_res) for item in pmasswidth: trappe.write(item) trappe.close() def get_montecarlo_masses_from_event(self,decay_struct, event_map, map_prod2full): """ from the production event curr_event and from the decay channel 'decay_struct' (which has just been selected randomly), get the MonteCarlo masses """ # in order to preserve the natural order in lhe file, # we need the inverse of the dico event_map inv_event_map={} for i in event_map.keys(): inv_event_map[event_map[i]]=i indices_for_mc_masses=[] values_for_mc_masses=[] for index in self.curr_event.event2mg.keys(): if self.curr_event.event2mg[index]>0: # no need to consider resonances in the production event file part=inv_event_map[self.curr_event.event2mg[index]-1]+1 # index for prod. matrix element part_for_curr_evt=self.curr_event.event2mg[index] # index for event file if part not in decay_struct: # get the pid curr_pid=abs(self.curr_event.particle[part_for_curr_evt]['pid']) if curr_pid in self.MC_masses: #print part #print map_prod2full indices_for_mc_masses.append(map_prod2full[part-1]) values_for_mc_masses.append(self.MC_masses[curr_pid]) else: # now we need to write the decay products in the event # follow the decay chain order, so that we can easily keep track of the mother index for res in range(-1,-len(decay_struct[part]["tree"].keys())-1,-1): index_d1=decay_struct[part]['mg_tree'][-res-1][1] index_d2=decay_struct[part]['mg_tree'][-res-1][2] pid_d1=abs(decay_struct[part]\ ["tree"][res]["d1"]["label"]) pid_d2=abs(decay_struct[part]\ ["tree"][res]["d2"]["label"]) if index_d1 >0 and pid_d1 in self.MC_masses: indices_for_mc_masses.append(index_d1) values_for_mc_masses.append(self.MC_masses[pid_d1]) if index_d2 >0 and pid_d2 in self.MC_masses: indices_for_mc_masses.append(index_d2) values_for_mc_masses.append(self.MC_masses[pid_d2]) return indices_for_mc_masses,values_for_mc_masses def decay_one_event_new(self,curr_event,decay_struct, event_map, momenta_in_decay, use_mc_masses, helicities): """Write down the event momenta is the list of momenta ordered according to the productin ME """ pid2color = self.pid2color decayed_event=Event() decayed_event.event2mg={} decayed_event.ievent=curr_event.ievent decayed_event.wgt=curr_event.wgt decayed_event.scale=curr_event.scale decayed_event.aqed=curr_event.aqed decayed_event.aqcd=curr_event.aqcd decayed_event.diese=curr_event.diese decayed_event.rwgt=curr_event.rwgt decayed_event.event_init_line=curr_event.event_init_line part_number=0 external=0 maxcol=curr_event.max_col # in order to preserve the natural order in lhe file, # we need the inverse of the dico event_map inv_event_map={} for i in event_map.keys(): inv_event_map[event_map[i]]=i sol_nb = None for index in curr_event.event2mg.keys(): if curr_event.event2mg[index]>0: part=inv_event_map[curr_event.event2mg[index]-1]+1 # index for prod. matrix element part_for_curr_evt=curr_event.event2mg[index] # index for event file if part not in decay_struct: external+=1 part_number+=1 decayed_event.particle[part_number]=curr_event.particle[part_for_curr_evt] decayed_event.event2mg[part_number]=part_number else: # now we need to write the decay products in the event # follow the decay chain order, so that we can easily keep track of the mother index map_to_part_number={} for res in range(-1,-len(decay_struct[part]["tree"].keys())-1,-1): index_res_for_mom=decay_struct[part]['mg_tree'][-res-1][0] if (res==-1): part_number+=1 mom=momenta_in_decay[index_res_for_mom].copy() pid=decay_struct[part]["tree"][res]['label'] istup=2 mothup1=curr_event.particle[part_for_curr_evt]["mothup1"] mothup2=curr_event.particle[part_for_curr_evt]["mothup2"] colup1=curr_event.particle[part_for_curr_evt]["colup1"] colup2=curr_event.particle[part_for_curr_evt]["colup2"] decay_struct[part]["tree"][res]["colup1"]=colup1 decay_struct[part]["tree"][res]["colup2"]=colup2 mass=mom.m helicity=0. decayed_event.particle[part_number]={"pid":pid,\ "istup":istup,"mothup1":mothup1,"mothup2":mothup2,\ "colup1":colup1,"colup2":colup2,"momentum":mom,\ "mass":mass,"helicity":helicity} decayed_event.event2mg[part_number]=part_number map_to_part_number[res]=part_number # # Extract color information so that we can write the color flow # colormother=pid2color[decay_struct[part]["tree"][res]["label"]] colord1=pid2color[decay_struct[part]\ ["tree"][res]["d1"]["label"]] colord2=pid2color[decay_struct[part]\ ["tree"][res]["d2"]["label"]] colup1=decay_struct[part]["tree"][res]["colup1"] colup2=decay_struct[part]["tree"][res]["colup2"] # now figure out what is the correct color flow informatio # Only consider 1,3, 3-bar and 8 color rep. # Normally, the color flow needs to be determined only # during the reshuffling phase, but it is currenlty assigned # for each "trial event" if abs(colord1)==1: d2colup1=colup1 d2colup2=colup2 d1colup1=0 d1colup2=0 elif abs(colord2)==1: d1colup1=colup1 d1colup2=colup2 d2colup1=0 d2colup2=0 elif colord1==3 and colord2==-3 and colormother ==1: maxcol+=1 d1colup1=maxcol d1colup2=0 d2colup1=0 d2colup2=maxcol elif colord1==3 and colord2==-3 and colormother ==8: d1colup1=colup1 d1colup2=0 d2colup1=0 d2colup2=colup2 elif colord1==-3 and colord2==3 and colormother ==8: d1colup1=0 d1colup2=colup2 d2colup1=colup1 d2colup2=0 elif colord1==-3 and colord2==3 and colormother ==1: maxcol+=1 d1colup1=0 d1colup2=maxcol d2colup1=maxcol d2colup2=0 elif colord1==3 and colord2==8 and colormother ==3: maxcol+=1 d2colup1=colup1 d2colup2=maxcol d1colup1=maxcol d1colup2=0 elif colord2==3 and colord1==8 and colormother ==3: maxcol+=1 d1colup1=colup1 d1colup2=maxcol d2colup1=maxcol d2colup2=0 elif colord1==-3 and colord2==8 and colormother ==-3: maxcol+=1 d2colup2=colup2 d2colup1=maxcol d1colup2=maxcol d1colup1=0 elif colord2==-3 and colord1==8 and colormother ==-3: maxcol+=1 d1colup2=colup2 d1colup1=maxcol d2colup2=maxcol d2colup1=0 elif colord1==-3 and colord2==-3 and colormother == 3: maxcol+=2 d1colup1=0 d1colup2=maxcol d2colup1=0 d2colup2=maxcol-1 elif (colord1==-3 and colord2==3 and colormother == 3) or\ (colord1==-3 and colord2==3 and colormother == -3): maxcol+=2 d1colup1 = 0 d1colup2 = maxcol d2colup1 = maxcol-1 d2colup2 = 0 elif (colord1==3 and colord2==-3 and colormother == 3) or\ (colord1==3 and colord2==-3 and colormother == -3): maxcol+=2 d1colup1=maxcol d1colup2=0 d2colup1=0 d2colup2=maxcol-1 elif colord1==3 and colord2==3 and colormother == -3: maxcol+=2 d1colup1=maxcol d1colup2=0 d2colup1=maxcol-1 d2colup2=0 elif colord2==8 and colord1==8 and colormother ==8: maxcol+=1 ran = random.random() if ran> 0.5: d1colup2=colup2 d1colup1=maxcol d2colup2=maxcol d2colup1=colup1 else: d1colup2=maxcol d1colup1=colup1 d2colup2=colup2 d2colup1=maxcol else: raise Exception, 'color combination not treated by MadSpin (yet). (%s,%s,%s)' \ % (colord1,colord2,colormother) part_number+=1 index_d1_for_mom=decay_struct[part]['mg_tree'][-res-1][1] mom=momenta_in_decay[index_d1_for_mom].copy() #mom=decay_products[decay_struct[part]\ # ["tree"][res]["d1"]["index"]]["momentum"] pid=decay_struct[part]\ ["tree"][res]["d1"]["label"] indexd1=decay_struct[part]["tree"][res]["d1"]["index"] if ( indexd1>0): hel=helicities[index_d1_for_mom-1] istup=1 external+=1 if not use_mc_masses or abs(pid) not in self.MC_masses: mass=self.banner.get('param_card','mass', abs(pid)).value else: mass=self.MC_masses[abs(pid)] else: hel=0. decay_struct[part]["tree"][indexd1]["colup1"]=d1colup1 decay_struct[part]["tree"][indexd1]["colup2"]=d1colup2 istup=2 mass=mom.m map_to_part_number[indexd1]=part_number mothup1=map_to_part_number[res] mothup2=map_to_part_number[res] decayed_event.particle[part_number]={"pid":pid,\ "istup":istup,"mothup1":mothup1,"mothup2":mothup2,\ "colup1":d1colup1,"colup2":d1colup2,"momentum":mom,\ "mass":mass,"helicity":hel} decayed_event.event2mg[part_number]=part_number part_number+=1 index_d2_for_mom=decay_struct[part]['mg_tree'][-res-1][2] mom=momenta_in_decay[index_d2_for_mom].copy() #mom=decay_products[decay_struct[part]["tree"][res]["d2"]\ # ["index"]]["momentum"] pid=decay_struct[part]["tree"][res]["d2"]\ ["label"] indexd2=decay_struct[part]["tree"][res]["d2"]["index"] if ( indexd2>0): hel=helicities[index_d2_for_mom-1] istup=1 external+=1 if not use_mc_masses or abs(pid) not in self.MC_masses: mass=self.banner.get('param_card','mass', abs(pid)).value else: mass=self.MC_masses[abs(pid)] else: hel=0. istup=2 decay_struct[part]["tree"][indexd2]["colup1"]=d2colup1 decay_struct[part]["tree"][indexd2]["colup2"]=d2colup2 mass=mom.m map_to_part_number[indexd2]=part_number mothup1=map_to_part_number[res] mothup2=map_to_part_number[res] decayed_event.particle[part_number]={"pid":pid,"istup":istup,\ "mothup1":mothup1,"mothup2":mothup2,"colup1":d2colup1,\ "colup2":d2colup2,\ "momentum":mom,"mass":mass,"helicity":hel} decayed_event.event2mg[part_number]=part_number else: # resonance in the production event part=curr_event.event2mg[index] part_number+=1 decayed_event.particle[part_number]=curr_event.resonance[part] decayed_event.event2mg[part_number]=part_number # Here I need to check that the daughters still have the correct mothup1 and mothup2 for part in curr_event.resonance.keys(): mothup1=curr_event.resonance[part]["mothup1"] mothup2=curr_event.resonance[part]["mothup2"] if mothup1==index: if mothup2!=index: print "Warning: mothup1!=mothup2" curr_event.resonance[part]["mothup1"]=part_number curr_event.resonance[part]["mothup2"]=part_number for part in curr_event.particle.keys(): mothup1=curr_event.particle[part]["mothup1"] mothup2=curr_event.particle[part]["mothup2"] if mothup1==index: if mothup2!=index: print "Warning: mothup1!=mothup2" curr_event.particle[part]["mothup1"]=part_number curr_event.particle[part]["mothup2"]=part_number decayed_event.nexternal=part_number return decayed_event def add_loose_decay(self): """ in presence of multiprocess with multiple decay options all the BR might not be identical. In such case, the total number of events should drop such that the events file is still a unweighted physical events sample. This routines add null decay (=> not written events) if appropriate.""" first = True max_br = max([m['total_br'] for m in self.all_ME.values()]) if max_br >= 1: if max_br > 1.0001: raise MadSpinError, 'BR is larger than one.' max_br = 1 for production in self.all_ME.values(): if production['total_br'] < max_br: if production['total_br'] > 0.9999: continue if first: first = False min_br = min([m['total_br'] for m in self.all_ME.values()]) logger.info('''All production process does not have the same total Branching Ratio. Therefore the total number of events after decay will be lower than the original file. [max_br = %s, min_br = %s]''' % (max_br, min_br),'$MG:color:BLACK') fake_decay = {'br': max_br - production['total_br'], 'path': None, 'matrix_element': None, 'finals': None, 'base_order': None, 'decay_struct':None, 'decay_tag': None} production['decays'].append(fake_decay) production['total_br'] = max_br def write_banner_information(self, eff=1): ms_banner = "" cross_section = True # tell if possible to write the cross-section in advance total_br = [] self.br_per_id = {} for production in self.all_ME.values(): one_br = 0 partial_br = 0 for decay in production['decays']: if not decay['decay_tag']: cross_section = False one_br += decay['br'] continue partial_br += decay['br'] ms_banner += "# %s\n" % ','.join(decay['decay_tag']).replace('\n',' ') ms_banner += "# BR: %s\n# max_weight: %s\n" % (decay['br'], decay['max_weight']) one_br += decay['br'] if production['Pid'] not in self.br_per_id: self.br_per_id[production['Pid']] = partial_br elif self.br_per_id[production['Pid']] != partial_br: self.br_per_id[production['Pid']] = -1 total_br.append(one_br) if __debug__: for production in self.all_ME.values(): assert production['total_br'] - min(total_br) < 1e-4 self.branching_ratio = max(total_br) * eff #self.banner['madspin'] += ms_banner # Update cross-section in the banner if 'mggenerationinfo' in self.banner: mg_info = self.banner['mggenerationinfo'].split('\n') for i,line in enumerate(mg_info): if 'Events' in line: if eff == 1: self.err_branching_ratio = 0 continue initial_event = int(mg_info[i].split()[-1]) nb_event = int(initial_event * eff) mg_info[i] = '# Number of Events : %i' % nb_event if eff >0.5: self.err_branching_ratio = max(total_br) * math.sqrt(initial_event - eff * initial_event)/initial_event else: self.err_branching_ratio = max(total_br) * math.sqrt(eff * initial_event)/initial_event continue if ':' not in line: continue info, value = line.rsplit(':',1) try: value = float(value) except: continue if cross_section: mg_info[i] = '%s : %s' % (info, value * self.branching_ratio) else: mg_info[i] = '%s : %s' % (info, value * self.branching_ratio) self.banner['mggenerationinfo'] = '\n'.join(mg_info) self.cross = 0 self.error = 0 if 'init' in self.banner and (eff!=1 or not any(v==-1 for v in self.br_per_id.values())): new_init ='' curr_proc = 0 has_missing=False for line in self.banner['init'].split('\n'): if len(line.split()) != 4: new_init += '%s\n' % line else: curr_proc += 1 data = [float(nb) for nb in line.split()] id = int(data[-1]) if id in self.br_per_id and not any(v==-1 for v in self.br_per_id.values()): data[:3] = [data[i] * self.br_per_id[id] for i in range(3)] else: data[:3] = [ data[i] * self.branching_ratio for i in range(3)] has_missing=True new_init += ' %.12E %.12E %.12E %i\n' % tuple(data) cross, error = [float(d) for d in data[:2]] self.cross += cross self.error += error**2 self.banner['init'] = new_init self.error = math.sqrt(self.error) if has_missing and curr_proc not in [0,1]: logger.warning('''The partial cross section for each subprocess can not be determine. due Reason: multiple final state in the same subprocess (and the presence of multiple BR) Consequence: the information of the lhe will therefore be incorrect. Please correct it if needed.''') self.banner.write(self.outputfile, close_tag=False) def terminate_fortran_executables(self, path_to_decay=0 ): """routine to terminate all fortran executables""" if not path_to_decay: for (mode, path) in self.calculator: if mode=='decay': external = self.calculator[(mode, path)] external.terminate() del external elif mode=='full': stdin_text="5 0 0 0 \n" # before closing, write down the seed external = self.calculator[('full',path)] external.stdin.write(stdin_text) ranmar_state=external.stdout.readline() ranmar_file=pjoin(path,'ranmar_state.dat') ranmar=open(ranmar_file, 'w') ranmar.write(ranmar_state) ranmar.close() external.stdin.close() external.stdout.close() external.terminate() del external else: try: external = self.calculator[('full', path_to_decay)] except Exception: pass else: stdin_text="5 0 0 0" external.stdin.write(stdin_text) external.stdin.close() external.stdout.close() external.terminate() del external self.calculator = {} class decay_all_events_onshell(decay_all_events): """special mode for onshell production""" @misc.mute_logger() @misc.set_global() def generate_all_matrix_element(self): """generate the full series of matrix element needed by Madspin. i.e. the undecayed and the decay one. And associate those to the madspin production_topo object""" # 1. compute the partial width # 2. compute the production matrix element # 3. create the all_topology object # 4. compute the full matrix element (use the partial to throw away # pointless decay. # 5. add the decay information to the all_topology object (with branching # ratio) # 0. clean previous run ------------------------------------------------ path_me = self.path_me try: shutil.rmtree(pjoin(path_me,'madspin_me')) except Exception: pass # 1. compute the partial width------------------------------------------ #self.get_branching_ratio() # 2. compute the production matrix element ----------------------------- processes = [line[9:].strip() for line in self.banner.proc_card if line.startswith('generate')] processes += [' '.join(line.split()[2:]) for line in self.banner.proc_card if re.search('^\s*add\s+process', line)] mgcmd = self.mgcmd modelpath = self.model.get('modelpath+restriction') commandline="import model %s" % modelpath if not self.model.mg5_name: commandline += ' --modelname' mgcmd.exec_cmd(commandline) # Handle the multiparticle of the banner #for name, definition in self.mscmd.multiparticles: if hasattr(self.mscmd, 'multiparticles_ms'): for name, pdgs in self.mscmd.multiparticles_ms.items(): if name == 'all': continue #self.banner.get('proc_card').get('multiparticles'): mgcmd.do_define("%s = %s" % (name, ' '.join(`i` for i in pdgs))) mgcmd.exec_cmd("set group_subprocesses False") logger.info('generating the production square matrix element') start = time.time() commandline='' for proc in processes: # deal with @ syntax need to move it after the decay specification if '@' in proc: proc, proc_nb = proc.split('@') try: proc_nb = int(proc_nb) except ValueError: raise MadSpinError, 'MadSpin didn\'t allow order restriction after the @ comment: \"%s\" not valid' % proc_nb proc_nb = '@ %i' % proc_nb else: proc_nb = '' if ',' in proc: raise MadSpinError, 'MadSpin can not decay event which comes from a decay chain.'+\ '\n The full decay chain should either be handle by MadGraph or by Masdspin.' if '[' not in proc: commandline+="add process %s %s --no_warning=duplicate;" % (proc, proc_nb) else: process, order, final = re.split('\[\s*(.*)\s*\]', proc) commandline+="add process %s %s --no_warning=duplicate;" % (process, proc_nb) if not order: continue elif not order.startswith('virt='): if '=' in order: order = order.split('=',1)[1] # define the list of particles that are needed for the radiateion pert = fks_common.find_pert_particles_interactions( mgcmd._curr_model,pert_order = order)['soft_particles'] commandline += "define pert_%s = %s;" % (order, ' '.join(map(str,pert)) ) # check if we have to increase by one the born order if '%s=' % order in process: result=re.split(' ',process) process='' for r in result: if '%s=' % order in r: ior=re.split('=',r) r='QCD=%i' % (int(ior[1])+1) process=process+r+' ' #handle special tag $ | / @ result = re.split('([/$@]|\w+=\w+)', process, 1) if len(result) ==3: process, split, rest = result commandline+="add process %s pert_%s %s%s %s --no_warning=duplicate;" % (process, order ,split, rest, proc_nb) else: commandline +='add process %s pert_%s %s --no_warning=duplicate;' % (process,order, proc_nb) # commandline = commandline.replace('add process', 'generate',1) # logger.info(commandline) # # mgcmd.exec_cmd(commandline, precmd=True) # commandline = 'output standalone_msP %s %s' % \ # (pjoin(path_me,'production_me'), ' '.join(self.list_branches.keys())) # mgcmd.exec_cmd(commandline, precmd=True) # logger.info('Done %.4g' % (time.time()-start)) # 3. Create all_ME + topology objects ---------------------------------- # matrix_elements = mgcmd._curr_matrix_elements.get_matrix_elements() # self.all_ME.adding_me(matrix_elements, pjoin(path_me,'production_me')) # 4. compute the full matrix element ----------------------------------- logger.info('generating the full matrix element squared (with decay)') # start = time.time() to_decay = self.mscmd.list_branches.keys() decay_text = [] for decays in self.mscmd.list_branches.values(): for decay in decays: if '=' not in decay: decay += ' QCD=99' if ',' in decay: decay_text.append('(%s)' % decay) else: decay_text.append(decay) decay_text = ', '.join(decay_text) # commandline = '' for proc in processes: # deal with @ syntax need to move it after the decay specification if '@' in proc: proc, proc_nb = proc.split('@') try: proc_nb = int(proc_nb) except ValueError: raise MadSpinError, 'MadSpin didn\'t allow order restriction after the @ comment: \"%s\" not valid' % proc_nb proc_nb = '@ %i' % proc_nb else: proc_nb = '' if '[' not in proc: nb_comma = proc.count(',') if nb_comma == 0: commandline+="add process %s, %s %s --no_warning=duplicate;" % (proc, decay_text, proc_nb) elif nb_comma == 1: before, after = proc.split(',') commandline+="add process %s, %s, (%s, %s) %s --no_warning=duplicate;" % (before, decay_text, after, decay_text, proc_nb) else: raise Exception, 'too much decay at MG level. this can not be done for the moment)' else: process, order, final = re.split('\[\s*(.*)\s*\]', proc) commandline+="add process %s, %s %s --no_warning=duplicate;" % (process, decay_text, proc_nb) if not order: continue elif not order.startswith('virt='): if '=' in order: order = order.split('=',1)[1] # define the list of particles that are needed for the radiateion pert = fks_common.find_pert_particles_interactions( mgcmd._curr_model,pert_order = order)['soft_particles'] commandline += "define pert_%s = %s;" % (order, ' '.join(map(str,pert)) ) # check if we have to increase by one the born order if '%s=' % order in process: result=re.split(' ',process) process='' for r in result: if '%s=' % order in r: ior=re.split('=',r) r='QCD=%i' % (int(ior[1])+1) process=process+r+' ' #handle special tag $ | / @ result = re.split('([/$@]|\w+=\w+)', process, 1) if len(result) ==3: process, split, rest = result commandline+="add process %s pert_%s %s%s , %s %s --no_warning=duplicate;" % \ (process, order, split, rest, decay_text, proc_nb) else: commandline +='add process %s pert_%s, %s %s --no_warning=duplicate;' % \ (process, order, decay_text, proc_nb) # commandline = commandline.replace('add process', 'generate',1) # logger.info(commandline) # mgcmd.exec_cmd(commandline, precmd=True) # remove decay with 0 branching ratio. # mgcmd.remove_pointless_decay(self.banner.param_card) # commandline = 'output standalone_msF %s %s' % (pjoin(path_me,'full_me'), # ' '.join(self.list_branches.keys())) # mgcmd.exec_cmd(commandline, precmd=True) # logger.info('Done %.4g' % (time.time()-start)) # 5. add the decay information to the all_topology object -------------- # for matrix_element in mgcmd._curr_matrix_elements.get_matrix_elements(): # me_path = pjoin(path_me,'full_me', 'SubProcesses', \ # "P%s" % matrix_element.get('processes')[0].shell_string()) # self.all_ME.add_decay(matrix_element, me_path) # 5.b import production matrix elements (+ related info) in the full process directory # list_prodfiles=['matrix_prod.f','configs_production.inc','props_production.inc','nexternal_prod.inc'] # for tag in self.all_ME: # prod_path=self.all_ME[tag]['path'] # nfinal=len(self.all_ME[tag]['base_order'][1]) # for dico in self.all_ME[tag]['decays']: # full_path=dico['path'] #print prod_path #print full_path #print ' ' # for item in list_prodfiles: #print full_path # prodfile=pjoin(prod_path,item) # destination=pjoin(full_path,item) # shutil.copyfile(prodfile, destination) # we need to write the file config_decays.inc # self.generate_configs_file(nfinal,dico,full_path) # if self.options["onlyhelicity"]: # return # 6. generate decay only part ------------------------------------------ logger.info('generate matrix element for decay only (1 - > N).') # start = time.time() # commandline = '' i=0 for processes in self.list_branches.values(): for proc in processes: commandline+="add process %s @%i --no_warning=duplicate --standalone;" % (proc,i) i+=1 commandline = commandline.replace('add process', 'generate',1) mgcmd.exec_cmd(commandline, precmd=True) # remove decay with 0 branching ratio. #mgcmd.remove_pointless_decay(self.banner.param_card) # commandline = 'output standalone %s' % pjoin(path_me,'madspin_me') logger.info(commandline) mgcmd.exec_cmd(commandline, precmd=True) logger.info('Done %.4g' % (time.time()-start)) self.all_me = {} # store information about matrix element for matrix_element in mgcmd._curr_matrix_elements.get_matrix_elements(): me_string = matrix_element.get('processes')[0].shell_string() for me in matrix_element.get('processes'): dirpath = pjoin(path_me,'madspin_me', 'SubProcesses', "P%s" % me_string) # get the orignal order: initial = [] final = [l.get('id') for l in me.get_legs_with_decays()\ if l.get('state') or initial.append(l.get('id'))] order = (tuple(initial), tuple(final)) initial.sort(), final.sort() tag = (tuple(initial), tuple(final)) self.all_me[tag] = {'pdir': "P%s" % me_string, 'order': order} return self.all_me def compile(self): logger.info('Compiling code') misc.compile(cwd=pjoin(self.path_me,'madspin_me', 'Source'), nb_core=self.mgcmd.options['nb_core']) misc.compile(['all'],cwd=pjoin(self.path_me,'madspin_me', 'SubProcesses'), nb_core=self.mgcmd.options['nb_core'])