Package madgraph :: Package core :: Module helas_objects
[hide private]
[frames] | no frames]

Source Code for Module madgraph.core.helas_objects

   1  ################################################################################ 
   2  # 
   3  # Copyright (c) 2009 The MadGraph5_aMC@NLO Development team and Contributors 
   4  # 
   5  # This file is a part of the MadGraph5_aMC@NLO project, an application which  
   6  # automatically generates Feynman diagrams and matrix elements for arbitrary 
   7  # high-energy processes in the Standard Model and beyond. 
   8  # 
   9  # It is subject to the MadGraph5_aMC@NLO license which should accompany this  
  10  # distribution. 
  11  # 
  12  # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 
  13  # 
  14  ################################################################################ 
  15   
  16  """Definitions of objects used to generate language-independent Helas 
  17  calls: HelasWavefunction, HelasAmplitude, HelasDiagram for the 
  18  generation of wavefunctions and amplitudes, HelasMatrixElement and 
  19  HelasMultiProcess for generation of complete matrix elements for 
  20  single and multiple processes; and HelasModel, which is the 
  21  language-independent base class for the language-specific classes for 
  22  writing Helas calls, found in the iolibs directory""" 
  23   
  24  import array 
  25  import copy 
  26  import collections 
  27  import logging 
  28  import itertools 
  29  import math 
  30   
  31  import aloha 
  32   
  33  import madgraph.core.base_objects as base_objects 
  34  import madgraph.core.diagram_generation as diagram_generation 
  35  import madgraph.core.color_amp as color_amp 
  36  import madgraph.loop.loop_diagram_generation as loop_diagram_generation 
  37  import madgraph.loop.loop_color_amp as loop_color_amp 
  38  import madgraph.core.color_algebra as color 
  39  import madgraph.various.misc as misc 
  40   
  41  from madgraph import InvalidCmd, MadGraph5Error 
  42   
  43  #=============================================================================== 
  44  #  
  45  #=============================================================================== 
  46   
  47  logger = logging.getLogger('madgraph.helas_objects') 
48 49 #=============================================================================== 50 # DiagramTag class to identify matrix elements 51 #=============================================================================== 52 53 -class IdentifyMETag(diagram_generation.DiagramTag):
54 """DiagramTag daughter class to identify processes with identical 55 matrix elements. Need to compare leg number, color, lorentz, 56 coupling, state, spin, self_antipart, mass, width, color, decay 57 and is_part. 58 59 Note that we also need to check that the processes agree on 60 has_mirror_process, process id, and 61 identical_particle_factor. Don't allow combining decay chains. 62 63 We also don't want to combine processes with different possibly 64 onshell s-channel propagators (i.e., with non-zero width and 65 onshell=True or None) since we want the right propagator written 66 in the event file in the end. This is done by the vertex.""" 67 68 # dec_number is used to separate between decay chains. 69 # This is needed since we don't want to merge different decays, 70 # in order to get the right factor for identical/non-identical particles 71 dec_number = 1 72 73 @classmethod
74 - def create_tag(cls, amplitude, identical_particle_factor = 0):
75 """Create a tag which identifies identical matrix elements""" 76 process = amplitude.get('process') 77 ninitial = process.get_ninitial() 78 model = process.get('model') 79 dc = 0 80 if process.get('is_decay_chain'): 81 dc = cls.dec_number 82 cls.dec_number += 1 83 if not identical_particle_factor: 84 identical_particle_factor = process.identical_particle_factor() 85 if process.get('perturbation_couplings') and \ 86 process.get('NLO_mode') not in ['virt', 'loop','noborn']: 87 sorted_tags = sorted([IdentifyMETagFKS(d, model, ninitial) for d in \ 88 amplitude.get('diagrams')]) 89 elif process.get('NLO_mode')=='noborn' or \ 90 (process.get('NLO_mode')=='virt' and not process.get('has_born')): 91 # For loop-induced processes, make sure to create the Tag based on 92 # the contracted diagram 93 sorted_tags = sorted([cls(d.get_contracted_loop_diagram(model, 94 amplitude.get('structure_repository')), model, ninitial) for d in \ 95 amplitude.get('diagrams')]) 96 else: 97 sorted_tags = sorted([cls(d, model, ninitial) for d in \ 98 amplitude.get('diagrams')]) 99 100 # Do not use this for loop diagrams as for now the HelasMultiProcess 101 # always contain only exactly one loop amplitude. 102 if sorted_tags and not isinstance(amplitude, \ 103 loop_diagram_generation.LoopAmplitude): 104 # Need to keep track of relative permutations for all diagrams, 105 # to make sure we indeed have different matrix elements, 106 # and not just identical diagrams disregarding particle order. 107 # However, identical particles should be treated symmetrically. 108 exts = sorted_tags[0].get_external_numbers() 109 comp_dict = IdentifyMETag.prepare_comp_dict(process, exts) 110 perms = [array.array('H', 111 sum([comp_dict[n] for n in p.get_external_numbers()], [])) 112 for p in sorted_tags[1:]] 113 else: 114 perms = [] 115 116 return [amplitude.get('has_mirror_process'), 117 process.get('id'), 118 process.get('is_decay_chain'), 119 identical_particle_factor, 120 dc, 121 perms, 122 sorted_tags]
123 124 @staticmethod
125 - def prepare_comp_dict(process, numbers):
126 """Prepare a dictionary from leg number to [positions] in such 127 a way that identical particles are treated in a symmetric way""" 128 129 # Crate dictionary from leg number to position 130 start_perm_dict = dict([(n,i) for (i,n) in enumerate(numbers)]) 131 132 # Ids for process legs 133 legs = [l.get('id') for l in sorted(process.get_legs_with_decays())] 134 if process.get('is_decay_chain'): 135 legs.insert(0,process.get('legs')[0].get('id')) 136 ninitial = len([l for l in process.get('legs') if \ 137 not l.get('state')]) 138 # Dictionary from leg id to position for final-state legs 139 id_num_dict = {} 140 for n in start_perm_dict.keys(): 141 if n > ninitial: 142 id_num_dict.setdefault(legs[n-1], []).append(\ 143 start_perm_dict[n]) 144 # Make each entry in start_perm_dict independent of position of 145 # identical particles by including all positions 146 for n in start_perm_dict.keys(): 147 if n <= ninitial: 148 # Just turn it into a list 149 start_perm_dict[n] = [start_perm_dict[n]] 150 else: 151 # Turn into list of positions for identical particles 152 start_perm_dict[n] = sorted(id_num_dict[legs[n-1]]) 153 154 return start_perm_dict
155 156 @staticmethod 175 176 @staticmethod
177 - def vertex_id_from_vertex(vertex, last_vertex, model, ninitial):
178 """Returns the info needed to identify matrix elements: 179 interaction color, lorentz, coupling, and wavefunction spin, 180 self_antipart, mass, width, color, decay and is_part, plus PDG 181 code if possible onshell s-channel prop. Note that is_part 182 and PDG code needs to be flipped if we move the final vertex around.""" 183 184 if vertex.get('id') in [0,-1]: 185 return ((vertex.get('id'),),) 186 187 if vertex.get('id') == -2: 188 ret_list = vertex.get('loop_tag') 189 else: 190 inter = model.get_interaction(vertex.get('id')) 191 coup_keys = sorted(inter.get('couplings').keys()) 192 ret_list = tuple([(key, inter.get('couplings')[key]) for key in \ 193 coup_keys] + \ 194 [str(c) for c in inter.get('color')] + \ 195 inter.get('lorentz')+sorted(inter.get('orders'))) 196 197 if last_vertex: 198 return ((ret_list,),) 199 else: 200 part = model.get_particle(vertex.get('legs')[-1].get('id')) 201 # If we have possibly onshell s-channel particles with 202 # identical properties but different PDG code, split the 203 # processes to ensure that we write the correct resonance 204 # in the event file 205 s_pdg = vertex.get_s_channel_id(model, ninitial) 206 if s_pdg and (part.get('width').lower() == 'zero' or \ 207 vertex.get('legs')[-1].get('onshell') == False): 208 s_pdg = 0 209 return (((part.get('spin'), part.get('color'), 210 part.get('self_antipart'), 211 part.get('mass'), part.get('width'), s_pdg), 212 ret_list),)
213 214 @staticmethod
215 - def flip_vertex(new_vertex, old_vertex, links):
216 """Move the wavefunction part of vertex id appropriately""" 217 218 if len(new_vertex[0]) == 1 and len(old_vertex[0]) == 2: 219 # We go from a last link to next-to-last link - add propagator info 220 return ((old_vertex[0][0],new_vertex[0][0]),) 221 elif len(new_vertex[0]) == 2 and len(old_vertex[0]) == 1: 222 # We go from next-to-last link to last link - remove propagator info 223 return ((new_vertex[0][1],),) 224 # We should not get here 225 assert(False)
226
227 228 -class IdentifyMETagFKS(IdentifyMETag):
229 """on top of what IdentifyMETag, the diagram tags also have the charge 230 difference along the fermionic flow in them for initial state legs.""" 231
232 - def __init__(self, diagram, model = None, ninitial = 2):
233 self.flow_charge_diff = diagram.get_flow_charge_diff(model) 234 super(IdentifyMETagFKS, self).__init__(diagram, model, ninitial)
235
236 - def __eq__(self, other):
237 return super(IdentifyMETag, self).__eq__(other) and \ 238 self.flow_charge_diff == other.flow_charge_diff
239
240 241 -class IdentifyMETagMadSpin(IdentifyMETag):
242 """Should ensure that the splitting is the same with and without decay 243 So we want to combine processes with different possibly 244 onshell s-channel propagators. This was done by the vertex. 245 """ 246 247 @staticmethod
248 - def vertex_id_from_vertex(vertex, last_vertex, model, ninitial):
249 """Returns the info needed to identify matrix elements: 250 interaction color, lorentz, coupling, and wavefunction spin, 251 self_antipart, mass, width, color, decay and is_part. 252 BUT NOT PDG code for possible onshell s-channel prop.""" 253 if last_vertex: 254 return IdentifyMETag.vertex_id_from_vertex(vertex, last_vertex, model, ninitial) 255 import random 256 data = IdentifyMETag.vertex_id_from_vertex(vertex, last_vertex, model, ninitial) 257 ((spin, color, selfanti, mass, width, pdg), ret_list) = data 258 return ((spin, color, selfanti, mass, width, random.random()), ret_list)
259
260 261 #=============================================================================== 262 # DiagramTag class to create canonical order configs 263 #=============================================================================== 264 265 -class CanonicalConfigTag(diagram_generation.DiagramTag):
266 """DiagramTag daughter class to create canonical order of 267 config. Need to compare leg number, mass, width, and color. 268 Also implement find s- and t-channels from the tag. 269 Warning! The sorting in this tag must be identical to that of 270 IdentifySGConfigTag in diagram_symmetry.py (apart from leg number) 271 to make sure symmetry works!""" 272 273
274 - def get_s_and_t_channels(self, ninitial, model, new_pdg, max_final_leg = 2):
275 """Get s and t channels from the tag, as two lists of vertices 276 ordered from the outermost s-channel and in/down towards the highest 277 number initial state leg. 278 Algorithm: Start from the final tag. Check for final leg number for 279 all links and move in the direction towards leg 2 (or 1, if 1 and 2 280 are in the same direction). 281 """ 282 283 final_leg = min(ninitial, max_final_leg) 284 285 # Look for final leg numbers in all links 286 done = [l for l in self.tag.links if \ 287 l.end_link and l.links[0][1][0] == final_leg] 288 while not done: 289 # Identify the chain closest to final_leg 290 right_num = -1 291 for num, link in enumerate(self.tag.links): 292 if len(link.vertex_id) == 3 and \ 293 link.vertex_id[1][-1] == final_leg: 294 right_num = num 295 if right_num == -1: 296 # We need to look for leg number 1 instead 297 for num, link in enumerate(self.tag.links): 298 if len(link.vertex_id) == 3 and \ 299 link.vertex_id[1][-1] == 1: 300 right_num = num 301 if right_num == -1: 302 # This should never happen 303 raise diagram_generation.DiagramTag.DiagramTagError, \ 304 "Error in CanonicalConfigTag, no link with number 1 or 2." 305 306 # Now move one step in the direction of right_link 307 right_link = self.tag.links[right_num] 308 # Create a new link corresponding to moving one step 309 new_links = list(self.tag.links[:right_num]) + \ 310 list(self.tag.links[right_num + 1:]) 311 312 new_link = diagram_generation.DiagramTagChainLink(\ 313 new_links, 314 self.flip_vertex(\ 315 self.tag.vertex_id, 316 right_link.vertex_id, 317 new_links)) 318 319 # Create a new final vertex in the direction of the right_link 320 other_links = list(right_link.links) + [new_link] 321 other_link = diagram_generation.DiagramTagChainLink(\ 322 other_links, 323 self.flip_vertex(\ 324 right_link.vertex_id, 325 self.tag.vertex_id, 326 other_links)) 327 328 self.tag = other_link 329 done = [l for l in self.tag.links if \ 330 l.end_link and l.links[0][1][0] == final_leg] 331 332 # Construct a diagram from the resulting tag 333 diagram = self.diagram_from_tag(model) 334 335 # Go through the vertices and add them to s-channel or t-channel 336 schannels = base_objects.VertexList() 337 tchannels = base_objects.VertexList() 338 339 for vert in diagram.get('vertices')[:-1]: 340 if vert.get('legs')[-1].get('number') > ninitial: 341 schannels.append(vert) 342 else: 343 tchannels.append(vert) 344 345 # Need to make sure leg number 2 is always last in last vertex 346 lastvertex = diagram.get('vertices')[-1] 347 legs = lastvertex.get('legs') 348 leg2 = [l.get('number') for l in legs].index(final_leg) 349 legs.append(legs.pop(leg2)) 350 if ninitial == 2: 351 # Last vertex always counts as t-channel 352 tchannels.append(lastvertex) 353 else: 354 legs[-1].set('id', 355 model.get_particle(legs[-1].get('id')).get_anti_pdg_code()) 356 schannels.append(lastvertex) 357 358 # Split up multiparticle vertices using fake s-channel propagators 359 multischannels = [(i, v) for (i, v) in enumerate(schannels) \ 360 if len(v.get('legs')) > 3] 361 multitchannels = [(i, v) for (i, v) in enumerate(tchannels) \ 362 if len(v.get('legs')) > 3] 363 364 increase = 0 365 for channel in multischannels + multitchannels: 366 newschannels = [] 367 vertex = channel[1] 368 while len(vertex.get('legs')) > 3: 369 # Pop the first two legs and create a new 370 # s-channel from them 371 popped_legs = \ 372 base_objects.LegList([vertex.get('legs').pop(0) \ 373 for i in [0,1]]) 374 popped_legs.append(base_objects.Leg({\ 375 'id': new_pdg, 376 'number': min([l.get('number') for l in popped_legs]), 377 'state': True, 378 'onshell': None})) 379 380 new_vertex = base_objects.Vertex({ 381 'id': vertex.get('id'), 382 'legs': popped_legs}) 383 384 # Insert the new s-channel before this vertex 385 if channel in multischannels: 386 schannels.insert(channel[0]+increase, new_vertex) 387 # Account for previous insertions 388 increase += 1 389 else: 390 schannels.append(new_vertex) 391 legs = vertex.get('legs') 392 # Insert the new s-channel into vertex 393 legs.insert(0, copy.copy(popped_legs[-1])) 394 # Renumber resulting leg according to minimum leg number 395 legs[-1].set('number', min([l.get('number') for l in legs[:-1]])) 396 397 # Finally go through all vertices, sort the legs and replace 398 # leg number with propagator number -1, -2, ... 399 number_dict = {} 400 nprop = 0 401 for vertex in schannels + tchannels: 402 # Sort the legs 403 legs = vertex.get('legs')[:-1] 404 if vertex in schannels: 405 legs.sort(lambda l1, l2: l2.get('number') - \ 406 l1.get('number')) 407 else: 408 legs.sort(lambda l1, l2: l1.get('number') - \ 409 l2.get('number')) 410 for ileg,leg in enumerate(legs): 411 newleg = copy.copy(leg) 412 try: 413 newleg.set('number', number_dict[leg.get('number')]) 414 except KeyError: 415 pass 416 else: 417 legs[ileg] = newleg 418 nprop = nprop - 1 419 last_leg = copy.copy(vertex.get('legs')[-1]) 420 number_dict[last_leg.get('number')] = nprop 421 last_leg.set('number', nprop) 422 legs.append(last_leg) 423 vertex.set('legs', base_objects.LegList(legs)) 424 425 return schannels, tchannels
426 427 @staticmethod 444 445 @staticmethod
446 - def vertex_id_from_vertex(vertex, last_vertex, model, ninitial):
447 """Returns the info needed to identify configs: 448 interaction color, mass, width. Also provide propagator PDG code. 449 The third element of the tuple vertex_id serves to store potential 450 necessary information regarding the shrunk loop.""" 451 452 if isinstance(vertex,base_objects.ContractedVertex): 453 inter = None 454 # I don't add here the 'loop_tag' because it is heavy and in principle 455 # not necessary. It can however be added in the future if proven 456 # necessary. 457 loop_info = {'PDGs':vertex.get('PDGs'), 458 'loop_orders':vertex.get('loop_orders')} 459 else: 460 # Not that it is going to be used here, but it might be eventually 461 #inter = model.get_interaction(vertex.get('id')) 462 loop_info = {} 463 464 if last_vertex: 465 return ((0,), 466 (vertex.get('id'), 467 min([l.get('number') for l in vertex.get('legs')])), 468 loop_info) 469 else: 470 part = model.get_particle(vertex.get('legs')[-1].get('id')) 471 return ((part.get('color'), 472 part.get('mass'), part.get('width')), 473 (vertex.get('id'), 474 vertex.get('legs')[-1].get('onshell'), 475 vertex.get('legs')[-1].get('number')), 476 loop_info)
477 478 @staticmethod
479 - def flip_vertex(new_vertex, old_vertex, links):
480 """Move the wavefunction part of propagator id appropriately""" 481 482 # Find leg numbers for new vertex 483 min_number = min([l.vertex_id[1][-1] for l in links if not l.end_link]\ 484 + [l.links[0][1][0] for l in links if l.end_link]) 485 486 if len(new_vertex[0]) == 1 and len(old_vertex[0]) > 1: 487 # We go from a last link to next-to-last link 488 return (old_vertex[0], 489 (new_vertex[1][0], old_vertex[1][1], min_number), new_vertex[2]) 490 elif len(new_vertex[0]) > 1 and len(old_vertex[0]) == 1: 491 # We go from next-to-last link to last link - remove propagator info 492 return (old_vertex[0], (new_vertex[1][0], min_number), new_vertex[2]) 493 494 # We should not get here 495 raise diagram_generation.DiagramTag.DiagramTagError, \ 496 "Error in CanonicalConfigTag, wrong setup of vertices in link."
497 498 @staticmethod 511 512 @classmethod 531 532 @staticmethod
533 - def id_from_vertex_id(vertex_id):
534 """Return the numerical vertex id from a link.vertex_id""" 535 536 return vertex_id[1][0]
537
538 539 #=============================================================================== 540 # HelasWavefunction 541 #=============================================================================== 542 -class HelasWavefunction(base_objects.PhysicsObject):
543 """HelasWavefunction object, has the information necessary for 544 writing a call to a HELAS wavefunction routine: the PDG number, 545 all relevant particle information, a list of mother wavefunctions, 546 interaction id, all relevant interaction information, fermion flow 547 state, wavefunction number 548 """ 549 550 supported_analytical_info = ['wavefunction_rank','interaction_rank'] 551 552 553 @staticmethod
554 - def spin_to_size(spin):
555 """ Returns the size of a wavefunction (i.e. number of element) carrying 556 a particle with spin 'spin' """ 557 558 sizes = {1:1,2:4,3:4,4:16,5:16} 559 try: 560 return sizes[abs(spin)] 561 except KeyError: 562 raise MadGraph5Error, "L-cut particle has spin %d which is not supported."%spin
563
564 - def default_setup(self):
565 """Default values for all properties""" 566 567 # Properties related to the particle propagator 568 # For an electron, would have the following values 569 # pdg_code = 11 570 # name = 'e-' 571 # antiname = 'e+' 572 # spin = '1' defined as 2 x spin + 1 573 # color = '1' 1= singlet, 3 = triplet, 8=octet 574 # mass = 'zero' 575 # width = 'zero' 576 # is_part = 'true' Particle not antiparticle 577 # self_antipart='false' gluon, photo, h, or majorana would be true 578 self['particle'] = base_objects.Particle() 579 self['antiparticle'] = base_objects.Particle() 580 self['is_part'] = True 581 # Properties related to the interaction generating the propagator 582 # For an e- produced from an e+e-A vertex would have the following 583 # proporties: 584 # interaction_id = the id of the interaction in the model 585 # pdg_codes = the pdg_codes property of the interaction, [11, -11, 22] 586 # inter_color = the 'color' property of the interaction: [] 587 # lorentz = the 'lorentz' property of the interaction: ('') 588 # couplings = the coupling names from the interaction: {(0,0):'MGVX12'} 589 self['interaction_id'] = 0 590 self['pdg_codes'] = [] 591 self['orders'] = {} 592 self['inter_color'] = None 593 self['lorentz'] = [] 594 self['coupling'] = ['none'] 595 # The color index used in this wavefunction 596 self['color_key'] = 0 597 # Properties relating to the leg/vertex 598 # state = initial/final (for external bosons), 599 # intermediate (for intermediate bosons), 600 # incoming/outgoing (for fermions) 601 # leg_state = initial/final for initial/final legs 602 # intermediate for non-external wavefunctions 603 # number_external = the 'number' property of the corresponding Leg, 604 # corresponds to the number of the first external 605 # particle contributing to this leg 606 # fermionflow = 1 fermions have +-1 for flow (bosons always +1), 607 # -1 is used only if there is a fermion flow clash 608 # due to a Majorana particle 609 # is_loop = logical true if this function builds a loop or belong 610 # to an external structure. 611 self['state'] = 'initial' 612 self['leg_state'] = True 613 self['mothers'] = HelasWavefunctionList() 614 self['number_external'] = 0 615 self['number'] = 0 616 self['me_id'] = 0 617 self['fermionflow'] = 1 618 self['is_loop'] = False 619 # Some analytical information about the interaction and wavefunction 620 # can be cached here in the form of a dictionary. 621 # An example of a key of this dictionary is 'rank' which is used in the 622 # open loop method and stores the rank of the polynomial describing the 623 # loop numerator up to this loop wavefunction. 624 # See the class variable 'supported_analytical_info' for the list of 625 # supporter analytical information 626 self['analytic_info'] = {} 627 # The lcut_size stores the size of the L-Cut wavefunction at the origin 628 # of the loopwavefunction. 629 self['lcut_size']=None 630 # The decay flag is used in processes with defined decay chains, 631 # to indicate that this wavefunction has a decay defined 632 self['decay'] = False 633 # The onshell flag is used in processes with defined decay 634 # chains, to indicate that this wavefunction is decayed and 635 # should be onshell (True), as well as for forbidden s-channels (False). 636 # Default is None 637 self['onshell'] = None 638 # conjugate_indices is a list [1,2,...] with fermion lines 639 # that need conjugates. Default is "None" 640 self['conjugate_indices'] = None 641 # 642 # 643 self['polarization'] = []
644 645 # Customized constructor
646 - def __init__(self, *arguments):
647 """Allow generating a HelasWavefunction from a Leg 648 """ 649 650 if len(arguments) > 2: 651 if isinstance(arguments[0], base_objects.Leg) and \ 652 isinstance(arguments[1], int) and \ 653 isinstance(arguments[2], base_objects.Model): 654 super(HelasWavefunction, self).__init__() 655 leg = arguments[0] 656 interaction_id = arguments[1] 657 model = arguments[2] 658 659 # decay_ids is the pdg codes for particles with decay 660 # chains defined 661 decay_ids = [] 662 if len(arguments) > 3: 663 decay_ids = arguments[3] 664 self.set('particle', leg.get('id'), model) 665 self.set('number_external', leg.get('number')) 666 self.set('number', leg.get('number')) 667 self.set('is_loop', leg.get('loop_line')) 668 self.set('state', {False: 'initial', True: 'final'}[leg.get('state')]) 669 if leg.get('onshell') == False: 670 # Denotes forbidden s-channel 671 self.set('onshell', leg.get('onshell')) 672 self.set('leg_state', leg.get('state')) 673 # Need to set 'decay' to True for particles which will be 674 # decayed later, in order to not combine such processes 675 # although they might have identical matrix elements before 676 # the decay is applied 677 if self['state'] == 'final' and self.get('pdg_code') in decay_ids: 678 self.set('decay', True) 679 else: 680 if 99 in leg.get('polarization'): 681 raise Exception("polarization A only valid for propagator.") 682 # Set fermion flow state. Initial particle and final 683 # antiparticle are incoming, and vice versa for 684 # outgoing 685 if self.is_fermion(): 686 if leg.get('polarization'): 687 pol = list(leg.get('polarization')) 688 self.set('polarization', pol) 689 690 if leg.get('state') == False and \ 691 self.get('is_part') or \ 692 leg.get('state') == True and \ 693 not self.get('is_part'): 694 self.set('state', 'incoming') 695 else: 696 self.set('state', 'outgoing') 697 else: 698 self.set('polarization', leg.get('polarization')) 699 self.set('interaction_id', interaction_id, model) 700 elif arguments: 701 super(HelasWavefunction, self).__init__(arguments[0]) 702 else: 703 super(HelasWavefunction, self).__init__()
704 705 706
707 - def filter(self, name, value):
708 """Filter for valid wavefunction property values.""" 709 710 if name in ['particle', 'antiparticle']: 711 if not isinstance(value, base_objects.Particle): 712 raise self.PhysicsObjectError, \ 713 "%s tag %s is not a particle" % (name, repr(value)) 714 715 if name == 'is_part': 716 if not isinstance(value, bool): 717 raise self.PhysicsObjectError, \ 718 "%s tag %s is not a boolean" % (name, repr(value)) 719 720 if name == 'interaction_id': 721 if not isinstance(value, int): 722 raise self.PhysicsObjectError, \ 723 "%s is not a valid integer " % str(value) + \ 724 " for wavefunction interaction id" 725 726 if name == 'pdg_codes': 727 #Should be a list of strings 728 if not isinstance(value, list): 729 raise self.PhysicsObjectError, \ 730 "%s is not a valid list of integers" % str(value) 731 for mystr in value: 732 if not isinstance(mystr, int): 733 raise self.PhysicsObjectError, \ 734 "%s is not a valid integer" % str(mystr) 735 736 if name == 'orders': 737 #Should be a dict with valid order names ask keys and int as values 738 if not isinstance(value, dict): 739 raise self.PhysicsObjectError, \ 740 "%s is not a valid dict for coupling orders" % \ 741 str(value) 742 for order in value.keys(): 743 if not isinstance(order, str): 744 raise self.PhysicsObjectError, \ 745 "%s is not a valid string" % str(order) 746 if not isinstance(value[order], int): 747 raise self.PhysicsObjectError, \ 748 "%s is not a valid integer" % str(value[order]) 749 750 751 if name == 'inter_color': 752 # Should be None or a color string 753 if value and not isinstance(value, color.ColorString): 754 raise self.PhysicsObjectError, \ 755 "%s is not a valid Color String" % str(value) 756 757 if name == 'lorentz': 758 #Should be a list of string 759 if not isinstance(value, list): 760 raise self.PhysicsObjectError, \ 761 "%s is not a valid list" % str(value) 762 for name in value: 763 if not isinstance(name, str): 764 raise self.PhysicsObjectError, \ 765 "%s doesn't contain only string" % str(value) 766 767 if name == 'coupling': 768 #Should be a list of string 769 if not isinstance(value, list): 770 raise self.PhysicsObjectError, \ 771 "%s is not a valid coupling string" % str(value) 772 for name in value: 773 if not isinstance(name, str): 774 raise self.PhysicsObjectError, \ 775 "%s doesn't contain only string" % str(value) 776 if len(value) == 0: 777 raise self.PhysicsObjectError, \ 778 "%s should have at least one value" % str(value) 779 780 if name == 'color_key': 781 if value and not isinstance(value, int): 782 raise self.PhysicsObjectError, \ 783 "%s is not a valid integer" % str(value) 784 785 if name == 'state': 786 if not isinstance(value, str): 787 raise self.PhysicsObjectError, \ 788 "%s is not a valid string for wavefunction state" % \ 789 str(value) 790 if value not in ['incoming', 'outgoing', 791 'intermediate', 'initial', 'final']: 792 raise self.PhysicsObjectError, \ 793 "%s is not a valid wavefunction " % str(value) + \ 794 "state (incoming|outgoing|intermediate)" 795 if name == 'leg_state': 796 if value not in [False, True]: 797 raise self.PhysicsObjectError, \ 798 "%s is not a valid wavefunction " % str(value) + \ 799 "state (incoming|outgoing|intermediate)" 800 if name in ['fermionflow']: 801 if not isinstance(value, int): 802 raise self.PhysicsObjectError, \ 803 "%s is not a valid integer" % str(value) 804 if not value in [-1, 1]: 805 raise self.PhysicsObjectError, \ 806 "%s is not a valid sign (must be -1 or 1)" % str(value) 807 808 if name in ['number_external', 'number']: 809 if not isinstance(value, int): 810 raise self.PhysicsObjectError, \ 811 "%s is not a valid integer" % str(value) + \ 812 " for wavefunction number" 813 814 if name == 'mothers': 815 if not isinstance(value, HelasWavefunctionList): 816 raise self.PhysicsObjectError, \ 817 "%s is not a valid list of mothers for wavefunction" % \ 818 str(value) 819 820 if name in ['decay']: 821 if not isinstance(value, bool): 822 raise self.PhysicsObjectError, \ 823 "%s is not a valid bool" % str(value) + \ 824 " for decay" 825 826 if name in ['onshell']: 827 if not isinstance(value, bool): 828 raise self.PhysicsObjectError, \ 829 "%s is not a valid bool" % str(value) + \ 830 " for onshell" 831 832 if name in ['is_loop']: 833 if not isinstance(value, bool): 834 raise self.PhysicsObjectError, \ 835 "%s is not a valid bool" % str(value) + \ 836 " for is_loop" 837 838 if name == 'conjugate_indices': 839 if not isinstance(value, tuple) and value != None: 840 raise self.PhysicsObjectError, \ 841 "%s is not a valid tuple" % str(value) + \ 842 " for conjugate_indices" 843 844 if name == 'rank': 845 if not isinstance(value, int) and value != None: 846 raise self.PhysicsObjectError, \ 847 "%s is not a valid int" % str(value) + \ 848 " for the rank" 849 850 if name == 'lcut_size': 851 if not isinstance(value, int) and value != None: 852 raise self.PhysicsObjectError, \ 853 "%s is not a valid int" % str(value) + \ 854 " for the lcut_size" 855 856 if name == 'polarization': 857 if not isinstance(value, list): 858 raise self.PhysicsObjectError, \ 859 "%s is not a valid list" % str(value) 860 for i in value: 861 if i not in [-1, 1, 2, -2, 3, -3, 0, 99]: 862 raise self.PhysicsObjectError, \ 863 "%s is not a valid polarization" % str(value) 864 865 return True
866 867 # Enhanced get function, where we can directly call the properties of the particle
868 - def get(self, name):
869 """When calling any property related to the particle, 870 automatically call the corresponding property of the particle.""" 871 872 # Set conjugate_indices if it's not already set 873 if name == 'conjugate_indices' and self[name] == None: 874 self['conjugate_indices'] = self.get_conjugate_index() 875 876 if name == 'lcut_size' and self[name] == None: 877 self['lcut_size'] = self.get_lcut_size() 878 879 if name in ['spin', 'mass', 'width', 'self_antipart']: 880 return self['particle'].get(name) 881 elif name == 'pdg_code': 882 return self['particle'].get_pdg_code() 883 elif name == 'color': 884 return self['particle'].get_color() 885 elif name == 'name': 886 return self['particle'].get_name() 887 elif name == 'antiname': 888 return self['particle'].get_anti_name() 889 elif name == 'me_id': 890 out = super(HelasWavefunction, self).get(name) 891 if out: 892 return out 893 else: 894 return super(HelasWavefunction, self).get('number') 895 else: 896 return super(HelasWavefunction, self).get(name)
897 898 899 # Enhanced set function, where we can append a model
900 - def set(self, *arguments):
901 """When setting interaction_id, if model is given (in tuple), 902 set all other interaction properties. When setting pdg_code, 903 if model is given, set all other particle properties.""" 904 905 assert len(arguments) >1, "Too few arguments for set" 906 907 name = arguments[0] 908 value = arguments[1] 909 910 if len(arguments) > 2 and \ 911 isinstance(value, int) and \ 912 isinstance(arguments[2], base_objects.Model): 913 model = arguments[2] 914 if name == 'interaction_id': 915 self.set('interaction_id', value) 916 if value > 0: 917 inter = model.get('interaction_dict')[value] 918 self.set('pdg_codes', 919 [part.get_pdg_code() for part in \ 920 inter.get('particles')]) 921 self.set('orders', inter.get('orders')) 922 # Note that the following values might change, if 923 # the relevant color/lorentz/coupling is not index 0 924 if inter.get('color'): 925 self.set('inter_color', inter.get('color')[0]) 926 if inter.get('lorentz'): 927 self.set('lorentz', [inter.get('lorentz')[0]]) 928 if inter.get('couplings'): 929 self.set('coupling', [inter.get('couplings').values()[0]]) 930 return True 931 elif name == 'particle': 932 self.set('particle', model.get('particle_dict')[value]) 933 self.set('is_part', self['particle'].get('is_part')) 934 if self['particle'].get('self_antipart'): 935 self.set('antiparticle', self['particle']) 936 else: 937 self.set('antiparticle', model.get('particle_dict')[-value]) 938 return True 939 else: 940 raise self.PhysicsObjectError, \ 941 "%s not allowed name for 3-argument set", name 942 else: 943 return super(HelasWavefunction, self).set(name, value)
944
945 - def get_sorted_keys(self):
946 """Return particle property names as a nicely sorted list.""" 947 948 return ['particle', 'antiparticle', 'is_part', 949 'interaction_id', 'pdg_codes', 'orders', 'inter_color', 950 'lorentz', 'coupling', 'color_key', 'state', 'number_external', 951 'number', 'fermionflow', 'mothers', 'is_loop']
952 953 # Helper functions 954
955 - def flip_part_antipart(self):
956 """Flip between particle and antiparticle.""" 957 part = self.get('particle') 958 self.set('particle', self.get('antiparticle')) 959 self.set('antiparticle', part)
960
961 - def is_anticommutating_ghost(self):
962 """ Return True if the particle of this wavefunction is a ghost""" 963 return self.get('particle').get('ghost')
964
965 - def is_fermion(self):
966 return self.get('spin') % 2 == 0
967
968 - def is_boson(self):
969 return not self.is_fermion()
970
971 - def is_majorana(self):
972 return self.is_fermion() and self.get('self_antipart')
973
974 - def get_analytic_info(self, info, alohaModel=None):
975 """ Returns a given analytic information about this loop wavefunction or 976 its characterizing interaction. The list of available information is in 977 the HelasWavefunction class variable 'supported_analytical_info'. An 978 example of analytic information is the 'interaction_rank', corresponding 979 to the power of the loop momentum q brought by the interaction 980 and propagator from which this loop wavefunction originates. This 981 is done in a general way by having aloha analyzing the lorentz structure 982 used. 983 Notice that if one knows that this analytic information has already been 984 computed before (for example because a call to compute_analytic_information 985 has been performed before, then alohaModel is not required since 986 the result can be recycled.""" 987 # This function makes no sense if not called for a loop interaction. 988 # At least for now 989 assert(self.get('is_loop')) 990 # Check that the required information is supported 991 assert(info in self.supported_analytical_info) 992 993 # Try to recycle the information 994 try: 995 return self['analytic_info'][info] 996 except KeyError: 997 # It then need be computed and for this, an alohaModel is necessary 998 if alohaModel is None: 999 raise MadGraph5Error,"The analytic information %s has"%info+\ 1000 " not been computed yet for this wavefunction and an"+\ 1001 " alohaModel was not specified, so that the information"+\ 1002 " cannot be retrieved." 1003 result = None 1004 1005 if info=="interaction_rank" and len(self['mothers'])==0: 1006 # It is of course zero for an external particle 1007 result = 0 1008 1009 elif info=="interaction_rank": 1010 # To get advanced analytic information about the interaction, aloha 1011 # has to work as if in the optimized output, namely treat individually 1012 # the loop momentum from the rest of the contributions. 1013 # The 'L' tag is therefore always followed by an integer representing 1014 # the place of the loop wavefunction in the mothers. 1015 # For this, optimized_output is set to True below, no matter what. 1016 aloha_info = self.get_aloha_info(True) 1017 # aloha_info[0] is the tuple of all lorent structures for this lwf, 1018 # aloha_info[1] are the tags and aloha_info[2] is the outgoing number. 1019 max_rank = max([ alohaModel.get_info('rank', lorentz, 1020 aloha_info[2], aloha_info[1], cached=True) 1021 for lorentz in aloha_info[0] ]) 1022 result = max_rank 1023 1024 elif info=="wavefunction_rank": 1025 # wavefunction_rank is the sum of all the interaction rank 1026 # from the history of open-loop call. 1027 loop_mothers=[wf for wf in self['mothers'] if wf['is_loop']] 1028 if len(loop_mothers)==0: 1029 # It is an external loop wavefunction 1030 result = 0 1031 elif len(loop_mothers)==1: 1032 result=loop_mothers[0].get_analytic_info('wavefunction_rank', 1033 alohaModel) 1034 result = result+self.get_analytic_info('interaction_rank', 1035 alohaModel) 1036 else: 1037 raise MadGraph5Error, "A loop wavefunction has more than one loop"+\ 1038 " mothers." 1039 1040 # Now cache the resulting analytic info 1041 self['analytic_info'][info] = result 1042 1043 return result
1044
1045 - def compute_analytic_information(self, alohaModel):
1046 """ Make sure that all analytic pieces of information about this 1047 wavefunction are computed so that they can be recycled later, typically 1048 without the need of specifying an alohaModel.""" 1049 1050 for analytic_info in self.supported_analytical_info: 1051 self.get_analytic_info(analytic_info, alohaModel)
1052
1053 - def to_array(self):
1054 """Generate an array with the information needed to uniquely 1055 determine if a wavefunction has been used before: interaction 1056 id and mother wavefunction numbers.""" 1057 1058 # Identification based on interaction id 1059 array_rep = array.array('i', [self['interaction_id']]) 1060 # Need the coupling key, to distinguish between 1061 # wavefunctions from the same interaction but different 1062 # color structures 1063 array_rep.append(self['color_key']) 1064 # Also need to specify if it is a loop wf 1065 array_rep.append(int(self['is_loop'])) 1066 1067 # Finally, the mother numbers 1068 array_rep.extend([mother['number'] for \ 1069 mother in self['mothers']]) 1070 1071 return array_rep
1072
1073 - def get_pdg_code(self):
1074 """Generate the corresponding pdg_code for an outgoing particle, 1075 taking into account fermion flow, for mother wavefunctions""" 1076 1077 return self.get('pdg_code')
1078
1079 - def get_anti_pdg_code(self):
1080 """Generate the corresponding pdg_code for an incoming particle, 1081 taking into account fermion flow, for mother wavefunctions""" 1082 1083 if self.get('self_antipart'): 1084 #This is its own antiparticle e.g. gluon 1085 return self.get('pdg_code') 1086 1087 return - self.get('pdg_code')
1088
1089 - def set_scalar_coupling_sign(self, model):
1090 """Check if we need to add a minus sign due to non-identical 1091 bosons in HVS type couplings""" 1092 1093 inter = model.get('interaction_dict')[self.get('interaction_id')] 1094 if [p.get('spin') for p in \ 1095 inter.get('particles')] == [3, 1, 1]: 1096 particles = inter.get('particles') 1097 # lambda p1, p2: p1.get('spin') - p2.get('spin')) 1098 if particles[1].get_pdg_code() != particles[2].get_pdg_code() \ 1099 and self.get('pdg_code') == \ 1100 particles[1].get_anti_pdg_code(): 1101 if not hasattr(self, 'flipped') or not self.flipped: 1102 self.flipped = True 1103 self.set('coupling', ['-%s' % c if not c.startswith('-') else c[1:] for c in self.get('coupling')])
1104 1105
1107 """For octet Majorana fermions, need an extra minus sign in 1108 the FVI (and FSI?) wavefunction in UFO models.""" 1109 1110 # Add minus sign to coupling of color octet Majorana 1111 # particles to g for FVI vertex 1112 if self.get('color') == 8 and \ 1113 self.get_spin_state_number() == -2 and \ 1114 self.get('self_antipart') and \ 1115 [m.get('color') for m in self.get('mothers')] == [8, 8]: 1116 if not hasattr(self, 'flipped') or not self.flipped: 1117 self.flipped = True 1118 self.set('coupling', ['-%s' % c if not c.startswith('-') else c[1:] for c in self.get('coupling')]) 1119 1120 else: 1121 return
1122
1123 - def set_state_and_particle(self, model):
1124 """Set incoming/outgoing state according to mother states and 1125 Lorentz structure of the interaction, and set PDG code 1126 according to the particles in the interaction""" 1127 1128 assert isinstance(model, base_objects.Model), \ 1129 "%s is not a valid model for call to set_state_and_particle" \ 1130 % repr(model) 1131 1132 # leg_state is final, unless there is exactly one initial 1133 # state particle involved in the combination -> t-channel 1134 if len(filter(lambda mother: mother.get('leg_state') == False, 1135 self.get('mothers'))) == 1: 1136 leg_state = False 1137 else: 1138 leg_state = True 1139 self.set('leg_state', leg_state) 1140 1141 # Start by setting the state of the wavefunction 1142 if self.is_boson(): 1143 # For boson, set state to intermediate 1144 self.set('state', 'intermediate') 1145 else: 1146 # For fermion, set state to same as other fermion (in the 1147 # right way) 1148 mother = self.find_mother_fermion() 1149 1150 if self.get('self_antipart'): 1151 self.set('state', mother.get_with_flow('state')) 1152 self.set('is_part', mother.get_with_flow('is_part')) 1153 else: 1154 self.set('state', mother.get('state')) 1155 self.set('fermionflow', mother.get('fermionflow')) 1156 # Check that the state is compatible with particle/antiparticle 1157 if self.get('is_part') and self.get('state') == 'incoming' or \ 1158 not self.get('is_part') and self.get('state') == 'outgoing': 1159 self.set('state', {'incoming':'outgoing', 1160 'outgoing':'incoming'}[self.get('state')]) 1161 self.set('fermionflow', -self.get('fermionflow')) 1162 return True
1163
1164 - def check_and_fix_fermion_flow(self, 1165 wavefunctions, 1166 diagram_wavefunctions, 1167 external_wavefunctions, 1168 wf_number):
1169 """Check for clashing fermion flow (N(incoming) != 1170 N(outgoing)) in mothers. This can happen when there is a 1171 Majorana particle in the diagram, which can flip the fermion 1172 flow. This is detected either by a wavefunctions or an 1173 amplitude, with 2 fermion mothers with same state. 1174 1175 In this case, we need to follow the fermion lines of the 1176 mother wavefunctions until we find the outermost Majorana 1177 fermion. For all fermions along the line up to (but not 1178 including) the Majorana fermion, we need to flip incoming <-> 1179 outgoing and particle id. For all fermions after the Majorana 1180 fermion, we need to flip the fermionflow property (1 <-> -1). 1181 1182 The reason for this is that in the Helas calls, we need to 1183 keep track of where the actual fermion flow clash happens 1184 (i.e., at the outermost Majorana), as well as having the 1185 correct fermion flow for all particles along the fermion line. 1186 1187 This is done by the mothers using 1188 HelasWavefunctionList.check_and_fix_fermion_flow, which in 1189 turn calls the recursive function 1190 check_majorana_and_flip_flow to trace the fermion lines. 1191 """ 1192 1193 # Use the HelasWavefunctionList helper function 1194 # Have to keep track of wavefunction number, since we might 1195 # need to add new wavefunctions. 1196 self.set('mothers', self.get('mothers').sort_by_pdg_codes(\ 1197 self.get('pdg_codes'), self.get_anti_pdg_code())[0]) 1198 1199 wf_number = self.get('mothers').\ 1200 check_and_fix_fermion_flow(wavefunctions, 1201 diagram_wavefunctions, 1202 external_wavefunctions, 1203 self, 1204 wf_number) 1205 1206 return self, wf_number
1207
1208 - def check_majorana_and_flip_flow(self, found_majorana, 1209 wavefunctions, 1210 diagram_wavefunctions, 1211 external_wavefunctions, 1212 wf_number, force_flip_flow=False, 1213 number_to_wavefunctions=[]):
1214 """Recursive function. Check for Majorana fermion. If found, 1215 continue down to external leg, then flip all the fermion flows 1216 on the way back up, in the correct way: 1217 Only flip fermionflow after the last Majorana fermion; for 1218 wavefunctions before the last Majorana fermion, instead flip 1219 particle identities and state. Return the new (or old) 1220 wavefunction, and the present wavefunction number. 1221 1222 Arguments: 1223 found_majorana: boolean 1224 wavefunctions: HelasWavefunctionList with previously 1225 defined wavefunctions 1226 diagram_wavefunctions: HelasWavefunctionList with the wavefunctions 1227 already defined in this diagram 1228 external_wavefunctions: dictionary from legnumber to external wf 1229 wf_number: The present wavefunction number 1230 """ 1231 1232 if not found_majorana: 1233 found_majorana = self.get('self_antipart') 1234 1235 new_wf = self 1236 flip_flow = False 1237 flip_sign = False 1238 1239 # Stop recursion at the external leg 1240 mothers = copy.copy(self.get('mothers')) 1241 if not mothers: 1242 if force_flip_flow: 1243 flip_flow = True 1244 elif not self.get('self_antipart'): 1245 flip_flow = found_majorana 1246 else: 1247 flip_sign = found_majorana 1248 else: 1249 # Follow fermion flow up through tree 1250 fermion_mother = self.find_mother_fermion() 1251 1252 if fermion_mother.get_with_flow('state') != \ 1253 self.get_with_flow('state'): 1254 new_mother = fermion_mother 1255 else: 1256 # Perform recursion by calling on mother 1257 new_mother, wf_number = fermion_mother.\ 1258 check_majorana_and_flip_flow(\ 1259 found_majorana, 1260 wavefunctions, 1261 diagram_wavefunctions, 1262 external_wavefunctions, 1263 wf_number, 1264 force_flip_flow) 1265 1266 # If this is Majorana and mother has different fermion 1267 # flow, we should flip the particle id and flow state. 1268 # Otherwise, if mother has different fermion flow, flip 1269 # flow 1270 flip_sign = new_mother.get_with_flow('state') != \ 1271 self.get_with_flow('state') and \ 1272 self.get('self_antipart') 1273 flip_flow = new_mother.get_with_flow('state') != \ 1274 self.get_with_flow('state') and \ 1275 not self.get('self_antipart') 1276 1277 # Replace old mother with new mother 1278 mothers[mothers.index(fermion_mother)] = new_mother 1279 1280 # Flip sign if needed 1281 if flip_flow or flip_sign: 1282 if self in wavefunctions: 1283 # Need to create a new copy, since we don't want to change 1284 # the wavefunction for previous diagrams 1285 new_wf = copy.copy(self) 1286 # Update wavefunction number 1287 wf_number = wf_number + 1 1288 new_wf.set('number', wf_number) 1289 try: 1290 # In call from insert_decay, we want to replace 1291 # also identical wavefunctions in the same diagram 1292 old_wf_index = diagram_wavefunctions.index(self) 1293 old_wf = diagram_wavefunctions[old_wf_index] 1294 if self.get('number') == old_wf.get('number'): 1295 # The wavefunction and old_wf are the same - 1296 # need to reset wf_number and new_wf number 1297 wf_number -= 1 1298 new_wf.set('number', old_wf.get('number')) 1299 diagram_wavefunctions[old_wf_index] = new_wf 1300 except ValueError: 1301 # Make sure that new_wf comes before any wavefunction 1302 # which has it as mother 1303 if len(self['mothers']) == 0: 1304 #insert at the beginning 1305 if diagram_wavefunctions: 1306 wf_nb = diagram_wavefunctions[0].get('number') 1307 for w in diagram_wavefunctions: 1308 w.set('number', w.get('number') + 1) 1309 new_wf.set('number', wf_nb) 1310 diagram_wavefunctions.insert(0, new_wf) 1311 else: 1312 diagram_wavefunctions.insert(0, new_wf) 1313 else: 1314 for i, wf in enumerate(diagram_wavefunctions): 1315 if self in wf.get('mothers'): 1316 # Update wf numbers 1317 new_wf.set('number', wf.get('number')) 1318 for w in diagram_wavefunctions[i:]: 1319 w.set('number', w.get('number') + 1) 1320 # Insert wavefunction 1321 diagram_wavefunctions.insert(i, new_wf) 1322 break 1323 else: 1324 # For loop processes, care is needed since 1325 # some loop wavefunctions in the diag_wfs might have 1326 # the new_wf in their mother, so we want to place 1327 # new_wf as early as possible in the list. 1328 # We first look if any mother of the wavefunction 1329 # we want to add appears in the diagram_wavefunctions 1330 # list. If it doesn't, max_mother_index is -1. 1331 # If it does, then max_mother_index is the maximum 1332 # index in diagram_wavefunctions of those of the 1333 # mothers present in this list. 1334 max_mother_index = max([-1]+ 1335 [diagram_wavefunctions.index(wf) for wf in 1336 mothers if wf in diagram_wavefunctions]) 1337 1338 # We want to insert this new_wf as early as 1339 # possible in the diagram_wavefunctions list so that 1340 # we are guaranteed that it will be placed *before* 1341 # wavefunctions that have new_wf as a mother. 1342 # We therefore place it at max_mother_index+1. 1343 if max_mother_index<len(diagram_wavefunctions)-1: 1344 new_wf.set('number',diagram_wavefunctions[ 1345 max_mother_index+1].get('number')) 1346 for wf in diagram_wavefunctions[max_mother_index+1:]: 1347 wf.set('number',wf.get('number')+1) 1348 diagram_wavefunctions.insert(max_mother_index+1, 1349 new_wf) 1350 1351 # Set new mothers 1352 new_wf.set('mothers', mothers) 1353 1354 # Now flip flow or sign 1355 if flip_flow: 1356 # Flip fermion flow 1357 new_wf.set('fermionflow', -new_wf.get('fermionflow')) 1358 1359 if flip_sign: 1360 # Flip state and particle identity 1361 # (to keep particle identity * flow state) 1362 new_wf.set('state', filter(lambda state: \ 1363 state != new_wf.get('state'), 1364 ['incoming', 'outgoing'])[0]) 1365 new_wf.set('is_part', not new_wf.get('is_part')) 1366 try: 1367 # Use the copy in wavefunctions instead. 1368 # Remove this copy from diagram_wavefunctions 1369 new_wf_number = new_wf.get('number') 1370 new_wf = wavefunctions[wavefunctions.index(new_wf)] 1371 diagram_wf_numbers = [w.get('number') for w in \ 1372 diagram_wavefunctions] 1373 index = diagram_wf_numbers.index(new_wf_number) 1374 diagram_wavefunctions.pop(index) 1375 # We need to decrease the wf number for later 1376 # diagram wavefunctions 1377 for wf in diagram_wavefunctions: 1378 if wf.get('number') > new_wf_number: 1379 wf.set('number', wf.get('number') - 1) 1380 # Since we reuse the old wavefunction, reset wf_number 1381 wf_number = wf_number - 1 1382 1383 # Need to replace wavefunction in number_to_wavefunctions 1384 # (in case this wavefunction is in another of the dicts) 1385 for n_to_wf_dict in number_to_wavefunctions: 1386 if new_wf in n_to_wf_dict.values(): 1387 for key in n_to_wf_dict.keys(): 1388 if n_to_wf_dict[key] == new_wf: 1389 n_to_wf_dict[key] = new_wf 1390 1391 if self.get('is_loop'): 1392 # fix a bug for the g g > go go g [virt=QCD] 1393 # when there is a wf which is replaced, we need to propagate 1394 # the change in all wavefunction of that diagrams which could 1395 # have this replaced wavefunction in their mothers. This 1396 # plays the role of the 'number_to_wavefunction' dictionary 1397 # used for tree level. 1398 for wf in diagram_wavefunctions: 1399 for i,mother_wf in enumerate(wf.get('mothers')): 1400 if mother_wf.get('number')==new_wf_number: 1401 wf.get('mothers')[i]=new_wf 1402 1403 except ValueError: 1404 pass 1405 1406 # Return the new (or old) wavefunction, and the new 1407 # wavefunction number 1408 return new_wf, wf_number
1409
1410 - def has_multifermion(self):
1411 """check the presence of 4 fermion vertex""" 1412 1413 mothers = self.get('mothers') 1414 if len(mothers) >2: 1415 nb_fermion = len([1 for wf in mothers if wf.is_fermion()]) 1416 if nb_fermion>2: 1417 return True 1418 1419 return any(wf.has_multifermion() for wf in self.get('mothers'))
1420 1421
1422 - def get_fermion_order(self):
1423 """Recursive function to get a list of fermion numbers 1424 corresponding to the order of fermions along fermion lines 1425 connected to this wavefunction, in the form [n1,n2,...] for a 1426 boson, and [N,[n1,n2,...]] for a fermion line""" 1427 1428 # End recursion if external wavefunction 1429 if not self.get('mothers'): 1430 if self.is_fermion(): 1431 return [self.get('number_external'), []] 1432 else: 1433 return [] 1434 1435 # Pick out fermion mother 1436 fermion_mother = None 1437 if self.is_fermion(): 1438 fermion_mother = self.find_mother_fermion() 1439 1440 other_fermions = [wf for wf in self.get('mothers') if \ 1441 wf.is_fermion() and wf != fermion_mother] 1442 # Pick out bosons 1443 bosons = filter(lambda wf: wf.is_boson(), self.get('mothers')) 1444 1445 fermion_number_list = [] 1446 1447 if self.is_fermion(): 1448 # Fermions return the result N from their mother 1449 # and the list from bosons, so [N,[n1,n2,...]] 1450 mother_list = fermion_mother.get_fermion_order() 1451 fermion_number_list.extend(mother_list[1]) 1452 1453 # If there are fermion line pairs, append them as 1454 # [NI,NO,n1,n2,...] 1455 fermion_numbers = [f.get_fermion_order() for f in other_fermions] 1456 for iferm in range(0, len(fermion_numbers), 2): 1457 fermion_number_list.append(fermion_numbers[iferm][0]) 1458 fermion_number_list.append(fermion_numbers[iferm+1][0]) 1459 fermion_number_list.extend(fermion_numbers[iferm][1]) 1460 fermion_number_list.extend(fermion_numbers[iferm+1][1]) 1461 1462 for boson in bosons: 1463 # Bosons return a list [n1,n2,...] 1464 fermion_number_list.extend(boson.get_fermion_order()) 1465 1466 if self.is_fermion(): 1467 return [mother_list[0], fermion_number_list] 1468 1469 return fermion_number_list
1470
1471 - def needs_hermitian_conjugate(self):
1472 """Returns true if any of the mothers have negative 1473 fermionflow""" 1474 1475 return self.get('conjugate_indices') != ()
1476
1477 - def get_with_flow(self, name):
1478 """Generate the is_part and state needed for writing out 1479 wavefunctions, taking into account the fermion flow""" 1480 1481 if self.get('fermionflow') > 0: 1482 # Just return (spin, state) 1483 return self.get(name) 1484 1485 # If fermionflow is -1, need to flip particle identity and state 1486 if name == 'is_part': 1487 return not self.get('is_part') 1488 if name == 'state': 1489 return filter(lambda state: state != self.get('state'), 1490 ['incoming', 'outgoing'])[0] 1491 return self.get(name)
1492
1494 """ Returns a dictionary for formatting this external wavefunction 1495 helas call """ 1496 1497 if self['mothers']: 1498 raise MadGraph5Error, "This function should be called only for"+\ 1499 " external wavefunctions." 1500 return_dict = {} 1501 if self.get('is_loop'): 1502 return_dict['conjugate'] = ('C' if self.needs_hermitian_conjugate() \ 1503 else '') 1504 return_dict['lcutspinletter'] = self.get_lcutspinletter() 1505 return_dict['number'] = self.get('number') 1506 return_dict['me_id'] = self.get('me_id') 1507 return_dict['number_external'] = self.get('number_external') 1508 return_dict['mass'] = self.get('mass') 1509 if self.is_boson(): 1510 return_dict['state_id'] = (-1) ** (self.get('state') == 'initial') 1511 else: 1512 return_dict['state_id'] = -(-1) ** self.get_with_flow('is_part') 1513 return_dict['number_external'] = self.get('number_external') 1514 1515 return return_dict
1516
1517 - def get_helas_call_dict(self, index=1, OptimizedOutput=False, 1518 specifyHel=True,**opt):
1519 """ return a dictionary to be used for formatting 1520 HELAS call. The argument index sets the flipping while optimized output 1521 changes the wavefunction specification in the arguments.""" 1522 1523 if index == 1: 1524 flip = 0 1525 else: 1526 flip = 1 1527 1528 output = {} 1529 if self.get('is_loop') and OptimizedOutput: 1530 output['vertex_rank']=self.get_analytic_info('interaction_rank') 1531 output['lcut_size']=self.get('lcut_size') 1532 output['out_size']=self.spin_to_size(self.get('spin')) 1533 1534 loop_mother_found=False 1535 for ind, mother in enumerate(self.get('mothers')): 1536 # temporary START 1537 # This temporary modification is only because aloha has the convention 1538 # of putting the loop polynomial mother wavefunction first in the 1539 # list of argument of the helas call. I this convention is changed 1540 # to be the 'natural' order in the interaction, this would not be 1541 # needed anymore. 1542 if OptimizedOutput and self.get('is_loop'): 1543 if mother.get('is_loop'): 1544 i=0 1545 else: 1546 if loop_mother_found: 1547 i=ind 1548 else: 1549 i=ind+1 1550 else: 1551 i=ind 1552 # temporary END 1553 nb = mother.get('me_id') - flip 1554 output[str(i)] = nb 1555 if not OptimizedOutput: 1556 if mother.get('is_loop'): 1557 output['WF%d'%i] = 'L(1,%d)'%nb 1558 else: 1559 output['WF%d'%i] = '(1,WE(%d)'%nb 1560 else: 1561 if mother.get('is_loop'): 1562 output['loop_mother_number']=nb 1563 output['loop_mother_rank']=\ 1564 mother.get_analytic_info('wavefunction_rank') 1565 output['in_size']=self.spin_to_size(mother.get('spin')) 1566 output['WF%d'%i] = 'PL(0,%d)'%nb 1567 loop_mother_found=True 1568 else: 1569 output['WF%d'%i] = 'W(1,%d'%nb 1570 if not mother.get('is_loop'): 1571 if specifyHel: 1572 output['WF%d'%i]=output['WF%d'%i]+',H)' 1573 else: 1574 output['WF%d'%i]=output['WF%d'%i]+')' 1575 1576 #fixed argument 1577 for i, coup in enumerate(self.get_with_flow('coupling')): 1578 # We do not include the - sign in front of the coupling of loop 1579 # wavefunctions (only the loop ones, the tree ones are treated normally) 1580 # in the non optimized output because this sign was already applied to 1581 # the coupling passed in argument when calling the loop amplitude. 1582 if not OptimizedOutput and self.get('is_loop'): 1583 output['coup%d'%i] = coup[1:] if coup.startswith('-') else coup 1584 else: 1585 output['coup%d'%i] = coup 1586 1587 output['out'] = self.get('me_id') - flip 1588 output['M'] = self.get('mass') 1589 output['W'] = self.get('width') 1590 output['propa'] = self.get('particle').get('propagator') 1591 if output['propa'] not in ['', None]: 1592 output['propa'] = 'P%s' % output['propa'] 1593 if self.get('polarization'): 1594 raise InvalidCmd, 'particle with custom propagator can not have polarization' 1595 elif self.get('polarization'): 1596 if self.get('polarization') == [0]: 1597 if self.get('spin') != 3: 1598 raise InvalidCmd, 'polarization not handle for decay particle' 1599 output['propa'] = 'P1L' 1600 elif self.get('polarization') == [1,-1]: 1601 if self.get('spin') != 3: 1602 raise InvalidCmd, 'polarization not handle for decay particle' 1603 output['propa'] = 'P1T' 1604 elif self.get('polarization') == [99]: 1605 if self.get('spin') != 3: 1606 raise InvalidCmd, 'polarization not handle for decay particle' 1607 output['propa'] = 'P1A' 1608 elif self.get('polarization') == [1]: 1609 if self.get('spin') != 2: 1610 raise InvalidCmd, 'polarization not handle for decay particle' 1611 output['propa'] = 'P1P' 1612 elif self.get('polarization') == [-1]: 1613 if self.get('spin') != 2: 1614 raise InvalidCmd, 'Left polarization not handle for decay particle for spin (2s+1=%s) particles' % self.get('spin') 1615 output['propa'] = 'P1M' 1616 else: 1617 raise InvalidCmd, 'polarization not handle for decay particle' 1618 1619 # optimization 1620 if aloha.complex_mass: 1621 if (self.get('width') == 'ZERO' or self.get('mass') == 'ZERO'): 1622 #print self.get('width'), self.get('mass') 1623 output['CM'] = '%s' % self.get('mass') 1624 else: 1625 output['CM'] ='CMASS_%s' % self.get('mass') 1626 output.update(opt) 1627 return output
1628
1629 - def get_spin_state_number(self, flip=False):
1630 """Returns the number corresponding to the spin state, with a 1631 minus sign for incoming fermions. For flip=True, this 1632 spin_state_number is suited for find the index in the interaction 1633 of a MOTHER wavefunction. """ 1634 1635 state_number = {'incoming':-1 if not flip else 1, 1636 'outgoing': 1 if not flip else -1, 1637 'intermediate': 1, 'initial': 1, 'final': 1} 1638 return self.get('fermionflow') * \ 1639 state_number[self.get('state')] * \ 1640 self.get('spin')
1641
1642 - def find_mother_fermion(self):
1643 """Return the fermion mother which is fermion flow connected to 1644 this fermion""" 1645 1646 if not self.is_fermion(): 1647 return None 1648 1649 part_number = self.find_outgoing_number() 1650 mother_number = (part_number-1)//2*2 1651 1652 return HelasMatrixElement.sorted_mothers(self)[mother_number]
1653
1654 - def find_outgoing_number(self):
1655 "Return the position of the resulting particles in the interactions" 1656 # First shot: just the index in the interaction 1657 1658 if self.get('interaction_id') == 0: 1659 return 0 1660 1661 return self.find_leg_index(self.get_anti_pdg_code(),\ 1662 self.get_spin_state_number())
1663
1664 - def find_leg_index(self, pdg_code, spin_state):
1665 """ Find the place in the interaction list of the given particle with 1666 pdg 'pdg_code' and spin 'spin_stat'. For interactions with several identical particles (or 1667 fermion pairs) the outgoing index is always the first occurence. 1668 """ 1669 wf_indices = self.get('pdg_codes') 1670 wf_index = wf_indices.index(pdg_code) 1671 1672 # If fermion, then we need to correct for I/O status 1673 if spin_state % 2 == 0: 1674 if wf_index % 2 == 0 and spin_state < 0: 1675 # Outgoing particle at even slot -> increase by 1 1676 wf_index += 1 1677 elif wf_index % 2 == 1 and spin_state > 0: 1678 # Incoming particle at odd slot -> decrease by 1 1679 wf_index -= 1 1680 return wf_index + 1
1681
1682 - def get_call_key(self):
1683 """Generate the (spin, number, C-state) tuple used as key for 1684 the helas call dictionaries in HelasModel""" 1685 1686 res = [] 1687 for mother in self.get('mothers'): 1688 res.append(mother.get_spin_state_number()) 1689 1690 # Sort according to spin and flow direction 1691 res.sort() 1692 res.append(self.get_spin_state_number()) 1693 outgoing =self.find_outgoing_number() 1694 res.append(outgoing) 1695 1696 if self['is_loop']: 1697 res.append(self.get_loop_index()) 1698 if not self.get('mothers'): 1699 res.append(self.get('is_part')) 1700 1701 res.append(tuple(self.get('polarization')) ) 1702 1703 # Check if we need to append a charge conjugation flag 1704 if self.needs_hermitian_conjugate(): 1705 res.append(self.get('conjugate_indices')) 1706 1707 1708 1709 1710 return (tuple(res), tuple(self.get('lorentz')))
1711
1712 - def get_base_vertices(self, wf_dict, vx_list = [], optimization = 1):
1713 """Recursive method to get a base_objects.VertexList 1714 corresponding to this wavefunction and its mothers.""" 1715 1716 vertices = base_objects.VertexList() 1717 1718 mothers = self.get('mothers') 1719 1720 if not mothers: 1721 return vertices 1722 1723 # Add vertices for all mothers 1724 for mother in mothers: 1725 # This is where recursion happens 1726 vertices.extend(mother.get_base_vertices(\ 1727 wf_dict, vx_list,optimization)) 1728 1729 vertex = self.get_base_vertex(wf_dict, vx_list, optimization) 1730 1731 try: 1732 index = vx_list.index(vertex) 1733 vertex = vx_list[index] 1734 except ValueError: 1735 pass 1736 1737 vertices.append(vertex) 1738 1739 return vertices
1740
1741 - def get_base_vertex(self, wf_dict, vx_list = [], optimization = 1):
1742 """Get a base_objects.Vertex corresponding to this 1743 wavefunction.""" 1744 1745 # Generate last vertex 1746 legs = base_objects.LegList() 1747 1748 # We use the onshell flag to indicate whether this outgoing 1749 # leg corresponds to a decaying (onshell) particle, forbidden 1750 # s-channel, or regular 1751 try: 1752 if self.get('is_loop'): 1753 # Loop wavefunction should always be redefined 1754 raise KeyError 1755 lastleg = wf_dict[(self.get('number'),self.get('onshell'))] 1756 except KeyError: 1757 lastleg = base_objects.Leg({ 1758 'id': self.get_pdg_code(), 1759 'number': self.get('number_external'), 1760 'state': self.get('leg_state'), 1761 'onshell': self.get('onshell'), 1762 'loop_line':self.get('is_loop') 1763 }) 1764 1765 if optimization != 0 and not self.get('is_loop'): 1766 wf_dict[(self.get('number'),self.get('onshell'))] = lastleg 1767 1768 for mother in self.get('mothers'): 1769 try: 1770 if mother.get('is_loop'): 1771 # Loop wavefunction should always be redefined 1772 raise KeyError 1773 leg = wf_dict[(mother.get('number'),False)] 1774 except KeyError: 1775 leg = base_objects.Leg({ 1776 'id': mother.get_pdg_code(), 1777 'number': mother.get('number_external'), 1778 'state': mother.get('leg_state'), 1779 'onshell': None, 1780 'loop_line':mother.get('is_loop'), 1781 'onshell': None 1782 }) 1783 if optimization != 0 and not mother.get('is_loop'): 1784 wf_dict[(mother.get('number'),False)] = leg 1785 legs.append(leg) 1786 1787 legs.append(lastleg) 1788 1789 vertex = base_objects.Vertex({ 1790 'id': self.get('interaction_id'), 1791 'legs': legs}) 1792 1793 return vertex
1794
1795 - def get_color_indices(self):
1796 """Recursive method to get the color indices corresponding to 1797 this wavefunction and its mothers.""" 1798 1799 if not self.get('mothers'): 1800 return [] 1801 1802 color_indices = [] 1803 1804 # Add color indices for all mothers 1805 for mother in self.get('mothers'): 1806 # This is where recursion happens 1807 color_indices.extend(mother.get_color_indices()) 1808 # Add this wf's color index 1809 color_indices.append(self.get('color_key')) 1810 1811 return color_indices
1812
1813 - def get_aloha_info(self, optimized_output=True):
1814 """Returns the tuple (lorentz_name, tag, outgoing_number) providing 1815 the necessary information to compute_subset of create_aloha to write 1816 out the HELAS-like routines.""" 1817 1818 # In principle this function should not be called for the case below, 1819 # or if it does it should handle specifically the None returned value. 1820 if self.get('interaction_id') in [0,-1]: 1821 return None 1822 1823 tags = ['C%s' % w for w in self.get_conjugate_index()] 1824 if self.get('is_loop'): 1825 if not optimized_output: 1826 tags.append('L') 1827 else: 1828 tags.append('L%d'%self.get_loop_index()) 1829 1830 if self.get('particle').get('propagator') not in ['', None]: 1831 tags.append('P%s' % str(self.get('particle').get('propagator'))) 1832 elif self.get('polarization'): 1833 if self.get('polarization') == [0]: 1834 tags.append('P1L') 1835 elif self.get('polarization') == [1,-1]: 1836 tags.append('P1T') 1837 elif self.get('polarization') == [99]: 1838 tags.append('P1A') 1839 elif self.get('polarization') == [1]: 1840 tags.append('P1P') 1841 elif self.get('polarization') == [-1]: 1842 tags.append('P1M') 1843 else: 1844 raise InvalidCmd, 'polarization not handle for decay particle' 1845 1846 return (tuple(self.get('lorentz')),tuple(tags),self.find_outgoing_number())
1847
1848 - def get_lcutspinletter(self):
1849 """Returns S,V or F depending on the spin of the mother loop particle. 1850 Return '' otherwise.""" 1851 1852 if self['is_loop'] and not self.get('mothers'): 1853 if self.get('spin') == 1: 1854 if self.get('particle').get('is_part'): 1855 return 'S' 1856 else: 1857 return 'AS' 1858 if self.get('spin') == 2: 1859 if self.get('particle').get('is_part'): 1860 return 'F' 1861 else: 1862 return 'AF' 1863 if self.get('spin') == 3: 1864 return 'V' 1865 else: 1866 raise MadGraph5Error,'L-cut particle type not supported' 1867 else: 1868 return ''
1869
1870 - def get_s_and_t_channels(self, ninitial, mother_leg, reverse_t_ch = False):
1871 """Returns two lists of vertices corresponding to the s- and 1872 t-channels that can be traced from this wavefunction, ordered 1873 from the outermost s-channel and in/down towards the highest 1874 (if not reverse_t_ch) or lowest (if reverse_t_ch) number initial 1875 state leg. mother_leg corresponds to self but with 1876 correct leg number = min(final state mothers).""" 1877 1878 schannels = base_objects.VertexList() 1879 tchannels = base_objects.VertexList() 1880 1881 mother_leg = copy.copy(mother_leg) 1882 1883 (startleg, finalleg) = (1,2) 1884 if reverse_t_ch: (startleg, finalleg) = (2,1) 1885 1886 # Add vertices for all s-channel mothers 1887 final_mothers = filter(lambda wf: wf.get('number_external') > ninitial, 1888 self.get('mothers')) 1889 1890 for mother in final_mothers: 1891 schannels.extend(mother.get_base_vertices({}, optimization = 0)) 1892 1893 # Extract initial state mothers 1894 init_mothers = filter(lambda wf: wf.get('number_external') <= ninitial, 1895 self.get('mothers')) 1896 1897 assert len(init_mothers) < 3 , \ 1898 "get_s_and_t_channels can only handle up to 2 initial states" 1899 1900 if len(init_mothers) == 1: 1901 # This is an s-channel or t-channel leg, or the initial 1902 # leg of a decay process. Add vertex and continue stepping 1903 # down towards external initial state 1904 legs = base_objects.LegList() 1905 mothers = final_mothers + init_mothers 1906 1907 for mother in mothers: 1908 legs.append(base_objects.Leg({ 1909 'id': mother.get_pdg_code(), 1910 'number': mother.get('number_external'), 1911 'state': mother.get('leg_state'), 1912 'onshell': mother.get('onshell') 1913 })) 1914 1915 if init_mothers[0].get('number_external') == startleg and \ 1916 not init_mothers[0].get('leg_state') and ninitial > 1: 1917 # If this is t-channel going towards external leg 1, 1918 # mother_leg is resulting wf 1919 legs.append(mother_leg) 1920 else: 1921 # For decay processes or if init_mother is an s-channel leg 1922 # or we are going towards external leg 2, mother_leg 1923 # is one of the mothers (placed next-to-last) 1924 legs.insert(-1, mother_leg) 1925 # Need to switch direction of the resulting s-channel 1926 legs[-1].set('id', init_mothers[0].get_anti_pdg_code()) 1927 1928 # Renumber resulting leg according to minimum leg number 1929 legs[-1].set('number', min([l.get('number') for l in legs[:-1]])) 1930 1931 vertex = base_objects.Vertex({ 1932 'id': self.get('interaction_id'), 1933 'legs': legs}) 1934 1935 # Add s- and t-channels from init_mother 1936 new_mother_leg = legs[-1] 1937 if init_mothers[0].get('number_external') == startleg and \ 1938 not init_mothers[0].get('leg_state') and \ 1939 ninitial > 1: 1940 # Mother of next vertex is init_mothers[0] 1941 # (next-to-last in legs) 1942 new_mother_leg = legs[-2] 1943 1944 mother_s, tchannels = \ 1945 init_mothers[0].get_s_and_t_channels(ninitial, 1946 new_mother_leg, 1947 reverse_t_ch) 1948 if ninitial == 1 or init_mothers[0].get('leg_state') == True: 1949 # This vertex is s-channel 1950 schannels.append(vertex) 1951 elif init_mothers[0].get('number_external') == startleg: 1952 # If init_mothers is going towards external leg 1, add 1953 # to t-channels, at end 1954 tchannels.append(vertex) 1955 else: 1956 # If init_mothers is going towards external leg 2, add to 1957 # t-channels, at start 1958 tchannels.insert(0, vertex) 1959 1960 schannels.extend(mother_s) 1961 1962 elif len(init_mothers) == 2: 1963 # This is a t-channel junction. Start with the leg going 1964 # towards external particle 1, and then do external 1965 # particle 2 1966 init_mothers1 = filter(lambda wf: wf.get('number_external') == \ 1967 startleg, 1968 init_mothers)[0] 1969 init_mothers2 = filter(lambda wf: wf.get('number_external') == \ 1970 finalleg, 1971 init_mothers)[0] 1972 1973 # Create vertex 1974 legs = base_objects.LegList() 1975 for mother in final_mothers + [init_mothers1, init_mothers2]: 1976 legs.append(base_objects.Leg({ 1977 'id': mother.get_pdg_code(), 1978 'number': mother.get('number_external'), 1979 'state': mother.get('leg_state'), 1980 'onshell': mother.get('onshell') 1981 })) 1982 legs.insert(0, mother_leg) 1983 1984 # Renumber resulting leg according to minimum leg number 1985 legs[-1].set('number', min([l.get('number') for l in legs[:-1]])) 1986 1987 vertex = base_objects.Vertex({ 1988 'id': self.get('interaction_id'), 1989 'legs': legs}) 1990 1991 # Add s- and t-channels going down towards leg 1 1992 mother_s, tchannels = \ 1993 init_mothers1.get_s_and_t_channels(ninitial, legs[-2], 1994 reverse_t_ch) 1995 schannels.extend(mother_s) 1996 1997 # Add vertex 1998 tchannels.append(vertex) 1999 2000 # Add s- and t-channels going down towards leg 2 2001 mother_s, mother_t = \ 2002 init_mothers2.get_s_and_t_channels(ninitial, legs[-1], 2003 reverse_t_ch) 2004 schannels.extend(mother_s) 2005 tchannels.extend(mother_t) 2006 2007 # Sort s-channels according to number 2008 schannels.sort(lambda x1,x2: x2.get('legs')[-1].get('number') - \ 2009 x1.get('legs')[-1].get('number')) 2010 2011 return schannels, tchannels
2012
2014 """ Return a set containing the ids of all the non-loop outter-most 2015 external legs attached to the loop at the interaction point of this 2016 loop wavefunction """ 2017 2018 if not self.get('mothers'): 2019 return set([self.get('number_external'),]) 2020 2021 res=set([]) 2022 for wf in self.get('mothers'): 2023 if not wf['is_loop']: 2024 res=res.union(wf.get_struct_external_leg_ids()) 2025 return res
2026 #
2027 - def get_loop_index(self):
2028 """Return the index of the wavefunction in the mothers which is the 2029 loop one""" 2030 2031 if not self.get('mothers'): 2032 return 0 2033 2034 try: 2035 loop_wf_index=\ 2036 [wf['is_loop'] for wf in self.get('mothers')].index(True) 2037 except ValueError: 2038 raise MadGraph5Error, "The loop wavefunctions should have exactly"+\ 2039 " one loop wavefunction mother." 2040 2041 if self.find_outgoing_number()-1<=loop_wf_index: 2042 # If the incoming loop leg is placed after the outgoing one we 2043 # need to increment once more its index in the interaction list ( 2044 # because the outgoing loop leg is not part of the mother wf list) 2045 return loop_wf_index+2 2046 else: 2047 # Basic increment of +1 because aloha counts particles in the 2048 # interaction starting at 1. 2049 return loop_wf_index+1
2050
2051 - def get_lcut_size(self):
2052 """ Return the size (i.e number of elements) of the L-Cut wavefunction 2053 this loop wavefunction originates from. """ 2054 2055 if not self['is_loop']: 2056 return 0 2057 2058 # Obtain the L-cut wavefunction this loop wavefunction comes from. 2059 # (I'm using two variable instead of one in order to have only one call 2060 # to get_loop_mother()) 2061 last_loop_wf=self 2062 last_loop_wf_loop_mother=last_loop_wf.get_loop_mother() 2063 while last_loop_wf_loop_mother: 2064 last_loop_wf=last_loop_wf_loop_mother 2065 last_loop_wf_loop_mother=last_loop_wf_loop_mother.get_loop_mother() 2066 2067 # Translate its spin into a wavefunction size. 2068 return self.spin_to_size(last_loop_wf.get('spin'))
2069
2070 - def get_loop_mother(self):
2071 """ Return the mother of type 'loop', if any. """ 2072 2073 if not self.get('mothers'): 2074 return None 2075 loop_wfs=[wf for wf in self.get('mothers') if wf['is_loop']] 2076 if loop_wfs: 2077 if len(loop_wfs)==1: 2078 return loop_wfs[0] 2079 else: 2080 raise MadGraph5Error, "The loop wavefunction must have either"+\ 2081 " no mothers, or exactly one mother with type 'loop'." 2082 else: 2083 return None
2084
2085 - def get_conjugate_index(self):
2086 """Return the index of the particle that should be conjugated.""" 2087 2088 if not any([(wf.get('fermionflow') < 0 or wf.is_majorana()) for wf in \ 2089 self.get('mothers')]) and \ 2090 (not self.get('interaction_id') or \ 2091 self.get('fermionflow') >= 0): 2092 return () 2093 2094 # Pick out first sorted mothers, then fermions 2095 mothers, self_index = \ 2096 self.get('mothers').sort_by_pdg_codes(self.get('pdg_codes'), 2097 self.get_anti_pdg_code()) 2098 fermions = HelasWavefunctionList([wf for wf in mothers if wf.is_fermion()]) 2099 2100 # Insert this wavefunction in list (in the right place) 2101 if self.is_fermion(): 2102 me = copy.copy(self) 2103 # Flip incoming/outgoing to make me equivalent to mother 2104 # as needed by majorana_conjugates 2105 me.set('state', [state for state in ['incoming', 'outgoing'] \ 2106 if state != me.get('state')][0]) 2107 fermions.insert(self_index, me) 2108 2109 # Initialize indices with indices due to Majoranas with wrong order 2110 indices = fermions.majorana_conjugates() 2111 2112 # Check for fermions with negative fermion flow 2113 for i in range(0,len(fermions), 2): 2114 if fermions[i].get('fermionflow') < 0 or \ 2115 fermions[i+1].get('fermionflow') < 0: 2116 indices.append(i/2 + 1) 2117 2118 return tuple(sorted(indices))
2119
2120 - def get_vertex_leg_numbers(self, 2121 veto_inter_id=base_objects.Vertex.ID_to_veto_for_multichanneling, 2122 max_n_loop=0):
2123 """Get a list of the number of legs in vertices in this diagram""" 2124 2125 if not self.get('mothers'): 2126 return [] 2127 2128 if max_n_loop == 0: 2129 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling 2130 2131 vertex_leg_numbers = [len(self.get('mothers')) + 1] if \ 2132 (self.get('interaction_id') not in veto_inter_id) or\ 2133 (self.get('interaction_id')==-2 and len(self.get('mothers'))+1 > 2134 max_n_loop) else [] 2135 for mother in self.get('mothers'): 2136 vertex_leg_numbers.extend(mother.get_vertex_leg_numbers( 2137 veto_inter_id = veto_inter_id)) 2138 2139 return vertex_leg_numbers
2140 2141 # Overloaded operators 2142
2143 - def __eq__(self, other):
2144 """Overloading the equality operator, to make comparison easy 2145 when checking if wavefunction is already written, or when 2146 checking for identical processes. Note that the number for 2147 this wavefunction, the pdg code, and the interaction id are 2148 irrelevant, while the numbers for the mothers are important. 2149 """ 2150 2151 if not isinstance(other, HelasWavefunction): 2152 return False 2153 2154 # Check relevant directly defined properties 2155 if self['number_external'] != other['number_external'] or \ 2156 self['fermionflow'] != other['fermionflow'] or \ 2157 self['color_key'] != other['color_key'] or \ 2158 self['lorentz'] != other['lorentz'] or \ 2159 self['coupling'] != other['coupling'] or \ 2160 self['state'] != other['state'] or \ 2161 self['onshell'] != other['onshell'] or \ 2162 self.get('spin') != other.get('spin') or \ 2163 self.get('self_antipart') != other.get('self_antipart') or \ 2164 self.get('mass') != other.get('mass') or \ 2165 self.get('width') != other.get('width') or \ 2166 self.get('color') != other.get('color') or \ 2167 self['decay'] != other['decay'] or \ 2168 self['decay'] and self['particle'] != other['particle']: 2169 return False 2170 2171 # Check that mothers have the same numbers (only relevant info) 2172 return sorted([mother['number'] for mother in self['mothers']]) == \ 2173 sorted([mother['number'] for mother in other['mothers']])
2174
2175 - def __ne__(self, other):
2176 """Overloading the nonequality operator, to make comparison easy""" 2177 return not self.__eq__(other)
2178 2179 #=============================================================================== 2180 # Start of the legacy of obsolete functions of the HelasWavefunction class. 2181 #=============================================================================== 2182
2184 """ Returns the power of the loop momentum q brought by the interaction 2185 and propagator from which this loop wavefunction originates. This 2186 is done in a SM ad-hoc way, but it should be promoted to be general in 2187 the future, by reading the lorentz structure of the interaction. 2188 This function is now rendered obsolete by the use of the function 2189 get_analytical_info. It is however kept for legacy.""" 2190 rank=0 2191 # First add the propagator power for a fermion of spin 1/2. 2192 # For the bosons, it is assumed to be in Feynman gauge so that the 2193 # propagator does not bring in any power of the loop momentum. 2194 if self.get('spin')==2: 2195 rank=rank+1 2196 2197 # Treat in an ad-hoc way the higgs effective theory 2198 spin_cols = [(self.get('spin'),abs(self.get('color')))]+\ 2199 [(w.get('spin'),abs(w.get('color'))) for w in self.get('mothers')] 2200 # HGG effective vertex 2201 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8)]): 2202 return rank+2 2203 # HGGG effective vertex 2204 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8),(3,8)]): 2205 return rank+1 2206 # HGGGG effective vertex 2207 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8),(3,8),(3,8)]): 2208 return rank 2209 2210 # Now add a possible power of the loop momentum depending on the 2211 # vertex creating this loop wavefunction. For now we don't read the 2212 # lorentz structure but just use an SM ad-hoc rule that only 2213 # the feynman rules for a three point vertex with only bosons bring 2214 # in one power of q. 2215 if self.is_boson() and len([w for w in self.get('mothers') \ 2216 if w.is_boson()])==2: 2217 rank=rank+1 2218 return rank
2219
2220 #=============================================================================== 2221 # End of the legacy of obsolete functions of the HelasWavefunction class. 2222 #=============================================================================== 2223 2224 #=============================================================================== 2225 # HelasWavefunctionList 2226 #=============================================================================== 2227 -class HelasWavefunctionList(base_objects.PhysicsObjectList):
2228 """List of HelasWavefunction objects. This class has the routine 2229 check_and_fix_fermion_flow, which checks for fermion flow clashes 2230 among the mothers of an amplitude or wavefunction. 2231 """ 2232
2233 - def is_valid_element(self, obj):
2234 """Test if object obj is a valid HelasWavefunction for the list.""" 2235 2236 return isinstance(obj, HelasWavefunction)
2237 2238 # Helper functions 2239
2240 - def to_array(self):
2241 return array.array('i', [w['number'] for w in self])
2242
2243 - def check_and_fix_fermion_flow(self, 2244 wavefunctions, 2245 diagram_wavefunctions, 2246 external_wavefunctions, 2247 my_wf, 2248 wf_number, 2249 force_flip_flow=False, 2250 number_to_wavefunctions=[]):
2251 2252 """Check for clashing fermion flow (N(incoming) != 2253 N(outgoing)). If found, we need to trace back through the 2254 mother structure (only looking at fermions), until we find a 2255 Majorana fermion. Then flip fermion flow along this line all 2256 the way from the initial clash to the external fermion (in the 2257 right way, see check_majorana_and_flip_flow), and consider an 2258 incoming particle with fermionflow -1 as outgoing (and vice 2259 versa). Continue until we have N(incoming) = N(outgoing). 2260 2261 Since the wavefunction number might get updated, return new 2262 wavefunction number. 2263 """ 2264 2265 # Clash is defined by whether any of the fermion lines are clashing 2266 fermion_mother = None 2267 2268 # Keep track of clashing fermion wavefunctions 2269 clashes = [] 2270 2271 # First check the fermion mother on the same fermion line 2272 if my_wf and my_wf.is_fermion(): 2273 fermion_mother = my_wf.find_mother_fermion() 2274 if my_wf.get_with_flow('state') != \ 2275 fermion_mother.get_with_flow('state'): 2276 clashes.append([fermion_mother]) 2277 2278 # Now check all other fermions 2279 other_fermions = [w for w in self if \ 2280 w.is_fermion() and w != fermion_mother] 2281 2282 for iferm in range(0, len(other_fermions), 2): 2283 if other_fermions[iferm].get_with_flow('state') == \ 2284 other_fermions[iferm+1].get_with_flow('state'): 2285 clashes.append([other_fermions[iferm], 2286 other_fermions[iferm+1]]) 2287 2288 if not clashes: 2289 return wf_number 2290 2291 # If any of the mothers have negative fermionflow, we need to 2292 # take this mother first. 2293 for clash in clashes: 2294 neg_fermionflow_mothers = [m for m in clash if \ 2295 m.get('fermionflow') < 0] 2296 2297 if not neg_fermionflow_mothers: 2298 neg_fermionflow_mothers = clash 2299 2300 for mother in neg_fermionflow_mothers: 2301 2302 # Call recursive function to check for Majorana fermions 2303 # and flip fermionflow if found 2304 2305 found_majorana = False 2306 state_before = mother.get_with_flow('state') 2307 new_mother, wf_number = mother.check_majorana_and_flip_flow(\ 2308 found_majorana, 2309 wavefunctions, 2310 diagram_wavefunctions, 2311 external_wavefunctions, 2312 wf_number, 2313 force_flip_flow, 2314 number_to_wavefunctions) 2315 2316 if new_mother.get_with_flow('state') == state_before: 2317 # Fermion flow was not flipped, try next mother 2318 continue 2319 2320 # Replace old mother with new mother 2321 mother_index = self.index(mother) 2322 self[self.index(mother)] = new_mother 2323 clash_index = clash.index(mother) 2324 clash[clash.index(mother)] = new_mother 2325 2326 # Fermion flow was flipped, abort loop 2327 break 2328 2329 if len(clash) == 1 and clash[0].get_with_flow('state') != \ 2330 my_wf.get_with_flow('state') or \ 2331 len(clash) == 2 and clash[0].get_with_flow('state') == \ 2332 clash[1].get_with_flow('state'): 2333 # No Majorana fermion in any relevant legs - try again, 2334 # but simply use the first relevant leg 2335 force_flip_flow = True 2336 wf_number = self.check_and_fix_fermion_flow(\ 2337 wavefunctions, 2338 diagram_wavefunctions, 2339 external_wavefunctions, 2340 my_wf, 2341 wf_number, 2342 force_flip_flow, 2343 number_to_wavefunctions) 2344 # Already ran for all clashes, abort loop 2345 break 2346 2347 return wf_number
2348
2349 - def insert_own_mothers(self):
2350 """Recursively go through a wavefunction list and insert the 2351 mothers of all wavefunctions, return the result. 2352 Assumes that all wavefunctions have unique numbers.""" 2353 2354 res = copy.copy(self) 2355 # Recursively build up res 2356 for wf in self: 2357 index = res.index(wf) 2358 res = res[:index] + wf.get('mothers').insert_own_mothers() \ 2359 + res[index:] 2360 2361 # Make sure no wavefunctions occur twice, by removing doublets 2362 # from the back 2363 i = len(res) - 1 2364 while res[:i]: 2365 if res[i].get('number') in [w.get('number') for w in res[:i]]: 2366 res.pop(i) 2367 i = i - 1 2368 2369 return res
2370
2371 - def sort_by_pdg_codes(self, pdg_codes, my_pdg_code = 0):
2372 """Sort this HelasWavefunctionList according to the cyclic 2373 order of the pdg codes given. my_pdg_code is the pdg code of 2374 the daughter wavefunction (or 0 if daughter is amplitude).""" 2375 2376 if not pdg_codes: 2377 return self, 0 2378 2379 pdg_codes = copy.copy(pdg_codes) 2380 2381 # Remove the argument wavefunction code from pdg_codes 2382 2383 my_index = -1 2384 if my_pdg_code: 2385 # Remember index of my code 2386 my_index = pdg_codes.index(my_pdg_code) 2387 pdg_codes.pop(my_index) 2388 2389 mothers = copy.copy(self) 2390 # Sort according to interaction pdg codes 2391 2392 mother_codes = [ wf.get_pdg_code() for wf \ 2393 in mothers ] 2394 if pdg_codes == mother_codes: 2395 # Already sorted - skip sort below 2396 return mothers, my_index 2397 2398 sorted_mothers = [] 2399 for i, code in enumerate(pdg_codes): 2400 index = mother_codes.index(code) 2401 mother_codes.pop(index) 2402 mother = mothers.pop(index) 2403 sorted_mothers.append(mother) 2404 2405 if mothers: 2406 raise base_objects.PhysicsObject.PhysicsObjectError 2407 2408 return HelasWavefunctionList(sorted_mothers), my_index
2409
2410 - def majorana_conjugates(self):
2411 """Returns a list [1,2,...] of fermion lines that need 2412 conjugate wfs due to wrong order of I/O Majorana particles 2413 compared to interaction order (or empty list if no Majorana 2414 particles). This is crucial if the Lorentz structure depends 2415 on the direction of the Majorana particles, as in MSSM with 2416 goldstinos.""" 2417 2418 if len([m for m in self if m.is_majorana()]) < 2: 2419 return [] 2420 2421 conjugates = [] 2422 2423 # Check if the order for Majorana fermions is correct 2424 for i in range(0, len(self), 2): 2425 if self[i].is_majorana() and self[i+1].is_majorana() \ 2426 and self[i].get_pdg_code() != \ 2427 self[i+1].get_pdg_code(): 2428 # Check if mother I/O order is correct (IO) 2429 if self[i].get_spin_state_number() > 0 and \ 2430 self[i + 1].get_spin_state_number() < 0: 2431 # Order is wrong, we need a conjugate here 2432 conjugates.append(True) 2433 else: 2434 conjugates.append(False) 2435 elif self[i].is_fermion(): 2436 # For non-Majorana case, always False 2437 conjugates.append(False) 2438 2439 # Return list 1,2,... for which indices are needed 2440 conjugates = [i+1 for (i,c) in enumerate(conjugates) if c] 2441 2442 return conjugates
2443 2444
2445 - def check_wavefunction_numbers_order(self, applyChanges=False, raiseError=True):
2446 """ This function only serves as an internal consistency check to 2447 make sure that when setting the 'wavefunctions' attribute of the 2448 diagram, their order is consistent, in the sense that all mothers 2449 of any given wavefunction appear before that wavefunction. 2450 This function returns True if there was no change and the original 2451 wavefunction list was consistent and False otherwise. 2452 The option 'applyChanges' controls whether the function should substitute 2453 the original list (self) with the new corrected one. For now, this function 2454 is only used for self-consistency checks and the changes are not applied.""" 2455 2456 if len(self)<2: 2457 return True 2458 2459 def RaiseError(): 2460 raise self.PhysicsObjectListError, \ 2461 "This wavefunction list does not have a consistent wavefunction ordering."+\ 2462 "\n Wf numbers: %s"%str([wf['number'] for wf in diag_wfs])+\ 2463 "\n Wf mothers: %s"%str([[mother['number'] for mother in wf['mothers']] \ 2464 for wf in diag_wfs])
2465 2466 # We want to work on a local copy of the wavefunction list attribute 2467 diag_wfs = copy.copy(self) 2468 2469 # We want to keep the original wf numbering (but beware that this 2470 # implies changing the 'number' attribute of some wf if this function 2471 # was used for actual reordering and not just self-consistency check) 2472 wfNumbers = [wf['number'] for wf in self] 2473 2474 exitLoop=False 2475 while not exitLoop: 2476 for i, wf in enumerate(diag_wfs): 2477 if i==len(diag_wfs)-1: 2478 exitLoop=True 2479 break 2480 found=False 2481 # Look at all subsequent wfs in the list placed after wf at 2482 # index i. None of them should have wf as its mother 2483 for w in diag_wfs[i+1:]: 2484 if w['number'] in [mwf['number'] for mwf in wf.get('mothers')]: 2485 # There is an inconsisent order so we must move this 2486 # mother w *before* wf which is placed at i. 2487 diag_wfs.remove(w) 2488 diag_wfs.insert(i,w) 2489 found=True 2490 if raiseError: RaiseError() 2491 if not applyChanges: 2492 return False 2493 break 2494 if found: 2495 break 2496 2497 if diag_wfs!=self: 2498 # After this, diag_wfs is the properly re-ordered and 2499 # consistent list that should be used, where each mother appear 2500 # before its daughter 2501 for i,wf in enumerate(diag_wfs): 2502 wf.set('number', wfNumbers[i]) 2503 2504 # Replace this wavefunction list by corrected one 2505 del self[:] 2506 self.extend(diag_wfs) 2507 2508 # The original list was inconsistent, so it returns False. 2509 return False 2510 2511 # The original list was consistent, so it returns True 2512 return True
2513 2514 @staticmethod
2515 - def extract_wavefunctions(mothers):
2516 """Recursively extract the wavefunctions from mothers of mothers""" 2517 2518 wavefunctions = copy.copy(mothers) 2519 for wf in mothers: 2520 wavefunctions.extend(HelasWavefunctionList.\ 2521 extract_wavefunctions(wf.get('mothers'))) 2522 2523 return wavefunctions
2524
2525 #=============================================================================== 2526 # HelasAmplitude 2527 #=============================================================================== 2528 -class HelasAmplitude(base_objects.PhysicsObject):
2529 """HelasAmplitude object, has the information necessary for 2530 writing a call to a HELAS amplitude routine:a list of mother wavefunctions, 2531 interaction id, amplitude number 2532 """ 2533
2534 - def default_setup(self):
2535 """Default values for all properties""" 2536 2537 # Properties related to the interaction generating the propagator 2538 self['interaction_id'] = 0 2539 # Base for born amplitude, the 'type' argument for the CT-vertices 2540 # and 'loop' for the HelasAmplitudes in a LoopHelasAmplitude. 2541 self['type'] = 'base' 2542 self['pdg_codes'] = [] 2543 self['orders'] = {} 2544 self['inter_color'] = None 2545 self['lorentz'] = [] 2546 self['coupling'] = ['none'] 2547 # The Lorentz and color index used in this amplitude 2548 self['color_key'] = 0 2549 # Properties relating to the vertex 2550 self['number'] = 0 2551 self['fermionfactor'] = 0 2552 self['color_indices'] = [] 2553 self['mothers'] = HelasWavefunctionList() 2554 # conjugate_indices is a list [1,2,...] with fermion lines 2555 # that need conjugates. Default is "None" 2556 self['conjugate_indices'] = None
2557 2558 # Customized constructor
2559 - def __init__(self, *arguments):
2560 """Allow generating a HelasAmplitude from a Vertex 2561 """ 2562 2563 if len(arguments) > 1: 2564 if isinstance(arguments[0], base_objects.Vertex) and \ 2565 isinstance(arguments[1], base_objects.Model): 2566 super(HelasAmplitude, self).__init__() 2567 self.set('interaction_id', 2568 arguments[0].get('id'), arguments[1]) 2569 elif arguments: 2570 super(HelasAmplitude, self).__init__(arguments[0]) 2571 else: 2572 super(HelasAmplitude, self).__init__()
2573
2574 - def filter(self, name, value):
2575 """Filter for valid property values.""" 2576 2577 if name == 'interaction_id': 2578 if not isinstance(value, int): 2579 raise self.PhysicsObjectError, \ 2580 "%s is not a valid integer for interaction id" % \ 2581 str(value) 2582 2583 if name == 'pdg_codes': 2584 #Should be a list of integers 2585 if not isinstance(value, list): 2586 raise self.PhysicsObjectError, \ 2587 "%s is not a valid list of integers" % str(value) 2588 for mystr in value: 2589 if not isinstance(mystr, int): 2590 raise self.PhysicsObjectError, \ 2591 "%s is not a valid integer" % str(mystr) 2592 2593 if name == 'orders': 2594 #Should be a dict with valid order names ask keys and int as values 2595 if not isinstance(value, dict): 2596 raise self.PhysicsObjectError, \ 2597 "%s is not a valid dict for coupling orders" % \ 2598 str(value) 2599 for order in value.keys(): 2600 if not isinstance(order, str): 2601 raise self.PhysicsObjectError, \ 2602 "%s is not a valid string" % str(order) 2603 if not isinstance(value[order], int): 2604 raise self.PhysicsObjectError, \ 2605 "%s is not a valid integer" % str(value[order]) 2606 2607 if name == 'inter_color': 2608 # Should be None or a color string 2609 if value and not isinstance(value, color.ColorString): 2610 raise self.PhysicsObjectError, \ 2611 "%s is not a valid Color String" % str(value) 2612 2613 if name == 'lorentz': 2614 #Should be a list of string 2615 if not isinstance(value, list): 2616 raise self.PhysicsObjectError, \ 2617 "%s is not a valid list of string" % str(value) 2618 for name in value: 2619 if not isinstance(name, str): 2620 raise self.PhysicsObjectError, \ 2621 "%s doesn't contain only string" % str(value) 2622 2623 if name == 'coupling': 2624 #Should be a list of string 2625 if not isinstance(value, list): 2626 raise self.PhysicsObjectError, \ 2627 "%s is not a valid coupling (list of string)" % str(value) 2628 2629 for name in value: 2630 if not isinstance(name, str): 2631 raise self.PhysicsObjectError, \ 2632 "%s doesn't contain only string" % str(value) 2633 if not len(value): 2634 raise self.PhysicsObjectError, \ 2635 'coupling should have at least one value' 2636 2637 if name == 'color_key': 2638 if value and not isinstance(value, int): 2639 raise self.PhysicsObjectError, \ 2640 "%s is not a valid integer" % str(value) 2641 2642 if name == 'number': 2643 if not isinstance(value, int): 2644 raise self.PhysicsObjectError, \ 2645 "%s is not a valid integer for amplitude number" % \ 2646 str(value) 2647 2648 if name == 'fermionfactor': 2649 if not isinstance(value, int): 2650 raise self.PhysicsObjectError, \ 2651 "%s is not a valid integer for fermionfactor" % \ 2652 str(value) 2653 if not value in [-1, 0, 1]: 2654 raise self.PhysicsObjectError, \ 2655 "%s is not a valid fermion factor (-1, 0 or 1)" % \ 2656 str(value) 2657 2658 if name == 'color_indices': 2659 #Should be a list of integers 2660 if not isinstance(value, list): 2661 raise self.PhysicsObjectError, \ 2662 "%s is not a valid list of integers" % str(value) 2663 for mystr in value: 2664 if not isinstance(mystr, int): 2665 raise self.PhysicsObjectError, \ 2666 "%s is not a valid integer" % str(mystr) 2667 2668 if name == 'mothers': 2669 if not isinstance(value, HelasWavefunctionList): 2670 raise self.PhysicsObjectError, \ 2671 "%s is not a valid list of mothers for amplitude" % \ 2672 str(value) 2673 2674 if name == 'conjugate_indices': 2675 if not isinstance(value, tuple) and value != None: 2676 raise self.PhysicsObjectError, \ 2677 "%s is not a valid tuple" % str(value) + \ 2678 " for conjugate_indices" 2679 2680 return True
2681
2682 - def __str__(self):
2683 """ practicle way to represent an HelasAmplitude""" 2684 2685 mystr = '{\n' 2686 for prop in self.get_sorted_keys(): 2687 if isinstance(self[prop], str): 2688 mystr = mystr + ' \'' + prop + '\': \'' + \ 2689 self[prop] + '\',\n' 2690 elif isinstance(self[prop], float): 2691 mystr = mystr + ' \'' + prop + '\': %.2f,\n' % self[prop] 2692 elif isinstance(self[prop], int): 2693 mystr = mystr + ' \'' + prop + '\': %s,\n' % self[prop] 2694 elif prop != 'mothers': 2695 mystr = mystr + ' \'' + prop + '\': ' + \ 2696 str(self[prop]) + ',\n' 2697 else: 2698 info = [m.get('pdg_code') for m in self['mothers']] 2699 mystr += ' \'%s\': %s,\n' % (prop, info) 2700 2701 mystr = mystr.rstrip(',\n') 2702 mystr = mystr + '\n}' 2703 2704 return mystr
2705
2706 - def has_multifermion(self):
2707 2708 return any(wf.has_multifermion() for wf in self.get('mothers'))
2709 2710
2711 - def nice_string(self):
2712 """ simple way to check which FD is related to this amplitude""" 2713 def get_structure(wf): 2714 """ funtion to allow to loop over helaswavefunction""" 2715 mothers = [] 2716 try: 2717 mothers = wf.get('mothers') 2718 except: 2719 if wf['is_loop']: 2720 return '%s*' % wf['particle'].get('pdg_code') 2721 else: 2722 return wf['particle'].get('pdg_code') 2723 2724 struct = [get_structure(w) for w in mothers] 2725 if struct: 2726 if 'is_loop' in wf: 2727 if wf['is_loop']: 2728 return (struct,'>%s*'%wf.get('pdg_code') ) 2729 else: 2730 return (struct,'>',wf.get('pdg_code') ) 2731 else: 2732 return (struct,'>', 0) 2733 else: 2734 if wf['is_loop']: 2735 return '%i*' %wf.get('pdg_code') 2736 else: 2737 return wf.get('pdg_code')
2738 2739 return get_structure(self)
2740 2741 2742 # Enhanced get function
2743 - def get(self, name):
2744 """Get the value of the property name.""" 2745 2746 if name == 'fermionfactor' and not self[name]: 2747 self.calculate_fermionfactor() 2748 2749 # Set conjugate_indices if it's not already set 2750 if name == 'conjugate_indices' and self[name] == None: 2751 self['conjugate_indices'] = self.get_conjugate_index() 2752 2753 return super(HelasAmplitude, self).get(name)
2754 2755 # Enhanced set function, where we can append a model 2756
2757 - def set(self, *arguments):
2758 """When setting interaction_id, if model is given (in tuple), 2759 set all other interaction properties. When setting pdg_code, 2760 if model is given, set all other particle properties.""" 2761 2762 assert len(arguments) > 1, "Too few arguments for set" 2763 2764 name = arguments[0] 2765 value = arguments[1] 2766 2767 if len(arguments) > 2 and \ 2768 isinstance(value, int) and \ 2769 isinstance(arguments[2], base_objects.Model): 2770 if name == 'interaction_id': 2771 self.set('interaction_id', value) 2772 if value > 0: 2773 inter = arguments[2].get('interaction_dict')[value] 2774 self.set('pdg_codes', 2775 [part.get_pdg_code() for part in \ 2776 inter.get('particles')]) 2777 self.set('orders', inter.get('orders')) 2778 # Note that the following values might change, if 2779 # the relevant color/lorentz/coupling is not index 0 2780 if inter.get('type'): 2781 self.set('type', inter.get('type')) 2782 if inter.get('color'): 2783 self.set('inter_color', inter.get('color')[0]) 2784 if inter.get('lorentz'): 2785 self.set('lorentz', [inter.get('lorentz')[0]]) 2786 if inter.get('couplings'): 2787 self.set('coupling', [inter.get('couplings').values()[0]]) 2788 return True 2789 else: 2790 raise self.PhysicsObjectError, \ 2791 "%s not allowed name for 3-argument set", name 2792 else: 2793 return super(HelasAmplitude, self).set(name, value)
2794
2795 - def get_sorted_keys(self):
2796 """Return particle property names as a nicely sorted list.""" 2797 2798 return ['interaction_id', 'pdg_codes', 'orders', 'inter_color', 2799 'lorentz', 'coupling', 'color_key', 'number', 'color_indices', 2800 'fermionfactor', 'mothers']
2801 2802 # Helper functions 2803
2804 - def check_and_fix_fermion_flow(self, 2805 wavefunctions, 2806 diagram_wavefunctions, 2807 external_wavefunctions, 2808 wf_number):
2809 """Check for clashing fermion flow (N(incoming) != 2810 N(outgoing)) in mothers. For documentation, check 2811 HelasWavefunction.check_and_fix_fermion_flow. 2812 """ 2813 2814 self.set('mothers', self.get('mothers').sort_by_pdg_codes(\ 2815 self.get('pdg_codes'), 0)[0]) 2816 2817 return self.get('mothers').check_and_fix_fermion_flow(\ 2818 wavefunctions, 2819 diagram_wavefunctions, 2820 external_wavefunctions, 2821 None, 2822 wf_number)
2823 2824
2825 - def needs_hermitian_conjugate(self):
2826 """Returns true if any of the mothers have negative 2827 fermionflow""" 2828 2829 return self.get('conjugate_indices') != ()
2830
2831 - def get_epsilon_order(self):
2832 """Based on the type of the amplitude, determines to which epsilon 2833 order it contributes""" 2834 2835 if '1eps' in self['type']: 2836 return 1 2837 elif '2eps' in self['type']: 2838 return 2 2839 else: 2840 return 0
2841
2842 - def get_call_key(self):
2843 """Generate the (spin, state) tuples used as key for the helas call 2844 dictionaries in HelasModel""" 2845 2846 res = [] 2847 for mother in self.get('mothers'): 2848 res.append(mother.get_spin_state_number()) 2849 2850 # Sort according to spin and flow direction 2851 res.sort() 2852 2853 # The call is different depending on the type of vertex. 2854 # For example, base would give AMP(%d), R2 would give AMPL(0,%d) 2855 # and a single pole UV counter-term would give AMPL(1,%d). 2856 # Also for loop amplitudes, one must have the tag 'loop' 2857 if self['type']!='base': 2858 res.append(self['type']) 2859 2860 # Check if we need to append a charge conjugation flag 2861 if self.needs_hermitian_conjugate(): 2862 res.append(self.get('conjugate_indices')) 2863 2864 return (tuple(res), tuple(self.get('lorentz')))
2865 2866
2867 - def calculate_fermionfactor(self):
2868 """Calculate the fermion factor for the diagram corresponding 2869 to this amplitude""" 2870 2871 # Pick out fermion mothers 2872 fermions = [wf for wf in self.get('mothers') if wf.is_fermion()] 2873 assert len(fermions) % 2 == 0 2874 2875 2876 # Pick out bosons 2877 bosons = filter(lambda wf: wf.is_boson(), self.get('mothers')) 2878 2879 fermion_number_list = [] 2880 2881 # If there are fermion line pairs, append them as 2882 # [NI,NO,n1,n2,...] 2883 fermion_numbers = [f.get_fermion_order() for f in fermions] 2884 2885 # Apply the right sign correction for anti-commutating ghost loops 2886 if self.get('type')=='loop': 2887 # Fetch the second l-cut wavefunctions 2888 lcuf_wf_2=[m for m in self.get('mothers') if m['is_loop'] and \ 2889 len(m.get('mothers'))==0][0] 2890 ghost_factor = -1 if lcuf_wf_2.is_anticommutating_ghost() else 1 2891 else: 2892 # no ghost at tree level 2893 ghost_factor = 1 2894 2895 fermion_loop_factor = 1 2896 2897 # Now put together the fermion line merging in this amplitude 2898 if self.get('type')=='loop' and len(fermion_numbers)>0: 2899 #misc.sprint(self.nice_string()) 2900 2901 # Remember that the amplitude closing the loop is always a 2-point 2902 # "fake interaction" attached on the second l-cut wavefunction. 2903 # So len(fermion_numbers) is either be 0 or 2. 2904 lcut_wf2_number = lcuf_wf_2.get('number_external') 2905 assert len(fermion_numbers)==2, "Incorrect number of fermions"+\ 2906 " (%d) for the amp. closing the loop."%len(fermion_numbers) 2907 # Fetch the first l-cut wavefunctions 2908 lcuf_wf_1=[m for m in self.get('mothers') if m['is_loop'] and \ 2909 len(m.get('mothers'))>0][0] 2910 while len(lcuf_wf_1.get('mothers'))>0: 2911 lcuf_wf_1 = lcuf_wf_1.get_loop_mother() 2912 lcut_wf1_number = lcuf_wf_1.get('number_external') 2913 2914 2915 # We must now close the loop fermion flow, if there is any. 2916 # This means merging the two lists representing the fermion flow of 2917 # each of the two l-cut fermions into one. Example for the process 2918 # g g > go go [virt=QCD] in the MSSM. 2919 # Loop diagram 21 has the fermion_number_list 2920 # [[3, [5, 4]], [6, []]] 2921 # and 22 has 2922 # [[6, []], [4, [3, 5]]] 2923 # Which should be merged into [3,4] both times 2924 2925 2926 # Here, iferm_to_replace is the position of the fermion line 2927 # pairing which is *not* [6,[]] in the above example. 2928 iferm_to_replace = (fermion_numbers.index([lcut_wf2_number,[]])+1)%2 2929 2930 2931 closed_loop = fermion_numbers[iferm_to_replace][0]==lcut_wf1_number 2932 2933 #if self.get('mothers')[0].is_fermion() and self.has_multifermion(): 2934 # closed_loop = False 2935 2936 if closed_loop: 2937 # We have a closed loop fermion flow here, so we must simply 2938 # add a minus sign (irrespectively of whether the closed loop 2939 # fermion flow goes clockwise or counter-clockwise) and not 2940 # consider the fermion loop line in the fermion connection list. 2941 fermion_number_list.extend(fermion_numbers[iferm_to_replace][1]) 2942 fermion_loop_factor = -1 2943 else: 2944 # The fermion flow escape the loop in this case. 2945 fermion_number_list = \ 2946 copy.copy(fermion_numbers[iferm_to_replace][1]) 2947 # We must find to which external fermion the lcut_wf1 is 2948 # connected (i.e. 5 being connected to 3(resp. 4) in the example 2949 # of diagram 22 (resp. 21) above) 2950 i_connected_fermion = fermion_number_list.index(lcut_wf1_number) 2951 fermion_number_list[i_connected_fermion] = \ 2952 fermion_numbers[iferm_to_replace][0] 2953 else: 2954 for iferm in range(0, len(fermion_numbers), 2): 2955 fermion_number_list.append(fermion_numbers[iferm][0]) 2956 fermion_number_list.append(fermion_numbers[iferm+1][0]) 2957 fermion_number_list.extend(fermion_numbers[iferm][1]) 2958 fermion_number_list.extend(fermion_numbers[iferm+1][1]) 2959 2960 2961 # Bosons are treated in the same way for a bosonic loop than for tree 2962 # level kind of amplitudes. 2963 for boson in bosons: 2964 # Bosons return a list [n1,n2,...] 2965 fermion_number_list.extend(boson.get_fermion_order()) 2966 2967 # if not hasattr(HelasAmplitude,"counter"): 2968 # HelasAmplitude.counter=1 2969 # print "MMMMME" 2970 # save1 = copy.deepcopy(fermion_number_list) 2971 # save2 = copy.deepcopy(fermion_number_list2) 2972 # save3 = copy.deepcopy(fermion_number_list) 2973 # save4 = copy.deepcopy(fermion_number_list2) 2974 # if HelasAmplitude.counter<500000 and self.get('type')=='loop' and \ 2975 # HelasAmplitude.sign_flips_to_order(save1)*HelasAmplitude.sign_flips_to_order(save2)==-1: 2976 # print "Before %i=%s"%(HelasAmplitude.counter,str(fermion_numbers_save)) 2977 # print "FOOOOR %i=%s"%(HelasAmplitude.counter,str(fermion_number_list)) 2978 # print "NEW %i=%s"%(HelasAmplitude.counter,str(fermion_number_list2)) 2979 # print "Relative sign =%d"%(HelasAmplitude.sign_flips_to_order(save3)*HelasAmplitude.sign_flips_to_order(save4)) 2980 # HelasAmplitude.counter=self.counter+1 2981 2982 #fermion_number_list = fermion_number_list2 2983 2984 fermion_factor = HelasAmplitude.sign_flips_to_order(fermion_number_list) 2985 2986 self['fermionfactor'] = fermion_factor*ghost_factor*fermion_loop_factor
2987 # print "foooor %i ="%HelasAmplitude.counter, fermion_factor, self.get('type') 2988 2989 @staticmethod
2990 - def sign_flips_to_order(fermions):
2991 """Gives the sign corresponding to the number of flips needed 2992 to place the fermion numbers in order""" 2993 2994 # Perform bubble sort on the fermions, and keep track of 2995 # the number of flips that are needed 2996 2997 nflips = 0 2998 2999 for i in range(len(fermions) - 1): 3000 for j in range(i + 1, len(fermions)): 3001 if fermions[j] < fermions[i]: 3002 fermions[i], fermions[j] = fermions[j], fermions[i] 3003 nflips = nflips + 1 3004 3005 return (-1) ** nflips
3006
3007 - def get_aloha_info(self, optimized_output=True):
3008 """Returns the tuple (lorentz_name, tag, outgoing_number) providing 3009 the necessary information to compute_subset of create_aloha to write 3010 out the HELAS-like routines.""" 3011 3012 # In principle this function should not be called for the case below, 3013 # or if it does it should handle specifically the None returned value. 3014 if self.get('interaction_id') in [0,-1]: 3015 return None 3016 3017 tags = ['C%s' % w for w in self.get_conjugate_index()] 3018 3019 return (tuple(self.get('lorentz')),tuple(tags),self.find_outgoing_number())
3020 3021
3022 - def get_base_diagram(self, wf_dict, vx_list = [], optimization = 1):
3023 """Return the base_objects.Diagram which corresponds to this 3024 amplitude, using a recursive method for the wavefunctions.""" 3025 3026 vertices = base_objects.VertexList() 3027 3028 # Add vertices for all mothers 3029 for mother in self.get('mothers'): 3030 vertices.extend(mother.get_base_vertices(wf_dict, vx_list, 3031 optimization)) 3032 # Generate last vertex 3033 vertex = self.get_base_vertex(wf_dict, vx_list, optimization) 3034 3035 vertices.append(vertex) 3036 3037 return base_objects.Diagram({'vertices': vertices})
3038
3039 - def get_base_vertex(self, wf_dict, vx_list = [], optimization = 1):
3040 """Get a base_objects.Vertex corresponding to this amplitude.""" 3041 3042 # Generate last vertex 3043 legs = base_objects.LegList() 3044 for mother in self.get('mothers'): 3045 try: 3046 if mother.get('is_loop'): 3047 # Loop wavefunction should always be redefined 3048 raise KeyError 3049 leg = wf_dict[(mother.get('number'),False)] 3050 except KeyError: 3051 leg = base_objects.Leg({ 3052 'id': mother.get_pdg_code(), 3053 'number': mother.get('number_external'), 3054 'state': mother.get('leg_state'), 3055 'onshell': None, 3056 'loop_line':mother.get('is_loop') 3057 }) 3058 if optimization != 0 and not mother.get('is_loop'): 3059 wf_dict[(mother.get('number'),False)] = leg 3060 legs.append(leg) 3061 3062 return base_objects.Vertex({ 3063 'id': self.get('interaction_id'), 3064 'legs': legs})
3065
3066 - def get_s_and_t_channels(self, ninitial, model, new_pdg, reverse_t_ch = False):
3067 """Returns two lists of vertices corresponding to the s- and 3068 t-channels of this amplitude/diagram, ordered from the outermost 3069 s-channel and in/down towards the highest number initial state 3070 leg.""" 3071 3072 # Create a CanonicalConfigTag to ensure that the order of 3073 # propagators is canonical 3074 wf_dict = {} 3075 max_final_leg = 2 3076 if reverse_t_ch: 3077 max_final_leg = 1 3078 # Note that here we do not specify a FDStructure repository, so that 3079 # each loop diagram will recreate them. This is ok at this point because 3080 # we do not need to have a canonical ID for the FD structures. 3081 tag = CanonicalConfigTag(self.get_base_diagram(wf_dict). 3082 get_contracted_loop_diagram(model), model) 3083 3084 return tag.get_s_and_t_channels(ninitial, model, new_pdg, max_final_leg)
3085 3086
3087 - def get_color_indices(self):
3088 """Get the color indices corresponding to 3089 this amplitude and its mothers, using a recursive function.""" 3090 3091 if not self.get('mothers'): 3092 return [] 3093 3094 color_indices = [] 3095 3096 # Add color indices for all mothers 3097 for mother in self.get('mothers'): 3098 # This is where recursion happens 3099 color_indices.extend(mother.get_color_indices()) 3100 3101 # Add this amp's color index 3102 if self.get('interaction_id') not in [0,-1]: 3103 color_indices.append(self.get('color_key')) 3104 3105 return color_indices
3106
3107 - def find_outgoing_number(self):
3108 """Return 0. Needed to treat HelasAmplitudes and 3109 HelasWavefunctions on same footing.""" 3110 3111 return 0
3112
3113 - def get_conjugate_index(self):
3114 """Return the index of the particle that should be conjugated.""" 3115 3116 if not any([(wf.get('fermionflow') < 0 or wf.is_majorana()) for wf in \ 3117 self.get('mothers')]): 3118 return () 3119 3120 # Pick out first sorted mothers, then fermions 3121 mothers, self_index = \ 3122 self.get('mothers').sort_by_pdg_codes(self.get('pdg_codes')) 3123 fermions = HelasWavefunctionList([wf for wf in mothers if wf.is_fermion()]) 3124 3125 # Initialize indices with indices due to Majoranas with wrong order 3126 indices = fermions.majorana_conjugates() 3127 3128 # Check for fermions with negative fermion flow 3129 for i in range(0,len(fermions), 2): 3130 if fermions[i].get('fermionflow') < 0 or \ 3131 fermions[i+1].get('fermionflow') < 0: 3132 indices.append(i/2 + 1) 3133 3134 return tuple(sorted(indices))
3135
3136 - def get_vertex_leg_numbers(self, 3137 veto_inter_id=base_objects.Vertex.ID_to_veto_for_multichanneling, 3138 max_n_loop=0):
3139 """Get a list of the number of legs in vertices in this diagram, 3140 This function is only used for establishing the multi-channeling, so that 3141 we exclude from it all the fake vertices and the vertices resulting from 3142 shrunk loops (id=-2)""" 3143 3144 if max_n_loop == 0: 3145 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling 3146 3147 vertex_leg_numbers = [len(self.get('mothers'))] if \ 3148 (self['interaction_id'] not in veto_inter_id) or \ 3149 (self['interaction_id']==-2 and len(self.get('mothers'))>max_n_loop) \ 3150 else [] 3151 for mother in self.get('mothers'): 3152 vertex_leg_numbers.extend(mother.get_vertex_leg_numbers( 3153 veto_inter_id = veto_inter_id)) 3154 3155 return vertex_leg_numbers
3156
3157 - def get_helas_call_dict(self,index=1,OptimizedOutput=False, 3158 specifyHel=True,**opt):
3159 """ return a dictionary to be used for formatting 3160 HELAS call.""" 3161 3162 if index == 1: 3163 flip = 0 3164 else: 3165 flip = 1 3166 3167 output = {} 3168 for i, mother in enumerate(self.get('mothers')): 3169 nb = mother.get('me_id') - flip 3170 output[str(i)] = nb 3171 if mother.get('is_loop'): 3172 output['WF%d' % i ] = 'L(1,%d)'%nb 3173 else: 3174 if specifyHel: 3175 output['WF%d' % i ] = '(1,WE(%d),H)'%nb 3176 else: 3177 output['WF%d' % i ] = '(1,WE(%d))'%nb 3178 3179 #fixed argument 3180 for i, coup in enumerate(self.get('coupling')): 3181 output['coup%d'%i] = str(coup) 3182 3183 output['out'] = self.get('number') - flip 3184 output['propa'] = '' 3185 output.update(opt) 3186 return output
3187 3188 3189
3190 - def set_coupling_color_factor(self):
3191 """Check if there is a mismatch between order of fermions 3192 w.r.t. color""" 3193 mothers = self.get('mothers') 3194 3195 # Sort mothers according to pdg codes if fermions with indentical 3196 # color but not identical pdg code. Needed for antisymmetric 3197 # color eps^{ijk}. 3198 for imo in range(len(mothers)-1): 3199 if mothers[imo].get('color') != 1 and \ 3200 mothers[imo].is_fermion() and \ 3201 mothers[imo].get('color') == mothers[imo+1].get('color') and \ 3202 mothers[imo].get('spin') == mothers[imo+1].get('spin') and \ 3203 mothers[imo].get('pdg_code') != mothers[imo+1].get('pdg_code'): 3204 mothers, my_index = \ 3205 mothers.sort_by_pdg_codes(self.get('pdg_codes')) 3206 break 3207 3208 if mothers != self.get('mothers') and \ 3209 not self.get('coupling').startswith('-'): 3210 # We have mismatch between fermion order for color and lorentz 3211 self.set('coupling', '-'+self.get('coupling'))
3212 3213 # Comparison between different amplitudes, to allow check for 3214 # identical processes. Note that we are then not interested in 3215 # interaction id, but in all other properties. 3216
3217 - def __eq__(self, other):
3218 """Comparison between different amplitudes, to allow check for 3219 identical processes. 3220 """ 3221 3222 if not isinstance(other, HelasAmplitude): 3223 return False 3224 3225 # Check relevant directly defined properties 3226 if self['lorentz'] != other['lorentz'] or \ 3227 self['coupling'] != other['coupling'] or \ 3228 self['number'] != other['number']: 3229 return False 3230 3231 # Check that mothers have the same numbers (only relevant info) 3232 return sorted([mother['number'] for mother in self['mothers']]) == \ 3233 sorted([mother['number'] for mother in other['mothers']])
3234
3235 - def __ne__(self, other):
3236 """Overloading the nonequality operator, to make comparison easy""" 3237 return not self.__eq__(other)
3238
3239 #=============================================================================== 3240 # HelasAmplitudeList 3241 #=============================================================================== 3242 -class HelasAmplitudeList(base_objects.PhysicsObjectList):
3243 """List of HelasAmplitude objects 3244 """ 3245
3246 - def is_valid_element(self, obj):
3247 """Test if object obj is a valid HelasAmplitude for the list.""" 3248 3249 return isinstance(obj, HelasAmplitude)
3250
3251 3252 #=============================================================================== 3253 # HelasDiagram 3254 #=============================================================================== 3255 -class HelasDiagram(base_objects.PhysicsObject):
3256 """HelasDiagram: list of HelasWavefunctions and a HelasAmplitude, 3257 plus the fermion factor associated with the corresponding diagram. 3258 """ 3259
3260 - def default_setup(self):
3261 """Default values for all properties""" 3262 3263 self['wavefunctions'] = HelasWavefunctionList() 3264 # In the optimized output the loop wavefunctions can be recycled as 3265 # well. If so, those are put in the list below, and are not mixed with 3266 # the tree wavefunctions above in order to keep the original structure. 3267 self['loop_wavefunctions'] = HelasWavefunctionList() 3268 # One diagram can have several amplitudes, if there are 3269 # different Lorentz or color structures associated with this 3270 # diagram 3271 self['amplitudes'] = HelasAmplitudeList() 3272 self['number'] = 0
3273
3274 - def filter(self, name, value):
3275 """Filter for valid diagram property values.""" 3276 3277 if name == 'wavefunctions' or name == 'loop_wavefunctions': 3278 if not isinstance(value, HelasWavefunctionList): 3279 raise self.PhysicsObjectError, \ 3280 "%s is not a valid HelasWavefunctionList object" % \ 3281 str(value) 3282 3283 if name == 'amplitudes': 3284 if not isinstance(value, HelasAmplitudeList): 3285 raise self.PhysicsObjectError, \ 3286 "%s is not a valid HelasAmplitudeList object" % \ 3287 str(value) 3288 3289 return True
3290
3291 - def get_sorted_keys(self):
3292 """Return particle property names as a nicely sorted list.""" 3293 3294 return ['wavefunctions', 'loop_wavefunctions', 'amplitudes']
3295
3296 - def calculate_orders(self):
3297 """Calculate the actual coupling orders of this diagram""" 3298 3299 wavefunctions = HelasWavefunctionList.extract_wavefunctions(\ 3300 self.get('amplitudes')[0].get('mothers')) 3301 3302 coupling_orders = {} 3303 for wf in wavefunctions + [self.get('amplitudes')[0]]: 3304 if not wf.get('orders'): continue 3305 for order in wf.get('orders').keys(): 3306 try: 3307 coupling_orders[order] += wf.get('orders')[order] 3308 except Exception: 3309 coupling_orders[order] = wf.get('orders')[order] 3310 3311 return coupling_orders
3312
3313 - def get_vertex_leg_numbers(self, 3314 veto_inter_id=base_objects.Vertex.ID_to_veto_for_multichanneling, 3315 max_n_loop=0):
3316 """Get a list of the number of legs in vertices in this diagram""" 3317 3318 if max_n_loop == 0: 3319 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling 3320 3321 return self.get('amplitudes')[0].get_vertex_leg_numbers( 3322 veto_inter_id=veto_inter_id, max_n_loop=max_n_loop)
3323
3324 - def get_regular_amplitudes(self):
3325 """ For regular HelasDiagrams, it is simply all amplitudes. 3326 It is overloaded in LoopHelasDiagram""" 3327 3328 return self['amplitudes']
3329
3330 #=============================================================================== 3331 # HelasDiagramList 3332 #=============================================================================== 3333 -class HelasDiagramList(base_objects.PhysicsObjectList):
3334 """List of HelasDiagram objects 3335 """ 3336
3337 - def is_valid_element(self, obj):
3338 """Test if object obj is a valid HelasDiagram for the list.""" 3339 3340 return isinstance(obj, HelasDiagram)
3341
3342 #=============================================================================== 3343 # HelasMatrixElement 3344 #=============================================================================== 3345 -class HelasMatrixElement(base_objects.PhysicsObject):
3346 """HelasMatrixElement: list of processes with identical Helas 3347 calls, and the list of HelasDiagrams associated with the processes. 3348 3349 If initiated with an Amplitude, HelasMatrixElement calls 3350 generate_helas_diagrams, which goes through the diagrams of the 3351 Amplitude and generates the corresponding Helas calls, taking into 3352 account possible fermion flow clashes due to Majorana 3353 particles. The optional optimization argument determines whether 3354 optimization is used (optimization = 1, default), for maximum 3355 recycling of wavefunctions, or no optimization (optimization = 0) 3356 when each diagram is written independently of all previous 3357 diagrams (this is useful for running with restricted memory, 3358 e.g. on a GPU). For processes with many diagrams, the total number 3359 or wavefunctions after optimization is ~15% of the number of 3360 amplitudes (diagrams). 3361 3362 By default, it will also generate the color information (color 3363 basis and color matrix) corresponding to the Amplitude. 3364 """ 3365
3366 - def default_setup(self):
3367 """Default values for all properties""" 3368 3369 self['processes'] = base_objects.ProcessList() 3370 self['diagrams'] = HelasDiagramList() 3371 self['identical_particle_factor'] = 0 3372 self['color_basis'] = color_amp.ColorBasis() 3373 self['color_matrix'] = color_amp.ColorMatrix(color_amp.ColorBasis()) 3374 # base_amplitude is the Amplitude to be used in color 3375 # generation, drawing etc. For decay chain processes, this is 3376 # the Amplitude which corresponds to the combined process. 3377 self['base_amplitude'] = None 3378 # has_mirror_process is True if the same process but with the 3379 # two incoming particles interchanged has been generated 3380 self['has_mirror_process'] = False
3381
3382 - def filter(self, name, value):
3383 """Filter for valid diagram property values.""" 3384 3385 if name == 'processes': 3386 if not isinstance(value, base_objects.ProcessList): 3387 raise self.PhysicsObjectError, \ 3388 "%s is not a valid ProcessList object" % str(value) 3389 if name == 'diagrams': 3390 if not isinstance(value, HelasDiagramList): 3391 raise self.PhysicsObjectError, \ 3392 "%s is not a valid HelasDiagramList object" % str(value) 3393 if name == 'identical_particle_factor': 3394 if not isinstance(value, int): 3395 raise self.PhysicsObjectError, \ 3396 "%s is not a valid int object" % str(value) 3397 if name == 'color_basis': 3398 if not isinstance(value, color_amp.ColorBasis): 3399 raise self.PhysicsObjectError, \ 3400 "%s is not a valid ColorBasis object" % str(value) 3401 if name == 'color_matrix': 3402 if not isinstance(value, color_amp.ColorMatrix): 3403 raise self.PhysicsObjectError, \ 3404 "%s is not a valid ColorMatrix object" % str(value) 3405 if name == 'base_amplitude': 3406 if value != None and not \ 3407 isinstance(value, diagram_generation.Amplitude): 3408 raise self.PhysicsObjectError, \ 3409 "%s is not a valid Amplitude object" % str(value) 3410 if name == 'has_mirror_process': 3411 if not isinstance(value, bool): 3412 raise self.PhysicsObjectError, \ 3413 "%s is not a valid boolean" % str(value) 3414 return True
3415
3416 - def get_sorted_keys(self):
3417 """Return particle property names as a nicely sorted list.""" 3418 3419 return ['processes', 'identical_particle_factor', 3420 'diagrams', 'color_basis', 'color_matrix', 3421 'base_amplitude', 'has_mirror_process']
3422 3423 # Enhanced get function
3424 - def get(self, name):
3425 """Get the value of the property name.""" 3426 3427 if name == 'base_amplitude' and not self[name]: 3428 self['base_amplitude'] = self.get_base_amplitude() 3429 3430 return super(HelasMatrixElement, self).get(name)
3431 3432 # Customized constructor
3433 - def __init__(self, amplitude=None, optimization=1, 3434 decay_ids=[], gen_color=True):
3435 """Constructor for the HelasMatrixElement. In particular allows 3436 generating a HelasMatrixElement from an Amplitude, with 3437 automatic generation of the necessary wavefunctions 3438 """ 3439 3440 if amplitude != None: 3441 if isinstance(amplitude, diagram_generation.Amplitude): 3442 super(HelasMatrixElement, self).__init__() 3443 self.get('processes').append(amplitude.get('process')) 3444 self.set('has_mirror_process', 3445 amplitude.get('has_mirror_process')) 3446 self.generate_helas_diagrams(amplitude, optimization, decay_ids) 3447 self.calculate_fermionfactors() 3448 self.calculate_identical_particle_factor() 3449 if gen_color and not self.get('color_basis'): 3450 self.process_color() 3451 else: 3452 # In this case, try to use amplitude as a dictionary 3453 super(HelasMatrixElement, self).__init__(amplitude) 3454 else: 3455 super(HelasMatrixElement, self).__init__()
3456 3457 # Comparison between different amplitudes, to allow check for 3458 # identical processes. Note that we are then not interested in 3459 # interaction id, but in all other properties.
3460 - def __eq__(self, other):
3461 """Comparison between different matrix elements, to allow check for 3462 identical processes. 3463 """ 3464 3465 if not isinstance(other, HelasMatrixElement): 3466 return False 3467 3468 # If no processes, this is an empty matrix element 3469 if not self['processes'] and not other['processes']: 3470 return True 3471 3472 # Should only check if diagrams and process id are identical 3473 # Except in case of decay processes: then also initial state 3474 # must be the same 3475 if self['processes'] and not other['processes'] or \ 3476 self['has_mirror_process'] != other['has_mirror_process'] or \ 3477 self['processes'] and \ 3478 self['processes'][0]['id'] != other['processes'][0]['id'] or \ 3479 self['processes'][0]['is_decay_chain'] or \ 3480 other['processes'][0]['is_decay_chain'] or \ 3481 self['identical_particle_factor'] != \ 3482 other['identical_particle_factor'] or \ 3483 self['diagrams'] != other['diagrams']: 3484 return False 3485 return True
3486
3487 - def __ne__(self, other):
3488 """Overloading the nonequality operator, to make comparison easy""" 3489 return not self.__eq__(other)
3490
3491 - def process_color(self):
3492 """ Perform the simple color processing from a single matrix element 3493 (without optimization then). This is called from the initialization 3494 and pulled out here in order to have the correct treatment in daughter 3495 classes.""" 3496 logger.debug('Computing the color basis') 3497 self.get('color_basis').build(self.get('base_amplitude')) 3498 self.set('color_matrix', 3499 color_amp.ColorMatrix(self.get('color_basis')))
3500
3501 - def generate_helas_diagrams(self, amplitude, optimization=1,decay_ids=[]):
3502 """Starting from a list of Diagrams from the diagram 3503 generation, generate the corresponding HelasDiagrams, i.e., 3504 the wave functions and amplitudes. Choose between default 3505 optimization (= 1, maximum recycling of wavefunctions) or no 3506 optimization (= 0, no recycling of wavefunctions, useful for 3507 GPU calculations with very restricted memory). 3508 3509 Note that we need special treatment for decay chains, since 3510 the end product then is a wavefunction, not an amplitude. 3511 """ 3512 3513 assert isinstance(amplitude, diagram_generation.Amplitude), \ 3514 "Missing or erraneous arguments for generate_helas_diagrams" 3515 assert isinstance(optimization, int), \ 3516 "Missing or erraneous arguments for generate_helas_diagrams" 3517 self.optimization = optimization 3518 3519 diagram_list = amplitude.get('diagrams') 3520 process = amplitude.get('process') 3521 3522 model = process.get('model') 3523 if not diagram_list: 3524 return 3525 3526 # All the previously defined wavefunctions 3527 wavefunctions = [] 3528 # List of minimal information for comparison with previous 3529 # wavefunctions 3530 wf_mother_arrays = [] 3531 # Keep track of wavefunction number 3532 wf_number = 0 3533 3534 # Generate wavefunctions for the external particles 3535 external_wavefunctions = dict([(leg.get('number'), 3536 HelasWavefunction(leg, 0, model, 3537 decay_ids)) \ 3538 for leg in process.get('legs')]) 3539 3540 # Initially, have one wavefunction for each external leg. 3541 wf_number = len(process.get('legs')) 3542 3543 # For initial state bosons, need to flip part-antipart 3544 # since all bosons should be treated as outgoing 3545 for key in external_wavefunctions.keys(): 3546 wf = external_wavefunctions[key] 3547 if wf.is_boson() and wf.get('state') == 'initial' and \ 3548 not wf.get('self_antipart'): 3549 wf.set('is_part', not wf.get('is_part')) 3550 3551 # For initial state particles, need to flip PDG code (if has 3552 # antipart) 3553 for key in external_wavefunctions.keys(): 3554 wf = external_wavefunctions[key] 3555 if wf.get('leg_state') == False and \ 3556 not wf.get('self_antipart'): 3557 wf.flip_part_antipart() 3558 3559 # Now go through the diagrams, looking for undefined wavefunctions 3560 3561 helas_diagrams = HelasDiagramList() 3562 3563 # Keep track of amplitude number and diagram number 3564 amplitude_number = 0 3565 diagram_number = 0 3566 3567 for diagram in diagram_list: 3568 3569 # List of dictionaries from leg number to wave function, 3570 # keeps track of the present position in the tree. 3571 # Need one dictionary per coupling multiplicity (diagram) 3572 number_to_wavefunctions = [{}] 3573 3574 # Need to keep track of the color structures for each amplitude 3575 color_lists = [[]] 3576 3577 # Initialize wavefunctions for this diagram 3578 diagram_wavefunctions = HelasWavefunctionList() 3579 3580 vertices = copy.copy(diagram.get('vertices')) 3581 3582 # Single out last vertex, since this will give amplitude 3583 lastvx = vertices.pop() 3584 3585 # Go through all vertices except the last and create 3586 # wavefunctions 3587 for vertex in vertices: 3588 3589 # In case there are diagrams with multiple Lorentz/color 3590 # structures, we need to keep track of the wavefunctions 3591 # for each such structure separately, and generate 3592 # one HelasDiagram for each structure. 3593 # We use the array number_to_wavefunctions to keep 3594 # track of this, with one dictionary per chain of 3595 # wavefunctions 3596 # Note that all wavefunctions relating to this diagram 3597 # will be written out before the first amplitude is written. 3598 new_number_to_wavefunctions = [] 3599 new_color_lists = [] 3600 for number_wf_dict, color_list in zip(number_to_wavefunctions, 3601 color_lists): 3602 legs = copy.copy(vertex.get('legs')) 3603 last_leg = legs.pop() 3604 # Generate list of mothers from legs 3605 mothers = self.getmothers(legs, number_wf_dict, 3606 external_wavefunctions, 3607 wavefunctions, 3608 diagram_wavefunctions) 3609 inter = model.get('interaction_dict')[vertex.get('id')] 3610 3611 # Now generate new wavefunction for the last leg 3612 3613 # Need one amplitude for each color structure, 3614 done_color = {} # store link to color 3615 for coupl_key in sorted(inter.get('couplings').keys()): 3616 color = coupl_key[0] 3617 if color in done_color: 3618 wf = done_color[color] 3619 wf.get('coupling').append(inter.get('couplings')[coupl_key]) 3620 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 3621 continue 3622 wf = HelasWavefunction(last_leg, vertex.get('id'), model) 3623 wf.set('coupling', [inter.get('couplings')[coupl_key]]) 3624 if inter.get('color'): 3625 wf.set('inter_color', inter.get('color')[coupl_key[0]]) 3626 done_color[color] = wf 3627 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 3628 wf.set('color_key', color) 3629 wf.set('mothers', mothers) 3630 # Need to set incoming/outgoing and 3631 # particle/antiparticle according to the fermion flow 3632 # of mothers 3633 wf.set_state_and_particle(model) 3634 # Need to check for clashing fermion flow due to 3635 # Majorana fermions, and modify if necessary 3636 # Also need to keep track of the wavefunction number. 3637 wf, wf_number = wf.check_and_fix_fermion_flow(\ 3638 wavefunctions, 3639 diagram_wavefunctions, 3640 external_wavefunctions, 3641 wf_number) 3642 # Create new copy of number_wf_dict 3643 new_number_wf_dict = copy.copy(number_wf_dict) 3644 3645 # Store wavefunction 3646 try: 3647 wf = diagram_wavefunctions[\ 3648 diagram_wavefunctions.index(wf)] 3649 except ValueError, error: 3650 # Update wf number 3651 wf_number = wf_number + 1 3652 wf.set('number', wf_number) 3653 try: 3654 # Use wf_mother_arrays to locate existing 3655 # wavefunction 3656 wf = wavefunctions[wf_mother_arrays.index(\ 3657 wf.to_array())] 3658 # Since we reuse the old wavefunction, reset 3659 # wf_number 3660 wf_number = wf_number - 1 3661 except ValueError: 3662 diagram_wavefunctions.append(wf) 3663 3664 new_number_wf_dict[last_leg.get('number')] = wf 3665 3666 # Store the new copy of number_wf_dict 3667 new_number_to_wavefunctions.append(\ 3668 new_number_wf_dict) 3669 # Add color index and store new copy of color_lists 3670 new_color_list = copy.copy(color_list) 3671 new_color_list.append(coupl_key[0]) 3672 new_color_lists.append(new_color_list) 3673 3674 number_to_wavefunctions = new_number_to_wavefunctions 3675 color_lists = new_color_lists 3676 3677 # Generate all amplitudes corresponding to the different 3678 # copies of this diagram 3679 helas_diagram = HelasDiagram() 3680 diagram_number = diagram_number + 1 3681 helas_diagram.set('number', diagram_number) 3682 for number_wf_dict, color_list in zip(number_to_wavefunctions, 3683 color_lists): 3684 # Now generate HelasAmplitudes from the last vertex. 3685 if lastvx.get('id'): 3686 inter = model.get_interaction(lastvx.get('id')) 3687 keys = sorted(inter.get('couplings').keys()) 3688 pdg_codes = [p.get_pdg_code() for p in \ 3689 inter.get('particles')] 3690 else: 3691 # Special case for decay chain - amplitude is just a 3692 # placeholder for replaced wavefunction 3693 inter = None 3694 keys = [(0, 0)] 3695 pdg_codes = None 3696 3697 # Find mothers for the amplitude 3698 legs = lastvx.get('legs') 3699 mothers = self.getmothers(legs, number_wf_dict, 3700 external_wavefunctions, 3701 wavefunctions, 3702 diagram_wavefunctions).\ 3703 sort_by_pdg_codes(pdg_codes, 0)[0] 3704 # Need to check for clashing fermion flow due to 3705 # Majorana fermions, and modify if necessary 3706 wf_number = mothers.check_and_fix_fermion_flow(wavefunctions, 3707 diagram_wavefunctions, 3708 external_wavefunctions, 3709 None, 3710 wf_number, 3711 False, 3712 number_to_wavefunctions) 3713 done_color = {} 3714 for i, coupl_key in enumerate(keys): 3715 color = coupl_key[0] 3716 if inter and color in done_color.keys(): 3717 amp = done_color[color] 3718 amp.get('coupling').append(inter.get('couplings')[coupl_key]) 3719 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 3720 continue 3721 amp = HelasAmplitude(lastvx, model) 3722 if inter: 3723 amp.set('coupling', [inter.get('couplings')[coupl_key]]) 3724 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 3725 if inter.get('color'): 3726 amp.set('inter_color', inter.get('color')[color]) 3727 amp.set('color_key', color) 3728 done_color[color] = amp 3729 amp.set('mothers', mothers) 3730 amplitude_number = amplitude_number + 1 3731 amp.set('number', amplitude_number) 3732 # Add the list with color indices to the amplitude 3733 new_color_list = copy.copy(color_list) 3734 if inter: 3735 new_color_list.append(color) 3736 3737 amp.set('color_indices', new_color_list) 3738 3739 # Add amplitude to amplitdes in helas_diagram 3740 helas_diagram.get('amplitudes').append(amp) 3741 3742 # After generation of all wavefunctions and amplitudes, 3743 # first sort the wavefunctions according to number 3744 diagram_wavefunctions.sort(lambda wf1, wf2: \ 3745 wf1.get('number') - wf2.get('number')) 3746 3747 # Then make sure that all mothers come before daughters 3748 iwf = len(diagram_wavefunctions) - 1 3749 while iwf > 0: 3750 this_wf = diagram_wavefunctions[iwf] 3751 moved = False 3752 for i,wf in enumerate(diagram_wavefunctions[:iwf]): 3753 if this_wf in wf.get('mothers'): 3754 diagram_wavefunctions.pop(iwf) 3755 diagram_wavefunctions.insert(i, this_wf) 3756 this_wf.set('number', wf.get('number')) 3757 for w in diagram_wavefunctions[i+1:]: 3758 w.set('number',w.get('number')+1) 3759 moved = True 3760 break 3761 if not moved: iwf -= 1 3762 3763 # Finally, add wavefunctions to diagram 3764 helas_diagram.set('wavefunctions', diagram_wavefunctions) 3765 3766 if optimization: 3767 wavefunctions.extend(diagram_wavefunctions) 3768 wf_mother_arrays.extend([wf.to_array() for wf \ 3769 in diagram_wavefunctions]) 3770 else: 3771 wf_number = len(process.get('legs')) 3772 3773 # Append this diagram in the diagram list 3774 helas_diagrams.append(helas_diagram) 3775 3776 3777 self.set('diagrams', helas_diagrams) 3778 3779 # Sort all mothers according to the order wanted in Helas calls 3780 for wf in self.get_all_wavefunctions(): 3781 wf.set('mothers', HelasMatrixElement.sorted_mothers(wf)) 3782 3783 for amp in self.get_all_amplitudes(): 3784 amp.set('mothers', HelasMatrixElement.sorted_mothers(amp)) 3785 amp.set('color_indices', amp.get_color_indices())
3786 3787
3788 - def reuse_outdated_wavefunctions(self, helas_diagrams):
3789 """change the wavefunctions id used in the writer to minimize the 3790 memory used by the wavefunctions.""" 3791 3792 if not self.optimization: 3793 for diag in helas_diagrams: 3794 for wf in diag['wavefunctions']: 3795 wf.set('me_id',wf.get('number')) 3796 return helas_diagrams 3797 3798 # First compute the first/last appearance of each wavefunctions 3799 # first takes the line number and return the id of the created wf 3800 # last_lign takes the id of the wf and return the line number 3801 last_lign={} 3802 first={} 3803 pos=0 3804 for diag in helas_diagrams: 3805 for wf in diag['wavefunctions']: 3806 pos+=1 3807 for wfin in wf.get('mothers'): 3808 last_lign[wfin.get('number')] = pos 3809 assert wfin.get('number') in first.values() 3810 first[pos] = wf.get('number') 3811 for amp in diag['amplitudes']: 3812 pos+=1 3813 for wfin in amp.get('mothers'): 3814 last_lign[wfin.get('number')] = pos 3815 3816 # last takes the line number and return the last appearing wf at 3817 #that particular line 3818 last=collections.defaultdict(list) 3819 for nb, pos in last_lign.items(): 3820 last[pos].append(nb) 3821 tag = list(set(last.keys()+first.keys())) 3822 tag.sort() #lines number where something happen (new in/out) 3823 3824 # Create the replacement id dictionary 3825 outdated = [] # wf id which ar not use any more at this stage 3826 replace = {} # replacement directory 3827 max_wf = 0 3828 for nb in tag: 3829 if outdated and nb in first: 3830 replace[first[nb]] = outdated.pop() 3831 elif nb in first: 3832 assert first[nb] not in replace, '%s already assigned' % first[nb] 3833 max_wf += 1 3834 replace[first[nb]] = max_wf 3835 if nb in last: 3836 for value in last[nb]: 3837 outdated.append(replace[value]) 3838 3839 3840 #replace the id 3841 for diag in helas_diagrams: 3842 for wf in diag['wavefunctions']: 3843 wf.set('me_id', replace[wf.get('number')]) 3844 3845 return helas_diagrams
3846
3848 """This restore the original memory print and revert 3849 change the wavefunctions id used in the writer to minimize the 3850 memory used by the wavefunctions.""" 3851 3852 helas_diagrams = self.get('diagrams') 3853 3854 for diag in helas_diagrams: 3855 for wf in diag['wavefunctions']: 3856 wf.set('me_id',wf.get('number')) 3857 3858 return helas_diagrams
3859 3860
3861 - def insert_decay_chains(self, decay_dict):
3862 """Iteratively insert decay chains decays into this matrix 3863 element. 3864 * decay_dict: a dictionary from external leg number 3865 to decay matrix element. 3866 """ 3867 3868 # First need to reset all legs_with_decays 3869 for proc in self.get('processes'): 3870 proc.set('legs_with_decays', base_objects.LegList()) 3871 3872 # We need to keep track of how the 3873 # wavefunction numbers change 3874 replace_dict = {} 3875 for number in decay_dict.keys(): 3876 # Find all wavefunctions corresponding to this external 3877 # leg number 3878 replace_dict[number] = [wf for wf in \ 3879 filter(lambda wf: not wf.get('mothers') and \ 3880 wf.get('number_external') == number, 3881 self.get_all_wavefunctions())] 3882 3883 # Keep track of wavefunction and amplitude numbers, to ensure 3884 # unique numbers for all new wfs and amps during manipulations 3885 numbers = [self.get_all_wavefunctions()[-1].get('number'), 3886 self.get_all_amplitudes()[-1].get('number')] 3887 3888 # Check if there are any Majorana particles present in any diagrams 3889 got_majoranas = False 3890 for wf in self.get_all_wavefunctions() + \ 3891 sum([d.get_all_wavefunctions() for d in \ 3892 decay_dict.values()], []): 3893 if wf.get('fermionflow') < 0 or \ 3894 wf.get('self_antipart') and wf.is_fermion(): 3895 got_majoranas = True 3896 3897 # Now insert decays for all legs that have decays 3898 for number in decay_dict.keys(): 3899 3900 self.insert_decay(replace_dict[number], 3901 decay_dict[number], 3902 numbers, 3903 got_majoranas) 3904 3905 # Remove all diagrams that surpass overall coupling orders 3906 overall_orders = self.get('processes')[0].get('overall_orders') 3907 if overall_orders: 3908 ndiag = len(self.get('diagrams')) 3909 idiag = 0 3910 while self.get('diagrams')[idiag:]: 3911 diagram = self.get('diagrams')[idiag] 3912 orders = diagram.calculate_orders() 3913 remove_diagram = False 3914 for order in orders.keys(): 3915 try: 3916 if orders[order] > \ 3917 overall_orders[order]: 3918 remove_diagram = True 3919 except KeyError: 3920 pass 3921 if remove_diagram: 3922 self.get('diagrams').pop(idiag) 3923 else: 3924 idiag += 1 3925 3926 if len(self.get('diagrams')) < ndiag: 3927 # We have removed some diagrams - need to go through 3928 # diagrams, renumber them and set new wavefunctions 3929 wf_numbers = [] 3930 ndiagrams = 0 3931 for diagram in self.get('diagrams'): 3932 ndiagrams += 1 3933 diagram.set('number', ndiagrams) 3934 # Extract all wavefunctions contributing to this amplitude 3935 diagram_wfs = HelasWavefunctionList() 3936 for amplitude in diagram.get('amplitudes'): 3937 wavefunctions = \ 3938 sorted(HelasWavefunctionList.\ 3939 extract_wavefunctions(amplitude.get('mothers')), 3940 lambda wf1, wf2: wf1.get('number') - \ 3941 wf2.get('number')) 3942 for wf in wavefunctions: 3943 # Check if wavefunction already used, otherwise add 3944 if wf.get('number') not in wf_numbers and \ 3945 wf not in diagram_wfs: 3946 diagram_wfs.append(wf) 3947 wf_numbers.append(wf.get('number')) 3948 diagram.set('wavefunctions', diagram_wfs) 3949 3950 # Final cleaning out duplicate wavefunctions - needed only if 3951 # we have multiple fermion flows, i.e., either multiple replaced 3952 # wavefunctions or majorana fermions and multiple diagrams 3953 flows = reduce(lambda i1, i2: i1 * i2, 3954 [len(replace_dict[i]) for i in decay_dict.keys()], 1) 3955 diagrams = reduce(lambda i1, i2: i1 * i2, 3956 [len(decay_dict[i].get('diagrams')) for i in \ 3957 decay_dict.keys()], 1) 3958 3959 if flows > 1 or (diagrams > 1 and got_majoranas): 3960 3961 # Clean out any doublet wavefunctions 3962 3963 earlier_wfs = [] 3964 3965 earlier_wf_arrays = [] 3966 3967 mothers = self.get_all_wavefunctions() + self.get_all_amplitudes() 3968 mother_arrays = [w['mothers'].to_array() \ 3969 for w in mothers] 3970 3971 for diagram in self.get('diagrams'): 3972 3973 if diagram.get('number') > 1: 3974 earlier_wfs.extend(self.get('diagrams')[\ 3975 diagram.get('number') - 2].get('wavefunctions')) 3976 3977 i = 0 3978 diag_wfs = diagram.get('wavefunctions') 3979 3980 3981 # Remove wavefunctions and replace mothers 3982 while diag_wfs[i:]: 3983 try: 3984 new_wf = earlier_wfs[\ 3985 earlier_wfs.index(diag_wfs[i])] 3986 wf = diag_wfs.pop(i) 3987 3988 self.update_later_mothers(wf, new_wf, mothers, 3989 mother_arrays) 3990 except ValueError: 3991 i = i + 1 3992 3993 # When we are done with all decays, set wavefunction and 3994 # amplitude numbers 3995 for i, wf in enumerate(self.get_all_wavefunctions()): 3996 wf.set('number', i + 1) 3997 for i, amp in enumerate(self.get_all_amplitudes()): 3998 amp.set('number', i + 1) 3999 # Update fermion factors for all amplitudes 4000 amp.calculate_fermionfactor() 4001 # Update color indices 4002 amp.set('color_indices', amp.get_color_indices()) 4003 4004 # Calculate identical particle factors for 4005 # this matrix element 4006 self.identical_decay_chain_factor(decay_dict.values())
4007 4008
4009 - def insert_decay(self, old_wfs, decay, numbers, got_majoranas):
4010 """Insert a decay chain matrix element into the matrix element. 4011 * old_wfs: the wavefunctions to be replaced. 4012 They all correspond to the same external particle, but might 4013 have different fermion flow directions 4014 * decay: the matrix element for the decay chain 4015 * numbers: the present wavefunction and amplitude number, 4016 to allow for unique numbering 4017 4018 Note that: 4019 1) All amplitudes and all wavefunctions using the decaying wf 4020 must be copied as many times as there are amplitudes in the 4021 decay matrix element 4022 2) In the presence of Majorana particles, we must make sure 4023 to flip fermion flow for the decay process if needed. 4024 4025 The algorithm is the following: 4026 1) Multiply the diagrams with the number of diagrams Ndiag in 4027 the decay element 4028 2) For each diagram in the decay element, work on the diagrams 4029 which corresponds to it 4030 3) Flip fermion flow for the decay wavefunctions if needed 4031 4) Insert all auxiliary wavefunctions into the diagram (i.e., all 4032 except the final wavefunctions, which directly replace the 4033 original final state wavefunctions) 4034 4) Replace the wavefunctions recursively, so that we always replace 4035 each old wavefunctions with Namp new ones, where Namp is 4036 the number of amplitudes in this decay element 4037 diagram. Do recursion for wavefunctions which have this 4038 wavefunction as mother. Simultaneously replace any 4039 amplitudes which have this wavefunction as mother. 4040 """ 4041 4042 #check that decay does not specify polarization 4043 wfs = filter(lambda w: w.get('state') == 'initial' , decay.get('diagrams')[0].get('wavefunctions')) 4044 if any(wf.get('polarization') for wf in wfs): 4045 raise InvalidCmd, 'In decay-chain polarization can only be specified in production not in decay. Please Retry' 4046 4047 4048 len_decay = len(decay.get('diagrams')) 4049 4050 number_external = old_wfs[0].get('number_external') 4051 4052 # Insert the decay process in the process 4053 for process in self.get('processes'): 4054 process.get('decay_chains').append(\ 4055 decay.get('processes')[0]) 4056 4057 # We need one copy of the decay element diagrams for each 4058 # old_wf to be replaced, since we need different wavefunction 4059 # numbers for them 4060 decay_elements = [copy.deepcopy(d) for d in \ 4061 [ decay.get('diagrams') ] * len(old_wfs)] 4062 4063 # Need to replace Particle in all wavefunctions to avoid 4064 # deepcopy 4065 for decay_element in decay_elements: 4066 for idiag, diagram in enumerate(decay.get('diagrams')): 4067 wfs = diagram.get('wavefunctions') 4068 decay_diag = decay_element[idiag] 4069 for i, wf in enumerate(decay_diag.get('wavefunctions')): 4070 wf.set('particle', wfs[i].get('particle')) 4071 wf.set('antiparticle', wfs[i].get('antiparticle')) 4072 4073 for decay_element in decay_elements: 4074 4075 # Remove the unwanted initial state wavefunctions from decay 4076 for decay_diag in decay_element: 4077 for wf in filter(lambda wf: wf.get('number_external') == 1, 4078 decay_diag.get('wavefunctions')): 4079 decay_diag.get('wavefunctions').remove(wf) 4080 4081 decay_wfs = sum([d.get('wavefunctions') for d in decay_element], []) 4082 4083 # External wavefunction offset for new wfs 4084 incr_new = number_external - \ 4085 decay_wfs[0].get('number_external') 4086 4087 for wf in decay_wfs: 4088 # Set number_external for new wavefunctions 4089 wf.set('number_external', wf.get('number_external') + incr_new) 4090 # Set unique number for new wavefunctions 4091 numbers[0] = numbers[0] + 1 4092 wf.set('number', numbers[0]) 4093 4094 # External wavefunction offset for old wfs, only the first 4095 # time this external wavefunction is replaced 4096 (nex, nin) = decay.get_nexternal_ninitial() 4097 incr_old = nex - 2 # due to the incoming particle 4098 wavefunctions = self.get_all_wavefunctions() 4099 for wf in wavefunctions: 4100 # Only modify number_external for wavefunctions above old_wf 4101 if wf.get('number_external') > number_external: 4102 wf.set('number_external', 4103 wf.get('number_external') + incr_old) 4104 4105 # Multiply the diagrams by Ndiag 4106 4107 diagrams = HelasDiagramList() 4108 for diagram in self.get('diagrams'): 4109 new_diagrams = [copy.copy(diag) for diag in \ 4110 [ diagram ] * (len_decay - 1)] 4111 # Update diagram number 4112 diagram.set('number', (diagram.get('number') - 1) * \ 4113 len_decay + 1) 4114 4115 for i, diag in enumerate(new_diagrams): 4116 # Set diagram number 4117 diag.set('number', diagram.get('number') + i + 1) 4118 # Copy over all wavefunctions 4119 diag.set('wavefunctions', 4120 copy.copy(diagram.get('wavefunctions'))) 4121 # Copy over the amplitudes 4122 amplitudes = HelasAmplitudeList(\ 4123 [copy.copy(amp) for amp in \ 4124 diag.get('amplitudes')]) 4125 # Renumber amplitudes 4126 for amp in amplitudes: 4127 numbers[1] = numbers[1] + 1 4128 amp.set('number', numbers[1]) 4129 diag.set('amplitudes', amplitudes) 4130 # Add old and new diagram to diagrams 4131 diagrams.append(diagram) 4132 diagrams.extend(new_diagrams) 4133 4134 self.set('diagrams', diagrams) 4135 4136 # Now we work by decay process diagram, parameterized by numdecay 4137 for numdecay in range(len_decay): 4138 4139 # Pick out the diagrams which correspond to this decay diagram 4140 diagrams = [self.get('diagrams')[i] for i in \ 4141 range(numdecay, len(self.get('diagrams')), len_decay)] 4142 4143 # Perform replacement for each of the wavefunctions in old_wfs 4144 for decay_element, old_wf in zip(decay_elements, old_wfs): 4145 4146 decay_diag = decay_element[numdecay] 4147 4148 # Find the diagrams which have old_wf 4149 my_diagrams = filter(lambda diag: (old_wf.get('number') in \ 4150 [wf.get('number') for wf in \ 4151 diag.get('wavefunctions')]), 4152 diagrams) 4153 4154 # Ignore possibility for unoptimizated generation for now 4155 if len(my_diagrams) > 1: 4156 raise self.PhysicsObjectError, \ 4157 "Decay chains not yet prepared for GPU" 4158 4159 for diagram in my_diagrams: 4160 4161 if got_majoranas: 4162 # If there are Majorana particles in any of 4163 # the matrix elements, we need to check for 4164 # fermion flow 4165 4166 # Earlier wavefunctions, will be used for fermion flow 4167 index = [d.get('number') for d in diagrams].\ 4168 index(diagram.get('number')) 4169 earlier_wavefunctions = \ 4170 sum([d.get('wavefunctions') for d in \ 4171 diagrams[:index]], []) 4172 4173 # Don't want to affect original decay 4174 # wavefunctions, so need to deepcopy 4175 decay_diag_wfs = copy.deepcopy(\ 4176 decay_diag.get('wavefunctions')) 4177 # Need to replace Particle in all 4178 # wavefunctions to avoid deepcopy 4179 for i, wf in enumerate(decay_diag.get('wavefunctions')): 4180 decay_diag_wfs[i].set('particle', \ 4181 wf.get('particle')) 4182 decay_diag_wfs[i].set('antiparticle', \ 4183 wf.get('antiparticle')) 4184 4185 # Complete decay_diag_wfs with the mother wavefunctions 4186 # to allow for independent fermion flow flips 4187 decay_diag_wfs = decay_diag_wfs.insert_own_mothers() 4188 4189 # These are the wavefunctions which directly replace old_wf 4190 final_decay_wfs = [amp.get('mothers')[1] for amp in \ 4191 decay_diag.get('amplitudes')] 4192 4193 # Since we made deepcopy, need to syncronize 4194 for i, wf in enumerate(final_decay_wfs): 4195 final_decay_wfs[i] = \ 4196 decay_diag_wfs[decay_diag_wfs.index(wf)] 4197 4198 # Remove final wavefunctions from decay_diag_wfs, 4199 # since these will be replaced separately by 4200 # replace_wavefunctions 4201 for wf in final_decay_wfs: 4202 decay_diag_wfs.remove(wf) 4203 4204 # Check fermion flow direction 4205 if old_wf.is_fermion() and \ 4206 old_wf.get_with_flow('state') != \ 4207 final_decay_wfs[0].get_with_flow('state'): 4208 4209 # Not same flow state - need to flip flow of wf 4210 4211 for i, wf in enumerate(final_decay_wfs): 4212 4213 # We use the function 4214 # check_majorana_and_flip_flow, as in the 4215 # helas diagram generation. Since we have 4216 # different flow, there is already a Majorana 4217 # particle along the fermion line. 4218 4219 final_decay_wfs[i], numbers[0] = \ 4220 wf.check_majorana_and_flip_flow(\ 4221 True, 4222 earlier_wavefunctions, 4223 decay_diag_wfs, 4224 {}, 4225 numbers[0]) 4226 4227 # Remove wavefunctions which are already present in 4228 # earlier_wavefunctions 4229 i = 0 4230 earlier_wavefunctions = \ 4231 sum([d.get('wavefunctions') for d in \ 4232 self.get('diagrams')[:diagram.get('number') - 1]], \ 4233 []) 4234 earlier_wf_numbers = [wf.get('number') for wf in \ 4235 earlier_wavefunctions] 4236 4237 i = 0 4238 mother_arrays = [w.get('mothers').to_array() for \ 4239 w in final_decay_wfs] 4240 while decay_diag_wfs[i:]: 4241 wf = decay_diag_wfs[i] 4242 try: 4243 new_wf = earlier_wavefunctions[\ 4244 earlier_wf_numbers.index(wf.get('number'))] 4245 # If the wavefunctions are not identical, 4246 # then we should keep this wavefunction, 4247 # and update its number so it is unique 4248 if wf != new_wf: 4249 numbers[0] = numbers[0] + 1 4250 wf.set('number', numbers[0]) 4251 continue 4252 decay_diag_wfs.pop(i) 4253 pres_mother_arrays = [w.get('mothers').to_array() for \ 4254 w in decay_diag_wfs[i:]] + \ 4255 mother_arrays 4256 self.update_later_mothers(wf, new_wf, 4257 decay_diag_wfs[i:] + \ 4258 final_decay_wfs, 4259 pres_mother_arrays) 4260 except ValueError: 4261 i = i + 1 4262 4263 # Since we made deepcopy, go through mothers and make 4264 # sure we are using the ones in earlier_wavefunctions 4265 for decay_wf in decay_diag_wfs + final_decay_wfs: 4266 mothers = decay_wf.get('mothers') 4267 for i, wf in enumerate(mothers): 4268 try: 4269 mothers[i] = earlier_wavefunctions[\ 4270 earlier_wf_numbers.index(wf.get('number'))] 4271 except ValueError: 4272 pass 4273 else: 4274 # If there are no Majorana particles, the 4275 # treatment is much simpler 4276 decay_diag_wfs = \ 4277 copy.copy(decay_diag.get('wavefunctions')) 4278 4279 # These are the wavefunctions which directly 4280 # replace old_wf 4281 final_decay_wfs = [amp.get('mothers')[1] for amp in \ 4282 decay_diag.get('amplitudes')] 4283 4284 # Remove final wavefunctions from decay_diag_wfs, 4285 # since these will be replaced separately by 4286 # replace_wavefunctions 4287 for wf in final_decay_wfs: 4288 decay_diag_wfs.remove(wf) 4289 4290 4291 diagram_wfs = diagram.get('wavefunctions') 4292 4293 old_wf_index = [wf.get('number') for wf in \ 4294 diagram_wfs].index(old_wf.get('number')) 4295 4296 old_wf_pol = diagram_wfs[old_wf_index].get('polarization') 4297 for w in final_decay_wfs: 4298 w.set('polarization', old_wf_pol) 4299 4300 4301 diagram_wfs = diagram_wfs[0:old_wf_index] + \ 4302 decay_diag_wfs + diagram_wfs[old_wf_index:] 4303 4304 diagram.set('wavefunctions', HelasWavefunctionList(diagram_wfs)) 4305 4306 # Set the decay flag for final_decay_wfs, to 4307 # indicate that these correspond to decayed 4308 # particles 4309 for wf in final_decay_wfs: 4310 wf.set('onshell', True) 4311 4312 if len_decay == 1 and len(final_decay_wfs) == 1: 4313 # Can use simplified treatment, by just modifying old_wf 4314 self.replace_single_wavefunction(old_wf, 4315 final_decay_wfs[0]) 4316 else: 4317 # Multiply wavefunctions and amplitudes and 4318 # insert mothers using a recursive function 4319 self.replace_wavefunctions(old_wf, 4320 final_decay_wfs, 4321 diagrams, 4322 numbers) 4323 # Now that we are done with this set of diagrams, we need 4324 # to clean out duplicate wavefunctions (i.e., remove 4325 # identical wavefunctions which are already present in 4326 # earlier diagrams) 4327 for diagram in diagrams: 4328 4329 # We can have duplicate wfs only from previous copies of 4330 # this diagram 4331 earlier_wfs = sum([d.get('wavefunctions') for d in \ 4332 self.get('diagrams')[\ 4333 diagram.get('number') - numdecay - 1:\ 4334 diagram.get('number') - 1]], []) 4335 4336 later_wfs = sum([d.get('wavefunctions') for d in \ 4337 self.get('diagrams')[\ 4338 diagram.get('number'):]], []) 4339 4340 later_amps = sum([d.get('amplitudes') for d in \ 4341 self.get('diagrams')[\ 4342 diagram.get('number') - 1:]], []) 4343 4344 i = 0 4345 diag_wfs = diagram.get('wavefunctions') 4346 4347 # Remove wavefunctions and replace mothers, to make 4348 # sure we only have one copy of each wavefunction 4349 # number 4350 4351 mother_arrays = [w.get('mothers').to_array() for \ 4352 w in later_wfs + later_amps] 4353 4354 while diag_wfs[i:]: 4355 try: 4356 index = [w.get('number') for w in earlier_wfs].\ 4357 index(diag_wfs[i].get('number')) 4358 wf = diag_wfs.pop(i) 4359 pres_mother_arrays = [w.get('mothers').to_array() for \ 4360 w in diag_wfs[i:]] + \ 4361 mother_arrays 4362 self.update_later_mothers(wf, earlier_wfs[index], 4363 diag_wfs[i:] + later_wfs + later_amps, 4364 pres_mother_arrays) 4365 except ValueError: 4366 i = i + 1
4367
4368 - def update_later_mothers(self, wf, new_wf, later_wfs, later_wf_arrays):
4369 """Update mothers for all later wavefunctions""" 4370 4371 daughters = filter(lambda tup: wf.get('number') in tup[1], 4372 enumerate(later_wf_arrays)) 4373 4374 for (index, mothers) in daughters: 4375 try: 4376 # Replace mother 4377 later_wfs[index].get('mothers')[\ 4378 mothers.index(wf.get('number'))] = new_wf 4379 except ValueError: 4380 pass
4381
4382 - def replace_wavefunctions(self, old_wf, new_wfs, 4383 diagrams, numbers):
4384 """Recursive function to replace old_wf with new_wfs, and 4385 multiply all wavefunctions or amplitudes that use old_wf 4386 4387 * old_wf: The wavefunction to be replaced 4388 * new_wfs: The replacing wavefunction 4389 * diagrams - the diagrams that are relevant for these new 4390 wavefunctions. 4391 * numbers: the present wavefunction and amplitude number, 4392 to allow for unique numbering 4393 """ 4394 4395 # Pick out the diagrams which have the old_wf 4396 my_diagrams = filter(lambda diag: old_wf.get('number') in \ 4397 [wf.get('number') for wf in diag.get('wavefunctions')], 4398 diagrams) 4399 4400 # Replace old_wf with new_wfs in the diagrams 4401 for diagram in my_diagrams: 4402 4403 diagram_wfs = diagram.get('wavefunctions') 4404 4405 old_wf_index = [wf.get('number') for wf in \ 4406 diagram.get('wavefunctions')].index(old_wf.get('number')) 4407 diagram_wfs = diagram_wfs[:old_wf_index] + \ 4408 new_wfs + diagram_wfs[old_wf_index + 1:] 4409 4410 diagram.set('wavefunctions', HelasWavefunctionList(diagram_wfs)) 4411 4412 # Now need to take care of amplitudes and wavefunctions which 4413 # are daughters of old_wf (only among the relevant diagrams) 4414 4415 # Pick out diagrams with amplitudes which are daughters of old_wf 4416 amp_diagrams = filter(lambda diag: old_wf.get('number') in \ 4417 sum([[wf.get('number') for wf in amp.get('mothers')] \ 4418 for amp in diag.get('amplitudes')], []), 4419 diagrams) 4420 4421 for diagram in amp_diagrams: 4422 4423 # Amplitudes in this diagram that are daughters of old_wf 4424 daughter_amps = filter(lambda amp: old_wf.get('number') in \ 4425 [wf.get('number') for wf in amp.get('mothers')], 4426 diagram.get('amplitudes')) 4427 4428 new_amplitudes = copy.copy(diagram.get('amplitudes')) 4429 4430 # Loop over daughter_amps, to multiply each amp by the 4431 # number of replacement wavefunctions and substitute mothers 4432 for old_amp in daughter_amps: 4433 # Create copies of this amp 4434 new_amps = [copy.copy(amp) for amp in \ 4435 [ old_amp ] * len(new_wfs)] 4436 # Replace the old mother with the new ones 4437 for i, (new_amp, new_wf) in enumerate(zip(new_amps, new_wfs)): 4438 mothers = copy.copy(new_amp.get('mothers')) 4439 old_wf_index = [wf.get('number') for wf in mothers].index(\ 4440 old_wf.get('number')) 4441 # Update mother 4442 mothers[old_wf_index] = new_wf 4443 new_amp.set('mothers', mothers) 4444 # Update amp numbers for replaced amp 4445 numbers[1] = numbers[1] + 1 4446 new_amp.set('number', numbers[1]) 4447 4448 # Insert the new amplitudes in diagram amplitudes 4449 index = [a.get('number') for a in new_amplitudes].\ 4450 index(old_amp.get('number')) 4451 new_amplitudes = new_amplitudes[:index] + \ 4452 new_amps + new_amplitudes[index + 1:] 4453 4454 # Replace diagram amplitudes with the new ones 4455 diagram.set('amplitudes', HelasAmplitudeList(new_amplitudes)) 4456 4457 # Find wavefunctions that are daughters of old_wf 4458 daughter_wfs = filter(lambda wf: old_wf.get('number') in \ 4459 [wf1.get('number') for wf1 in wf.get('mothers')], 4460 sum([diag.get('wavefunctions') for diag in \ 4461 diagrams], [])) 4462 4463 # Loop over daughter_wfs, multiply them and replace mothers 4464 for daughter_wf in daughter_wfs: 4465 4466 # Pick out the diagrams where daughter_wf occurs 4467 wf_diagrams = filter(lambda diag: daughter_wf.get('number') in \ 4468 [wf.get('number') for wf in \ 4469 diag.get('wavefunctions')], 4470 diagrams) 4471 4472 if len(wf_diagrams) > 1: 4473 raise self.PhysicsObjectError, \ 4474 "Decay chains not yet prepared for GPU" 4475 4476 for diagram in wf_diagrams: 4477 4478 # Now create new wfs with updated mothers 4479 replace_daughters = [ copy.copy(wf) for wf in \ 4480 [daughter_wf] * len(new_wfs) ] 4481 4482 index = [wf.get('number') for wf in \ 4483 daughter_wf.get('mothers')].index(old_wf.get('number')) 4484 4485 # Replace the old mother with the new ones, update wf numbers 4486 for i, (new_daughter, new_wf) in \ 4487 enumerate(zip(replace_daughters, new_wfs)): 4488 mothers = copy.copy(new_daughter.get('mothers')) 4489 mothers[index] = new_wf 4490 new_daughter.set('mothers', mothers) 4491 numbers[0] = numbers[0] + 1 4492 new_daughter.set('number', numbers[0]) 4493 4494 # This is where recursion happens. We need to replace 4495 # the daughter wavefunction, and fix amplitudes and 4496 # wavefunctions which have it as mothers. 4497 4498 self.replace_wavefunctions(daughter_wf, 4499 replace_daughters, 4500 diagrams, 4501 numbers)
4502
4503 - def replace_single_wavefunction(self, old_wf, new_wf):
4504 """Insert decay chain by simply modifying wavefunction. This 4505 is possible only if there is only one diagram in the decay.""" 4506 4507 4508 for key in old_wf.keys(): 4509 old_wf.set(key, new_wf[key])
4510
4511 - def identical_decay_chain_factor(self, decay_chains):
4512 """Calculate the denominator factor from identical decay chains""" 4513 4514 final_legs = [leg.get('id') for leg in \ 4515 filter(lambda leg: leg.get('state') == True, \ 4516 self.get('processes')[0].get('legs'))] 4517 4518 # Leg ids for legs being replaced by decay chains 4519 decay_ids = [decay.get('legs')[0].get('id') for decay in \ 4520 self.get('processes')[0].get('decay_chains')] 4521 4522 # Find all leg ids which are not being replaced by decay chains 4523 non_decay_legs = filter(lambda id: id not in decay_ids, 4524 final_legs) 4525 4526 # Identical particle factor for legs not being decayed 4527 identical_indices = {} 4528 for id in non_decay_legs: 4529 if id in identical_indices: 4530 identical_indices[id] = \ 4531 identical_indices[id] + 1 4532 else: 4533 identical_indices[id] = 1 4534 non_chain_factor = reduce(lambda x, y: x * y, 4535 [ math.factorial(val) for val in \ 4536 identical_indices.values() ], 1) 4537 4538 # Identical particle factor for decay chains 4539 # Go through chains to find identical ones 4540 chains = copy.copy(decay_chains) 4541 iden_chains_factor = 1 4542 while chains: 4543 ident_copies = 1 4544 first_chain = chains.pop(0) 4545 i = 0 4546 while i < len(chains): 4547 chain = chains[i] 4548 if HelasMatrixElement.check_equal_decay_processes(\ 4549 first_chain, chain): 4550 ident_copies = ident_copies + 1 4551 chains.pop(i) 4552 else: 4553 i = i + 1 4554 iden_chains_factor = iden_chains_factor * \ 4555 math.factorial(ident_copies) 4556 4557 self['identical_particle_factor'] = non_chain_factor * \ 4558 iden_chains_factor * \ 4559 reduce(lambda x1, x2: x1 * x2, 4560 [me.get('identical_particle_factor') \ 4561 for me in decay_chains], 1)
4562
4563 - def calculate_fermionfactors(self):
4564 """Generate the fermion factors for all diagrams in the matrix element 4565 """ 4566 for diagram in self.get('diagrams'): 4567 for amplitude in diagram.get('amplitudes'): 4568 amplitude.get('fermionfactor')
4569
4571 """Calculate the denominator factor for identical final state particles 4572 """ 4573 4574 self["identical_particle_factor"] = self.get('processes')[0].\ 4575 identical_particle_factor()
4576
4577 - def get_base_amplitude(self):
4578 """Generate a diagram_generation.Amplitude from a 4579 HelasMatrixElement. This is used to generate both color 4580 amplitudes and diagram drawing.""" 4581 4582 # Need to take care of diagram numbering for decay chains 4583 # before this can be used for those! 4584 4585 optimization = 1 4586 if len(filter(lambda wf: wf.get('number') == 1, 4587 self.get_all_wavefunctions())) > 1: 4588 optimization = 0 4589 4590 model = self.get('processes')[0].get('model') 4591 4592 wf_dict = {} 4593 vx_list = [] 4594 diagrams = base_objects.DiagramList() 4595 for diag in self.get('diagrams'): 4596 diagrams.append(diag.get('amplitudes')[0].get_base_diagram(\ 4597 wf_dict, vx_list, optimization)) 4598 4599 for diag in diagrams: 4600 diag.calculate_orders(self.get('processes')[0].get('model')) 4601 4602 return diagram_generation.Amplitude({\ 4603 'process': self.get('processes')[0], 4604 'diagrams': diagrams})
4605 4606 # Helper methods 4607
4608 - def getmothers(self, legs, number_to_wavefunctions, 4609 external_wavefunctions, wavefunctions, 4610 diagram_wavefunctions):
4611 """Generate list of mothers from number_to_wavefunctions and 4612 external_wavefunctions""" 4613 4614 mothers = HelasWavefunctionList() 4615 4616 for leg in legs: 4617 try: 4618 # The mother is an existing wavefunction 4619 wf = number_to_wavefunctions[leg.get('number')] 4620 except KeyError: 4621 # This is an external leg, pick from external_wavefunctions 4622 wf = external_wavefunctions[leg.get('number')] 4623 number_to_wavefunctions[leg.get('number')] = wf 4624 if not wf in wavefunctions and not wf in diagram_wavefunctions: 4625 diagram_wavefunctions.append(wf) 4626 mothers.append(wf) 4627 4628 return mothers
4629 4630 4631
4632 - def get_num_configs(self):
4633 """Get number of diagrams, which is always more than number of 4634 configs""" 4635 4636 model = self.get('processes')[0].\ 4637 get('model') 4638 4639 next, nini = self.get_nexternal_ninitial() 4640 return sum([d.get_num_configs(model, nini) for d in \ 4641 self.get('base_amplitude').get('diagrams')])
4642
4644 """Gives the total number of wavefunctions for this ME""" 4645 4646 out = max([wf.get('me_id') for wfs in self.get('diagrams') 4647 for wf in wfs.get('wavefunctions')]) 4648 if out: 4649 return out 4650 return sum([ len(d.get('wavefunctions')) for d in \ 4651 self.get('diagrams')])
4652
4653 - def get_all_wavefunctions(self):
4654 """Gives a list of all wavefunctions for this ME""" 4655 4656 return sum([d.get('wavefunctions') for d in \ 4657 self.get('diagrams')], [])
4658 4659
4660 - def get_all_mass_widths(self):
4661 """Gives a list of all widths used by this ME (from propagator)""" 4662 4663 return set([(d.get('mass'),d.get('width')) for d in self.get_all_wavefunctions()])
4664 4665
4666 - def get_all_amplitudes(self):
4667 """Gives a list of all amplitudes for this ME""" 4668 4669 return sum([d.get('amplitudes') for d in \ 4670 self.get('diagrams')], [])
4671
4672 - def get_external_wavefunctions(self):
4673 """Gives the external wavefunctions for this ME""" 4674 4675 external_wfs = filter(lambda wf: not wf.get('mothers'), 4676 self.get('diagrams')[0].get('wavefunctions')) 4677 4678 external_wfs.sort(lambda w1, w2: w1.get('number_external') - \ 4679 w2.get('number_external')) 4680 4681 i = 1 4682 while i < len(external_wfs): 4683 if external_wfs[i].get('number_external') == \ 4684 external_wfs[i - 1].get('number_external'): 4685 external_wfs.pop(i) 4686 else: 4687 i = i + 1 4688 return external_wfs
4689
4690 - def get_number_of_amplitudes(self):
4691 """Gives the total number of amplitudes for this ME""" 4692 4693 return sum([ len(d.get('amplitudes')) for d in \ 4694 self.get('diagrams')])
4695
4696 - def get_nexternal_ninitial(self):
4697 """Gives (number or external particles, number of 4698 incoming particles)""" 4699 4700 external_wfs = filter(lambda wf: not wf.get('mothers'), 4701 self.get_all_wavefunctions()) 4702 4703 return (len(set([wf.get('number_external') for wf in \ 4704 external_wfs])), 4705 len(set([wf.get('number_external') for wf in \ 4706 filter(lambda wf: wf.get('leg_state') == False, 4707 external_wfs)])))
4708
4709 - def get_external_masses(self):
4710 """Gives the list of the strings corresponding to the masses of the 4711 external particles.""" 4712 4713 mass_list=[] 4714 external_wfs = sorted(filter(lambda wf: wf.get('leg_state') != \ 4715 'intermediate', self.get_all_wavefunctions()),\ 4716 key=lambda w: w['number_external']) 4717 external_number=1 4718 for wf in external_wfs: 4719 if wf.get('number_external')==external_number: 4720 external_number=external_number+1 4721 mass_list.append(wf.get('particle').get('mass')) 4722 4723 return mass_list
4724
4725 - def get_helicity_combinations(self):
4726 """Gives the number of helicity combinations for external 4727 wavefunctions""" 4728 4729 if not self.get('processes'): 4730 return None 4731 4732 model = self.get('processes')[0].get('model') 4733 hel_per_part = [ len(wf.get('polarization')) if wf.get('polarization') 4734 else len(model.get('particle_dict')[\ 4735 wf.get('pdg_code')].get_helicity_states()) 4736 for wf in self.get_external_wavefunctions()] 4737 4738 return reduce(lambda x, y: x * y, 4739 hel_per_part)
4740
4741 - def get_helicity_matrix(self, allow_reverse=True):
4742 """Gives the helicity matrix for external wavefunctions""" 4743 4744 if not self.get('processes'): 4745 return None 4746 4747 process = self.get('processes')[0] 4748 model = process.get('model') 4749 4750 hel_per_part = [ wf.get('polarization') if wf.get('polarization') 4751 else model.get('particle_dict')[\ 4752 wf.get('pdg_code')].get_helicity_states(allow_reverse) 4753 for wf in self.get_external_wavefunctions()] 4754 4755 return apply(itertools.product, hel_per_part)
4756
4757 - def get_hel_avg_factor(self):
4758 """ Calculate the denominator factor due to the average over initial 4759 state spin only """ 4760 4761 model = self.get('processes')[0].get('model') 4762 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4763 self.get('processes')[0].get('legs')) 4764 hel_per_part = [ len(leg.get('polarization')) if leg.get('polarization') 4765 else len(model.get('particle_dict')[\ 4766 leg.get('id')].get_helicity_states()) 4767 for leg in initial_legs] 4768 4769 return reduce(lambda x, y: x * y, hel_per_part, 1)
4770
4771 - def get_spin_state_initial(self):
4772 """Gives (number of state for each initial particle)""" 4773 4774 model = self.get('processes')[0].get('model') 4775 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4776 self.get('processes')[0].get('legs')) 4777 hel_per_part = [ len(leg.get('polarization')) if leg.get('polarization') 4778 else len(model.get('particle_dict')[\ 4779 leg.get('id')].get_helicity_states()) 4780 for leg in initial_legs] 4781 4782 if len(hel_per_part) == 1: 4783 hel_per_part.append(0) 4784 4785 return hel_per_part
4786
4787 - def get_beams_hel_avg_factor(self):
4788 """ Calculate the denominator factor due to the average over initial 4789 state spin only. Returns the result for beam one and two separately 4790 so that the averaging can be done correctly for partial polarization.""" 4791 4792 model = self.get('processes')[0].get('model') 4793 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4794 self.get('processes')[0].get('legs')) 4795 4796 beam_avg_factors = [ len(model.get('particle_dict')[leg.get('id')].\ 4797 get_helicity_states()) for leg in initial_legs ] 4798 if len(beam_avg_factors)==1: 4799 # For a 1->N process, we simply return 1 for the second entry. 4800 return beam_avg_factors[0],1 4801 else: 4802 return beam_avg_factors[0],beam_avg_factors[1]
4803
4804 - def get_denominator_factor(self):
4805 """Calculate the denominator factor due to: 4806 Averaging initial state color and spin, and 4807 identical final state particles""" 4808 4809 model = self.get('processes')[0].get('model') 4810 4811 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4812 self.get('processes')[0].get('legs')) 4813 4814 color_factor = reduce(lambda x, y: x * y, 4815 [ model.get('particle_dict')[leg.get('id')].\ 4816 get('color')\ 4817 for leg in initial_legs ]) 4818 4819 spin_factor = reduce(lambda x, y: x * y, 4820 [ len(model.get('particle_dict')[leg.get('id')].\ 4821 get_helicity_states()) 4822 if not leg.get('polarization') else 4823 len(leg.get('polarization')) 4824 for leg in initial_legs ]) 4825 4826 return spin_factor * color_factor * self['identical_particle_factor']
4827
4828 - def generate_color_amplitudes(self, color_basis, diagrams):
4829 """ Return a list of (coefficient, amplitude number) lists, 4830 corresponding to the JAMPs for the HelasDiagrams and color basis passed 4831 in argument. The coefficients are given in the format (fermion factor, 4832 colorcoeff (frac), imaginary, Nc power). """ 4833 4834 if not color_basis: 4835 # No color, simply add all amplitudes with correct factor 4836 # for first color amplitude 4837 col_amp = [] 4838 for diagram in diagrams: 4839 for amplitude in diagram.get('amplitudes'): 4840 col_amp.append(((amplitude.get('fermionfactor'), 4841 1, False, 0), 4842 amplitude.get('number'))) 4843 return [col_amp] 4844 4845 # There is a color basis - create a list of coefficients and 4846 # amplitude numbers 4847 4848 col_amp_list = [] 4849 for i, col_basis_elem in \ 4850 enumerate(sorted(color_basis.keys())): 4851 4852 col_amp = [] 4853 for diag_tuple in color_basis[col_basis_elem]: 4854 res_amps = filter(lambda amp: \ 4855 tuple(amp.get('color_indices')) == diag_tuple[1], 4856 diagrams[diag_tuple[0]].get('amplitudes')) 4857 if not res_amps: 4858 raise self.PhysicsObjectError, \ 4859 """No amplitude found for color structure 4860 %s and color index chain (%s) (diagram %i)""" % \ 4861 (col_basis_elem, 4862 str(diag_tuple[1]), 4863 diag_tuple[0]) 4864 4865 for res_amp in res_amps: 4866 col_amp.append(((res_amp.get('fermionfactor'), 4867 diag_tuple[2], 4868 diag_tuple[3], 4869 diag_tuple[4]), 4870 res_amp.get('number'))) 4871 4872 col_amp_list.append(col_amp) 4873 4874 return col_amp_list
4875
4876 - def get_color_amplitudes(self):
4877 """Return a list of (coefficient, amplitude number) lists, 4878 corresponding to the JAMPs for this matrix element. The 4879 coefficients are given in the format (fermion factor, color 4880 coeff (frac), imaginary, Nc power).""" 4881 4882 return self.generate_color_amplitudes(self['color_basis'],self['diagrams'])
4883
4884 - def sort_split_orders(self, split_orders):
4885 """ Sort the 'split_orders' list given in argument so that the orders of 4886 smaller weights appear first. Do nothing if not all split orders have 4887 a hierarchy defined.""" 4888 order_hierarchy=\ 4889 self.get('processes')[0].get('model').get('order_hierarchy') 4890 if set(order_hierarchy.keys()).union(set(split_orders))==\ 4891 set(order_hierarchy.keys()): 4892 split_orders.sort(key=lambda order: order_hierarchy[order])
4893
4894 - def get_split_orders_mapping_for_diagram_list(self, diag_list, split_orders, 4895 get_amp_number_function = lambda amp: amp.get('number'), 4896 get_amplitudes_function = lambda diag: diag.get('amplitudes')):
4897 """ This a helper function for get_split_orders_mapping to return, for 4898 the HelasDiagram list given in argument, the list amp_orders detailed in 4899 the description of the 'get_split_orders_mapping' function. 4900 """ 4901 4902 order_hierarchy=\ 4903 self.get('processes')[0].get('model').get('order_hierarchy') 4904 # Find the list of amplitude numbers and what amplitude order they 4905 # correspond to. For its construction, amp_orders is a dictionary with 4906 # is then translated into an ordered list of tuples before being returned. 4907 amp_orders={} 4908 for diag in diag_list: 4909 diag_orders=diag.calculate_orders() 4910 # Add the WEIGHTED order information 4911 diag_orders['WEIGHTED']=sum(order_hierarchy[order]*value for \ 4912 order, value in diag_orders.items()) 4913 # Complement the missing split_orders with 0 4914 for order in split_orders: 4915 if not order in diag_orders.keys(): 4916 diag_orders[order]=0 4917 key = tuple([diag_orders[order] for order in split_orders]) 4918 try: 4919 amp_orders[key].extend([get_amp_number_function(amp) for amp in \ 4920 get_amplitudes_function(diag)]) 4921 except KeyError: 4922 amp_orders[key] = [get_amp_number_function(amp) for amp in \ 4923 get_amplitudes_function(diag)] 4924 amp_orders=[(order[0],tuple(order[1])) for order in amp_orders.items()] 4925 # Again make sure a proper hierarchy is defined and order the list 4926 # according to it if possible 4927 if set(order_hierarchy.keys()).union(set(split_orders))==\ 4928 set(order_hierarchy.keys()): 4929 # Order the contribution starting from the minimum WEIGHTED one 4930 amp_orders.sort(key= lambda elem: 4931 sum([order_hierarchy[split_orders[i]]*order_power for \ 4932 i, order_power in enumerate(elem[0])])) 4933 4934 return amp_orders
4935
4936 - def get_split_orders_mapping(self):
4937 """This function returns two lists, squared_orders, amp_orders. 4938 If process['split_orders'] is empty, the function returns two empty lists. 4939 4940 squared_orders : All possible contributing squared orders among those 4941 specified in the process['split_orders'] argument. The elements of 4942 the list are tuples of the format format (OrderValue1,OrderValue2,...) 4943 with OrderValue<i> correspond to the value of the <i>th order in 4944 process['split_orders'] (the others are summed over and therefore 4945 left unspecified). 4946 Ex for dijet with process['split_orders']=['QCD','QED']: 4947 => [(4,0),(2,2),(0,4)] 4948 4949 amp_orders : Exactly as for squared order except that this list specifies 4950 the contributing order values for the amplitude (i.e. not 'squared'). 4951 Also, the tuple describing the amplitude order is nested with a 4952 second one listing all amplitude numbers contributing to this order. 4953 Ex for dijet with process['split_orders']=['QCD','QED']: 4954 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))] 4955 4956 Keep in mind that the orders of the element of the list is important as 4957 it dicatates the order of the corresponding "order indices" in the 4958 code output by the exporters. 4959 """ 4960 4961 split_orders=self.get('processes')[0].get('split_orders') 4962 # If no split_orders are defined, then return the obvious 4963 if len(split_orders)==0: 4964 return (),() 4965 4966 # First make sure that the 'split_orders' are ordered according to their 4967 # weight. 4968 self.sort_split_orders(split_orders) 4969 4970 amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 4971 self.get('diagrams'),split_orders) 4972 4973 # Now we construct the interference splitting order matrix for 4974 # convenience 4975 squared_orders = [] 4976 for i, amp_order in enumerate(amp_orders): 4977 for j in range(0,i+1): 4978 key = tuple([ord1 + ord2 for ord1,ord2 in \ 4979 zip(amp_order[0],amp_orders[j][0])]) 4980 # We don't use the set structure here since keeping the 4981 # construction order guarantees us that the squared_orders will 4982 # also be ordered according to the WEIGHT. 4983 if not key in squared_orders: 4984 squared_orders.append(key) 4985 4986 return squared_orders, amp_orders
4987 4988 4989
4990 - def get_used_lorentz(self):
4991 """Return a list of (lorentz_name, conjugate_tag, outgoing) with 4992 all lorentz structures used by this HelasMatrixElement.""" 4993 4994 output = [] 4995 for wa in self.get_all_wavefunctions() + self.get_all_amplitudes(): 4996 if wa.get('interaction_id') in [0,-1]: 4997 continue 4998 output.append(wa.get_aloha_info()); 4999 5000 return output
5001
5002 - def get_used_couplings(self, output=str):
5003 """Return a list with all couplings used by this 5004 HelasMatrixElement.""" 5005 5006 tmp = [wa.get('coupling') for wa in \ 5007 self.get_all_wavefunctions() + self.get_all_amplitudes() \ 5008 if wa.get('interaction_id') not in [0,-1]] 5009 #some coupling have a minus one associated -> need to remove those 5010 if output == str: 5011 return [ [t] if not t.startswith('-') else [t[1:]] for t2 in tmp for t in t2] 5012 elif output=="set": 5013 return set(sum([ [t] if not t.startswith('-') else [t[1:]] for t2 in tmp for t in t2],[]))
5014 5015
5016 - def get_mirror_processes(self):
5017 """Return a list of processes with initial states interchanged 5018 if has mirror processes""" 5019 5020 if not self.get('has_mirror_process'): 5021 return [] 5022 processes = base_objects.ProcessList() 5023 for proc in self.get('processes'): 5024 legs = copy.copy(proc.get('legs')) 5025 legs[0:2] = [legs[1],legs[0]] 5026 decay_legs = copy.copy(proc.get('legs_with_decays')) 5027 decay_legs[0:2] = [decay_legs[1],decay_legs[0]] 5028 process = copy.copy(proc) 5029 process.set('legs', legs) 5030 process.set('legs_with_decays', decay_legs) 5031 processes.append(process) 5032 return processes
5033 5034 @staticmethod
5035 - def check_equal_decay_processes(decay1, decay2):
5036 """Check if two single-sided decay processes 5037 (HelasMatrixElements) are equal. 5038 5039 Note that this has to be called before any combination of 5040 processes has occured. 5041 5042 Since a decay processes for a decay chain is always generated 5043 such that all final state legs are completely contracted 5044 before the initial state leg is included, all the diagrams 5045 will have identical wave function, independently of the order 5046 of final state particles. 5047 5048 Note that we assume that the process definitions have all 5049 external particles, corresponding to the external 5050 wavefunctions. 5051 """ 5052 5053 assert len(decay1.get('processes')) == 1 == len(decay2.get('processes')), \ 5054 "Can compare only single process HelasMatrixElements" 5055 5056 assert len(filter(lambda leg: leg.get('state') == False, \ 5057 decay1.get('processes')[0].get('legs'))) == 1 and \ 5058 len(filter(lambda leg: leg.get('state') == False, \ 5059 decay2.get('processes')[0].get('legs'))) == 1, \ 5060 "Call to check_decay_processes_equal requires " + \ 5061 "both processes to be unique" 5062 5063 # Compare bulk process properties (number of external legs, 5064 # identity factors, number of diagrams, number of wavefunctions 5065 # initial leg, final state legs 5066 if len(decay1.get('processes')[0].get("legs")) != \ 5067 len(decay2.get('processes')[0].get("legs")) or \ 5068 len(decay1.get('diagrams')) != len(decay2.get('diagrams')) or \ 5069 decay1.get('identical_particle_factor') != \ 5070 decay2.get('identical_particle_factor') or \ 5071 sum(len(d.get('wavefunctions')) for d in \ 5072 decay1.get('diagrams')) != \ 5073 sum(len(d.get('wavefunctions')) for d in \ 5074 decay2.get('diagrams')) or \ 5075 decay1.get('processes')[0].get('legs')[0].get('id') != \ 5076 decay2.get('processes')[0].get('legs')[0].get('id') or \ 5077 sorted([leg.get('id') for leg in \ 5078 decay1.get('processes')[0].get('legs')[1:]]) != \ 5079 sorted([leg.get('id') for leg in \ 5080 decay2.get('processes')[0].get('legs')[1:]]): 5081 return False 5082 5083 # Run a quick check to see if the processes are already 5084 # identical (i.e., the wavefunctions are in the same order) 5085 if [leg.get('id') for leg in \ 5086 decay1.get('processes')[0].get('legs')] == \ 5087 [leg.get('id') for leg in \ 5088 decay2.get('processes')[0].get('legs')] and \ 5089 decay1 == decay2: 5090 return True 5091 5092 # Now check if all diagrams are identical. This is done by a 5093 # recursive function starting from the last wavefunction 5094 # (corresponding to the initial state), since it is the 5095 # same steps for each level in mother wavefunctions 5096 5097 amplitudes2 = copy.copy(reduce(lambda a1, d2: a1 + \ 5098 d2.get('amplitudes'), 5099 decay2.get('diagrams'), [])) 5100 5101 for amplitude1 in reduce(lambda a1, d2: a1 + d2.get('amplitudes'), 5102 decay1.get('diagrams'), []): 5103 foundamplitude = False 5104 for amplitude2 in amplitudes2: 5105 if HelasMatrixElement.check_equal_wavefunctions(\ 5106 amplitude1.get('mothers')[-1], 5107 amplitude2.get('mothers')[-1]): 5108 foundamplitude = True 5109 # Remove amplitude2, since it has already been matched 5110 amplitudes2.remove(amplitude2) 5111 break 5112 if not foundamplitude: 5113 return False 5114 5115 return True
5116 5117 @staticmethod
5118 - def check_equal_wavefunctions(wf1, wf2):
5119 """Recursive function to check if two wavefunctions are equal. 5120 First check that mothers have identical pdg codes, then repeat for 5121 all mothers with identical pdg codes.""" 5122 5123 # End recursion with False if the wavefunctions do not have 5124 # the same mother pdgs 5125 if sorted([wf.get('pdg_code') for wf in wf1.get('mothers')]) != \ 5126 sorted([wf.get('pdg_code') for wf in wf2.get('mothers')]): 5127 return False 5128 5129 # End recursion with True if these are external wavefunctions 5130 # (note that we have already checked that the pdgs are 5131 # identical) 5132 if not wf1.get('mothers') and not wf2.get('mothers'): 5133 return True 5134 5135 mothers2 = copy.copy(wf2.get('mothers')) 5136 5137 for mother1 in wf1.get('mothers'): 5138 # Compare mother1 with all mothers in wf2 that have not 5139 # yet been used and have identical pdg codes 5140 equalmothers = filter(lambda wf: wf.get('pdg_code') == \ 5141 mother1.get('pdg_code'), 5142 mothers2) 5143 foundmother = False 5144 for mother2 in equalmothers: 5145 if HelasMatrixElement.check_equal_wavefunctions(\ 5146 mother1, mother2): 5147 foundmother = True 5148 # Remove mother2, since it has already been matched 5149 mothers2.remove(mother2) 5150 break 5151 if not foundmother: 5152 return False 5153 5154 return True
5155 5156 @staticmethod
5157 - def sorted_mothers(arg):
5158 """Gives a list of mother wavefunctions sorted according to 5159 1. The order of the particles in the interaction 5160 2. Cyclic reordering of particles in same spin group 5161 3. Fermions ordered IOIOIO... according to the pairs in 5162 the interaction.""" 5163 5164 assert isinstance(arg, (HelasWavefunction, HelasAmplitude)), \ 5165 "%s is not a valid HelasWavefunction or HelasAmplitude" % repr(arg) 5166 5167 if not arg.get('interaction_id'): 5168 return arg.get('mothers') 5169 5170 my_pdg_code = 0 5171 my_spin = 0 5172 if isinstance(arg, HelasWavefunction): 5173 my_pdg_code = arg.get_anti_pdg_code() 5174 my_spin = arg.get_spin_state_number() 5175 5176 sorted_mothers, my_index = arg.get('mothers').sort_by_pdg_codes(\ 5177 arg.get('pdg_codes'), my_pdg_code) 5178 5179 # If fermion, partner is the corresponding fermion flow partner 5180 partner = None 5181 if isinstance(arg, HelasWavefunction) and arg.is_fermion(): 5182 # Fermion case, just pick out the fermion flow partner 5183 if my_index % 2 == 0: 5184 # partner is after arg 5185 partner_index = my_index 5186 else: 5187 # partner is before arg 5188 partner_index = my_index - 1 5189 partner = sorted_mothers.pop(partner_index) 5190 # If partner is incoming, move to before arg 5191 if partner.get_spin_state_number() > 0: 5192 my_index = partner_index 5193 else: 5194 my_index = partner_index + 1 5195 5196 # Reorder fermions pairwise according to incoming/outgoing 5197 for i in range(0, len(sorted_mothers), 2): 5198 if sorted_mothers[i].is_fermion(): 5199 # This is a fermion, order between this fermion and its brother 5200 if sorted_mothers[i].get_spin_state_number() > 0 and \ 5201 sorted_mothers[i + 1].get_spin_state_number() < 0: 5202 # Switch places between outgoing and incoming 5203 sorted_mothers = sorted_mothers[:i] + \ 5204 [sorted_mothers[i+1], sorted_mothers[i]] + \ 5205 sorted_mothers[i+2:] 5206 elif sorted_mothers[i].get_spin_state_number() < 0 and \ 5207 sorted_mothers[i + 1].get_spin_state_number() > 0: 5208 # This is the right order 5209 pass 5210 else: 5211 # No more fermions in sorted_mothers 5212 break 5213 5214 # Put back partner into sorted_mothers 5215 if partner: 5216 sorted_mothers.insert(partner_index, partner) 5217 5218 # Next sort according to spin_state_number 5219 return HelasWavefunctionList(sorted_mothers)
5220
5221 #=============================================================================== 5222 # HelasMatrixElementList 5223 #=============================================================================== 5224 -class HelasMatrixElementList(base_objects.PhysicsObjectList):
5225 """List of HelasMatrixElement objects 5226 """ 5227
5228 - def is_valid_element(self, obj):
5229 """Test if object obj is a valid HelasMatrixElement for the list.""" 5230 5231 return isinstance(obj, HelasMatrixElement)
5232
5233 - def remove(self,obj):
5234 pos = (i for i in xrange(len(self)) if self[i] is obj) 5235 for i in pos: 5236 del self[i] 5237 break
5238
5239 #=============================================================================== 5240 # HelasDecayChainProcess 5241 #=============================================================================== 5242 -class HelasDecayChainProcess(base_objects.PhysicsObject):
5243 """HelasDecayChainProcess: If initiated with a DecayChainAmplitude 5244 object, generates the HelasMatrixElements for the core process(es) 5245 and decay chains. Then call combine_decay_chain_processes in order 5246 to generate the matrix elements for all combined processes.""" 5247
5248 - def default_setup(self):
5249 """Default values for all properties""" 5250 5251 self['core_processes'] = HelasMatrixElementList() 5252 self['decay_chains'] = HelasDecayChainProcessList()
5253
5254 - def filter(self, name, value):
5255 """Filter for valid process property values.""" 5256 5257 if name == 'core_processes': 5258 if not isinstance(value, HelasMatrixElementList): 5259 raise self.PhysicsObjectError, \ 5260 "%s is not a valid HelasMatrixElementList object" % \ 5261 str(value) 5262 5263 if name == 'decay_chains': 5264 if not isinstance(value, HelasDecayChainProcessList): 5265 raise self.PhysicsObjectError, \ 5266 "%s is not a valid HelasDecayChainProcessList object" % \ 5267 str(value) 5268 5269 return True
5270
5271 - def get_sorted_keys(self):
5272 """Return process property names as a nicely sorted list.""" 5273 5274 return ['core_processes', 'decay_chains']
5275
5276 - def __init__(self, argument=None):
5277 """Allow initialization with DecayChainAmplitude""" 5278 5279 if isinstance(argument, diagram_generation.DecayChainAmplitude): 5280 super(HelasDecayChainProcess, self).__init__() 5281 self.generate_matrix_elements(argument) 5282 elif argument: 5283 # call the mother routine 5284 super(HelasDecayChainProcess, self).__init__(argument) 5285 else: 5286 # call the mother routine 5287 super(HelasDecayChainProcess, self).__init__()
5288
5289 - def nice_string(self, indent = 0):
5290 """Returns a nicely formatted string of the matrix element processes.""" 5291 5292 mystr = "" 5293 5294 for process in self.get('core_processes'): 5295 mystr += process.get('processes')[0].nice_string(indent) + "\n" 5296 5297 if self.get('decay_chains'): 5298 mystr += " " * indent + "Decays:\n" 5299 for dec in self.get('decay_chains'): 5300 mystr += dec.nice_string(indent + 2) + "\n" 5301 5302 return mystr[:-1]
5303
5304 - def generate_matrix_elements(self, dc_amplitude):
5305 """Generate the HelasMatrixElements for the core processes and 5306 decay processes (separately)""" 5307 5308 assert isinstance(dc_amplitude, diagram_generation.DecayChainAmplitude), \ 5309 "%s is not a valid DecayChainAmplitude" % dc_amplitude 5310 5311 5312 # Extract the pdg codes of all particles decayed by decay chains 5313 # since these should not be combined in a MultiProcess 5314 decay_ids = dc_amplitude.get_decay_ids() 5315 5316 matrix_elements = HelasMultiProcess.generate_matrix_elements(\ 5317 dc_amplitude.get('amplitudes'), 5318 False, 5319 decay_ids) 5320 5321 self.set('core_processes', matrix_elements) 5322 5323 while dc_amplitude.get('decay_chains'): 5324 # Pop the amplitude to save memory space 5325 decay_chain = dc_amplitude.get('decay_chains').pop(0) 5326 self['decay_chains'].append(HelasDecayChainProcess(\ 5327 decay_chain))
5328 5329
5330 - def combine_decay_chain_processes(self, combine=True):
5331 """Recursive function to generate complete 5332 HelasMatrixElements, combining the core process with the decay 5333 chains. 5334 5335 * If the number of decay chains is the same as the number of 5336 decaying particles, apply each decay chain to the corresponding 5337 final state particle. 5338 * If the number of decay chains and decaying final state particles 5339 don't correspond, all decays applying to a given particle type are 5340 combined (without double counting). 5341 * combine allow to merge identical ME 5342 """ 5343 5344 # End recursion when there are no more decay chains 5345 if not self['decay_chains']: 5346 # Just return the list of matrix elements 5347 return self['core_processes'] 5348 5349 # decay_elements is a list of HelasMatrixElementLists with 5350 # all decay processes 5351 decay_elements = [] 5352 5353 for decay_chain in self['decay_chains']: 5354 # This is where recursion happens 5355 decay_elements.append(decay_chain.combine_decay_chain_processes(combine)) 5356 5357 # Store the result in matrix_elements 5358 matrix_elements = HelasMatrixElementList() 5359 # Store matrix element tags in me_tags, for precise comparison 5360 me_tags = [] 5361 # Store external id permutations 5362 permutations = [] 5363 5364 # List of list of ids for the initial state legs in all decay 5365 # processes 5366 decay_is_ids = [[element.get('processes')[0].get_initial_ids()[0] \ 5367 for element in elements] 5368 for elements in decay_elements] 5369 5370 while self['core_processes']: 5371 # Pop the process to save memory space 5372 core_process = self['core_processes'].pop(0) 5373 # Get all final state legs that have a decay chain defined 5374 fs_legs = filter(lambda leg: any([any([id == leg.get('id') for id \ 5375 in is_ids]) for is_ids in decay_is_ids]), 5376 core_process.get('processes')[0].get_final_legs()) 5377 # List of ids for the final state legs 5378 fs_ids = [leg.get('id') for leg in fs_legs] 5379 # Create a dictionary from id to (index, leg number) 5380 fs_numbers = {} 5381 fs_indices = {} 5382 for i, leg in enumerate(fs_legs): 5383 fs_numbers[leg.get('id')] = \ 5384 fs_numbers.setdefault(leg.get('id'), []) + \ 5385 [leg.get('number')] 5386 fs_indices[leg.get('id')] = \ 5387 fs_indices.setdefault(leg.get('id'), []) + \ 5388 [i] 5389 5390 decay_lists = [] 5391 # Loop over unique final state particle ids 5392 for fs_id in set(fs_ids): 5393 # decay_list has the leg numbers and decays for this 5394 # fs particle id: 5395 # decay_list = [[[n1,d1],[n2,d2]],[[n1,d1'],[n2,d2']],...] 5396 5397 decay_list = [] 5398 5399 # Two cases: Either number of decay elements is same 5400 # as number of decaying particles: Then use the 5401 # corresponding decay for each particle. Or the number 5402 # of decay elements is different: Then use any decay 5403 # chain which defines the decay for this particle. 5404 5405 chains = [] 5406 if len(fs_legs) == len(decay_elements) and \ 5407 all([fs in ids for (fs, ids) in \ 5408 zip(fs_ids, decay_is_ids)]): 5409 # The decay of the different fs parts is given 5410 # by the different decay chains, respectively. 5411 # Chains is a list of matrix element lists 5412 for index in fs_indices[fs_id]: 5413 chains.append(filter(lambda me: \ 5414 me.get('processes')[0].\ 5415 get_initial_ids()[0] == fs_id, 5416 decay_elements[index])) 5417 5418 if len(fs_legs) != len(decay_elements) or not chains or not chains[0]: 5419 # In second case, or no chains are found 5420 # (e.g. because the order of decays is reversed), 5421 # all decays for this particle type are used 5422 chain = sum([filter(lambda me: \ 5423 me.get('processes')[0].\ 5424 get_initial_ids()[0] == fs_id, 5425 decay_chain) for decay_chain in \ 5426 decay_elements], []) 5427 5428 chains = [chain] * len(fs_numbers[fs_id]) 5429 5430 red_decay_chains = [] 5431 for prod in itertools.product(*chains): 5432 5433 # Now, need to ensure that we don't append 5434 # duplicate chain combinations, e.g. (a>bc, a>de) and 5435 # (a>de, a>bc) 5436 5437 # Remove double counting between final states 5438 if sorted([p.get('processes')[0] for p in prod], 5439 lambda x1, x2: x1.compare_for_sort(x2)) \ 5440 in red_decay_chains: 5441 continue 5442 5443 # Store already used combinations 5444 red_decay_chains.append(\ 5445 sorted([p.get('processes')[0] for p in prod], 5446 lambda x1, x2: x1.compare_for_sort(x2))) 5447 5448 # Add the decays to the list 5449 decay_list.append(zip(fs_numbers[fs_id], prod)) 5450 5451 decay_lists.append(decay_list) 5452 5453 # Finally combine all decays for this process, 5454 # and combine them, decay by decay 5455 for decays in itertools.product(*decay_lists): 5456 5457 # Generate a dictionary from leg number to decay process 5458 decay_dict = dict(sum(decays, [])) 5459 5460 # Make sure to not modify the original matrix element 5461 model_bk = core_process.get('processes')[0].get('model') 5462 # Avoid Python copying the complete model every time 5463 for i, process in enumerate(core_process.get('processes')): 5464 process.set('model',base_objects.Model()) 5465 matrix_element = copy.deepcopy(core_process) 5466 # Avoid Python copying the complete model every time 5467 for i, process in enumerate(matrix_element.get('processes')): 5468 process.set('model', model_bk) 5469 core_process.get('processes')[i].set('model', model_bk) 5470 # Need to replace Particle in all wavefunctions to avoid 5471 # deepcopy 5472 org_wfs = core_process.get_all_wavefunctions() 5473 for i, wf in enumerate(matrix_element.get_all_wavefunctions()): 5474 wf.set('particle', org_wfs[i].get('particle')) 5475 wf.set('antiparticle', org_wfs[i].get('antiparticle')) 5476 5477 # Insert the decay chains 5478 logger.info("Combine %s with decays %s" % \ 5479 (core_process.get('processes')[0].nice_string().\ 5480 replace('Process: ', ''), \ 5481 ", ".join([d.get('processes')[0].nice_string().\ 5482 replace('Process: ', '') \ 5483 for d in decay_dict.values()]))) 5484 5485 matrix_element.insert_decay_chains(decay_dict) 5486 5487 if combine: 5488 me_tag = IdentifyMETag.create_tag(\ 5489 matrix_element.get_base_amplitude(), 5490 matrix_element.get('identical_particle_factor')) 5491 try: 5492 if not combine: 5493 raise ValueError 5494 # If an identical matrix element is already in the list, 5495 # then simply add this process to the list of 5496 # processes for that matrix element 5497 me_index = me_tags.index(me_tag) 5498 except ValueError: 5499 # Otherwise, if the matrix element has any diagrams, 5500 # add this matrix element. 5501 if matrix_element.get('processes') and \ 5502 matrix_element.get('diagrams'): 5503 matrix_elements.append(matrix_element) 5504 if combine: 5505 me_tags.append(me_tag) 5506 permutations.append(me_tag[-1][0].\ 5507 get_external_numbers()) 5508 else: # try 5509 other_processes = matrix_elements[me_index].get('processes') 5510 logger.info("Combining process with %s" % \ 5511 other_processes[0].nice_string().replace('Process: ', '')) 5512 for proc in matrix_element.get('processes'): 5513 other_processes.append(HelasMultiProcess.\ 5514 reorder_process(proc, 5515 permutations[me_index], 5516 me_tag[-1][0].get_external_numbers())) 5517 5518 return matrix_elements
5519
5520 #=============================================================================== 5521 # HelasDecayChainProcessList 5522 #=============================================================================== 5523 -class HelasDecayChainProcessList(base_objects.PhysicsObjectList):
5524 """List of HelasDecayChainProcess objects 5525 """ 5526
5527 - def is_valid_element(self, obj):
5528 """Test if object obj is a valid HelasDecayChainProcess for the list.""" 5529 5530 return isinstance(obj, HelasDecayChainProcess)
5531
5532 #=============================================================================== 5533 # HelasMultiProcess 5534 #=============================================================================== 5535 -class HelasMultiProcess(base_objects.PhysicsObject):
5536 """HelasMultiProcess: If initiated with an AmplitudeList, 5537 generates the HelasMatrixElements for the Amplitudes, identifying 5538 processes with identical matrix elements""" 5539
5540 - def default_setup(self):
5541 """Default values for all properties""" 5542 5543 self['matrix_elements'] = HelasMatrixElementList()
5544
5545 - def filter(self, name, value):
5546 """Filter for valid process property values.""" 5547 5548 if name == 'matrix_elements': 5549 if not isinstance(value, HelasMatrixElementList): 5550 raise self.PhysicsObjectError, \ 5551 "%s is not a valid HelasMatrixElementList object" % str(value) 5552 return True
5553
5554 - def get_sorted_keys(self):
5555 """Return process property names as a nicely sorted list.""" 5556 5557 return ['matrix_elements']
5558
5559 - def __init__(self, argument=None, combine_matrix_elements=True, 5560 matrix_element_opts={}, compute_loop_nc = False):
5561 """Allow initialization with AmplitudeList. Matrix_element_opts are 5562 potential options to be passed to the constructor of the 5563 HelasMatrixElements created. By default it is none, but when called from 5564 LoopHelasProcess, this options will contain 'optimized_output'.""" 5565 5566 5567 if isinstance(argument, diagram_generation.AmplitudeList): 5568 super(HelasMultiProcess, self).__init__() 5569 self.set('matrix_elements', self.generate_matrix_elements(argument, 5570 combine_matrix_elements = combine_matrix_elements, 5571 matrix_element_opts=matrix_element_opts, 5572 compute_loop_nc = compute_loop_nc)) 5573 elif isinstance(argument, diagram_generation.MultiProcess): 5574 super(HelasMultiProcess, self).__init__() 5575 self.set('matrix_elements', 5576 self.generate_matrix_elements(argument.get('amplitudes'), 5577 combine_matrix_elements = combine_matrix_elements, 5578 matrix_element_opts = matrix_element_opts, 5579 compute_loop_nc = compute_loop_nc)) 5580 elif isinstance(argument, diagram_generation.Amplitude): 5581 super(HelasMultiProcess, self).__init__() 5582 self.set('matrix_elements', self.generate_matrix_elements(\ 5583 diagram_generation.AmplitudeList([argument]), 5584 combine_matrix_elements = combine_matrix_elements, 5585 matrix_element_opts = matrix_element_opts, 5586 compute_loop_nc = compute_loop_nc)) 5587 elif argument: 5588 # call the mother routine 5589 super(HelasMultiProcess, self).__init__(argument) 5590 else: 5591 # call the mother routine 5592 super(HelasMultiProcess, self).__init__()
5593
5594 - def get_used_lorentz(self):
5595 """Return a list of (lorentz_name, conjugate, outgoing) with 5596 all lorentz structures used by this HelasMultiProcess.""" 5597 helas_list = [] 5598 5599 for me in self.get('matrix_elements'): 5600 helas_list.extend(me.get_used_lorentz()) 5601 5602 return list(set(helas_list))
5603
5604 - def get_used_couplings(self):
5605 """Return a list with all couplings used by this 5606 HelasMatrixElement.""" 5607 5608 coupling_list = [] 5609 5610 for me in self.get('matrix_elements'): 5611 coupling_list.extend([c for l in me.get_used_couplings() for c in l]) 5612 5613 return list(set(coupling_list))
5614
5615 - def get_matrix_elements(self):
5616 """Extract the list of matrix elements""" 5617 5618 return self.get('matrix_elements')
5619 5620 #=========================================================================== 5621 # generate_matrix_elements 5622 #=========================================================================== 5623 5624 @classmethod
5625 - def process_color(cls,matrix_element, color_information, compute_loop_nc=None):
5626 """ Process the color information for a given matrix 5627 element made of a tree diagram. compute_loop_nc is dummy here for the 5628 tree-level Nc and present for structural reasons only.""" 5629 5630 if compute_loop_nc: 5631 raise MadGraph5Error, "The tree-level function 'process_color' "+\ 5632 " of class HelasMultiProcess cannot be called with a value for compute_loop_nc" 5633 5634 # Define the objects stored in the contained color_information 5635 for key in color_information: 5636 exec("%s=color_information['%s']"%(key,key)) 5637 5638 # Always create an empty color basis, and the 5639 # list of raw colorize objects (before 5640 # simplification) associated with amplitude 5641 col_basis = color_amp.ColorBasis() 5642 new_amp = matrix_element.get_base_amplitude() 5643 matrix_element.set('base_amplitude', new_amp) 5644 5645 colorize_obj = col_basis.create_color_dict_list(\ 5646 matrix_element.get('base_amplitude')) 5647 #list_colorize = [] 5648 #list_color_basis = [] 5649 #list_color_matrices = [] 5650 5651 try: 5652 # If the color configuration of the ME has 5653 # already been considered before, recycle 5654 # the information 5655 col_index = list_colorize.index(colorize_obj) 5656 except ValueError: 5657 # If not, create color basis and color 5658 # matrix accordingly 5659 list_colorize.append(colorize_obj) 5660 col_basis.build() 5661 list_color_basis.append(col_basis) 5662 col_matrix = color_amp.ColorMatrix(col_basis) 5663 list_color_matrices.append(col_matrix) 5664 col_index = -1 5665 logger.info(\ 5666 "Processing color information for %s" % \ 5667 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 5668 replace('Process', 'process')) 5669 else: # Found identical color 5670 logger.info(\ 5671 "Reusing existing color information for %s" % \ 5672 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 5673 replace('Process', 'process')) 5674 5675 matrix_element.set('color_basis', 5676 list_color_basis[col_index]) 5677 matrix_element.set('color_matrix', 5678 list_color_matrices[col_index])
5679 5680 # Below is the type of HelasMatrixElement which should be created by this 5681 # HelasMultiProcess class 5682 matrix_element_class = HelasMatrixElement 5683 5684 @classmethod
5685 - def generate_matrix_elements(cls, amplitudes, gen_color = True, 5686 decay_ids = [], combine_matrix_elements = True, 5687 compute_loop_nc = False, matrix_element_opts = {}):
5688 """Generate the HelasMatrixElements for the amplitudes, 5689 identifying processes with identical matrix elements, as 5690 defined by HelasMatrixElement.__eq__. Returns a 5691 HelasMatrixElementList and an amplitude map (used by the 5692 SubprocessGroup functionality). decay_ids is a list of decayed 5693 particle ids, since those should not be combined even if 5694 matrix element is identical. 5695 The compute_loop_nc sets wheter independent tracking of Nc power coming 5696 from the color loop trace is necessary or not (it is time consuming). 5697 Matrix_element_opts are potential additional options to be passed to 5698 the HelasMatrixElements constructed.""" 5699 5700 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 5701 "%s is not valid AmplitudeList" % type(amplitudes) 5702 5703 combine = combine_matrix_elements 5704 if 'mode' in matrix_element_opts and matrix_element_opts['mode']=='MadSpin': 5705 combine = False 5706 del matrix_element_opts['mode'] 5707 5708 # Keep track of already generated color objects, to reuse as 5709 # much as possible 5710 list_colorize = [] 5711 list_color_basis = [] 5712 list_color_matrices = [] 5713 5714 # Now this keeps track of the color matrices created from the loop-born 5715 # color basis. Keys are 2-tuple with the index of the loop and born basis 5716 # in the list above and the value is the resulting matrix. 5717 dict_loopborn_matrices = {} 5718 5719 # The dictionary below is simply a container for convenience to be 5720 # passed to the function process_color. 5721 color_information = { 'list_colorize' : list_colorize, 5722 'list_color_basis' : list_color_basis, 5723 'list_color_matrices' : list_color_matrices, 5724 'dict_loopborn_matrices' : dict_loopborn_matrices} 5725 5726 # List of valid matrix elements 5727 matrix_elements = HelasMatrixElementList() 5728 # List of identified matrix_elements 5729 identified_matrix_elements = [] 5730 # List of amplitude tags, synchronized with identified_matrix_elements 5731 amplitude_tags = [] 5732 # List of the external leg permutations for the amplitude_tags, 5733 # which allows to reorder the final state particles in the right way 5734 # for maximal process combination 5735 permutations = [] 5736 for amplitude in amplitudes: 5737 if isinstance(amplitude, diagram_generation.DecayChainAmplitude): 5738 # Might get multiple matrix elements from this amplitude 5739 tmp_matrix_element_list = HelasDecayChainProcess(amplitude).\ 5740 combine_decay_chain_processes(combine) 5741 # Use IdentifyMETag to check if matrix elements present 5742 matrix_element_list = [] 5743 for matrix_element in tmp_matrix_element_list: 5744 assert isinstance(matrix_element, HelasMatrixElement), \ 5745 "Not a HelasMatrixElement: %s" % matrix_element 5746 5747 # If the matrix element has no diagrams, 5748 # remove this matrix element. 5749 if not matrix_element.get('processes') or \ 5750 not matrix_element.get('diagrams'): 5751 continue 5752 5753 # Create IdentifyMETag 5754 amplitude_tag = IdentifyMETag.create_tag(\ 5755 matrix_element.get_base_amplitude()) 5756 try: 5757 if not combine: 5758 raise ValueError 5759 me_index = amplitude_tags.index(amplitude_tag) 5760 except ValueError: 5761 # Create matrix element for this amplitude 5762 matrix_element_list.append(matrix_element) 5763 if combine_matrix_elements: 5764 amplitude_tags.append(amplitude_tag) 5765 identified_matrix_elements.append(matrix_element) 5766 permutations.append(amplitude_tag[-1][0].\ 5767 get_external_numbers()) 5768 else: # try 5769 # Identical matrix element found 5770 other_processes = identified_matrix_elements[me_index].\ 5771 get('processes') 5772 # Reorder each of the processes 5773 # Since decay chain, only reorder legs_with_decays 5774 for proc in matrix_element.get('processes'): 5775 other_processes.append(cls.reorder_process(\ 5776 proc, 5777 permutations[me_index], 5778 amplitude_tag[-1][0].get_external_numbers())) 5779 logger.info("Combined %s with %s" % \ 5780 (matrix_element.get('processes')[0].\ 5781 nice_string().\ 5782 replace('Process: ', 'process '), 5783 other_processes[0].nice_string().\ 5784 replace('Process: ', 'process '))) 5785 # Go on to next matrix element 5786 continue 5787 else: # not DecayChainAmplitude 5788 # Create tag identifying the matrix element using 5789 # IdentifyMETag. If two amplitudes have the same tag, 5790 # they have the same matrix element 5791 amplitude_tag = IdentifyMETag.create_tag(amplitude) 5792 try: 5793 me_index = amplitude_tags.index(amplitude_tag) 5794 except ValueError: 5795 # Create matrix element for this amplitude 5796 logger.info("Generating Helas calls for %s" % \ 5797 amplitude.get('process').nice_string().\ 5798 replace('Process', 'process')) 5799 # Correctly choose the helas matrix element class depending 5800 # on the type of 'HelasMultiProcess' this static function 5801 # is called from. 5802 matrix_element_list = [cls.matrix_element_class(amplitude, 5803 decay_ids=decay_ids, 5804 gen_color=False, 5805 **matrix_element_opts)] 5806 me = matrix_element_list[0] 5807 if me.get('processes') and me.get('diagrams'): 5808 # Keep track of amplitude tags 5809 if combine_matrix_elements: 5810 amplitude_tags.append(amplitude_tag) 5811 identified_matrix_elements.append(me) 5812 permutations.append(amplitude_tag[-1][0].\ 5813 get_external_numbers()) 5814 else: 5815 matrix_element_list = [] 5816 else: 5817 # Identical matrix element found 5818 other_processes = identified_matrix_elements[me_index].\ 5819 get('processes') 5820 other_processes.append(cls.reorder_process(\ 5821 amplitude.get('process'), 5822 permutations[me_index], 5823 amplitude_tag[-1][0].get_external_numbers())) 5824 logger.info("Combined %s with %s" % \ 5825 (other_processes[-1].nice_string().\ 5826 replace('Process: ', 'process '), 5827 other_processes[0].nice_string().\ 5828 replace('Process: ', 'process '))) 5829 # Go on to next amplitude 5830 continue 5831 5832 # Deal with newly generated matrix elements 5833 for matrix_element in copy.copy(matrix_element_list): 5834 assert isinstance(matrix_element, HelasMatrixElement), \ 5835 "Not a HelasMatrixElement: %s" % matrix_element 5836 5837 # Add this matrix element to list 5838 matrix_elements.append(matrix_element) 5839 5840 if not gen_color: 5841 continue 5842 5843 # The treatment of color is quite different for loop amplitudes 5844 # than for regular tree ones. So the function below is overloaded 5845 # in LoopHelasProcess 5846 cls.process_color(matrix_element,color_information,\ 5847 compute_loop_nc=compute_loop_nc) 5848 5849 if not matrix_elements: 5850 raise InvalidCmd, \ 5851 "No matrix elements generated, check overall coupling orders" 5852 5853 return matrix_elements
5854 5855 @staticmethod
5856 - def reorder_process(process, org_perm, proc_perm):
5857 """Reorder the legs in the process according to the difference 5858 between org_perm and proc_perm""" 5859 5860 leglist = base_objects.LegList(\ 5861 [copy.copy(process.get('legs_with_decays')[i]) for i in \ 5862 diagram_generation.DiagramTag.reorder_permutation(\ 5863 proc_perm, org_perm)]) 5864 new_proc = copy.copy(process) 5865 new_proc.set('legs_with_decays', leglist) 5866 5867 if not new_proc.get('decay_chains'): 5868 new_proc.set('legs', leglist) 5869 5870 return new_proc
5871