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