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