1
################################################################################
3
# Copyright (c) 2009 The MadGraph Development team and Contributors
5
# This file is a part of the MadGraph 5 project, an application which
6
# automatically generates Feynman diagrams and matrix elements for arbitrary
7
# high-energy processes in the Standard Model and beyond.
9
# It is subject to the MadGraph license which should accompany this
12
# For more information, please visit: http://madgraph.phys.ucl.ac.be
14
################################################################################
15
"""Unit test Library for importing and restricting model"""
16
from __future__ import division
18
from __future__ import absolute_import
26
import tests.unit_tests as unittest
27
import madgraph.core.base_objects as base_objects
29
import models.import_ufo as import_ufo
30
import models.usermod as usermod
31
import models as ufomodels
32
import models.model_reader as model_reader
33
import madgraph.iolibs.export_v4 as export_v4
34
import madgraph.various.misc as misc
36
from six.moves import range
37
from six.moves import zip
39
_file_path = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
45
# UFO CLASS SINCE THEY WILL BE USEFULL!
48
class UFOBaseClass(object):
49
"""The class from which all FeynRules classes are derived."""
53
def __init__(self, *args, **options):
54
assert(len(self.require_args) == len (args))
56
for i, name in enumerate(self.require_args):
57
setattr(self, name, args[i])
59
for (option, value) in options.items():
60
setattr(self, option, value)
63
return getattr(self, name)
65
def set(self, name, value):
66
setattr(self, name, value)
69
"""Return a dictionary containing all the information of the object"""
75
def nice_string(self):
76
""" return string with the full information """
77
return '\n'.join(['%s \t: %s' %(name, value) for name, value in self.__dict__.items()])
90
for orig,sub in replacements:
91
text = text.replace(orig,sub)
100
class Particle(UFOBaseClass):
101
"""A standard Particle"""
103
require_args=['pdg_code', 'name', 'antiname', 'spin', 'color', 'mass', 'width', 'texname', 'antitexname', 'charge']
105
require_args_all = ['pdg_code', 'name', 'antiname', 'spin', 'color', 'mass', 'width', 'texname', 'antitexname', 'charge', 'line', 'propagating', 'goldstoneboson']
107
def __init__(self, pdg_code, name, antiname, spin, color, mass, width, texname,
108
antitexname, charge , line=None, propagating=True, goldstoneboson=False, **options):
110
args= (pdg_code, name, antiname, spin, color, mass, width, texname,
111
antitexname, float(charge))
113
UFOBaseClass.__init__(self, *args, **options)
116
all_particles.append(self)
118
self.propagating = propagating
119
self.goldstoneboson= goldstoneboson
121
self.selfconjugate = (name == antiname)
123
self.line = self.find_line_type()
130
def find_line_type(self):
131
""" find how we draw a line if not defined
132
valid output: dashed/straight/wavy/curly/double/swavy/scurly
142
if not self.selfconjugate:
159
return 'dashed' # not supported yet
162
if self.selfconjugate:
163
raise Exception('%s has no anti particle.' % self.name)
165
for k,v in six.iteritems(self.__dict__):
166
if k not in self.require_args_all:
168
if self.color in [1,8]:
169
newcolor = self.color
171
newcolor = -self.color
173
return Particle(-self.pdg_code, self.antiname, self.name, self.spin, newcolor, self.mass, self.width,
174
self.antitexname, self.texname, -self.charge, self.line, self.propagating, self.goldstoneboson, **outdic)
180
class Parameter(UFOBaseClass):
182
require_args=['name', 'nature', 'type', 'value', 'texname']
184
def __init__(self, name, nature, type, value, texname, lhablock=None, lhacode=None):
186
args = (name,nature,type,value,texname)
188
UFOBaseClass.__init__(self, *args)
190
args=(name,nature,type,value,texname)
192
global all_parameters
193
all_parameters.append(self)
195
if (lhablock is None or lhacode is None) and nature == 'external':
196
raise Exception('Need LHA information for external parameter "%s".' % name)
197
self.lhablock = lhablock
198
self.lhacode = lhacode
202
class Vertex(UFOBaseClass):
204
require_args=['name', 'particles', 'color', 'lorentz', 'couplings']
206
def __init__(self, name, particles, color, lorentz, couplings, **opt):
208
args = (name, particles, color, lorentz, couplings)
210
UFOBaseClass.__init__(self, *args, **opt)
212
args=(particles,color,lorentz,couplings)
215
all_vertices.append(self)
219
class Coupling(UFOBaseClass):
221
require_args=['name', 'value', 'order']
223
def __init__(self, name, value, order, **opt):
225
args =(name, value, order)
226
UFOBaseClass.__init__(self, *args, **opt)
228
all_couplings.append(self)
234
class Lorentz(UFOBaseClass):
236
require_args=['name','spins','structure']
238
def __init__(self, name, spins, structure='external', **opt):
239
args = (name, spins, structure)
240
UFOBaseClass.__init__(self, *args, **opt)
243
all_lorentz.append(self)
248
class Function(object):
250
def __init__(self, name, arguments, expression):
253
all_functions.append(self)
256
self.arguments = arguments
257
self.expr = expression
259
def __call__(self, *opt):
261
for i, arg in enumerate(self.arguments):
262
exec('%s = %s' % (arg, opt[i] ))
264
return eval(self.expr)
268
class CouplingOrder(object):
270
def __init__(self, name, expansion_order, hierarchy, perturbative_expansion = 0):
273
all_orders.append(self)
276
self.expansion_order = expansion_order
277
self.hierarchy = hierarchy
281
class Decay(UFOBaseClass):
282
require_args = ['particle','partial_widths']
284
def __init__(self, particle, partial_widths, **opt):
285
args = (particle, partial_widths)
286
UFOBaseClass.__init__(self, *args, **opt)
289
all_decays.append(self)
291
# Add the information directly to the particle
292
particle.partial_widths = partial_widths
294
all_form_factors = []
296
class FormFactor(UFOBaseClass):
297
require_args = ['name','type','value']
299
def __init__(self, name, type, value, **opt):
300
args = (name, type, value)
301
UFOBaseClass.__init__(self, *args, **opt)
303
global all_form_factors
304
all_form_factors.append(self)
309
global all_form_factors, all_particles, all_decays,all_orders, all_functions,\
310
all_lorentz,all_couplings, all_vertices, all_parameters
312
self.all_form_factors = all_form_factors
313
self.all_particles = all_particles
314
self.all_decays = all_decays
315
self.all_orders = all_orders
316
self.all_functions = all_functions
317
self.all_lorentz = all_lorentz
318
self.all_couplings = all_couplings
319
self.all_vertices = all_vertices
320
self.all_parameters = all_parameters
324
#===============================================================================
325
# Test The UFO usermod package
326
#===============================================================================
327
class TestModUFO(unittest.TestCase):
328
"""Test class for the USERMOD object"""
336
self.path = tempfile.mkdtemp(prefix='unitest_usermod')
339
self.sm_path = import_ufo.find_ufo_path('sm')
340
self.base_model = usermod.UFOModel(self.sm_path)
345
shutil.rmtree(self.path)
346
self.assertFalse(self.debug)
348
def test_write_model(self):
349
""" Check that we can write all the require UFO files """
351
output = pjoin(self.path, 'usrmod')
352
self.base_model.write(output)
353
sm_path = import_ufo.find_ufo_path('sm')
355
len([1 for name in os.listdir(sm_path) if name.endswith('.py')]),
356
'New file in UFO format, usrmod need to be modified')
359
len([1 for name in os.listdir(output) if name.endswith('.py')]))
361
sys.path.insert(0, os.path.dirname(output))
365
def compare(self, text1, text2, optional=[], default={}):
368
texts= [text1, text2]
373
data.append(curr_data)
375
for line in text.split('\n'):
377
if line.endswith(',') or line.endswith(')'):
380
if (line.count('=') == 2 and line.count('(') == 1):
382
curr_data.append(curr_object)
383
curr_object = dict(default)
384
k,value = line.split('(')[1].split('=')
385
curr_object[k.strip()] = value.strip()
386
elif line.count('=') == 1:
387
k,value = line.split('=')
388
curr_object[k.strip()] = value.strip()
391
curr_data.append(curr_object)
393
for element in data[0]:
394
#print element, type(element)
395
for i in range(1, len(data)):
396
#for element2 in data[i]:
398
# if element == element2:
404
# self.assertFalse(True)
405
self.assertTrue(element in data[i])
408
def test_write_orders(self):
409
"""Check that the content of the file is valid"""
412
self.base_model.write_orders(output)
413
filename = os.path.join(output, 'coupling_orders.py')
414
text = open(os.path.join(filename)).read()
417
# This file was automatically created by The UFO_usermod
419
from object_library import all_orders, CouplingOrder
420
QCD = CouplingOrder(name = 'QCD',
421
expansion_order = 99,
423
perturbative_expansion = 0)
426
QED = CouplingOrder(name = 'QED',
427
expansion_order = 99,
429
perturbative_expansion = 0)
433
self.compare(target, text, default={'perturbative_expansion':'0'})
439
def test_write_particles(self):
440
"""Check that the content of the file is valid"""
443
self.base_model.write_particles(output)
444
filename = os.path.join(output, 'particles.py')
445
text = open(os.path.join(filename)).read()
446
target = open(pjoin(self.sm_path, 'particles.py')).read()
451
target = target.replace('0.0,','0,')
452
target = target.replace('1/3,','0.333333333333,')
453
target = target.replace('2/3,','0.666666666667,')
454
target = target.split('\n')
455
target = [l.strip() for l in target
456
if l.strip() and not l.strip().startswith('#') and
457
not l.split('=')[0].strip() in ['line', 'propagating', 'goldstoneboson', 'GoldstoneBoson','selfconjugate']]
459
target = [l for l in target if not '.anti()' in l or duplicate.append(l.split('=')[0].strip())]
461
text = text.replace('.0,',',')
462
text = text.replace('1/3,','0.333333333333,')
463
text = text.replace('2/3,','0.666666666667,')
464
text = text.replace('0.6666666666666666', '0.666666666667')
465
text = text.replace('0.3333333333333333', '0.333333333333')
466
text = text.split('\n')
467
text = [l.strip() for l in text
468
if l.strip() and not l.strip().startswith('#') and
469
not l.split('=')[0].strip() in ['line', 'propagating', 'goldstoneboson', 'GoldstoneBoson','selfconjugate']]
475
if 'Particle' in line:
476
if line.split('=')[0].strip() in duplicate:
483
new_text.append(line)
486
for line1, line2 in zip(target, text):
487
self.assertEqual(line1.replace(',',')'), line2.replace(',',')'))
490
def test_write_vertices(self):
491
"""Check that the content of the file is valid"""
494
self.base_model.vertices = self.base_model.vertices[:2]
495
self.base_model.write_vertices(output)
496
filename = os.path.join(output, 'vertices.py')
497
text = open(os.path.join(filename)).read()
498
target = """V_1 = Vertex(name = 'V_1',
499
particles = [P.G0, P.G0, P.G0, P.G0],
502
couplings = {(0,0): C.GC_33})
505
V_2 = Vertex(name = 'V_2',
506
particles = [P.G0, P.G0, P.G__minus__, P.G__plus__],
509
couplings = {(0,0): C.GC_31})
514
#===============================================================================
515
# Test The UFO usermod package
516
#===============================================================================
517
class Test_ADDON_UFO(unittest.TestCase):
518
"""Test class for the USERMOD object"""
523
self.path = tempfile.mkdtemp(prefix='unitest_usermod')
526
self.sm_path = import_ufo.find_ufo_path('sm')
527
self.base_model = usermod.UFOModel(self.sm_path)
528
self.mymodel = Model()
529
self.sm = models.load_model('sm')
530
for key in self.mymodel.__dict__:
531
obj = getattr(self.mymodel, key)
537
shutil.rmtree(self.path)
539
def test_add_particle(self):
540
"""Check that we can an external parameter consistently"""
542
#ZERO is define in all model => we should just do nothing
543
ZERO = Parameter(name = 'ZERO',
549
MH = Parameter(name = 'MH',
553
texname = '\\text{MH}',
557
WH = Parameter(name = 'WH',
561
texname = '\\text{MH}',
565
H = Particle(pdg_code = 25,
579
number_particles = len(self.base_model.particles)
581
#Add a particle which is exactly the Higgs like in the Standard Model
582
self.base_model.add_particle(H)
583
self.assertEqual( number_particles, len(self.base_model.particles))
584
self.assertEqual( number_particles, len(self.sm.all_particles))
586
#Same name but different pid ->add but with rename
587
H = Particle(pdg_code = 26,
600
self.base_model.add_particle(H)
601
self.assertEqual( number_particles+1, len(self.base_model.particles))
602
self.assertEqual( number_particles, len(self.sm.all_particles))
603
orig_number_particles = number_particles
605
self.assertEqual(H.name, 'H__1')
607
#Different name and different pid keep it
608
H = Particle(pdg_code = 26,
621
self.base_model.add_particle(H)
622
self.assertEqual( number_particles+1, len(self.base_model.particles))
623
self.assertEqual( orig_number_particles, len(self.sm.all_particles))
625
self.assertEqual(H.name, 'H2')
626
#Different name But different pid.
627
H = Particle(pdg_code = 25,
640
self.base_model.add_particle(H)
641
self.assertEqual( number_particles, len(self.base_model.particles))
642
self.assertEqual( orig_number_particles, len(self.sm.all_particles))
644
self.assertEqual(H.name, 'H3')
646
###################################################
647
## ALL THOSE TEST WERE NOT CHEKING MASS / WIDTH ##
648
###################################################
649
# plugin to zero -> keep the one of the model
650
H = Particle(pdg_code = 25,
663
self.base_model.add_particle(H)
664
self.assertEqual( number_particles, len(self.base_model.particles))
665
self.assertEqual( orig_number_particles, len(self.sm.all_particles))
666
self.assertEqual(H.name, 'H')
667
self.assertEqual(H.mass.name, 'ZERO')
668
true_higgs = self.base_model.particle_dict[25]
669
self.assertEqual(true_higgs.name, 'H')
670
self.assertEqual(true_higgs.mass.name, 'MH')
672
# base_model to zero -> keep the one of the plugin
673
M5 = Parameter(name = 'M5',
677
texname = '\\text{MH}',
680
W5 = Parameter(name = 'W5',
684
texname = '\\text{MH}',
687
B = Particle(pdg_code = 5,
701
self.base_model.add_parameter(M5)
702
self.base_model.add_parameter(W5)
703
self.base_model.add_particle(B)
704
self.assertEqual( number_particles, len(self.base_model.particles))
705
self.assertEqual( orig_number_particles, len(self.sm.all_particles))
706
# For the mass both are define, so this is should be a merge
707
self.assertEqual(B.name, 'B')
708
self.assertEqual(B.mass.name, 'M5')
709
true_b = self.base_model.particle_dict[5]
710
self.assertEqual(true_b.name, 'b')
711
self.assertEqual(true_b.mass.name, 'MB') # keep MB since M5 is merge on MB
712
self.assertEqual(self.base_model.old_new['M5'], 'MB')
713
# For the width the model one is zero => overwrite
714
self.assertEqual(B.name, 'B')
715
self.assertEqual(B.width.name, 'W5')
716
self.assertEqual(true_b.width.name, 'W5')
722
def test_add_external_parameters(self):
723
"""Check that we can an external parameter consistently"""
725
nb_param = len(self.base_model.parameters)
726
#ZERO is define in all model => we should just do nothing
727
ZERO = Parameter(name = 'ZERO',
732
# add it and check that nothing happen!
733
self.base_model.add_parameter(ZERO)
734
self.assertEqual(nb_param, len(self.base_model.parameters))
737
# MH is already define
738
MH = Parameter(name = 'MH',
742
texname = '\\text{MH}',
746
# add it and check that nothing happen!
747
self.base_model.add_parameter(MH)
748
self.assertEqual(nb_param, len(self.base_model.parameters))
750
# MH is already definebut has a different name ib both model
751
MH = Parameter(name = 'MH2',
755
texname = '\\text{MH}',
759
# add it and check that nothing happen!
760
self.base_model.add_parameter(MH)
761
self.assertEqual(nb_param, len(self.base_model.parameters))
762
# But the information should be present in the old->new dict
763
self.assertEqual(self.base_model.old_new['MH2'], 'MH')
765
# Add an internal parameter depending of MH2
766
GH = Parameter(name = 'GH',
769
texname = '\\text{MH}',
770
value = '25*MH2**2*AMH2*MH25')
772
self.base_model.add_parameter(GH)
773
self.assertEqual(nb_param+1, len(self.base_model.parameters))
774
#check that the expression of GH is correctly modified
775
self.assertEqual(GH.value, '25*MH**2*AMH2*MH25')
776
self.assertEqual(GH.name, 'GH')
777
nb_param = nb_param+1
779
# Add an internal parameter depending of MH2
780
# But with a name conflict
781
Gf = Parameter(name = 'Gf',
784
texname = '\\text{MH}',
785
value = '25*MH2**2*AMH2*MH25')
787
self.base_model.add_parameter(Gf)
788
self.assertEqual(nb_param+1, len(self.base_model.parameters))
789
#check that the expression of GH is correctly modified
790
self.assertEqual(Gf.value, '25*MH**2*AMH2*MH25')
791
self.assertEqual(Gf.name, 'Gf__1')
792
self.assertEqual(self.base_model.old_new['Gf'], 'Gf__1')
793
nb_param = nb_param+1
795
# Add an internal parameter depending of MH2 and of Gf
796
# But with a name conflict
797
Gf2 = Parameter(name = 'Gf2',
800
texname = '\\text{MH}',
801
value = '25*MH2**2*AMH2*MH25*math.cmath(Gf)')
803
self.base_model.add_parameter(Gf2)
804
self.assertEqual(nb_param+1, len(self.base_model.parameters))
805
#check that the expression of GH is correctly modified
806
self.assertEqual(Gf2.value, '25*MH**2*AMH2*MH25*math.cmath(Gf__1)')
807
self.assertEqual(Gf2.name, 'Gf2')
808
nb_param = nb_param+1
810
# MH250 is a completely new external parameter
811
MH250 = Parameter(name = 'MH250',
815
texname = '\\text{MH}',
818
self.base_model.add_parameter(MH250)
819
self.assertEqual(nb_param+1, len(self.base_model.parameters))
822
# MH251 is a completely new external parameter with same name as MH250
823
MH251 = Parameter(name = 'MH250',
827
texname = '\\text{MH}',
831
self.base_model.add_parameter(MH251)
832
self.assertEqual(nb_param+1, len(self.base_model.parameters))
833
self.assertEqual(self.base_model.old_new['MH250'], 'MH250__1')
834
self.assertEqual(MH251.name, 'MH250__1')
839
def test_couplings(self):
841
nb_coup = len(self.base_model.couplings)
844
GC_107 = Coupling(name = 'GC_107',
845
value = '(ee*complex(0,1)*complexconjugate(CKM3x2))/(sw*cmath.sqrt(2))',
848
self.base_model.add_coupling(GC_107)
849
self.assertEqual(nb_coup, len(self.base_model.couplings))
850
self.assertEqual(nb_coup, len(self.sm.all_couplings))
851
self.assertTrue(hasattr(GC_107, 'replace'))
852
self.assertEqual(nb_coup, len(self.sm.all_couplings))
854
GC_107 = Coupling(name = 'GC_110',
855
value = '(ee*complex(0,1)*complexconjugate(CKM3x2))/(sw*cmath.sqrt(2))',
858
self.base_model.add_coupling(GC_107)
859
self.assertEqual(nb_coup, len(self.base_model.couplings))
860
self.assertEqual(nb_coup, len(self.sm.all_couplings))
861
self.assertTrue(hasattr(GC_107, 'replace'))
862
self.assertEqual(nb_coup, len(self.sm.all_couplings))
864
GC_107 = Coupling(name = 'GC_107',
865
value = '(ee*complex(0,1)*complexconjugate(CKM3x99))/(sw*cmath.sqrt(2))',
868
self.base_model.add_coupling(GC_107)
869
self.assertEqual(nb_coup+1, len(self.base_model.couplings))
870
self.assertEqual(nb_coup, len(self.sm.all_couplings))
871
self.assertFalse(hasattr(GC_107, 'replace'))
874
def test_interaction(self):
876
GC_1 = Coupling(name = 'GC_1',
877
value = '(ee*complex(0,1)*complexconjugate(CKM3x100))/(sw*cmath.sqrt(2))',
879
self.base_model.add_coupling(GC_1)
880
M5 = Parameter(name = 'M5',
884
texname = '\\text{MH}',
887
W5 = Parameter(name = 'W5',
891
texname = '\\text{MH}',
894
self.base_model.add_parameter(M5)
895
self.base_model.add_parameter(W5)
897
L = Lorentz(name = 'FFS2',
899
structure = 'Identity(2,1)')
900
self.base_model.add_lorentz(L)
902
B = Particle(pdg_code = 5,
915
self.base_model.add_particle(B)
917
V_2 = Vertex(name = 'V_2',
918
particles = [ B, B, B, B ],
921
couplings = {(0,0): GC_1})
923
# check the size for avoiding border effect
924
self.assertEqual(len(all_particles),1)
925
self.assertEqual(len(self.mymodel.all_particles),1)
926
self.assertEqual(len(self.mymodel.all_vertices),1)
928
orig = len(self.base_model.vertices)
929
self.base_model.add_interaction(V_2, self.mymodel)
930
self.assertEqual(orig+1, len(self.base_model.vertices))
931
added = self.base_model.vertices[-1]
932
self.assertEqual(added.name, 'V_2__1')
933
self.assertNotEqual(id(added.particles[0]), id(B))
935
# check the size for avoiding border effect
936
self.assertEqual(len(all_particles),1)
937
self.assertEqual(len(self.mymodel.all_particles),1)
938
self.assertEqual(len(self.mymodel.all_vertices),1)
942
## add a second time the interaction to check that she is not added
943
orig = len(self.base_model.vertices)
944
self.base_model.add_interaction(V_2, self.mymodel)
945
self.assertEqual(orig, len(self.base_model.vertices))
947
## check that the sm model is not impacted
948
self.assertNotEqual(orig, len(self.sm.all_vertices))
951
def test_identify_particle(self):
953
GC_1 = Coupling(name = 'GC_1',
954
value = '(ee*complex(0,1)*complexconjugate(CKM3x100))/(sw*cmath.sqrt(2))',
956
#self.base_model.add_coupling(GC_1)
957
M5 = Parameter(name = 'M5',
961
texname = '\\text{MH}',
964
W5 = Parameter(name = 'W5',
968
texname = '\\text{MH}',
971
#self.base_model.add_parameter(M5)
972
#self.base_model.add_parameter(W5)
974
L = Lorentz(name = 'FFS2',
976
structure = 'Identity(2,1)')
977
#self.base_model.add_lorentz(L)
979
B = Particle(pdg_code = 105,
992
#self.base_model.add_particle(B)
994
V_2 = Vertex(name = 'V_2',
995
particles = [ B, B, B, B ],
998
couplings = {(0,0): GC_1})
999
self.mymodel.__path__ = '.'
1000
self.base_model.add_model(self.mymodel, identify_particles={'B':'H'})
1002
# check that the B object still has is name/pdg_code
1003
self.assertEqual(B.pdg_code, 105)
1004
self.assertEqual(B.name, 'B')
1005
# check that the original model still has the H particles
1006
model = ufomodels.load_model(self.sm_path)
1007
particles_name = [p.name for p in model.all_particles]
1008
self.assertTrue('H' in particles_name)
1009
self.assertFalse('B' in particles_name)
1011
parameters_name = [p.name for p in model.all_parameters]
1012
self.assertTrue('MH' in parameters_name)
1013
self.assertFalse('M5' in parameters_name)