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 - def get_all_amplitudes(self):
4573 """Gives a list of all amplitudes for this ME""" 4574 4575 return sum([d.get('amplitudes') for d in \ 4576 self.get('diagrams')], [])
4577
4578 - def get_external_wavefunctions(self):
4579 """Gives the external wavefunctions for this ME""" 4580 4581 external_wfs = filter(lambda wf: not wf.get('mothers'), 4582 self.get('diagrams')[0].get('wavefunctions')) 4583 4584 external_wfs.sort(lambda w1, w2: w1.get('number_external') - \ 4585 w2.get('number_external')) 4586 4587 i = 1 4588 while i < len(external_wfs): 4589 if external_wfs[i].get('number_external') == \ 4590 external_wfs[i - 1].get('number_external'): 4591 external_wfs.pop(i) 4592 else: 4593 i = i + 1 4594 return external_wfs
4595
4596 - def get_number_of_amplitudes(self):
4597 """Gives the total number of amplitudes for this ME""" 4598 4599 return sum([ len(d.get('amplitudes')) for d in \ 4600 self.get('diagrams')])
4601
4602 - def get_nexternal_ninitial(self):
4603 """Gives (number or external particles, number of 4604 incoming particles)""" 4605 4606 external_wfs = filter(lambda wf: not wf.get('mothers'), 4607 self.get_all_wavefunctions()) 4608 4609 return (len(set([wf.get('number_external') for wf in \ 4610 external_wfs])), 4611 len(set([wf.get('number_external') for wf in \ 4612 filter(lambda wf: wf.get('leg_state') == False, 4613 external_wfs)])))
4614
4615 - def get_external_masses(self):
4616 """Gives the list of the strings corresponding to the masses of the 4617 external particles.""" 4618 4619 mass_list=[] 4620 external_wfs = sorted(filter(lambda wf: wf.get('leg_state') != \ 4621 'intermediate', self.get_all_wavefunctions()),\ 4622 key=lambda w: w['number_external']) 4623 external_number=1 4624 for wf in external_wfs: 4625 if wf.get('number_external')==external_number: 4626 external_number=external_number+1 4627 mass_list.append(wf.get('particle').get('mass')) 4628 4629 return mass_list
4630
4631 - def get_helicity_combinations(self):
4632 """Gives the number of helicity combinations for external 4633 wavefunctions""" 4634 4635 if not self.get('processes'): 4636 return None 4637 4638 model = self.get('processes')[0].get('model') 4639 4640 return reduce(lambda x, y: x * y, 4641 [ len(model.get('particle_dict')[wf.get('pdg_code')].\ 4642 get_helicity_states())\ 4643 for wf in self.get_external_wavefunctions() ], 1)
4644
4645 - def get_helicity_matrix(self, allow_reverse=True):
4646 """Gives the helicity matrix for external wavefunctions""" 4647 4648 if not self.get('processes'): 4649 return None 4650 4651 process = self.get('processes')[0] 4652 model = process.get('model') 4653 4654 return apply(itertools.product, [ model.get('particle_dict')[\ 4655 wf.get('pdg_code')].get_helicity_states(allow_reverse)\ 4656 for wf in self.get_external_wavefunctions()])
4657
4658 - def get_hel_avg_factor(self):
4659 """ Calculate the denominator factor due to the average over initial 4660 state spin only """ 4661 4662 model = self.get('processes')[0].get('model') 4663 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4664 self.get('processes')[0].get('legs')) 4665 4666 return reduce(lambda x, y: x * y, 4667 [ len(model.get('particle_dict')[leg.get('id')].\ 4668 get_helicity_states())\ 4669 for leg in initial_legs ])
4670
4671 - def get_beams_hel_avg_factor(self):
4672 """ Calculate the denominator factor due to the average over initial 4673 state spin only. Returns the result for beam one and two separately 4674 so that the averaging can be done correctly for partial polarization.""" 4675 4676 model = self.get('processes')[0].get('model') 4677 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4678 self.get('processes')[0].get('legs')) 4679 4680 beam_avg_factors = [ len(model.get('particle_dict')[leg.get('id')].\ 4681 get_helicity_states()) for leg in initial_legs ] 4682 if len(beam_avg_factors)==1: 4683 # For a 1->N process, we simply return 1 for the second entry. 4684 return beam_avg_factors[0],1 4685 else: 4686 return beam_avg_factors[0],beam_avg_factors[1]
4687
4688 - def get_denominator_factor(self):
4689 """Calculate the denominator factor due to: 4690 Averaging initial state color and spin, and 4691 identical final state particles""" 4692 4693 model = self.get('processes')[0].get('model') 4694 4695 initial_legs = filter(lambda leg: leg.get('state') == False, \ 4696 self.get('processes')[0].get('legs')) 4697 4698 color_factor = reduce(lambda x, y: x * y, 4699 [ model.get('particle_dict')[leg.get('id')].\ 4700 get('color')\ 4701 for leg in initial_legs ]) 4702 4703 spin_factor = reduce(lambda x, y: x * y, 4704 [ len(model.get('particle_dict')[leg.get('id')].\ 4705 get_helicity_states())\ 4706 for leg in initial_legs ]) 4707 4708 return spin_factor * color_factor * self['identical_particle_factor']
4709
4710 - def generate_color_amplitudes(self, color_basis, diagrams):
4711 """ Return a list of (coefficient, amplitude number) lists, 4712 corresponding to the JAMPs for the HelasDiagrams and color basis passed 4713 in argument. The coefficients are given in the format (fermion factor, 4714 colorcoeff (frac), imaginary, Nc power). """ 4715 4716 if not color_basis: 4717 # No color, simply add all amplitudes with correct factor 4718 # for first color amplitude 4719 col_amp = [] 4720 for diagram in diagrams: 4721 for amplitude in diagram.get('amplitudes'): 4722 col_amp.append(((amplitude.get('fermionfactor'), 4723 1, False, 0), 4724 amplitude.get('number'))) 4725 return [col_amp] 4726 4727 # There is a color basis - create a list of coefficients and 4728 # amplitude numbers 4729 4730 col_amp_list = [] 4731 for i, col_basis_elem in \ 4732 enumerate(sorted(color_basis.keys())): 4733 4734 col_amp = [] 4735 for diag_tuple in color_basis[col_basis_elem]: 4736 res_amps = filter(lambda amp: \ 4737 tuple(amp.get('color_indices')) == diag_tuple[1], 4738 diagrams[diag_tuple[0]].get('amplitudes')) 4739 if not res_amps: 4740 raise self.PhysicsObjectError, \ 4741 """No amplitude found for color structure 4742 %s and color index chain (%s) (diagram %i)""" % \ 4743 (col_basis_elem, 4744 str(diag_tuple[1]), 4745 diag_tuple[0]) 4746 4747 for res_amp in res_amps: 4748 col_amp.append(((res_amp.get('fermionfactor'), 4749 diag_tuple[2], 4750 diag_tuple[3], 4751 diag_tuple[4]), 4752 res_amp.get('number'))) 4753 4754 col_amp_list.append(col_amp) 4755 4756 return col_amp_list
4757
4758 - def get_color_amplitudes(self):
4759 """Return a list of (coefficient, amplitude number) lists, 4760 corresponding to the JAMPs for this matrix element. The 4761 coefficients are given in the format (fermion factor, color 4762 coeff (frac), imaginary, Nc power).""" 4763 4764 return self.generate_color_amplitudes(self['color_basis'],self['diagrams'])
4765
4766 - def sort_split_orders(self, split_orders):
4767 """ Sort the 'split_orders' list given in argument so that the orders of 4768 smaller weights appear first. Do nothing if not all split orders have 4769 a hierarchy defined.""" 4770 order_hierarchy=\ 4771 self.get('processes')[0].get('model').get('order_hierarchy') 4772 if set(order_hierarchy.keys()).union(set(split_orders))==\ 4773 set(order_hierarchy.keys()): 4774 split_orders.sort(key=lambda order: order_hierarchy[order])
4775
4776 - def get_split_orders_mapping_for_diagram_list(self, diag_list, split_orders, 4777 get_amp_number_function = lambda amp: amp.get('number'), 4778 get_amplitudes_function = lambda diag: diag.get('amplitudes')):
4779 """ This a helper function for get_split_orders_mapping to return, for 4780 the HelasDiagram list given in argument, the list amp_orders detailed in 4781 the description of the 'get_split_orders_mapping' function. 4782 """ 4783 4784 order_hierarchy=\ 4785 self.get('processes')[0].get('model').get('order_hierarchy') 4786 # Find the list of amplitude numbers and what amplitude order they 4787 # correspond to. For its construction, amp_orders is a dictionary with 4788 # is then translated into an ordered list of tuples before being returned. 4789 amp_orders={} 4790 for diag in diag_list: 4791 diag_orders=diag.calculate_orders() 4792 # Add the WEIGHTED order information 4793 diag_orders['WEIGHTED']=sum(order_hierarchy[order]*value for \ 4794 order, value in diag_orders.items()) 4795 # Complement the missing split_orders with 0 4796 for order in split_orders: 4797 if not order in diag_orders.keys(): 4798 diag_orders[order]=0 4799 key = tuple([diag_orders[order] for order in split_orders]) 4800 try: 4801 amp_orders[key].extend([get_amp_number_function(amp) for amp in \ 4802 get_amplitudes_function(diag)]) 4803 except KeyError: 4804 amp_orders[key] = [get_amp_number_function(amp) for amp in \ 4805 get_amplitudes_function(diag)] 4806 amp_orders=[(order[0],tuple(order[1])) for order in amp_orders.items()] 4807 # Again make sure a proper hierarchy is defined and order the list 4808 # according to it if possible 4809 if set(order_hierarchy.keys()).union(set(split_orders))==\ 4810 set(order_hierarchy.keys()): 4811 # Order the contribution starting from the minimum WEIGHTED one 4812 amp_orders.sort(key= lambda elem: 4813 sum([order_hierarchy[split_orders[i]]*order_power for \ 4814 i, order_power in enumerate(elem[0])])) 4815 4816 return amp_orders
4817
4818 - def get_split_orders_mapping(self):
4819 """This function returns two lists, squared_orders, amp_orders. 4820 If process['split_orders'] is empty, the function returns two empty lists. 4821 4822 squared_orders : All possible contributing squared orders among those 4823 specified in the process['split_orders'] argument. The elements of 4824 the list are tuples of the format format (OrderValue1,OrderValue2,...) 4825 with OrderValue<i> correspond to the value of the <i>th order in 4826 process['split_orders'] (the others are summed over and therefore 4827 left unspecified). 4828 Ex for dijet with process['split_orders']=['QCD','QED']: 4829 => [(4,0),(2,2),(0,4)] 4830 4831 amp_orders : Exactly as for squared order except that this list specifies 4832 the contributing order values for the amplitude (i.e. not 'squared'). 4833 Also, the tuple describing the amplitude order is nested with a 4834 second one listing all amplitude numbers contributing to this order. 4835 Ex for dijet with process['split_orders']=['QCD','QED']: 4836 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))] 4837 4838 Keep in mind that the orders of the element of the list is important as 4839 it dicatates the order of the corresponding "order indices" in the 4840 code output by the exporters. 4841 """ 4842 4843 split_orders=self.get('processes')[0].get('split_orders') 4844 # If no split_orders are defined, then return the obvious 4845 if len(split_orders)==0: 4846 return (),() 4847 4848 # First make sure that the 'split_orders' are ordered according to their 4849 # weight. 4850 self.sort_split_orders(split_orders) 4851 4852 amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 4853 self.get('diagrams'),split_orders) 4854 4855 # Now we construct the interference splitting order matrix for 4856 # convenience 4857 squared_orders = [] 4858 for i, amp_order in enumerate(amp_orders): 4859 for j in range(0,i+1): 4860 key = tuple([ord1 + ord2 for ord1,ord2 in \ 4861 zip(amp_order[0],amp_orders[j][0])]) 4862 # We don't use the set structure here since keeping the 4863 # construction order guarantees us that the squared_orders will 4864 # also be ordered according to the WEIGHT. 4865 if not key in squared_orders: 4866 squared_orders.append(key) 4867 4868 return squared_orders, amp_orders
4869 4870 4871
4872 - def get_used_lorentz(self):
4873 """Return a list of (lorentz_name, conjugate_tag, outgoing) with 4874 all lorentz structures used by this HelasMatrixElement.""" 4875 4876 output = [] 4877 for wa in self.get_all_wavefunctions() + self.get_all_amplitudes(): 4878 if wa.get('interaction_id') in [0,-1]: 4879 continue 4880 output.append(wa.get_aloha_info()); 4881 4882 return output
4883
4884 - def get_used_couplings(self):
4885 """Return a list with all couplings used by this 4886 HelasMatrixElement.""" 4887 4888 tmp = [wa.get('coupling') for wa in \ 4889 self.get_all_wavefunctions() + self.get_all_amplitudes() \ 4890 if wa.get('interaction_id') not in [0,-1]] 4891 #some coupling have a minus one associated -> need to remove those 4892 return [ [t] if not t.startswith('-') else [t[1:]] for t2 in tmp for t in t2]
4893 4894
4895 - def get_mirror_processes(self):
4896 """Return a list of processes with initial states interchanged 4897 if has mirror processes""" 4898 4899 if not self.get('has_mirror_process'): 4900 return [] 4901 processes = base_objects.ProcessList() 4902 for proc in self.get('processes'): 4903 legs = copy.copy(proc.get('legs')) 4904 legs[0:2] = [legs[1],legs[0]] 4905 decay_legs = copy.copy(proc.get('legs_with_decays')) 4906 decay_legs[0:2] = [decay_legs[1],decay_legs[0]] 4907 process = copy.copy(proc) 4908 process.set('legs', legs) 4909 process.set('legs_with_decays', decay_legs) 4910 processes.append(process) 4911 return processes
4912 4913 @staticmethod
4914 - def check_equal_decay_processes(decay1, decay2):
4915 """Check if two single-sided decay processes 4916 (HelasMatrixElements) are equal. 4917 4918 Note that this has to be called before any combination of 4919 processes has occured. 4920 4921 Since a decay processes for a decay chain is always generated 4922 such that all final state legs are completely contracted 4923 before the initial state leg is included, all the diagrams 4924 will have identical wave function, independently of the order 4925 of final state particles. 4926 4927 Note that we assume that the process definitions have all 4928 external particles, corresponding to the external 4929 wavefunctions. 4930 """ 4931 4932 assert len(decay1.get('processes')) == 1 == len(decay2.get('processes')), \ 4933 "Can compare only single process HelasMatrixElements" 4934 4935 assert len(filter(lambda leg: leg.get('state') == False, \ 4936 decay1.get('processes')[0].get('legs'))) == 1 and \ 4937 len(filter(lambda leg: leg.get('state') == False, \ 4938 decay2.get('processes')[0].get('legs'))) == 1, \ 4939 "Call to check_decay_processes_equal requires " + \ 4940 "both processes to be unique" 4941 4942 # Compare bulk process properties (number of external legs, 4943 # identity factors, number of diagrams, number of wavefunctions 4944 # initial leg, final state legs 4945 if len(decay1.get('processes')[0].get("legs")) != \ 4946 len(decay2.get('processes')[0].get("legs")) or \ 4947 len(decay1.get('diagrams')) != len(decay2.get('diagrams')) or \ 4948 decay1.get('identical_particle_factor') != \ 4949 decay2.get('identical_particle_factor') or \ 4950 sum(len(d.get('wavefunctions')) for d in \ 4951 decay1.get('diagrams')) != \ 4952 sum(len(d.get('wavefunctions')) for d in \ 4953 decay2.get('diagrams')) or \ 4954 decay1.get('processes')[0].get('legs')[0].get('id') != \ 4955 decay2.get('processes')[0].get('legs')[0].get('id') or \ 4956 sorted([leg.get('id') for leg in \ 4957 decay1.get('processes')[0].get('legs')[1:]]) != \ 4958 sorted([leg.get('id') for leg in \ 4959 decay2.get('processes')[0].get('legs')[1:]]): 4960 return False 4961 4962 # Run a quick check to see if the processes are already 4963 # identical (i.e., the wavefunctions are in the same order) 4964 if [leg.get('id') for leg in \ 4965 decay1.get('processes')[0].get('legs')] == \ 4966 [leg.get('id') for leg in \ 4967 decay2.get('processes')[0].get('legs')] and \ 4968 decay1 == decay2: 4969 return True 4970 4971 # Now check if all diagrams are identical. This is done by a 4972 # recursive function starting from the last wavefunction 4973 # (corresponding to the initial state), since it is the 4974 # same steps for each level in mother wavefunctions 4975 4976 amplitudes2 = copy.copy(reduce(lambda a1, d2: a1 + \ 4977 d2.get('amplitudes'), 4978 decay2.get('diagrams'), [])) 4979 4980 for amplitude1 in reduce(lambda a1, d2: a1 + d2.get('amplitudes'), 4981 decay1.get('diagrams'), []): 4982 foundamplitude = False 4983 for amplitude2 in amplitudes2: 4984 if HelasMatrixElement.check_equal_wavefunctions(\ 4985 amplitude1.get('mothers')[-1], 4986 amplitude2.get('mothers')[-1]): 4987 foundamplitude = True 4988 # Remove amplitude2, since it has already been matched 4989 amplitudes2.remove(amplitude2) 4990 break 4991 if not foundamplitude: 4992 return False 4993 4994 return True
4995 4996 @staticmethod
4997 - def check_equal_wavefunctions(wf1, wf2):
4998 """Recursive function to check if two wavefunctions are equal. 4999 First check that mothers have identical pdg codes, then repeat for 5000 all mothers with identical pdg codes.""" 5001 5002 # End recursion with False if the wavefunctions do not have 5003 # the same mother pdgs 5004 if sorted([wf.get('pdg_code') for wf in wf1.get('mothers')]) != \ 5005 sorted([wf.get('pdg_code') for wf in wf2.get('mothers')]): 5006 return False 5007 5008 # End recursion with True if these are external wavefunctions 5009 # (note that we have already checked that the pdgs are 5010 # identical) 5011 if not wf1.get('mothers') and not wf2.get('mothers'): 5012 return True 5013 5014 mothers2 = copy.copy(wf2.get('mothers')) 5015 5016 for mother1 in wf1.get('mothers'): 5017 # Compare mother1 with all mothers in wf2 that have not 5018 # yet been used and have identical pdg codes 5019 equalmothers = filter(lambda wf: wf.get('pdg_code') == \ 5020 mother1.get('pdg_code'), 5021 mothers2) 5022 foundmother = False 5023 for mother2 in equalmothers: 5024 if HelasMatrixElement.check_equal_wavefunctions(\ 5025 mother1, mother2): 5026 foundmother = True 5027 # Remove mother2, since it has already been matched 5028 mothers2.remove(mother2) 5029 break 5030 if not foundmother: 5031 return False 5032 5033 return True
5034 5035 @staticmethod
5036 - def sorted_mothers(arg):
5037 """Gives a list of mother wavefunctions sorted according to 5038 1. The order of the particles in the interaction 5039 2. Cyclic reordering of particles in same spin group 5040 3. Fermions ordered IOIOIO... according to the pairs in 5041 the interaction.""" 5042 5043 assert isinstance(arg, (HelasWavefunction, HelasAmplitude)), \ 5044 "%s is not a valid HelasWavefunction or HelasAmplitude" % repr(arg) 5045 5046 if not arg.get('interaction_id'): 5047 return arg.get('mothers') 5048 5049 my_pdg_code = 0 5050 my_spin = 0 5051 if isinstance(arg, HelasWavefunction): 5052 my_pdg_code = arg.get_anti_pdg_code() 5053 my_spin = arg.get_spin_state_number() 5054 5055 sorted_mothers, my_index = arg.get('mothers').sort_by_pdg_codes(\ 5056 arg.get('pdg_codes'), my_pdg_code) 5057 5058 # If fermion, partner is the corresponding fermion flow partner 5059 partner = None 5060 if isinstance(arg, HelasWavefunction) and arg.is_fermion(): 5061 # Fermion case, just pick out the fermion flow partner 5062 if my_index % 2 == 0: 5063 # partner is after arg 5064 partner_index = my_index 5065 else: 5066 # partner is before arg 5067 partner_index = my_index - 1 5068 partner = sorted_mothers.pop(partner_index) 5069 # If partner is incoming, move to before arg 5070 if partner.get_spin_state_number() > 0: 5071 my_index = partner_index 5072 else: 5073 my_index = partner_index + 1 5074 5075 # Reorder fermions pairwise according to incoming/outgoing 5076 for i in range(0, len(sorted_mothers), 2): 5077 if sorted_mothers[i].is_fermion(): 5078 # This is a fermion, order between this fermion and its brother 5079 if sorted_mothers[i].get_spin_state_number() > 0 and \ 5080 sorted_mothers[i + 1].get_spin_state_number() < 0: 5081 # Switch places between outgoing and incoming 5082 sorted_mothers = sorted_mothers[:i] + \ 5083 [sorted_mothers[i+1], sorted_mothers[i]] + \ 5084 sorted_mothers[i+2:] 5085 elif sorted_mothers[i].get_spin_state_number() < 0 and \ 5086 sorted_mothers[i + 1].get_spin_state_number() > 0: 5087 # This is the right order 5088 pass 5089 else: 5090 # No more fermions in sorted_mothers 5091 break 5092 5093 # Put back partner into sorted_mothers 5094 if partner: 5095 sorted_mothers.insert(partner_index, partner) 5096 5097 # Next sort according to spin_state_number 5098 return HelasWavefunctionList(sorted_mothers)
5099
5100 #=============================================================================== 5101 # HelasMatrixElementList 5102 #=============================================================================== 5103 -class HelasMatrixElementList(base_objects.PhysicsObjectList):
5104 """List of HelasMatrixElement objects 5105 """ 5106
5107 - def is_valid_element(self, obj):
5108 """Test if object obj is a valid HelasMatrixElement for the list.""" 5109 5110 return isinstance(obj, HelasMatrixElement)
5111
5112 - def remove(self,obj):
5113 pos = (i for i in xrange(len(self)) if self[i] is obj) 5114 for i in pos: 5115 del self[i] 5116 break
5117
5118 #=============================================================================== 5119 # HelasDecayChainProcess 5120 #=============================================================================== 5121 -class HelasDecayChainProcess(base_objects.PhysicsObject):
5122 """HelasDecayChainProcess: If initiated with a DecayChainAmplitude 5123 object, generates the HelasMatrixElements for the core process(es) 5124 and decay chains. Then call combine_decay_chain_processes in order 5125 to generate the matrix elements for all combined processes.""" 5126
5127 - def default_setup(self):
5128 """Default values for all properties""" 5129 5130 self['core_processes'] = HelasMatrixElementList() 5131 self['decay_chains'] = HelasDecayChainProcessList()
5132
5133 - def filter(self, name, value):
5134 """Filter for valid process property values.""" 5135 5136 if name == 'core_processes': 5137 if not isinstance(value, HelasMatrixElementList): 5138 raise self.PhysicsObjectError, \ 5139 "%s is not a valid HelasMatrixElementList object" % \ 5140 str(value) 5141 5142 if name == 'decay_chains': 5143 if not isinstance(value, HelasDecayChainProcessList): 5144 raise self.PhysicsObjectError, \ 5145 "%s is not a valid HelasDecayChainProcessList object" % \ 5146 str(value) 5147 5148 return True
5149
5150 - def get_sorted_keys(self):
5151 """Return process property names as a nicely sorted list.""" 5152 5153 return ['core_processes', 'decay_chains']
5154
5155 - def __init__(self, argument=None):
5156 """Allow initialization with DecayChainAmplitude""" 5157 5158 if isinstance(argument, diagram_generation.DecayChainAmplitude): 5159 super(HelasDecayChainProcess, self).__init__() 5160 self.generate_matrix_elements(argument) 5161 elif argument: 5162 # call the mother routine 5163 super(HelasDecayChainProcess, self).__init__(argument) 5164 else: 5165 # call the mother routine 5166 super(HelasDecayChainProcess, self).__init__()
5167
5168 - def nice_string(self, indent = 0):
5169 """Returns a nicely formatted string of the matrix element processes.""" 5170 5171 mystr = "" 5172 5173 for process in self.get('core_processes'): 5174 mystr += process.get('processes')[0].nice_string(indent) + "\n" 5175 5176 if self.get('decay_chains'): 5177 mystr += " " * indent + "Decays:\n" 5178 for dec in self.get('decay_chains'): 5179 mystr += dec.nice_string(indent + 2) + "\n" 5180 5181 return mystr[:-1]
5182
5183 - def generate_matrix_elements(self, dc_amplitude):
5184 """Generate the HelasMatrixElements for the core processes and 5185 decay processes (separately)""" 5186 5187 assert isinstance(dc_amplitude, diagram_generation.DecayChainAmplitude), \ 5188 "%s is not a valid DecayChainAmplitude" % dc_amplitude 5189 5190 5191 # Extract the pdg codes of all particles decayed by decay chains 5192 # since these should not be combined in a MultiProcess 5193 decay_ids = dc_amplitude.get_decay_ids() 5194 5195 matrix_elements = HelasMultiProcess.generate_matrix_elements(\ 5196 dc_amplitude.get('amplitudes'), 5197 False, 5198 decay_ids) 5199 5200 self.set('core_processes', matrix_elements) 5201 5202 while dc_amplitude.get('decay_chains'): 5203 # Pop the amplitude to save memory space 5204 decay_chain = dc_amplitude.get('decay_chains').pop(0) 5205 self['decay_chains'].append(HelasDecayChainProcess(\ 5206 decay_chain))
5207 5208
5209 - def combine_decay_chain_processes(self, combine=True):
5210 """Recursive function to generate complete 5211 HelasMatrixElements, combining the core process with the decay 5212 chains. 5213 5214 * If the number of decay chains is the same as the number of 5215 decaying particles, apply each decay chain to the corresponding 5216 final state particle. 5217 * If the number of decay chains and decaying final state particles 5218 don't correspond, all decays applying to a given particle type are 5219 combined (without double counting). 5220 * combine allow to merge identical ME 5221 """ 5222 5223 # End recursion when there are no more decay chains 5224 if not self['decay_chains']: 5225 # Just return the list of matrix elements 5226 return self['core_processes'] 5227 5228 # decay_elements is a list of HelasMatrixElementLists with 5229 # all decay processes 5230 decay_elements = [] 5231 5232 for decay_chain in self['decay_chains']: 5233 # This is where recursion happens 5234 decay_elements.append(decay_chain.combine_decay_chain_processes(combine)) 5235 5236 # Store the result in matrix_elements 5237 matrix_elements = HelasMatrixElementList() 5238 # Store matrix element tags in me_tags, for precise comparison 5239 me_tags = [] 5240 # Store external id permutations 5241 permutations = [] 5242 5243 # List of list of ids for the initial state legs in all decay 5244 # processes 5245 decay_is_ids = [[element.get('processes')[0].get_initial_ids()[0] \ 5246 for element in elements] 5247 for elements in decay_elements] 5248 5249 while self['core_processes']: 5250 # Pop the process to save memory space 5251 core_process = self['core_processes'].pop(0) 5252 # Get all final state legs that have a decay chain defined 5253 fs_legs = filter(lambda leg: any([any([id == leg.get('id') for id \ 5254 in is_ids]) for is_ids in decay_is_ids]), 5255 core_process.get('processes')[0].get_final_legs()) 5256 # List of ids for the final state legs 5257 fs_ids = [leg.get('id') for leg in fs_legs] 5258 # Create a dictionary from id to (index, leg number) 5259 fs_numbers = {} 5260 fs_indices = {} 5261 for i, leg in enumerate(fs_legs): 5262 fs_numbers[leg.get('id')] = \ 5263 fs_numbers.setdefault(leg.get('id'), []) + \ 5264 [leg.get('number')] 5265 fs_indices[leg.get('id')] = \ 5266 fs_indices.setdefault(leg.get('id'), []) + \ 5267 [i] 5268 5269 decay_lists = [] 5270 # Loop over unique final state particle ids 5271 for fs_id in set(fs_ids): 5272 # decay_list has the leg numbers and decays for this 5273 # fs particle id: 5274 # decay_list = [[[n1,d1],[n2,d2]],[[n1,d1'],[n2,d2']],...] 5275 5276 decay_list = [] 5277 5278 # Two cases: Either number of decay elements is same 5279 # as number of decaying particles: Then use the 5280 # corresponding decay for each particle. Or the number 5281 # of decay elements is different: Then use any decay 5282 # chain which defines the decay for this particle. 5283 5284 chains = [] 5285 if len(fs_legs) == len(decay_elements) and \ 5286 all([fs in ids for (fs, ids) in \ 5287 zip(fs_ids, decay_is_ids)]): 5288 # The decay of the different fs parts is given 5289 # by the different decay chains, respectively. 5290 # Chains is a list of matrix element lists 5291 for index in fs_indices[fs_id]: 5292 chains.append(filter(lambda me: \ 5293 me.get('processes')[0].\ 5294 get_initial_ids()[0] == fs_id, 5295 decay_elements[index])) 5296 5297 if len(fs_legs) != len(decay_elements) or not chains or not chains[0]: 5298 # In second case, or no chains are found 5299 # (e.g. because the order of decays is reversed), 5300 # all decays for this particle type are used 5301 chain = sum([filter(lambda me: \ 5302 me.get('processes')[0].\ 5303 get_initial_ids()[0] == fs_id, 5304 decay_chain) for decay_chain in \ 5305 decay_elements], []) 5306 5307 chains = [chain] * len(fs_numbers[fs_id]) 5308 5309 red_decay_chains = [] 5310 for prod in itertools.product(*chains): 5311 5312 # Now, need to ensure that we don't append 5313 # duplicate chain combinations, e.g. (a>bc, a>de) and 5314 # (a>de, a>bc) 5315 5316 # Remove double counting between final states 5317 if sorted([p.get('processes')[0] for p in prod], 5318 lambda x1, x2: x1.compare_for_sort(x2)) \ 5319 in red_decay_chains: 5320 continue 5321 5322 # Store already used combinations 5323 red_decay_chains.append(\ 5324 sorted([p.get('processes')[0] for p in prod], 5325 lambda x1, x2: x1.compare_for_sort(x2))) 5326 5327 # Add the decays to the list 5328 decay_list.append(zip(fs_numbers[fs_id], prod)) 5329 5330 decay_lists.append(decay_list) 5331 5332 # Finally combine all decays for this process, 5333 # and combine them, decay by decay 5334 for decays in itertools.product(*decay_lists): 5335 5336 # Generate a dictionary from leg number to decay process 5337 decay_dict = dict(sum(decays, [])) 5338 5339 # Make sure to not modify the original matrix element 5340 model_bk = core_process.get('processes')[0].get('model') 5341 # Avoid Python copying the complete model every time 5342 for i, process in enumerate(core_process.get('processes')): 5343 process.set('model',base_objects.Model()) 5344 matrix_element = copy.deepcopy(core_process) 5345 # Avoid Python copying the complete model every time 5346 for i, process in enumerate(matrix_element.get('processes')): 5347 process.set('model', model_bk) 5348 core_process.get('processes')[i].set('model', model_bk) 5349 # Need to replace Particle in all wavefunctions to avoid 5350 # deepcopy 5351 org_wfs = core_process.get_all_wavefunctions() 5352 for i, wf in enumerate(matrix_element.get_all_wavefunctions()): 5353 wf.set('particle', org_wfs[i].get('particle')) 5354 wf.set('antiparticle', org_wfs[i].get('antiparticle')) 5355 5356 # Insert the decay chains 5357 logger.info("Combine %s with decays %s" % \ 5358 (core_process.get('processes')[0].nice_string().\ 5359 replace('Process: ', ''), \ 5360 ", ".join([d.get('processes')[0].nice_string().\ 5361 replace('Process: ', '') \ 5362 for d in decay_dict.values()]))) 5363 5364 matrix_element.insert_decay_chains(decay_dict) 5365 5366 if combine: 5367 me_tag = IdentifyMETag.create_tag(\ 5368 matrix_element.get_base_amplitude(), 5369 matrix_element.get('identical_particle_factor')) 5370 try: 5371 if not combine: 5372 raise ValueError 5373 # If an identical matrix element is already in the list, 5374 # then simply add this process to the list of 5375 # processes for that matrix element 5376 me_index = me_tags.index(me_tag) 5377 except ValueError: 5378 # Otherwise, if the matrix element has any diagrams, 5379 # add this matrix element. 5380 if matrix_element.get('processes') and \ 5381 matrix_element.get('diagrams'): 5382 matrix_elements.append(matrix_element) 5383 if combine: 5384 me_tags.append(me_tag) 5385 permutations.append(me_tag[-1][0].\ 5386 get_external_numbers()) 5387 else: # try 5388 other_processes = matrix_elements[me_index].get('processes') 5389 logger.info("Combining process with %s" % \ 5390 other_processes[0].nice_string().replace('Process: ', '')) 5391 for proc in matrix_element.get('processes'): 5392 other_processes.append(HelasMultiProcess.\ 5393 reorder_process(proc, 5394 permutations[me_index], 5395 me_tag[-1][0].get_external_numbers())) 5396 5397 return matrix_elements
5398
5399 #=============================================================================== 5400 # HelasDecayChainProcessList 5401 #=============================================================================== 5402 -class HelasDecayChainProcessList(base_objects.PhysicsObjectList):
5403 """List of HelasDecayChainProcess objects 5404 """ 5405
5406 - def is_valid_element(self, obj):
5407 """Test if object obj is a valid HelasDecayChainProcess for the list.""" 5408 5409 return isinstance(obj, HelasDecayChainProcess)
5410
5411 #=============================================================================== 5412 # HelasMultiProcess 5413 #=============================================================================== 5414 -class HelasMultiProcess(base_objects.PhysicsObject):
5415 """HelasMultiProcess: If initiated with an AmplitudeList, 5416 generates the HelasMatrixElements for the Amplitudes, identifying 5417 processes with identical matrix elements""" 5418
5419 - def default_setup(self):
5420 """Default values for all properties""" 5421 5422 self['matrix_elements'] = HelasMatrixElementList()
5423
5424 - def filter(self, name, value):
5425 """Filter for valid process property values.""" 5426 5427 if name == 'matrix_elements': 5428 if not isinstance(value, HelasMatrixElementList): 5429 raise self.PhysicsObjectError, \ 5430 "%s is not a valid HelasMatrixElementList object" % str(value) 5431 return True
5432
5433 - def get_sorted_keys(self):
5434 """Return process property names as a nicely sorted list.""" 5435 5436 return ['matrix_elements']
5437
5438 - def __init__(self, argument=None, combine_matrix_elements=True, 5439 matrix_element_opts={}, compute_loop_nc = False):
5440 """Allow initialization with AmplitudeList. Matrix_element_opts are 5441 potential options to be passed to the constructor of the 5442 HelasMatrixElements created. By default it is none, but when called from 5443 LoopHelasProcess, this options will contain 'optimized_output'.""" 5444 5445 5446 if isinstance(argument, diagram_generation.AmplitudeList): 5447 super(HelasMultiProcess, self).__init__() 5448 self.set('matrix_elements', self.generate_matrix_elements(argument, 5449 combine_matrix_elements = combine_matrix_elements, 5450 matrix_element_opts=matrix_element_opts, 5451 compute_loop_nc = compute_loop_nc)) 5452 elif isinstance(argument, diagram_generation.MultiProcess): 5453 super(HelasMultiProcess, self).__init__() 5454 self.set('matrix_elements', 5455 self.generate_matrix_elements(argument.get('amplitudes'), 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.Amplitude): 5460 super(HelasMultiProcess, self).__init__() 5461 self.set('matrix_elements', self.generate_matrix_elements(\ 5462 diagram_generation.AmplitudeList([argument]), 5463 combine_matrix_elements = combine_matrix_elements, 5464 matrix_element_opts = matrix_element_opts, 5465 compute_loop_nc = compute_loop_nc)) 5466 elif argument: 5467 # call the mother routine 5468 super(HelasMultiProcess, self).__init__(argument) 5469 else: 5470 # call the mother routine 5471 super(HelasMultiProcess, self).__init__()
5472
5473 - def get_used_lorentz(self):
5474 """Return a list of (lorentz_name, conjugate, outgoing) with 5475 all lorentz structures used by this HelasMultiProcess.""" 5476 helas_list = [] 5477 5478 for me in self.get('matrix_elements'): 5479 helas_list.extend(me.get_used_lorentz()) 5480 5481 return list(set(helas_list))
5482
5483 - def get_used_couplings(self):
5484 """Return a list with all couplings used by this 5485 HelasMatrixElement.""" 5486 5487 coupling_list = [] 5488 5489 for me in self.get('matrix_elements'): 5490 coupling_list.extend([c for l in me.get_used_couplings() for c in l]) 5491 5492 return list(set(coupling_list))
5493
5494 - def get_matrix_elements(self):
5495 """Extract the list of matrix elements""" 5496 5497 return self.get('matrix_elements')
5498 5499 #=========================================================================== 5500 # generate_matrix_elements 5501 #=========================================================================== 5502 5503 @classmethod
5504 - def process_color(cls,matrix_element, color_information, compute_loop_nc=None):
5505 """ Process the color information for a given matrix 5506 element made of a tree diagram. compute_loop_nc is dummy here for the 5507 tree-level Nc and present for structural reasons only.""" 5508 5509 if compute_loop_nc: 5510 raise MadGraph5Error, "The tree-level function 'process_color' "+\ 5511 " of class HelasMultiProcess cannot be called with a value for compute_loop_nc" 5512 5513 # Define the objects stored in the contained color_information 5514 for key in color_information: 5515 exec("%s=color_information['%s']"%(key,key)) 5516 5517 # Always create an empty color basis, and the 5518 # list of raw colorize objects (before 5519 # simplification) associated with amplitude 5520 col_basis = color_amp.ColorBasis() 5521 new_amp = matrix_element.get_base_amplitude() 5522 matrix_element.set('base_amplitude', new_amp) 5523 5524 colorize_obj = col_basis.create_color_dict_list(\ 5525 matrix_element.get('base_amplitude')) 5526 #list_colorize = [] 5527 #list_color_basis = [] 5528 #list_color_matrices = [] 5529 5530 try: 5531 # If the color configuration of the ME has 5532 # already been considered before, recycle 5533 # the information 5534 col_index = list_colorize.index(colorize_obj) 5535 except ValueError: 5536 # If not, create color basis and color 5537 # matrix accordingly 5538 list_colorize.append(colorize_obj) 5539 col_basis.build() 5540 list_color_basis.append(col_basis) 5541 col_matrix = color_amp.ColorMatrix(col_basis) 5542 list_color_matrices.append(col_matrix) 5543 col_index = -1 5544 logger.info(\ 5545 "Processing color information for %s" % \ 5546 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 5547 replace('Process', 'process')) 5548 else: # Found identical color 5549 logger.info(\ 5550 "Reusing existing color information for %s" % \ 5551 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 5552 replace('Process', 'process')) 5553 5554 matrix_element.set('color_basis', 5555 list_color_basis[col_index]) 5556 matrix_element.set('color_matrix', 5557 list_color_matrices[col_index])
5558 5559 # Below is the type of HelasMatrixElement which should be created by this 5560 # HelasMultiProcess class 5561 matrix_element_class = HelasMatrixElement 5562 5563 @classmethod
5564 - def generate_matrix_elements(cls, amplitudes, gen_color = True, 5565 decay_ids = [], combine_matrix_elements = True, 5566 compute_loop_nc = False, matrix_element_opts = {}):
5567 """Generate the HelasMatrixElements for the amplitudes, 5568 identifying processes with identical matrix elements, as 5569 defined by HelasMatrixElement.__eq__. Returns a 5570 HelasMatrixElementList and an amplitude map (used by the 5571 SubprocessGroup functionality). decay_ids is a list of decayed 5572 particle ids, since those should not be combined even if 5573 matrix element is identical. 5574 The compute_loop_nc sets wheter independent tracking of Nc power coming 5575 from the color loop trace is necessary or not (it is time consuming). 5576 Matrix_element_opts are potential additional options to be passed to 5577 the HelasMatrixElements constructed.""" 5578 5579 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 5580 "%s is not valid AmplitudeList" % type(amplitudes) 5581 5582 combine = combine_matrix_elements 5583 if 'mode' in matrix_element_opts and matrix_element_opts['mode']=='MadSpin': 5584 combine = False 5585 del matrix_element_opts['mode'] 5586 5587 # Keep track of already generated color objects, to reuse as 5588 # much as possible 5589 list_colorize = [] 5590 list_color_basis = [] 5591 list_color_matrices = [] 5592 5593 # Now this keeps track of the color matrices created from the loop-born 5594 # color basis. Keys are 2-tuple with the index of the loop and born basis 5595 # in the list above and the value is the resulting matrix. 5596 dict_loopborn_matrices = {} 5597 5598 # The dictionary below is simply a container for convenience to be 5599 # passed to the function process_color. 5600 color_information = { 'list_colorize' : list_colorize, 5601 'list_color_basis' : list_color_basis, 5602 'list_color_matrices' : list_color_matrices, 5603 'dict_loopborn_matrices' : dict_loopborn_matrices} 5604 5605 # List of valid matrix elements 5606 matrix_elements = HelasMatrixElementList() 5607 # List of identified matrix_elements 5608 identified_matrix_elements = [] 5609 # List of amplitude tags, synchronized with identified_matrix_elements 5610 amplitude_tags = [] 5611 # List of the external leg permutations for the amplitude_tags, 5612 # which allows to reorder the final state particles in the right way 5613 # for maximal process combination 5614 permutations = [] 5615 for amplitude in amplitudes: 5616 if isinstance(amplitude, diagram_generation.DecayChainAmplitude): 5617 # Might get multiple matrix elements from this amplitude 5618 tmp_matrix_element_list = HelasDecayChainProcess(amplitude).\ 5619 combine_decay_chain_processes(combine) 5620 # Use IdentifyMETag to check if matrix elements present 5621 matrix_element_list = [] 5622 for matrix_element in tmp_matrix_element_list: 5623 assert isinstance(matrix_element, HelasMatrixElement), \ 5624 "Not a HelasMatrixElement: %s" % matrix_element 5625 5626 # If the matrix element has no diagrams, 5627 # remove this matrix element. 5628 if not matrix_element.get('processes') or \ 5629 not matrix_element.get('diagrams'): 5630 continue 5631 5632 # Create IdentifyMETag 5633 amplitude_tag = IdentifyMETag.create_tag(\ 5634 matrix_element.get_base_amplitude()) 5635 try: 5636 if not combine: 5637 raise ValueError 5638 me_index = amplitude_tags.index(amplitude_tag) 5639 except ValueError: 5640 # Create matrix element for this amplitude 5641 matrix_element_list.append(matrix_element) 5642 if combine_matrix_elements: 5643 amplitude_tags.append(amplitude_tag) 5644 identified_matrix_elements.append(matrix_element) 5645 permutations.append(amplitude_tag[-1][0].\ 5646 get_external_numbers()) 5647 else: # try 5648 # Identical matrix element found 5649 other_processes = identified_matrix_elements[me_index].\ 5650 get('processes') 5651 # Reorder each of the processes 5652 # Since decay chain, only reorder legs_with_decays 5653 for proc in matrix_element.get('processes'): 5654 other_processes.append(cls.reorder_process(\ 5655 proc, 5656 permutations[me_index], 5657 amplitude_tag[-1][0].get_external_numbers())) 5658 logger.info("Combined %s with %s" % \ 5659 (matrix_element.get('processes')[0].\ 5660 nice_string().\ 5661 replace('Process: ', 'process '), 5662 other_processes[0].nice_string().\ 5663 replace('Process: ', 'process '))) 5664 # Go on to next matrix element 5665 continue 5666 else: # not DecayChainAmplitude 5667 # Create tag identifying the matrix element using 5668 # IdentifyMETag. If two amplitudes have the same tag, 5669 # they have the same matrix element 5670 amplitude_tag = IdentifyMETag.create_tag(amplitude) 5671 try: 5672 me_index = amplitude_tags.index(amplitude_tag) 5673 except ValueError: 5674 # Create matrix element for this amplitude 5675 logger.info("Generating Helas calls for %s" % \ 5676 amplitude.get('process').nice_string().\ 5677 replace('Process', 'process')) 5678 # Correctly choose the helas matrix element class depending 5679 # on the type of 'HelasMultiProcess' this static function 5680 # is called from. 5681 matrix_element_list = [cls.matrix_element_class(amplitude, 5682 decay_ids=decay_ids, 5683 gen_color=False, 5684 **matrix_element_opts)] 5685 me = matrix_element_list[0] 5686 if me.get('processes') and me.get('diagrams'): 5687 # Keep track of amplitude tags 5688 if combine_matrix_elements: 5689 amplitude_tags.append(amplitude_tag) 5690 identified_matrix_elements.append(me) 5691 permutations.append(amplitude_tag[-1][0].\ 5692 get_external_numbers()) 5693 else: 5694 matrix_element_list = [] 5695 else: 5696 # Identical matrix element found 5697 other_processes = identified_matrix_elements[me_index].\ 5698 get('processes') 5699 other_processes.append(cls.reorder_process(\ 5700 amplitude.get('process'), 5701 permutations[me_index], 5702 amplitude_tag[-1][0].get_external_numbers())) 5703 logger.info("Combined %s with %s" % \ 5704 (other_processes[-1].nice_string().\ 5705 replace('Process: ', 'process '), 5706 other_processes[0].nice_string().\ 5707 replace('Process: ', 'process '))) 5708 # Go on to next amplitude 5709 continue 5710 5711 # Deal with newly generated matrix elements 5712 for matrix_element in copy.copy(matrix_element_list): 5713 assert isinstance(matrix_element, HelasMatrixElement), \ 5714 "Not a HelasMatrixElement: %s" % matrix_element 5715 5716 # Add this matrix element to list 5717 matrix_elements.append(matrix_element) 5718 5719 if not gen_color: 5720 continue 5721 5722 # The treatment of color is quite different for loop amplitudes 5723 # than for regular tree ones. So the function below is overloaded 5724 # in LoopHelasProcess 5725 cls.process_color(matrix_element,color_information,\ 5726 compute_loop_nc=compute_loop_nc) 5727 5728 if not matrix_elements: 5729 raise InvalidCmd, \ 5730 "No matrix elements generated, check overall coupling orders" 5731 5732 return matrix_elements
5733 5734 @staticmethod
5735 - def reorder_process(process, org_perm, proc_perm):
5736 """Reorder the legs in the process according to the difference 5737 between org_perm and proc_perm""" 5738 5739 leglist = base_objects.LegList(\ 5740 [copy.copy(process.get('legs_with_decays')[i]) for i in \ 5741 diagram_generation.DiagramTag.reorder_permutation(\ 5742 proc_perm, org_perm)]) 5743 new_proc = copy.copy(process) 5744 new_proc.set('legs_with_decays', leglist) 5745 5746 if not new_proc.get('decay_chains'): 5747 new_proc.set('legs', leglist) 5748 5749 return new_proc
5750