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