Package madgraph :: Package iolibs :: Module helas_call_writers
[hide private]
[frames] | no frames]

Source Code for Module madgraph.iolibs.helas_call_writers

   1  ################################################################################ 
   2  # 
   3  # Copyright (c) 2010 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  """Classes for writing Helas calls. HelasCallWriter is the base class.""" 
  16  from __future__ import absolute_import 
  17   
  18   
  19  import re 
  20  import madgraph.core.base_objects as base_objects 
  21  import madgraph.core.helas_objects as helas_objects 
  22  import madgraph.loop.loop_helas_objects as loop_helas_objects 
  23  import models.check_param_card as check_param_card 
  24  import aloha.aloha_writers as aloha_writers 
  25  import aloha 
  26  from madgraph import MadGraph5Error 
  27  import madgraph.various.misc as misc 
  28  from six.moves import range 
29 30 -class HelasWriterError(Exception):
31 """Class for the error of this module """ 32 pass
33
34 35 #=============================================================================== 36 # HelasCallWriter 37 #=============================================================================== 38 -class HelasCallWriter(base_objects.PhysicsObject):
39 """Language independent base class for writing Helas calls. The 40 calls are stored in two dictionaries, wavefunctions and 41 amplitudes, with entries being a mapping from a set of spin, 42 incoming/outgoing states and Lorentz structure to a function which 43 writes the corresponding wavefunction/amplitude call (taking a 44 HelasWavefunction/HelasAmplitude as argument).""" 45 46 # Dictionaries used for automatic generation of Helas calls 47 # Dictionaries from spin states to letters in Helas call 48 mother_dict = {1: 'S', 2: 'O', -2: 'I', 3: 'V', 5: 'T', 4:'OR', -4:'IR', 49 99:'P'} 50 51 @staticmethod
53 """ Place holder for PLUGIN/... 54 (used by madevent output for small width handling) 55 """ 56 return call, arg
57 58 @staticmethod
60 return call,arg
61 #customize_argument_for_all_other_helas_object = fct_customize_argument_for_all_other_helas_object 62 #default_customize_argument_for_all_other_helas_object = fct_customize_argument_for_all_other_helas_object 63
64 - def default_setup(self):
65 66 self['model'] = base_objects.Model() 67 self['wavefunctions'] = {} 68 self['amplitudes'] = {} 69 self.width_tchannel_set_tozero = False
70
71 - def filter(self, name, value):
72 """Filter for model property values""" 73 74 if name == 'model': 75 if not isinstance(value, base_objects.Model): 76 raise self.PhysicsObjectError("Object of type %s is not a model" % type(value)) 77 78 if name == 'wavefunctions': 79 # Should be a dictionary of functions returning strings, 80 # with keys (spins, flow state) 81 if not isinstance(value, dict): 82 raise self.PhysicsObjectError("%s is not a valid dictionary for wavefunction" % \ 83 str(value)) 84 85 for key in value.keys(): 86 self.add_wavefunction(key, value[key]) 87 88 if name == 'amplitudes': 89 # Should be a dictionary of functions returning strings, 90 # with keys (spins, flow state) 91 if not isinstance(value, dict): 92 raise self.PhysicsObjectError("%s is not a valid dictionary for amplitude" % \ 93 str(value)) 94 95 for key in value.keys(): 96 self.add_amplitude(key, value[key]) 97 98 return True
99
100 - def get_sorted_keys(self):
101 """Return process property names as a nicely sorted list.""" 102 103 return ['model', 'wavefunctions', 'amplitudes']
104 105 106 107 108 109
110 - def get_loop_amp_helas_calls(self, matrix_element):
111 """Return a list of strings, corresponding to the Helas calls 112 for building loop amplitudes (AMPL) only.""" 113 114 assert isinstance(matrix_element, loop_helas_objects.LoopHelasMatrixElement), \ 115 "%s not valid argument for get_loop_amp_helas_calls" % \ 116 repr(matrix_element) 117 118 res = [] 119 120 for diagram in matrix_element.get_loop_diagrams(): 121 res.append("# Loop amplitude for loop diagram with ID %d" % \ 122 diagram.get('number')) 123 for amplitude in diagram.get_loop_amplitudes(): 124 # Substitute the proc_prefix 125 res.append(self.get_amplitude_call(amplitude)) 126 127 return res
128
129 - def get_sqso_target_skip_code(self, number_checked, sqso_target_numbers, 130 continue_label, split_orders, squared_orders, comment):
131 """Treat the optimization for the squared order target so that 132 unnecessary computations can be skipped. This function returns the lines 133 of codes for doing so, based on the number_checked (typically amplitude number) 134 to be checked and the list of squared order contributions 135 sqso_target_numbers specifying for each of them the maximum contributing 136 number. The continue_label informs where the code must 'goto' if indeed 137 the rest of this part of the computation must be skipped. 138 The split_orders and squared_orders values lists, as well as the string 139 comment template is just for printing out a nice comment in the fortran 140 code explaining what is going on.""" 141 142 res = [] 143 for sqso_index, target in enumerate(sqso_target_numbers): 144 if target!=number_checked: 145 continue 146 sqso_name = ' '.join(['%s=%d'%(split_orders[i],val) for i, \ 147 val in enumerate(squared_orders[sqso_index][0])]) 148 sqso_identifier = "(%s), i.e. of split order ID=%d,"\ 149 %(sqso_name, sqso_index+1) 150 res.append(comment%sqso_identifier) 151 res.append("IF(FILTER_SO.AND.SQSO_TARGET."+\ 152 "EQ.%d) GOTO %d"%(sqso_index+1,continue_label)) 153 return res
154
155 - def get_born_ct_helas_calls(self, matrix_element, include_CT=True, 156 squared_orders=[], split_orders=[]):
157 """Return a two lists of strings, the first corresponding to the Helas 158 calls for building the non-loop wavefunctions, the born and CT (only if 159 include_CT=True). The second correspond to the Helas calls for the 160 UVCT amplitudes only. The squared_orders can provide the information of 161 what is the maximum contributing CT amp number.""" 162 163 assert isinstance(matrix_element, loop_helas_objects.LoopHelasMatrixElement), \ 164 "%s not valid argument for get_born_ct_helas_calls" % \ 165 repr(matrix_element) 166 167 res = [] 168 169 sqso_max_uvctamp = [sqso[1][0] for sqso in squared_orders] 170 sqso_max_ctamp = [sqso[1][1] for sqso in squared_orders] 171 172 for diagram in matrix_element.get_born_diagrams(): 173 res.extend([ self.get_wavefunction_call(wf) for \ 174 wf in diagram.get('wavefunctions') ]) 175 res.append("# Amplitude(s) for born diagram with ID %d" % \ 176 diagram.get('number')) 177 for amplitude in diagram.get('amplitudes'): 178 res.append(self.get_amplitude_call(amplitude)) 179 180 for diagram in matrix_element.get_loop_diagrams(): 181 res.extend([ self.get_wavefunction_call(wf) for \ 182 wf in diagram.get('wavefunctions') ]) 183 if diagram.get_ct_amplitudes() and include_CT: 184 res.append("# Counter-term amplitude(s) for loop diagram number %d" % \ 185 diagram.get('number')) 186 for ctamp in diagram.get_ct_amplitudes(): 187 res.append(self.get_amplitude_call(ctamp)) 188 res.extend(self.get_sqso_target_skip_code(ctamp.get('number'), 189 sqso_max_ctamp, 2000, split_orders, squared_orders, 190 "# At this point, all CT amps needed for %s are computed.")) 191 192 if not include_CT: 193 return res, [] 194 195 res_UVCT = [] 196 for diagram in matrix_element.get_loop_UVCT_diagrams(): 197 res_UVCT.extend([ self.get_wavefunction_call(wf) for \ 198 wf in diagram.get('wavefunctions') ]) 199 res_UVCT.append("# Amplitude(s) for UVCT diagram with ID %d" % \ 200 diagram.get('number')) 201 for ctamp in diagram.get('amplitudes'): 202 res_UVCT.append(self.get_amplitude_call(ctamp)) 203 res_UVCT.extend(self.get_sqso_target_skip_code(ctamp.get('number'), 204 sqso_max_uvctamp, 3000, split_orders, squared_orders, 205 "# At this point, all UVCT amps needed for %s are computed.")) 206 207 return res, res_UVCT
208
209 - def get_loop_matrix_element_calls(self, loop_matrix_element):
210 """Return a list of strings, corresponding to the Helas calls 211 for the loop matrix element""" 212 213 res_born_CT, res_UVCT = self.get_born_ct_helas_calls(loop_matrix_element) 214 res = res_born_CT + res_UVCT + \ 215 self.get_loop_amp_helas_calls(loop_matrix_element) 216 return res
217 218
219 - def get_matrix_element_calls(self, matrix_element):
220 """Return a list of strings, corresponding to the Helas calls 221 for the matrix element""" 222 223 assert isinstance(matrix_element, helas_objects.HelasMatrixElement), \ 224 "%s not valid argument for get_matrix_element_calls" % \ 225 type(matrix_element) 226 227 # Do not reuse the wavefunctions for loop matrix elements 228 if isinstance(matrix_element, loop_helas_objects.LoopHelasMatrixElement): 229 return self.get_loop_matrix_element_calls(matrix_element) 230 231 me = matrix_element.get('diagrams') 232 matrix_element.reuse_outdated_wavefunctions(me) 233 234 res = [] 235 for diagram in matrix_element.get('diagrams'): 236 237 238 res.extend([ self.get_wavefunction_call(wf) for \ 239 wf in diagram.get('wavefunctions') ]) 240 res.append("# Amplitude(s) for diagram number %d" % \ 241 diagram.get('number')) 242 for amplitude in diagram.get('amplitudes'): 243 res.append(self.get_amplitude_call(amplitude)) 244 245 return res
246
247 - def get_wavefunction_calls(self, wavefunctions):
248 """Return a list of strings, corresponding to the Helas calls 249 for the matrix element""" 250 251 assert isinstance(wavefunctions, helas_objects.HelasWavefunctionList), \ 252 "%s not valid argument for get_wavefunction_calls" % \ 253 repr(wavefunctions) 254 255 res = [self.get_wavefunction_call(wf) for wf in wavefunctions] 256 257 return res
258
259 - def get_amplitude_calls(self, matrix_element):
260 """Return a list of strings, corresponding to the Helas calls 261 for the matrix element""" 262 263 assert isinstance(matrix_element, helas_objects.HelasMatrixElement), \ 264 "%s not valid argument for get_matrix_element_calls" % \ 265 repr(matrix_element) 266 267 res = [] 268 for diagram in matrix_element.get('diagrams'): 269 res.append("# Amplitude(s) for diagram number %d" % \ 270 diagram.get('number')) 271 for amplitude in diagram.get('amplitudes'): 272 res.append(self.get_amplitude_call(amplitude)) 273 274 return res
275
276 - def get_wavefunction_call(self, wavefunction):
277 """Return the function for writing the wavefunction 278 corresponding to the key""" 279 280 try: 281 #misc.sprint(wavefunction['number_external']) 282 call = self["wavefunctions"][wavefunction.get_call_key()](\ 283 wavefunction) 284 except KeyError as error: 285 return "" 286 287 if self.options['zerowidth_tchannel'] and wavefunction.is_t_channel(): 288 call, n = re.subn(',\s*fk_(?!ZERO)\w*\s*,', ', ZERO,', str(call), flags=re.I) 289 if n: 290 self.width_tchannel_set_tozero = True 291 return call
292 293
294 - def get_amplitude_call(self, amplitude):
295 """Return the function for writing the amplitude 296 corresponding to the key""" 297 298 try: 299 call = self["amplitudes"][amplitude.get_call_key()] 300 except KeyError as error: 301 return "" 302 else: 303 return call(amplitude)
304
305 - def add_wavefunction(self, key, function):
306 """Set the function for writing the wavefunction 307 corresponding to the key""" 308 309 assert isinstance(key, tuple), \ 310 "%s is not a valid tuple for wavefunction key" % key 311 312 assert callable(function), \ 313 "%s is not a valid function for wavefunction string" % function 314 315 self.get('wavefunctions')[key] = function 316 return True
317
318 - def add_amplitude(self, key, function):
319 """Set the function for writing the amplitude 320 corresponding to the key""" 321 322 assert isinstance(key, tuple), \ 323 "%s is not a valid tuple for amplitude key" % str(key) 324 325 assert callable(function), \ 326 "%s is not a valid function for amplitude string" % str(function) 327 328 329 self.get('amplitudes')[key] = function 330 return True
331
332 - def get_model_name(self):
333 """Return the model name""" 334 return self['model'].get('name')
335 336 # Customized constructor 337
338 - def __init__(self, argument={}, options={}):
339 """Allow generating a HelasCallWriter from a Model 340 """ 341 342 default_options = {'zerowidth_tchannel': True} 343 344 self.options = dict(default_options) 345 self.options.update(options) 346 347 if isinstance(argument, base_objects.Model): 348 super(HelasCallWriter, self).__init__() 349 self.set('model', argument) 350 else: 351 super(HelasCallWriter, self).__init__(argument)
352
353 #=============================================================================== 354 # FortranHelasCallWriter 355 #=============================================================================== 356 -class FortranHelasCallWriter(HelasCallWriter):
357 """The class for writing Helas calls in Fortran, starting from 358 HelasWavefunctions and HelasAmplitudes. 359 360 Includes the function generate_helas_call, which automatically 361 generates the Fortran Helas call based on the Lorentz structure of 362 the interaction.""" 363 364 # Dictionaries used for automatic generation of Helas calls 365 # Dictionaries from spin states to letters in Helas call 366 self_dict = {1: 'H', 2: 'F', -2: 'F', 3: 'J', 5: 'U'} 367 # Dictionaries used for sorting the letters in the Helas call 368 sort_wf = {'O': 0, 'I': 1, 'S': 2, 'T': 3, 'V': 4} 369 sort_amp = {'S': 0, 'V': 2, 'T': 1, 'O': 3, 'I': 4} 370
371 - def default_setup(self):
372 """Set up special Helas calls (wavefunctions and amplitudes) 373 that can not be done automatically by generate_helas_call""" 374 375 super(FortranHelasCallWriter, self).default_setup() 376 377 # Add special fortran Helas calls, which are not automatically 378 # generated 379 380 # Gluon 4-vertex division tensor calls ggT for the FR sm and mssm 381 382 key = ((3, 3, 5, 3,tuple()), ('A',)) 383 call = lambda wf: \ 384 "CALL UVVAXX(W(1,%d),W(1,%d),%s,zero,zero,zero,W(1,%d))" % \ 385 (FortranHelasCallWriter.sorted_mothers(wf)[0].get('me_id'), 386 FortranHelasCallWriter.sorted_mothers(wf)[1].get('me_id'), 387 wf.get('coupling')[0], 388 wf.get('me_id')) 389 self.add_wavefunction(key, call) 390 391 key = ((3, 5, 3, 1,tuple()), ('A',)) 392 call = lambda wf: \ 393 "CALL JVTAXX(W(1,%d),W(1,%d),%s,zero,zero,W(1,%d))" % \ 394 (FortranHelasCallWriter.sorted_mothers(wf)[0].get('me_id'), 395 FortranHelasCallWriter.sorted_mothers(wf)[1].get('me_id'), 396 wf.get('coupling')[0], 397 wf.get('me_id')) 398 self.add_wavefunction(key, call) 399 400 key = ((3, 3, 5), ('A',)) 401 call = lambda amp: \ 402 "CALL VVTAXX(W(1,%d),W(1,%d),W(1,%d),%s,zero,AMP(%d))" % \ 403 (FortranHelasCallWriter.sorted_mothers(amp)[0].get('me_id'), 404 FortranHelasCallWriter.sorted_mothers(amp)[1].get('me_id'), 405 FortranHelasCallWriter.sorted_mothers(amp)[2].get('me_id'), 406 amp.get('coupling')[0], 407 amp.get('number')) 408 self.add_amplitude(key, call) 409 410 # SM gluon 4-vertex components 411 412 key = ((3, 3, 3, 3, 1,tuple()), ('gggg3',)) 413 call = lambda wf: \ 414 "CALL JGGGXX(W(1,%d),W(1,%d),W(1,%d),%s,W(1,%d))" % \ 415 (FortranHelasCallWriter.sorted_mothers(wf)[1].get('me_id'), 416 FortranHelasCallWriter.sorted_mothers(wf)[0].get('me_id'), 417 FortranHelasCallWriter.sorted_mothers(wf)[2].get('me_id'), 418 wf.get('coupling')[0], 419 wf.get('me_id')) 420 self.add_wavefunction(key, call) 421 key = ((3, 3, 3, 3), ('gggg1',)) 422 call = lambda amp: \ 423 "CALL GGGGXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),%s,AMP(%d))" % \ 424 (FortranHelasCallWriter.sorted_mothers(amp)[0].get('me_id'), 425 FortranHelasCallWriter.sorted_mothers(amp)[1].get('me_id'), 426 FortranHelasCallWriter.sorted_mothers(amp)[2].get('me_id'), 427 FortranHelasCallWriter.sorted_mothers(amp)[3].get('me_id'), 428 amp.get('coupling')[0], 429 amp.get('number')) 430 self.add_amplitude(key, call) 431 key = ((3, 3, 3, 3, 1 ,tuple()), ('gggg2',)) 432 call = lambda wf: \ 433 "CALL JGGGXX(W(1,%d),W(1,%d),W(1,%d),%s,W(1,%d))" % \ 434 (FortranHelasCallWriter.sorted_mothers(wf)[0].get('me_id'), 435 FortranHelasCallWriter.sorted_mothers(wf)[2].get('me_id'), 436 FortranHelasCallWriter.sorted_mothers(wf)[1].get('me_id'), 437 wf.get('coupling')[0], 438 wf.get('me_id')) 439 self.add_wavefunction(key, call) 440 key = ((3, 3, 3, 3), ('gggg2',)) 441 call = lambda amp: \ 442 "CALL GGGGXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),%s,AMP(%d))" % \ 443 (FortranHelasCallWriter.sorted_mothers(amp)[2].get('me_id'), 444 FortranHelasCallWriter.sorted_mothers(amp)[0].get('me_id'), 445 FortranHelasCallWriter.sorted_mothers(amp)[1].get('me_id'), 446 FortranHelasCallWriter.sorted_mothers(amp)[3].get('me_id'), 447 amp.get('coupling')[0], 448 amp.get('number')) 449 self.add_amplitude(key, call) 450 key = ((3, 3, 3, 3, 1,tuple()), ('gggg1',)) 451 call = lambda wf: \ 452 "CALL JGGGXX(W(1,%d),W(1,%d),W(1,%d),%s,W(1,%d))" % \ 453 (FortranHelasCallWriter.sorted_mothers(wf)[2].get('me_id'), 454 FortranHelasCallWriter.sorted_mothers(wf)[1].get('me_id'), 455 FortranHelasCallWriter.sorted_mothers(wf)[0].get('me_id'), 456 wf.get('coupling')[0], 457 wf.get('me_id')) 458 self.add_wavefunction(key, call) 459 key = ((3, 3, 3, 3), ('gggg3',)) 460 call = lambda amp: \ 461 "CALL GGGGXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),%s,AMP(%d))" % \ 462 (FortranHelasCallWriter.sorted_mothers(amp)[1].get('me_id'), 463 FortranHelasCallWriter.sorted_mothers(amp)[2].get('me_id'), 464 FortranHelasCallWriter.sorted_mothers(amp)[0].get('me_id'), 465 FortranHelasCallWriter.sorted_mothers(amp)[3].get('me_id'), 466 amp.get('coupling')[0], 467 amp.get('number')) 468 self.add_amplitude(key, call) 469 470 # HEFT VVVS calls 471 472 key = ((1, 3, 3, 3, 3,tuple()), ('',)) 473 call = lambda wf: \ 474 "CALL JVVSXX(W(1,%d),W(1,%d),W(1,%d),DUM1,%s,%s,%s,W(1,%d))" % \ 475 (wf.get('mothers')[0].get('me_id'), 476 wf.get('mothers')[1].get('me_id'), 477 wf.get('mothers')[2].get('me_id'), 478 wf.get('coupling')[0], 479 wf.get('mass'), 480 wf.get('width'), 481 wf.get('me_id')) 482 self.add_wavefunction(key, call) 483 484 key = ((3, 3, 3, 1, 4,tuple()), ('',)) 485 call = lambda wf: \ 486 "CALL HVVVXX(W(1,%d),W(1,%d),W(1,%d),DUM1,%s,%s,%s,W(1,%d))" % \ 487 (wf.get('mothers')[0].get('me_id'), 488 wf.get('mothers')[1].get('me_id'), 489 wf.get('mothers')[2].get('me_id'), 490 wf.get('coupling')[0], 491 wf.get('mass'), 492 wf.get('width'), 493 wf.get('me_id')) 494 self.add_wavefunction(key, call) 495 496 key = ((1, 3, 3, 3), ('',)) 497 call = lambda amp: \ 498 "CALL VVVSXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),DUM1,%s,AMP(%d))" % \ 499 (amp.get('mothers')[0].get('me_id'), 500 amp.get('mothers')[1].get('me_id'), 501 amp.get('mothers')[2].get('me_id'), 502 amp.get('mothers')[3].get('me_id'), 503 amp.get('coupling')[0], 504 amp.get('number')) 505 self.add_amplitude(key, call) 506 507 # HEFT VVVS calls 508 509 key = ((1, 3, 3, 3, 1,tuple()), ('',)) 510 call = lambda wf: \ 511 "CALL JVVSXX(W(1,%d),W(1,%d),W(1,%d),DUM1,%s,%s,%s,W(1,%d))" % \ 512 (wf.get('mothers')[0].get('me_id'), 513 wf.get('mothers')[1].get('me_id'), 514 wf.get('mothers')[2].get('me_id'), 515 wf.get('coupling')[0], 516 wf.get('mass'), 517 wf.get('width'), 518 wf.get('me_id')) 519 self.add_wavefunction(key, call) 520 521 key = ((3, 3, 3, 1, 4,tuple()), ('',)) 522 call = lambda wf: \ 523 "CALL HVVVXX(W(1,%d),W(1,%d),W(1,%d),DUM1,%s,%s,%s,W(1,%d))" % \ 524 (wf.get('mothers')[0].get('me_id'), 525 wf.get('mothers')[1].get('me_id'), 526 wf.get('mothers')[2].get('me_id'), 527 wf.get('coupling')[0], 528 wf.get('mass'), 529 wf.get('width'), 530 wf.get('me_id')) 531 self.add_wavefunction(key, call) 532 533 key = ((1, 3, 3, 3), ('',)) 534 call = lambda amp: \ 535 "CALL VVVSXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),DUM1,%s,AMP(%d))" % \ 536 (amp.get('mothers')[0].get('me_id'), 537 amp.get('mothers')[1].get('me_id'), 538 amp.get('mothers')[2].get('me_id'), 539 amp.get('mothers')[3].get('me_id'), 540 amp.get('coupling')[0], 541 amp.get('number')) 542 self.add_amplitude(key, call) 543 544 # Spin2 Helas Routine 545 key = ((-2, 2, 5), ('',)) 546 call = lambda amp: \ 547 "CALL IOTXXX(W(1,%d),W(1,%d),W(1,%d),%s,%s,AMP(%d))" % \ 548 (amp.get('mothers')[0].get('me_id'), 549 amp.get('mothers')[1].get('me_id'), 550 amp.get('mothers')[2].get('me_id'), 551 amp.get('coupling')[0], 552 amp.get('mothers')[0].get('mass'), 553 amp.get('number')) 554 self.add_amplitude(key, call) 555 556 key = ((-2, 2, 5, 3,tuple()), ('',)) 557 call = lambda wf: \ 558 "CALL UIOXXX(W(1,%d),W(1,%d),%s,%s,%s,%s,W(1,%d))" % \ 559 (wf.get('mothers')[0].get('me_id'), 560 wf.get('mothers')[1].get('me_id'), 561 wf.get('coupling')[0], 562 wf.get('mothers')[0].get('mass'), 563 wf.get('mass'), 564 wf.get('width'), 565 wf.get('me_id')) 566 self.add_wavefunction(key, call) 567 568 key = ((3,3,3,5),('',)) 569 call = lambda amp: \ 570 "CALL VVVTXX(W(1,%d),W(1,%d),W(1,%d),W(1,%d),1d0,%s,AMP(%d))" % \ 571 (amp.get('mothers')[0].get('me_id'), 572 amp.get('mothers')[1].get('me_id'), 573 amp.get('mothers')[2].get('me_id'), 574 amp.get('mothers')[3].get('me_id'), 575 amp.get('coupling')[0], 576 amp.get('number')) 577 self.add_amplitude(key, call) 578 579 key = ((3,3,5),('',)) 580 call = lambda amp: \ 581 "CALL VVTXXX(W(1,%d),W(1,%d),W(1,%d),%s,%s,AMP(%d))" % \ 582 (amp.get('mothers')[0].get('me_id'), 583 amp.get('mothers')[1].get('me_id'), 584 amp.get('mothers')[2].get('me_id'), 585 amp.get('coupling')[0], 586 amp.get('mothers')[0].get('mass'), 587 amp.get('number')) 588 self.add_amplitude(key, call)
589 590
591 - def get_wavefunction_call(self, wavefunction):
592 """Return the function for writing the wavefunction 593 corresponding to the key. If the function doesn't exist, 594 generate_helas_call is called to automatically create the 595 function.""" 596 597 if wavefunction.get('spin') == 1 and \ 598 wavefunction.get('interaction_id') != 0: 599 # Special feature: For HVS vertices with the two 600 # scalars different, we need extra minus sign in front 601 # of coupling for one of the two scalars since the HVS 602 # is asymmetric in the two scalars 603 wavefunction.set_scalar_coupling_sign(self['model']) 604 605 val = super(FortranHelasCallWriter, self).get_wavefunction_call(wavefunction) 606 607 if val: 608 return val 609 610 # If function not already existing, try to generate it. 611 612 if len(wavefunction.get('mothers')) > 3: 613 raise self.PhysicsObjectError("""Automatic generation of Fortran wavefunctions not 614 implemented for > 3 mothers""") 615 616 self.generate_helas_call(wavefunction) 617 return super(FortranHelasCallWriter, self).get_wavefunction_call(\ 618 wavefunction)
619
620 - def get_amplitude_call(self, amplitude):
621 """Return the function for writing the amplitude corresponding 622 to the key. If the function doesn't exist, generate_helas_call 623 is called to automatically create the function.""" 624 625 val = super(FortranHelasCallWriter, self).get_amplitude_call(amplitude) 626 627 if val: 628 return val 629 630 # If function not already existing, try to generate it. 631 632 if len(amplitude.get('mothers')) > 4: 633 raise self.PhysicsObjectError("""Automatic generation of Fortran amplitudes not 634 implemented for > 4 mothers""") 635 636 self.generate_helas_call(amplitude) 637 return super(FortranHelasCallWriter, self).get_amplitude_call(amplitude)
638
639 - def generate_helas_call(self, argument):
640 """Routine for automatic generation of Fortran Helas calls 641 according to just the spin structure of the interaction. 642 643 First the call string is generated, using a dictionary to go 644 from the spin state of the calling wavefunction and its 645 mothers, or the mothers of the amplitude, to letters. 646 647 Then the call function is generated, as a lambda which fills 648 the call string with the information of the calling 649 wavefunction or amplitude. The call has different structure, 650 depending on the spin of the wavefunction and the number of 651 mothers (multiplicity of the vertex). The mother 652 wavefunctions, when entering the call, must be sorted in the 653 correct way - this is done by the sorted_mothers routine. 654 655 Finally the call function is stored in the relevant 656 dictionary, in order to be able to reuse the function the next 657 time a wavefunction with the same Lorentz structure is needed. 658 """ 659 660 if not isinstance(argument, helas_objects.HelasWavefunction) and \ 661 not isinstance(argument, helas_objects.HelasAmplitude): 662 raise self.PhysicsObjectError("get_helas_call must be called with wavefunction or amplitude") 663 664 call = "CALL " 665 666 call_function = None 667 668 if isinstance(argument, helas_objects.HelasAmplitude) and \ 669 argument.get('interaction_id') == 0: 670 call = "#" 671 call_function = lambda amp: call 672 673 self.add_amplitude(argument.get_call_key(), call_function) 674 return 675 676 if isinstance(argument, helas_objects.HelasWavefunction) and \ 677 not argument.get('mothers'): 678 # String is just IXXXXX, OXXXXX, VXXXXX or SXXXXX 679 call = call + HelasCallWriter.mother_dict[\ 680 argument.get_spin_state_number()] 681 # Fill out with X up to 6 positions 682 call = call + 'X' * (11 - len(call)) 683 call = call + "(P(0,%d)," 684 if argument.get('spin') != 1: 685 # For non-scalars, need mass and helicity 686 call = call + "%s,NHEL(%d)," 687 call = call + "%+d*IC(%d),W(1,%d))" 688 if argument.get('spin') == 1: 689 call_function = lambda wf: call % \ 690 (wf.get('number_external'), 691 # For boson, need initial/final here 692 (-1) ** (wf.get('state') == 'initial'), 693 wf.get('number_external'), 694 wf.get('me_id')) 695 elif argument.is_boson(): 696 call_function = lambda wf: call % \ 697 (wf.get('number_external'), 698 wf.get('mass'), 699 wf.get('number_external'), 700 # For boson, need initial/final here 701 (-1) ** (wf.get('state') == 'initial'), 702 wf.get('number_external'), 703 wf.get('me_id')) 704 else: 705 call_function = lambda wf: call % \ 706 (wf.get('number_external'), 707 wf.get('mass'), 708 wf.get('number_external'), 709 # For fermions, need particle/antiparticle 710 - (-1) ** wf.get_with_flow('is_part'), 711 wf.get('number_external'), 712 wf.get('me_id')) 713 else: 714 # String is FOVXXX, FIVXXX, JIOXXX etc. 715 if isinstance(argument, helas_objects.HelasWavefunction): 716 call = call + \ 717 FortranHelasCallWriter.self_dict[\ 718 argument.get_spin_state_number()] 719 720 mother_letters = FortranHelasCallWriter.sorted_letters(argument) 721 722 # If Lorentz structure is given, by default add this 723 # to call name 724 lor_name = argument.get('lorentz')[0] 725 726 # Take care of special case: WWWW or WWVV calls 727 if len(lor_name) > 3 and lor_name[:2] == "WW": 728 if lor_name[:4] == "WWWW": 729 mother_letters = "WWWW"[:len(mother_letters)] 730 if lor_name[:4] == "WWVV": 731 mother_letters = "W3W3"[:len(mother_letters)] 732 lor_name = lor_name[4:] 733 734 call = call + mother_letters 735 call = call + lor_name 736 737 # Check if we need to append a charge conjugation flag 738 if argument.needs_hermitian_conjugate(): 739 call = call + 'C' 740 741 assert len(call) < 12, "Call to Helas routine %s should be maximum 6 chars" \ 742 % call[5:] 743 744 # Fill out with X up to 6 positions 745 call = call + 'X' * (11 - len(call)) + '(' 746 # Wavefunctions 747 call = call + "W(1,%d)," * len(argument.get('mothers')) 748 # Couplings 749 call = call + "%s," 750 751 752 if isinstance(argument, helas_objects.HelasWavefunction): 753 # Extra dummy coupling for 4-vector vertices 754 if argument.get('lorentz') == ['WWVV']: 755 # SM W3W3 vertex 756 call = call + "1D0," 757 elif argument.get('lorentz') == ['WWWW']: 758 # SM WWWW vertex 759 call = call + "0D0," 760 elif argument.get('spin') == 3 and \ 761 [wf.get('spin') for wf in argument.get('mothers')] == \ 762 [3, 3, 3]: 763 # All other 4-vector vertices (FR) - note that gggg 764 # has already been defined 765 call = call + "DUM0," 766 # Mass and width 767 call = call + "%s,%s," 768 # New wavefunction 769 call = call + "W(1,%d))" 770 else: 771 # Extra dummy coupling for 4-particle vertices 772 # Need to replace later with the correct type 773 if argument.get('lorentz') == ['WWVV']: 774 # SM W3W3 vertex 775 call = call + "1D0," 776 elif argument.get('lorentz') == ['WWWW']: 777 # SM WWWW vertex 778 call = call + "0D0," 779 elif [wf.get('spin') for wf in argument.get('mothers')] == \ 780 [3, 3, 3, 3]: 781 # Other 4-vector vertices (FR) - note that gggg 782 # has already been defined 783 call = call + "DUM0," 784 # Amplitude 785 call = call + "AMP(%d))" 786 787 if isinstance(argument, helas_objects.HelasWavefunction): 788 # Create call for wavefunction 789 if len(argument.get('mothers')) == 2: 790 call_function = lambda wf: call % \ 791 (FortranHelasCallWriter.sorted_mothers(wf)[0].\ 792 get('me_id'), 793 FortranHelasCallWriter.sorted_mothers(wf)[1].\ 794 get('me_id'), 795 ','.join(wf.get_with_flow('coupling')), 796 wf.get('mass'), 797 wf.get('width'), 798 wf.get('me_id')) 799 else: 800 call_function = lambda wf: call % \ 801 (FortranHelasCallWriter.sorted_mothers(wf)[0].\ 802 get('me_id'), 803 FortranHelasCallWriter.sorted_mothers(wf)[1].\ 804 get('me_id'), 805 FortranHelasCallWriter.sorted_mothers(wf)[2].\ 806 get('me_id'), 807 ','.join(wf.get_with_flow('coupling')), 808 wf.get('mass'), 809 wf.get('width'), 810 wf.get('me_id')) 811 else: 812 # Create call for amplitude 813 if len(argument.get('mothers')) == 3: 814 call_function = lambda amp: call % \ 815 (FortranHelasCallWriter.sorted_mothers(amp)[0].\ 816 get('me_id'), 817 FortranHelasCallWriter.sorted_mothers(amp)[1].\ 818 get('me_id'), 819 FortranHelasCallWriter.sorted_mothers(amp)[2].\ 820 get('me_id'), 821 822 ','.join(amp.get('coupling')), 823 amp.get('number')) 824 else: 825 call_function = lambda amp: call % \ 826 (FortranHelasCallWriter.sorted_mothers(amp)[0].\ 827 get('me_id'), 828 FortranHelasCallWriter.sorted_mothers(amp)[1].\ 829 get('me_id'), 830 FortranHelasCallWriter.sorted_mothers(amp)[2].\ 831 get('me_id'), 832 FortranHelasCallWriter.sorted_mothers(amp)[3].\ 833 get('me_id'), 834 ','.join(amp.get('coupling')), 835 amp.get('number')) 836 837 # Add the constructed function to wavefunction or amplitude dictionary 838 if isinstance(argument, helas_objects.HelasWavefunction): 839 self.add_wavefunction(argument.get_call_key(), call_function) 840 else: 841 self.add_amplitude(argument.get_call_key(), call_function)
842 843 # Static helper functions 844 845 @staticmethod
846 - def sorted_letters(arg):
847 """Gives a list of letters sorted according to 848 the order of letters in the Fortran Helas calls""" 849 850 if isinstance(arg, helas_objects.HelasWavefunction): 851 return "".join(sorted([HelasCallWriter.mother_dict[\ 852 wf.get_spin_state_number()] for wf in arg.get('mothers')], 853 key= lambda l: FortranHelasCallWriter.sort_wf[l], 854 reverse=True)) 855 856 if isinstance(arg, helas_objects.HelasAmplitude): 857 return "".join(sorted([HelasCallWriter.mother_dict[\ 858 wf.get_spin_state_number()] for wf in arg.get('mothers')], 859 key= lambda l: FortranHelasCallWriter.sort_amp[l], 860 reverse=True))
861 862 @staticmethod
863 - def sorted_mothers(arg):
864 """Gives a list of mother wavefunctions sorted according to 865 1. The order of the particles in the interaction 866 2. Cyclic reordering of particles in same spin group 867 3. Fermions ordered IOIOIO... according to the pairs in 868 the interaction.""" 869 870 assert isinstance(arg, (helas_objects.HelasWavefunction, helas_objects.HelasAmplitude)), \ 871 "%s is not a valid HelasWavefunction or HelasAmplitude" % repr(arg) 872 873 if not arg.get('interaction_id'): 874 return arg.get('mothers') 875 my_pdg_code = 0 876 my_spin = 0 877 if isinstance(arg, helas_objects.HelasWavefunction): 878 my_pdg_code = arg.get_anti_pdg_code() 879 my_spin = arg.get_spin_state_number() 880 881 sorted_mothers, my_index = arg.get('mothers').sort_by_pdg_codes(\ 882 arg.get('pdg_codes'), my_pdg_code) 883 884 # If fermion, partner is the corresponding fermion flow partner 885 partner = None 886 if isinstance(arg, helas_objects.HelasWavefunction) and arg.is_fermion(): 887 # Fermion case, just pick out the fermion flow partner 888 if my_index % 2 == 0: 889 # partner is after arg 890 partner_index = my_index 891 else: 892 # partner is before arg 893 partner_index = my_index - 1 894 partner = sorted_mothers.pop(partner_index) 895 # If partner is incoming, move to before arg 896 if partner.get_spin_state_number() > 0: 897 my_index = partner_index 898 else: 899 my_index = partner_index + 1 900 901 # Reorder fermions pairwise according to incoming/outgoing 902 for i in range(0, len(sorted_mothers), 2): 903 if sorted_mothers[i].is_fermion(): 904 # This is a fermion, order between this fermion and its brother 905 if sorted_mothers[i].get_spin_state_number() > 0 and \ 906 sorted_mothers[i + 1].get_spin_state_number() < 0: 907 # Switch places between outgoing and incoming 908 sorted_mothers = sorted_mothers[:i] + \ 909 [sorted_mothers[i+1], sorted_mothers[i]] + \ 910 sorted_mothers[i+2:] 911 elif sorted_mothers[i].get_spin_state_number() < 0 and \ 912 sorted_mothers[i + 1].get_spin_state_number() > 0: 913 # This is the right order 914 pass 915 else: 916 # No more fermions in sorted_mothers 917 break 918 919 # Put back partner into sorted_mothers 920 if partner: 921 sorted_mothers.insert(partner_index, partner) 922 923 same_spin_mothers = [] 924 if isinstance(arg, helas_objects.HelasWavefunction): 925 # Pick out mothers with same spin, for cyclic reordering 926 same_spin_index = -1 927 i=0 928 while i < len(sorted_mothers): 929 if abs(sorted_mothers[i].get_spin_state_number()) == \ 930 abs(my_spin): 931 if same_spin_index < 0: 932 # Remember starting index for same spin states 933 same_spin_index = i 934 same_spin_mothers.append(sorted_mothers.pop(i)) 935 else: 936 i += 1 937 938 # Make cyclic reordering of mothers with same spin as this wf 939 if same_spin_mothers: 940 same_spin_mothers = same_spin_mothers[my_index - same_spin_index:] \ 941 + same_spin_mothers[:my_index - same_spin_index] 942 943 # Insert same_spin_mothers in sorted_mothers 944 sorted_mothers = sorted_mothers[:same_spin_index] + \ 945 same_spin_mothers + sorted_mothers[same_spin_index:] 946 947 # Next sort according to spin_state_number 948 return helas_objects.HelasWavefunctionList(sorted_mothers)
949
950 951 #=============================================================================== 952 # UFOHelasCallWriter 953 #=============================================================================== 954 -class UFOHelasCallWriter(HelasCallWriter):
955 """The class for writing Helas calls in Fortran, starting from 956 HelasWavefunctions and HelasAmplitudes. 957 958 Includes the function generate_helas_call, which automatically 959 generates the Fortran Helas call based on the Lorentz structure of 960 the interaction.""" 961 962
963 - def get_wavefunction_call(self, wavefunction, **opt):
964 """Return the function for writing the wavefunction 965 corresponding to the key. If the function doesn't exist, 966 generate_helas_call is called to automatically create the 967 function. -UFO ROUTINE-""" 968 969 # Special feature: For octet Majorana fermions, need an extra 970 # minus sign in the FVI (and FSI?) wavefunction in UFO 971 # models. For MG4 models, this is taken care of by calling 972 # different routines (in import_v4.py) 973 wavefunction.set_octet_majorana_coupling_sign() 974 975 val = super(UFOHelasCallWriter, self).get_wavefunction_call(wavefunction) 976 if val: 977 return val 978 979 # If function not already existing, try to generate it. 980 self.generate_helas_call(wavefunction, **opt) 981 return super(UFOHelasCallWriter, self).get_wavefunction_call(\ 982 wavefunction)
983
984 - def get_amplitude_call(self, amplitude):
985 """Return the function for writing the amplitude corresponding 986 to the key. If the function doesn't exist, generate_helas_call 987 is called to automatically create the function.""" 988 989 val = super(UFOHelasCallWriter, self).get_amplitude_call(amplitude) 990 if val: 991 return val 992 993 # If function not already existing, try to generate it. 994 self.generate_helas_call(amplitude) 995 return super(UFOHelasCallWriter, self).get_amplitude_call(amplitude)
996 997 # Helper function
998 - def write_factor(self, factor):
999 """Create a suitable string for the factor of the form 1000 (fraction, is_imaginary?).""" 1001 imag_dict = {True: "IMAG1", False: "ONE"} 1002 return str(factor[0]*factor[1]) + "*" + imag_dict[factor[2]]
1003
1004 #=============================================================================== 1005 # FortranUFOHelasCallWriter 1006 #=============================================================================== 1007 -class FortranUFOHelasCallWriter(UFOHelasCallWriter):
1008 """The class for writing Helas calls in Fortran, starting from 1009 HelasWavefunctions and HelasAmplitudes. 1010 1011 Includes the function generate_helas_call, which automatically 1012 generates the Fortran Helas call based on the Lorentz structure of 1013 the interaction.""" 1014 1015 mp_prefix = check_param_card.ParamCard.mp_prefix 1016
1017 - def __init__(self, argument={}, hel_sum = False, options={}):
1018 """Allow generating a HelasCallWriter from a Model.The hel_sum argument 1019 specifies if amplitude and wavefunctions must be stored specifying the 1020 helicity, i.e. W(1,i) vs W(1,i,H). 1021 """ 1022 self.hel_sum = hel_sum 1023 super(FortranUFOHelasCallWriter, self).__init__(argument, options=options)
1024
1025 - def format_helas_object(self, prefix, number):
1026 """ Returns the string for accessing the wavefunction with number in 1027 argument. Typical output is {prefix}(1,{number}) """ 1028 1029 if self.hel_sum: 1030 return '%s%s,H)'%(prefix, number) 1031 else: 1032 return '%s%s)'%(prefix, number)
1033
1034 - def get_amplitude_call(self, amplitude,**opts):
1035 """ We overwrite this function here because we must call 1036 set_octet_majorana_coupling_sign for all wavefunction taking part in 1037 this loopHelasAmplitude. This is not necessary in the optimized mode""" 1038 1039 # Special feature: For octet Majorana fermions, need an extra 1040 # minus sign in the FVI (and FSI?) wavefunction in UFO 1041 # models. 1042 if isinstance(amplitude,loop_helas_objects.LoopHelasAmplitude): 1043 for lwf in amplitude.get('wavefunctions'): 1044 lwf.set_octet_majorana_coupling_sign() 1045 amplitude.set('coupling',amplitude.get_couplings()) 1046 1047 return super(FortranUFOHelasCallWriter, self).get_amplitude_call( 1048 amplitude,**opts)
1049 1050 1051
1052 - def generate_loop_amplitude_call(self, loopamp):
1053 """ Routine for automatic generation of a call to CutTools for loop 1054 amplitudes.""" 1055 1056 call = "LOOP%(numLoopLines)s" 1057 if (len(loopamp.get('pairing')) != len(loopamp.get('mothers'))): 1058 call += "%(numMotherWfs)s%(numCouplings)s(%(numeratorNumber)d," 1059 for i in range(len(loopamp.get('pairing'))): 1060 call = call + "%(Pairing{0})d,".format(i) 1061 else: 1062 call += "%(numCouplings)s(%(numeratorNumber)d," 1063 for i in range(len(loopamp.get('mothers'))): 1064 call = call + "%(MotherID{0})d,".format(i+1) 1065 for i in range(len(loopamp.get('wavefunctions'))-2): 1066 call = call + \ 1067 "DCMPLX(%(LoopMass{0})s),CMPLX({1}%(LoopMass{0})s,KIND=16),"\ 1068 .format(i+1,self.mp_prefix) 1069 for i in range(len(loopamp.get('coupling'))): 1070 call = call + \ 1071 "%(LoopCoupling{0})s,%(MPLoopCoupling{0})s,".format(i+1) 1072 call = call + "%(LoopRank)d," 1073 call = call + "%(LoopSymmetryFactor)d,%(LoopMultiplier)d," 1074 call = call + "%(ampNumber)d,AMPL(1,%(ampNumber)d),S(%(ampNumber)d))" 1075 1076 def create_loop_amp(amplitude): 1077 helas_dict = amplitude.get_helas_call_dict() 1078 # Make sure the potential minus sign on coupling appears at the 1079 # right place when specifying the mp_coupling. It must be 1080 # -MP__GC10 and not MP__-GC10 1081 for i in range(len(loopamp.get('coupling'))): 1082 coupl = helas_dict['LoopCoupling%i'%(i+1)] 1083 helas_dict['MPLoopCoupling%i'%(i+1)]= \ 1084 '-%s%s'%(self.mp_prefix,coupl[1:]) if coupl.startswith('-') \ 1085 else '%s%s'%(self.mp_prefix,coupl) 1086 # We add here the placeholde for the proc_prefix 1087 return 'CALL %(proc_prefix)s'+call%helas_dict
1088 1089 self.add_amplitude(loopamp.get_call_key(), create_loop_amp) 1090 return
1091
1092 - def generate_helas_call(self, argument, startingExternalWFNumber=0):
1093 """Routine for automatic generation of Fortran Helas calls 1094 according to just the spin structure of the interaction. 1095 """ 1096 1097 if not isinstance(argument, helas_objects.HelasWavefunction) and \ 1098 not isinstance(argument, helas_objects.HelasAmplitude): 1099 raise self.PhysicsObjectError("generate_helas_call must be called with wavefunction or amplitude") 1100 1101 call = "CALL " 1102 1103 call_function = None 1104 1105 if isinstance(argument, helas_objects.HelasAmplitude) and \ 1106 not isinstance(argument, loop_helas_objects.LoopHelasAmplitude) and \ 1107 argument.get('interaction_id') == 0: 1108 call = "#" 1109 call_function = lambda amp: call 1110 self.add_amplitude(argument.get_call_key(), call_function) 1111 return 1112 1113 if isinstance(argument, helas_objects.HelasWavefunction) and \ 1114 not argument.get('mothers'): 1115 self.generate_external_wavefunction(argument) 1116 return 1117 1118 if isinstance(argument,loop_helas_objects.LoopHelasAmplitude): 1119 self.generate_loop_amplitude_call(argument) 1120 return 1121 1122 self.generate_all_other_helas_objects(argument)
1123
1124 - def generate_external_wavefunction(self,argument):
1125 """ Generate an external wavefunction """ 1126 1127 call="CALL " 1128 call_function = None 1129 if argument.get('is_loop'): 1130 call=call+"LCUT_%(conjugate)s%(lcutspinletter)s(Q(0),I,WL(1,%(number)d))" 1131 else: 1132 # String is just IXXXXX, OXXXXX, VXXXXX or SXXXXX 1133 call = call + HelasCallWriter.mother_dict[\ 1134 argument.get_spin_state_number()] 1135 # Fill out with X up to 6 positions 1136 call = call + 'X' * (11 - len(call)) 1137 call = call + "(P(0,%(number_external)d)," 1138 if argument.get('spin') != 1: 1139 # For non-scalars, need mass and helicity 1140 call = call + "%(mass)s,NHEL(%(number_external)d)," 1141 call = call + "%(state_id)+d*IC(%(number_external)d),{0})".format(\ 1142 self.format_helas_object('W(1,','%(me_id)d')) 1143 1144 call_function = lambda wf: call % wf.get_external_helas_call_dict() 1145 self.add_wavefunction(argument.get_call_key(), call_function)
1146
1147 - def generate_all_other_helas_objects(self,argument):
1148 """ Generate all the helas objects for which no special handlers was 1149 placed in generate_helas_call """ 1150 1151 1152 if isinstance(argument, helas_objects.HelasWavefunction): 1153 outgoing = argument.find_outgoing_number() 1154 else: 1155 outgoing = 0 1156 1157 1158 1159 # Check if we need to append a charge conjugation flag 1160 l = [str(l) for l in argument.get('lorentz')] 1161 flag = [] 1162 if argument.needs_hermitian_conjugate(): 1163 flag = ['C%d' % i for i in \ 1164 argument.get_conjugate_index()] 1165 if (isinstance(argument, helas_objects.HelasWavefunction) and \ 1166 argument.get('is_loop') or \ 1167 (isinstance(argument, helas_objects.HelasAmplitude) and \ 1168 argument.get('type')=='loop')): 1169 flag.insert(0,"L") 1170 1171 # Creating line formatting: 1172 call = 'CALL %(routine_name)s(%(wf)s%(coup)s%(mass)s%(out)s)' 1173 1174 arg = {'routine_name': aloha_writers.combine_name(\ 1175 '%s' % l[0], l[1:], outgoing, flag, True), 1176 'coup': ("%%(coup%d)s," * len(argument.get('coupling'))) % \ 1177 tuple(range(len(argument.get('coupling')))) 1178 } 1179 1180 # select how to write a single wf 1181 if (isinstance(argument,helas_objects.HelasWavefunction) \ 1182 and argument.get('is_loop')) or \ 1183 ((isinstance(argument,helas_objects.HelasAmplitude) \ 1184 and argument['type']=='loop')): 1185 base_wf = "W%(WF{0})s," 1186 else: 1187 base_wf = self.format_helas_object('W(1,','%({0})d')+',' 1188 1189 # compute the full list of wf 1190 wf = '' 1191 for i in range(len(argument.get('mothers'))): 1192 wf += base_wf.format(i) 1193 arg['wf'] = wf 1194 1195 1196 # Treat other argument 1197 # First WaveFunction 1198 if isinstance(argument, helas_objects.HelasWavefunction): 1199 if argument['is_loop']: 1200 arg['out'] = 'WL(1,%(out)d)' 1201 if aloha.complex_mass: 1202 arg['mass'] = "ML(%(out)d)," 1203 else: 1204 arg['mass'] = "ML(%(out)d),ZERO," 1205 else: 1206 arg['out']=self.format_helas_object('W(1,','%(out)d') 1207 if aloha.complex_mass: 1208 arg['mass'] = "DCMPLX(%(CM)s)," 1209 else: 1210 arg['mass'] = "%(M)s,%(W)s," 1211 # Standard Amplitude 1212 elif argument['type'] == 'base': 1213 arg['mass'] = '' 1214 arg['out'] = self.format_helas_object('AMP(','%(out)d') 1215 # Loop Amplitude 1216 elif argument['type'] == 'loop': 1217 arg['mass'] = '' 1218 arg['out'] = 'BUFF(I)' 1219 # UV Counterterm (and other) 1220 else: 1221 arg['mass'] = '' 1222 ampl = "AMPL({0},%(out)d)".format(argument.get_epsilon_order()+1) 1223 arg['out'] = '%s' % ampl 1224 if isinstance(argument,loop_helas_objects.LoopHelasUVCTAmplitude)\ 1225 and argument.get_UVCT_couplings()!='1.0d0': 1226 # add a second line to take into account the multiplicative factor 1227 call += "\n %(second_line)s " 1228 arg['second_line'] = ampl+"="+ampl+"*(%(uvct)s)" 1229 1230 # ALL ARGUMENT FORMATTED ############################################### 1231 call, arg = HelasCallWriter.customize_argument_for_all_other_helas_object(call, arg) 1232 # Store the result. 1233 call = call % arg 1234 # Now we have a line correctly formatted 1235 call_function = lambda wf: call % wf.get_helas_call_dict(\ 1236 OptimizedOutput=False, specifyHel=self.hel_sum) 1237 1238 # Add the constructed function to wavefunction or amplitude dictionary 1239 if isinstance(argument, helas_objects.HelasWavefunction): 1240 self.add_wavefunction(argument.get_call_key(), call_function) 1241 else: 1242 self.add_amplitude(argument.get_call_key(), call_function)
1243 1244 1245
1246 - def get_loop_amplitude_helas_calls(self, loop_matrix_element):
1247 """ Returns a list of strings corresponding to the Helas calls for each 1248 loop amplitude of this loop matrix element. This function is placed in 1249 this class and not in HelasWriter, because it contains fortran-specific 1250 code.""" 1251 1252 res = [] 1253 loopHelasAmpNumberTreated=[] 1254 for ldiag in loop_matrix_element.get_loop_diagrams(): 1255 for lamp in ldiag.get_loop_amplitudes(): 1256 if lamp.get('number') in loopHelasAmpNumberTreated: 1257 continue 1258 else: 1259 loopHelasAmpNumberTreated.append(lamp.get('number')) 1260 lcutpart=self['model'].get_particle(lamp['type']) 1261 res.append("ELSEIF (ID.EQ.%d) THEN"%lamp.get('number')) 1262 res.append("#Loop diagram number %d (might be others, just an example)"\ 1263 %ldiag.get('number')) 1264 if lcutpart.get('spin')==1: 1265 res.append("DO I=1,1") 1266 elif lcutpart.get('spin')==2 or lcutpart.get('spin')==3: 1267 res.append("DO I=1,4") 1268 else: 1269 raise self.PhysicsObjectError("The L-cut particle type is not supported") 1270 # Temporarily relabel the 'me_id' attribute of the external wfs 1271 # in this wavefunction's mothers so to have them matching the 1272 # convention in the loop helas calls. 1273 # The same relabeling is performed for couplings. 1274 # We save the original values to reset them afterwards. 1275 externalWfNumber=1 1276 originalNumbers=[] 1277 couplingNumber=1 1278 originalCouplings=[] 1279 for lwf in lamp.get('wavefunctions'): 1280 if lwf.get('coupling')!=['none']: 1281 originalCouplings.append(lwf.get('coupling')) 1282 couplings=[] 1283 for coup in lwf.get('coupling'): 1284 couplings.append("LC(%d)"%couplingNumber) 1285 couplingNumber=couplingNumber+1 1286 lwf.set('coupling',couplings) 1287 for mother in lwf.get('mothers'): 1288 if not mother.get('is_loop'): 1289 originalNumbers.append(mother.get('number')) 1290 mother.set('me_id',externalWfNumber) 1291 externalWfNumber=externalWfNumber+1 1292 # Now we can generate the call for the starting loop wavefunction 1293 res.append(self.get_wavefunction_call(\ 1294 lamp.get_starting_loop_wavefunction())) 1295 # And now for all the other wavefunctions 1296 res.extend([ self.get_wavefunction_call(wf) for \ 1297 wf in lamp.get('wavefunctions') if wf.get('mothers')]) 1298 1299 # Get the last wf generated and the corresponding loop 1300 # wavefunction number 1301 for lwf in lamp.get('amplitudes')[0].get('mothers'): 1302 if lwf.get('mothers'): 1303 last_lwf_number=lwf.get('number') 1304 break 1305 res.append('BUFF(I)=WL(I+4,%d)'%last_lwf_number) 1306 # And re-establish the original numbering 1307 indexMothers=0 1308 indexWfs=0 1309 for lwf in lamp.get('wavefunctions'): 1310 if lwf.get('coupling')!=['none']: 1311 lwf.set('coupling',originalCouplings[indexWfs]) 1312 indexWfs=indexWfs+1 1313 for mother in lwf.get('mothers'): 1314 if not mother.get('is_loop'): 1315 mother.set('me_id',originalNumbers[indexMothers]) 1316 indexMothers=indexMothers+1 1317 res.append('ENDDO') 1318 if lcutpart.get('spin')==1: 1319 res.append("CALL CLOSE_1(BUFF(1),RES)") 1320 elif lcutpart.get('spin')==2 or lcutpart.get('spin')==3: 1321 res.append("CALL CLOSE_4(BUFF(1),RES)") 1322 # We must change the first 'ELSE IF' into an 'IF' 1323 res[0]=res[0][4:] 1324 # And add an ENDIF at the end 1325 res.append('ENDIF') 1326 1327 return res
1328
1329 #=============================================================================== 1330 # FortranUFOHelasCallWriterOptimized 1331 #=============================================================================== 1332 -class FortranUFOHelasCallWriterOptimized(FortranUFOHelasCallWriter):
1333 """ Version of FortranUFOHelasCallWriter meeting the needs of the optimized 1334 output for loop processes """ 1335
1336 - def get_amplitude_call(self, *args, **opts):
1337 """ We overwrite this function here because in the optimized mode one 1338 does not need to call the function set_octet_majorana_coupling_sign 1339 for the wavefunctions of the loop amplitudes. So we directly call 1340 the mother of the mother, namely UFOHelasCallWriter. """ 1341 1342 return super(FortranUFOHelasCallWriter, self).get_amplitude_call( 1343 *args,**opts)
1344
1345 - def format_helas_object(self, prefix, number):
1346 """ Returns the string for accessing the wavefunction with number in 1347 argument. Typical output is {prefix}(1,{number}) """ 1348 1349 if self.hel_sum: 1350 return '%s%s,H)'%(prefix, number) 1351 else: 1352 return '%s%s)'%(prefix, number)
1353
1354 - def get_coef_construction_calls(self, matrix_element, group_loops=True, 1355 squared_orders=[], split_orders=[]):
1356 """ Return the calls to the helas routines to construct the coefficients 1357 of the polynomial representation of the loop numerator (i.e. Pozzorini 1358 method). Group the coefficients of the loop with same denominator 1359 together if group_loops is set True. The squared_orders can provide the 1360 information of what is the maximum contributing loop amp number.""" 1361 1362 assert isinstance(matrix_element, loop_helas_objects.LoopHelasMatrixElement), \ 1363 "%s not valid argument for get_coef_construction_calls" % \ 1364 repr(matrix_element) 1365 loop_induced = (not matrix_element.get('processes')[0].get('has_born')) 1366 res = [] 1367 sqso_max_lamp = [sqso[1][2] for sqso in squared_orders] 1368 1369 i=0 1370 for ldiag in matrix_element.get_loop_diagrams(): 1371 res.append("# Coefficient construction for loop diagram with ID %d"\ 1372 %ldiag.get('number')) 1373 for lwf in ldiag.get('loop_wavefunctions'): 1374 res.append(self.get_wavefunction_call(lwf)) 1375 for lamp in ldiag.get_loop_amplitudes(): 1376 # If the loop grouping is not desired, then make sure it is 1377 # turned off here. It is of course not to be included for 1378 # loop-induced processes 1379 if (not group_loops) or loop_induced: 1380 lamp.set('loop_group_id',i) 1381 i=i+1 1382 create_coef=[ 1383 'CREATE_LOOP_COEFS(WL(1,0,1,%(number)d)', 1384 '%(loop_rank)d','%(lcut_size)d', 1385 '%(loop_number)d','%(LoopSymmetryFactor)d', 1386 '%(LoopMultiplier)d'] 1387 if not loop_induced: 1388 create_coef.append('%(amp_number)d,H)') 1389 else: 1390 create_coef.append('%(amp_number)d)') 1391 1392 res.append('CALL %(proc_prefix)s'+','.join(create_coef)%{\ 1393 'number':lamp.get_final_loop_wavefunction().get('number'), 1394 'loop_rank':lamp.get_analytic_info('wavefunction_rank'), 1395 'lcut_size':lamp.get_lcut_size(), 1396 # For the loop_number below, we used the id of the 'loop_group' this 1397 # amplitude belongs to. All amplitudes of such loop_group will therefore 1398 # be added into the same LOOPCOEF array component. 1399 'loop_number':(lamp.get('loop_group_id')+1), 1400 'amp_number':lamp.get('amplitudes')[0].get('number'), 1401 'LoopSymmetryFactor':lamp.get('loopsymmetryfactor'), 1402 'LoopMultiplier':lamp.get('multiplier')}) 1403 res.extend(self.get_sqso_target_skip_code( 1404 lamp.get('amplitudes')[0].get('number'), 1405 sqso_max_lamp, 4000, split_orders, squared_orders, 1406 "# At this point, all loop coefficients needed"+ 1407 " for %s are computed.")) 1408 1409 coef_merge=['C Grouping of loop diagrams now done directly when '+\ 1410 'creating the LOOPCOEFS.'] 1411 1412 return res, coef_merge
1413
1414 - def get_loop_CT_calls(self, matrix_element, group_loops=True, 1415 squared_orders=[], split_orders=[]):
1416 """ Return the calls to CutTools interface routines to launch the 1417 computation of the contribution of one loop group. The squared_orders 1418 can provide the information of the maximum reference loop group ID for 1419 each contributing squared loop orders.""" 1420 1421 assert isinstance(matrix_element, loop_helas_objects.LoopHelasMatrixElement), \ 1422 "%s not valid argument for get_loop_CT_calls" % \ 1423 repr(matrix_element) 1424 1425 res = [] 1426 1427 sqso_max_lgroup_refs = [sqso[1][3] for sqso in squared_orders] 1428 1429 # Either call CutTools for all loop diagrams or for only the reference 1430 # amplitude for each group (for which the coefficients are the sum of 1431 # all others) 1432 if group_loops and matrix_element.get('processes')[0].get('has_born'): 1433 # Reformat the loop group list in a convenient form 1434 loop_group_refs=[(lamps[1][0],lamps[1][1:]) for lamps in \ 1435 matrix_element.get('loop_groups')] 1436 for (lamp_ref, lamps) in loop_group_refs: 1437 res.append("# CutTools call for loop numbers %s"%\ 1438 ','.join(['%d'%lamp_ref.get('number'),]+\ 1439 ['%d'%lamp.get('number') for lamp in lamps])) 1440 res.append(self.get_amplitude_call(lamp_ref)) 1441 res.extend(self.get_sqso_target_skip_code( 1442 lamp_ref.get('loop_group_id'), sqso_max_lgroup_refs, 5000, 1443 split_orders, squared_orders, 1444 "# At this point, all reductions needed for %s are computed.")) 1445 else: 1446 for ldiag in matrix_element.get_loop_diagrams(): 1447 res.append("# CutTools call for loop # %d"%ldiag.get('number')) 1448 for lamp in ldiag.get_loop_amplitudes(): 1449 # Make sure the loop number is used instead of the loop 1450 # group number 1451 loop_group_id_tmp = lamp.get('loop_group_id') 1452 lamp.set('loop_group_id',lamp.get('number')-1) 1453 res.append(self.get_amplitude_call(lamp)) 1454 lamp.set('loop_group_id',loop_group_id_tmp) 1455 1456 return res
1457
1458 - def generate_external_wavefunction(self,argument):
1459 """ Generate an external wavefunction """ 1460 1461 call_function = None 1462 if argument.get('is_loop'): 1463 call="LCUT_OPT(PL(0,%(number)d),WL(1,1,1,%(number)d))" 1464 call_function = lambda wf: "CALL %(proc_prefix)s"+ \ 1465 call % {'number':wf.get('number')} 1466 self.add_wavefunction(argument.get_call_key(), call_function) 1467 else: 1468 # For the tree external wavefunction, just call the mother 1469 FortranUFOHelasCallWriter.generate_external_wavefunction(self, 1470 argument)
1471
1472 - def generate_loop_amplitude_call(self, loopamp):
1473 """ Routine for automatic generation of a call to CutTools for loop 1474 amplitudes for the optimized output.""" 1475 1476 call = "LOOP%(numLoopLines)s" 1477 if (len(loopamp.get('pairing')) != len(loopamp.get('mothers'))): 1478 call += "%(numMotherWfs)s(" 1479 for i in range(len(loopamp.get('pairing'))): 1480 call = call + "%(Pairing{0})d,".format(i) 1481 else: 1482 call += "(" 1483 for i in range(len(loopamp.get('mothers'))): 1484 call = call + "%(MotherID{0})d,".format(i+1) 1485 for i in range(len(loopamp.get('wavefunctions'))-2): 1486 call = call + \ 1487 "DCMPLX(%(LoopMass{0})s),".format(i+1) 1488 call = call + "%(LoopRank)d," 1489 call = call + "I_SO,%(loop_group_id)d)" 1490 1491 # We add here the placeholde for the proc_prefix 1492 call_function = lambda amp: 'CALL %(proc_prefix)s'+\ 1493 call % amp.get_helas_call_dict(OptimizedOutput=True) 1494 self.add_amplitude(loopamp.get_call_key(), call_function) 1495 return
1496
1497 - def generate_all_other_helas_objects(self,argument):
1498 """ Generate all the helas objects for which no special handlers was 1499 placed in generate_helas_call """ 1500 1501 if isinstance(argument, helas_objects.HelasWavefunction): 1502 outgoing = argument.find_outgoing_number() 1503 else: 1504 outgoing = 0 1505 1506 if isinstance(argument, helas_objects.HelasAmplitude) and \ 1507 argument.get('type')=='loop': 1508 raise MadGraph5Error('There should not be any helas call '+\ 1509 'associated with helas amplitudes of type loop.') 1510 1511 # Check if we need to append a charge conjugation flag 1512 l = [str(l) for l in argument.get('lorentz')] 1513 flag = [] 1514 if argument.needs_hermitian_conjugate(): 1515 flag = ['C%d' % i for i in argument.get_conjugate_index()] 1516 1517 if (isinstance(argument, helas_objects.HelasWavefunction) and \ 1518 argument.get('is_loop')): 1519 flag.insert(0,"L%d"%argument.get_loop_index()) 1520 1521 # Creating line formatting: 1522 call = 'CALL %(routine_name)s(%(wf)s%(coup)s%(mass)s%(out)s)' 1523 arg = {'routine_name': aloha_writers.combine_name(\ 1524 '%s' % l[0], l[1:], outgoing, flag, True), 1525 'coup': ("%%(coup%d)s," * len(argument.get('coupling'))) % \ 1526 tuple(range(len(argument.get('coupling')))) 1527 } 1528 1529 # select how to write a single wf 1530 if (isinstance(argument,helas_objects.HelasWavefunction) \ 1531 and argument.get('is_loop')): 1532 base_wf = "%(WF{0})s," 1533 else: 1534 base_wf = self.format_helas_object('W(1,','%({0})d')+',' 1535 1536 # compute the full list of wf 1537 wf = '' 1538 for i in range(len(argument.get('mothers'))): 1539 wf += base_wf.format(i) 1540 arg['wf'] = wf 1541 1542 1543 # Treat other argument 1544 # First WaveFunction 1545 if isinstance(argument, helas_objects.HelasWavefunction): 1546 if argument['is_loop']: 1547 arg['out'] = 'PL(0,%(out)d),COEFS' 1548 else: 1549 arg['out']=self.format_helas_object('W(1,','%(out)d') 1550 if aloha.complex_mass: 1551 arg['mass'] = "DCMPLX(%(CM)s)," 1552 else: 1553 arg['mass'] = "%(M)s,%(W)s," 1554 # Standard Amplitude 1555 elif argument['type'] == 'base': 1556 arg['mass'] = '' 1557 arg['out'] = self.format_helas_object('AMP(','%(out)d') 1558 # UV Counterterm (and other) 1559 else: 1560 arg['mass'] = '' 1561 ampl = "AMPL({0},%(out)d)".format(argument.get_epsilon_order()+1) 1562 arg['out'] = '%s' % ampl 1563 if isinstance(argument,loop_helas_objects.LoopHelasUVCTAmplitude)\ 1564 and argument.get_UVCT_couplings()!='1.0d0': 1565 # add a second line to take into account the multiplicative factor 1566 call += "\n %(second_line)s " 1567 arg['second_line'] = ampl+"="+ampl+"*(%(uvct)s)" 1568 1569 # ALL ARGUMENT FORMATTED ############################################### 1570 # Store the result. 1571 call = call % arg 1572 if (isinstance(argument, helas_objects.HelasWavefunction) and \ 1573 argument.get('is_loop')): 1574 # We add here the call to the UPDATE_COEF subroutine 1575 call += "\nCALL {0}UPDATE_WL_%(loop_mother_rank)d_%(vertex_rank)d(" 1576 call += "WL(1,0,1,%(loop_mother_number)d),%(lcut_size)d,COEFS," 1577 call += "%(in_size)d,%(out_size)d,WL(1,0,1,%(out)d))" 1578 # Now we have a line correctly formatted, with the proc_prefix 1579 call_function = lambda wf:\ 1580 (call%wf.get_helas_call_dict(OptimizedOutput=True, 1581 specifyHel=self.hel_sum)).format('%(proc_prefix)s') 1582 1583 # Add the constructed function to wavefunction or amplitude dictionary 1584 if isinstance(argument, helas_objects.HelasWavefunction): 1585 self.add_wavefunction(argument.get_call_key(), call_function) 1586 else: 1587 self.add_amplitude(argument.get_call_key(), call_function)
1588
1589 #=============================================================================== 1590 # CPPUFOHelasCallWriter 1591 #=============================================================================== 1592 -class CPPUFOHelasCallWriter(UFOHelasCallWriter):
1593 """The class for writing Helas calls in C++, starting from 1594 HelasWavefunctions and HelasAmplitudes. 1595 1596 Includes the function generate_helas_call, which automatically 1597 generates the C++ Helas call based on the Lorentz structure of 1598 the interaction.""" 1599
1600 - def generate_helas_call(self, argument):
1601 """Routine for automatic generation of C++ Helas calls 1602 according to just the spin structure of the interaction. 1603 1604 First the call string is generated, using a dictionary to go 1605 from the spin state of the calling wavefunction and its 1606 mothers, or the mothers of the amplitude, to difenrentiate wich call is 1607 done. 1608 1609 Then the call function is generated, as a lambda which fills 1610 the call string with the information of the calling 1611 wavefunction or amplitude. The call has different structure, 1612 depending on the spin of the wavefunction and the number of 1613 mothers (multiplicity of the vertex). The mother 1614 wavefunctions, when entering the call, must be sorted in the 1615 correct way - this is done by the sorted_mothers routine. 1616 1617 Finally the call function is stored in the relevant 1618 dictionary, in order to be able to reuse the function the next 1619 time a wavefunction with the same Lorentz structure is needed. 1620 """ 1621 1622 if not isinstance(argument, helas_objects.HelasWavefunction) and \ 1623 not isinstance(argument, helas_objects.HelasAmplitude): 1624 raise self.PhysicsObjectError("get_helas_call must be called with wavefunction or amplitude") 1625 1626 call = "" 1627 1628 call_function = None 1629 1630 if isinstance(argument, helas_objects.HelasAmplitude) and \ 1631 argument.get('interaction_id') == 0: 1632 call = "#" 1633 call_function = lambda amp: call 1634 self.add_amplitude(argument.get_call_key(), call_function) 1635 return 1636 1637 if isinstance(argument, helas_objects.HelasWavefunction) and \ 1638 not argument.get('mothers'): 1639 # String is just ixxxxx, oxxxxx, vxxxxx or sxxxxx 1640 call = call + HelasCallWriter.mother_dict[\ 1641 argument.get_spin_state_number()].lower() 1642 # Fill out with X up to 6 positions 1643 call = call + 'x' * (6 - len(call)) 1644 # Specify namespace for Helas calls 1645 call = call + "(p[perm[%d]]," 1646 if argument.get('spin') != 1: 1647 # For non-scalars, need mass and helicity 1648 call = call + "mME[%d],hel[%d]," 1649 call = call + "%+d,w[%d]);" 1650 if argument.get('spin') == 1: 1651 call_function = lambda wf: call % \ 1652 (wf.get('number_external')-1, 1653 # For boson, need initial/final here 1654 (-1) ** (wf.get('state') == 'initial'), 1655 wf.get('me_id')-1) 1656 elif argument.is_boson(): 1657 call_function = lambda wf: call % \ 1658 (wf.get('number_external')-1, 1659 wf.get('number_external')-1, 1660 wf.get('number_external')-1, 1661 # For boson, need initial/final here 1662 (-1) ** (wf.get('state') == 'initial'), 1663 wf.get('me_id')-1) 1664 else: 1665 call_function = lambda wf: call % \ 1666 (wf.get('number_external')-1, 1667 wf.get('number_external')-1, 1668 wf.get('number_external')-1, 1669 # For fermions, need particle/antiparticle 1670 - (-1) ** wf.get_with_flow('is_part'), 1671 wf.get('me_id')-1) 1672 else: 1673 if isinstance(argument, helas_objects.HelasWavefunction): 1674 outgoing = argument.find_outgoing_number() 1675 else: 1676 outgoing = 0 1677 1678 # Check if we need to append a charge conjugation flag 1679 l = [str(l) for l in argument.get('lorentz')] 1680 flag = [] 1681 if argument.needs_hermitian_conjugate(): 1682 flag = ['C%d' % i for i in argument.get_conjugate_index()] 1683 1684 1685 # Creating line formatting: 1686 call = '%(routine_name)s(%(wf)s%(coup)s%(mass)s%(out)s);' 1687 # compute wf 1688 arg = {'routine_name': aloha_writers.combine_name(\ 1689 '%s' % l[0], l[1:], outgoing, flag,True), 1690 'wf': ("w[%%(%d)d]," * len(argument.get('mothers'))) % \ 1691 tuple(range(len(argument.get('mothers')))), 1692 'coup': ("pars->%%(coup%d)s," * len(argument.get('coupling'))) % \ 1693 tuple(range(len(argument.get('coupling')))) 1694 } 1695 if isinstance(argument, helas_objects.HelasWavefunction): 1696 arg['out'] = 'w[%(out)d]' 1697 if aloha.complex_mass: 1698 arg['mass'] = "pars->%(CM)s," 1699 else: 1700 arg['mass'] = "pars->%(M)s,pars->%(W)s," 1701 else: 1702 arg['out'] = 'amp[%(out)d]' 1703 arg['mass'] = '' 1704 1705 call = call % arg 1706 # Now we have a line correctly formatted 1707 call_function = lambda wf: self.format_coupling( 1708 call % wf.get_helas_call_dict(index=0)) 1709 1710 # Add the constructed function to wavefunction or amplitude dictionary 1711 if isinstance(argument, helas_objects.HelasWavefunction): 1712 self.add_wavefunction(argument.get_call_key(), call_function) 1713 else: 1714 self.add_amplitude(argument.get_call_key(), call_function)
1715 1716 @staticmethod
1717 - def format_coupling(call):
1718 """Format the coupling so any minus signs are put in front""" 1719 1720 return call.replace('pars->-', '-pars->')
1721
1722 1723 #=============================================================================== 1724 # PythonUFOHelasCallWriter 1725 #=============================================================================== 1726 -class PythonUFOHelasCallWriter(UFOHelasCallWriter):
1727 """The class for writing Helas calls in Python, starting from 1728 HelasWavefunctions and HelasAmplitudes. 1729 1730 Includes the function generate_helas_call, which automatically 1731 generates the Python Helas call based on the Lorentz structure of 1732 the interaction.""" 1733
1734 - def get_matrix_element_calls(self, matrix_element, gauge_check=False):
1735 """Return a list of strings, corresponding to the Helas calls 1736 for the matrix element""" 1737 1738 assert isinstance(matrix_element, helas_objects.HelasMatrixElement), \ 1739 "%s not valid argument for get_matrix_element_calls" % \ 1740 repr(matrix_element) 1741 1742 me = matrix_element.get('diagrams') 1743 matrix_element.reuse_outdated_wavefunctions(me) 1744 1745 res = [] 1746 for diagram in matrix_element.get('diagrams'): 1747 wfs = diagram.get('wavefunctions') 1748 if gauge_check and diagram.get('number') == 1: 1749 gauge_check_wfs = [wf for wf in wfs if not wf.get('mothers') \ 1750 and wf.get('spin') == 3 \ 1751 and wf.get('mass').lower() == 'zero'] 1752 if not gauge_check_wfs: 1753 raise HelasWriterError('no massless spin one particle for gauge check') 1754 gauge_check_wf = wfs.pop(wfs.index(gauge_check_wfs[0])) 1755 res.append(self.generate_helas_call(gauge_check_wf, True)(\ 1756 gauge_check_wf)) 1757 res.extend([ self.get_wavefunction_call(wf) for wf in wfs ]) 1758 res.append("# Amplitude(s) for diagram number %d" % \ 1759 diagram.get('number')) 1760 for amplitude in diagram.get('amplitudes'): 1761 res.append(self.get_amplitude_call(amplitude)) 1762 1763 return res
1764 1765 1766
1767 - def generate_helas_call(self, argument, gauge_check=False):
1768 """Routine for automatic generation of Python Helas calls 1769 according to just the spin structure of the interaction. 1770 """ 1771 1772 if not isinstance(argument, helas_objects.HelasWavefunction) and \ 1773 not isinstance(argument, helas_objects.HelasAmplitude): 1774 raise self.PhysicsObjectError("get_helas_call must be called with wavefunction or amplitude") 1775 1776 call_function = None 1777 1778 if isinstance(argument, helas_objects.HelasAmplitude) and \ 1779 argument.get('interaction_id') == 0: 1780 call = "#" 1781 call_function = lambda amp: call 1782 self.add_amplitude(argument.get_call_key(), call_function) 1783 return 1784 1785 if isinstance(argument, helas_objects.HelasWavefunction) and \ 1786 not argument.get('mothers'): 1787 # String is just IXXXXX, OXXXXX, VXXXXX or SXXXXX 1788 call = "w[%d] = " 1789 1790 call = call + HelasCallWriter.mother_dict[\ 1791 argument.get_spin_state_number()].lower() 1792 # Fill out with X up to 6 positions 1793 call = call + 'x' * (14 - len(call)) 1794 call = call + "(p[%d]," 1795 if argument.get('spin') != 1: 1796 # For non-scalars, need mass and helicity 1797 if gauge_check and argument.get('spin') == 3 and \ 1798 argument.get('mass') == 'ZERO': 1799 call = call + "%s, 4," 1800 else: 1801 call = call + "%s,hel[%d]," 1802 call = call + "%+d)" 1803 if argument.get('spin') == 1: 1804 call_function = lambda wf: call % \ 1805 (wf.get('me_id')-1, 1806 wf.get('number_external')-1, 1807 # For boson, need initial/final here 1808 (-1)**(wf.get('state') == 'initial')) 1809 elif argument.is_boson(): 1810 if not gauge_check or argument.get('mass') != 'ZERO': 1811 call_function = lambda wf: call % \ 1812 (wf.get('me_id')-1, 1813 wf.get('number_external')-1, 1814 wf.get('mass'), 1815 wf.get('number_external')-1, 1816 # For boson, need initial/final here 1817 (-1)**(wf.get('state') == 'initial')) 1818 else: 1819 call_function = lambda wf: call % \ 1820 (wf.get('me_id')-1, 1821 wf.get('number_external')-1, 1822 'ZERO', 1823 # For boson, need initial/final here 1824 (-1)**(wf.get('state') == 'initial')) 1825 else: 1826 call_function = lambda wf: call % \ 1827 (wf.get('me_id')-1, 1828 wf.get('number_external')-1, 1829 wf.get('mass'), 1830 wf.get('number_external')-1, 1831 # For fermions, need particle/antiparticle 1832 -(-1)**wf.get_with_flow('is_part')) 1833 else: 1834 # String is LOR1_0, LOR1_2 etc. 1835 1836 if isinstance(argument, helas_objects.HelasWavefunction): 1837 outgoing = argument.find_outgoing_number() 1838 else: 1839 outgoing = 0 1840 1841 # Check if we need to append a charge conjugation flag 1842 l = [str(l) for l in argument.get('lorentz')] 1843 flag = [] 1844 if argument.needs_hermitian_conjugate(): 1845 flag = ['C%d' % i for i in argument.get_conjugate_index()] 1846 1847 1848 # Creating line formatting: 1849 call = '%(out)s= %(routine_name)s(%(wf)s%(coup)s%(mass)s)' 1850 # compute wf 1851 arg = {'routine_name': aloha_writers.combine_name(\ 1852 '%s' % l[0], l[1:], outgoing, flag, True), 1853 'wf': ("w[%%(%d)d]," * len(argument.get('mothers'))) % \ 1854 tuple(range(len(argument.get('mothers')))), 1855 'coup': ("%%(coup%d)s," * len(argument.get('coupling'))) % \ 1856 tuple(range(len(argument.get('coupling')))) 1857 } 1858 1859 if isinstance(argument, helas_objects.HelasWavefunction): 1860 arg['out'] = 'w[%(out)d]' 1861 if aloha.complex_mass: 1862 arg['mass'] = "%(CM)s" 1863 else: 1864 arg['mass'] = "%(M)s,%(W)s" 1865 else: 1866 arg['coup'] = arg['coup'][:-1] #removing the last coma 1867 arg['out'] = 'amp[%(out)d]' 1868 arg['mass'] = '' 1869 1870 call = call % arg 1871 # Now we have a line correctly formatted 1872 call_function = lambda wf: call % wf.get_helas_call_dict(index=0) 1873 1874 routine_name = aloha_writers.combine_name( 1875 '%s' % l[0], l[1:], outgoing, flag) 1876 1877 # Add the constructed function to wavefunction or amplitude dictionary 1878 if isinstance(argument, helas_objects.HelasWavefunction): 1879 if not gauge_check: 1880 self.add_wavefunction(argument.get_call_key(), call_function) 1881 else: 1882 self.add_amplitude(argument.get_call_key(), call_function) 1883 1884 return call_function
1885