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