pyDipole - pyDipole - automatized calculation of I-terms following the Catani-Seymour formalism

git clone git://git.bcharge.de/pyDipole.git

About | Log | Files | Refs | License

commit 9747ccd9f09d9053e211124fb57edf50268ef415
Author: Bakar Chargeishvili <bakar@bcharge.de>
Date:   Tue, 29 Mar 2022 17:44:53 +0200

Import the project

Diffstat:
ALICENSE | 29+++++++++++++++++++++++++++++
AREADME.md | 10++++++++++
Acalculate.py | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ApyDipole/__init__.py | 2++
ApyDipole/particles.py | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ApyDipole/process.py | 31+++++++++++++++++++++++++++++++
6 files changed, 448 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Bakar Chargeishvili +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md @@ -0,0 +1,10 @@ +# pyDipole - python module to automatize dipole subtraction term calculations + +Instructions will follow... + + +## References: + +- Massless - [hep-ph/0201036](https://arxiv.org/abs/hep-ph/9605323) +- Massive - [hep-ph/9605323](https://arxiv.org/abs/hep-ph/0201036) +- AutoDipole - [0911.4371 [hep-ph]](https://arxiv.org/abs/0911.4371) diff --git a/calculate.py b/calculate.py @@ -0,0 +1,314 @@ +import subprocess +import logging +import sympy as sy +import re + +from pyDipole import Process + +logging.getLogger().setLevel(logging.INFO) + + +proc = Process(['g','g'],['t','tbar'],['t']) + +expressions = '' +par1_rules = '' +par1_defs = 'S ' +nu_rules = '' +gamma6_rules = '' + + +def eq_c27(id1, id2): + global expressions + expressions += '''\ +L I{0}{1} = -Alfas/(2*pi)*InvGamma(1-eps)*Denom(Col({0},{0}))*Nu0({0})*Col({0},{1})*(4*pi*mu^2*s({0},{1})^(-1))^eps;\n +'''.format(id1,id2) + +def eq_616(id1,id2): + global expressions + expressions += '''\ +L I{0}{1} = -Alfas*(4*pi)**eps/(2*pi)*InvGamma(1-eps)*Denom(Col({0},{0}))*Col({0},{1})*(Col({0},{0})*(mu^2*s({0},{1})^(-1))^eps* +(Nu616({0},{1})-pi^2/3)+Gamma616({0})+Gamm({0})*log(mu^2*s({0},{1})^(-1))+Gamm({0})+K({0})); +'''.format(id1,id2) + +def eq_652(id1,id2): + global expressions + expressions += '''\ +L I{0}{1} = -Alfas*(4*pi)**eps/(2*pi)*InvGamma(1-eps)*Denom(Col({0},{0}))*Col({0},{1})*(Col({0},{0})*(mu^2*s({0},{1})^(-1))^eps* +(Nu616({0},{1})-pi^2/3)+Gamma616({0})+Gamm({0})*log(mu^2*s({0},{1})^(-1))+Gamm({0})+K({0})); +'''.format(id1,id2) + +for par1 in proc.all_particles: + if not par1.isQCD: + continue + + par1_defs += 'm{0}, p{0}, '.format(par1.id) + + if par1.name == 'g': + par1_rules += '''\ +id Col({0},{0}) = CA; +id Gamm({0}) = CA*11/6-Tr*Nfl*2/3; +id K({0}) = (67/18-pi^2/6)*CA-Tr*Nfl*10/9;\n +'''.format(par1.id) + else: + par1_rules += '''\ +id Col({0},{0}) = CF; +id Gamm({0}) = CF*3/2; +id K({0}) = (7/2-pi^2/6)*CF; +'''.format(par1.id) + + for par2 in proc.all_particles: + if par1.id == par2.id: + continue + if not par2.isQCD: + continue + + logging.info('Evaluating I_{0}{1}...'.format(par1.id,par2.id)) + + #final fermion + if (par1.name != 'g' and not par1.initial): + if not par2.initial: + if not par1.isMassive and not par2.isMassive: + #C.27 + logging.info('(i,k)=(f,k), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.16 + logging.info('(i,k)=(f,k), massive, using eq 6.16') + eq_616(par1.id, par2.id) + else: + if not par1.isMassive: + #C.27 + logging.info('(i,k)=(f,b), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.52 + logging.info('(i,k)=(f,b), massive, using eq 6.52') + eq_652(par1.id, par2.id) + #final gluon + elif (par1.name == 'g' and not par1.initial): + if not par2.initial: + if not par1.isMassive and not proc.mF_list: + #C.27 + logging.info('(i,k)=(g,k), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.16 + logging.info('(i,k)=(g,k), massive, using eq 6.16') + eq_616(par1.id, par2.id) + else: + if not proc.mF_list: + #C.27 + logging.info('(i,k)=(g,b), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.52 + logging.info('(i,k)=(g,b), massive, using eq 6.52') + eq_652(par1.id, par2.id) + #initial fermion + elif (par1.name != 'g' and par1.initial): + if not par2.initial: + if not par2.isMassive: + #C.27 + logging.info('(a,k)=(f,k), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.52 + logging.info('(a,k)=(f,k), massive, using eq 6.52') + eq_652(par1.id, par2.id) + else: + #C.27 + logging.info('(a,b)=(f,b), massless, using eq C.27') + eq_c27(par1.id, par2.id) + #initial gluon + elif (par1.name == 'g' and par1.initial): + if not par2.initial: + if not par2.isMassive: + #C.27 + logging.info('(a,k)=(g,k), massless, using eq C.27') + eq_c27(par1.id, par2.id) + else: + #6.52 + logging.info('(a,k)=(g,k), massive, using eq 6.52') + eq_652(par1.id, par2.id) + else: + #C.27 + logging.info('(a,b)=(g,b), massless, using eq C.27') + eq_c27(par1.id, par2.id) + + #Nu replacement rules + if (par1.isMassive and par2.isMassive): + nu_rules += '''\ +id Nu616S({0},{1}) = Denom(v({0},{1}))*(eps^(-1)*log(rho({0},{1}))- +1/4*log(rhon({0},{1},{0})^2)^2-1/4*log(rhon({0},{1},{1})^2)^2-pi^2/6+ +log(rho({0},{1}))*log(Qij({0},{1})^2/s({0},{1}))); +'''.format(par1.id, par2.id) + elif (par1.isMassive and not par2.isMassive): + nu_rules += '''\ +id Nu616S({0},{1}) = eps^-2/2+eps^(-1)/2*log(m({0})^2/s({0},{1}))- +1/4*log(m({0})^2/s({0},{1}))-pi^2/12-1/2*log(m({0})^2/s({0},{1}))*log(s({0},{1})*Qij({0},{1})^-2)- +1/2*log(m({0})^2*Qij({0},{1})^-2)*log(s({0},{1})*Qij({0},{1})^-2); +'''.format(par1.id, par2.id) + elif (not par1.isMassive and par2.isMassive): + nu_rules += '''\ +id Nu616S({1},{0}) = eps^-2/2+eps^(-1)/2*log(m({0})^2/s({0},{1}))- +1/4*log(m({0})^2/s({0},{1}))-pi^2/12-1/2*log(m({0})^2/s({0},{1}))*log(s({0},{1})*Qij({0},{1})^-2)- +1/2*log(m({0})^2*Qij({0},{1})^-2)*log(s({0},{1})*Qij({0},{1})^-2); +'''.format(par2.id, par1.id) + elif (not par1.isMassive and not par2.isMassive): + nu_rules += '''\ +id Nu616S({0},{1}) = eps^-2; +'''.format(par1.id, par2.id) + + #gamma616 replacement rules (singular part only) + if not par1.isMassive: + gamma6_rules += 'id Gamma616({0}) = eps^(-1)*Gamm({0});\n'.format(par1.id) + else: + gamma6_rules += 'id Gamma616({0}) = eps^(-1)*CF;\n'.format(par1.id) + +print(expressions) + + +code = '''\ +#- +off stats; + +S Alfas, pi, eps, CF, CA, Tr, Nfl, mu; +CF Denom; +CF I,InvGamma, Col, Nu0, Nu616 Gamm, K, Gamma616; +CF Nu616S, Nu616Ns; +CF Mu0, Mu616, log; +CF Qij, s, rho, rhon, v, m; +CF NuSmm, NuSmz, NuSzm; +CF NuNSmm, NuNSmz, NuNSzm; +CF sqrt, mun; +AutoDeclare I i; +''' + +par1_defs = par1_defs[:-2]+';\n\n' +code += par1_defs + +code += expressions+'\n\n' + +code += '''\ +id Nu0(i1?) = Col(i1,i1)*(eps^(-2)-pi^2/3)+Gamm(i1)*eps^(-1)+Gamm(i1)+K(i1); +id Nu616(?k) = Nu616Ns(?k) + Nu616S(?k); +\n\n +''' + +code += nu_rules + +rep_rules = '''\ +id rho(i1?,i2?) = sqrt((1-v(i1,i2))*Denom(1+v(i1,i2))); +id rhon(i1?,i2?,i3?) = sqrt((1-v(i1,i2)+2*mun(i1,i2,i3)^2*Denom(1-mun(i1,i2,i1)^2-mun(i1,i2,i2)^2))* +Denom(1+v(i1,i2)+2*mun(i1,i2,i3)^2*Denom(1-mun(i1,i2,i1)^2-mun(i1,i2,i2)^2))); + +id v(i1?,i2?) = (s(i1,i2))^(-1)*sqrt((s(i1,i2))^(2)-(4*m(i1)^2*m(i2)^2) ); +id mun(i1?,i2?,i3?) = m(i3)*Denom(Qij(i1,i2)); +id Qij(i1?,i2?) = sqrt(s(i1,i2) + m(i1)^2 + m(i2)^2); + +''' + +code += '''\ +argument log; +{0} +endargument; + +argument Denom; +{0} +endargument; + +argument sqrt; +{0} +endargument; + +argument log; +argument sqrt; +{0} +endargument; +endargument; + +argument log; +argument Denom; +{0} +endargument; +endargument; + +argument sqrt; +argument Denom; +{0} +endargument; +endargument; + +argument log; +argument sqrt; +argument Denom; +{0} +endargument; +endargument; +endargument; + +{0} +\n\n +'''.format(rep_rules) + +code += gamma6_rules+'\n\n' + +code += par1_rules + +code += '''\ +\nargument Denom; +{0} +endargument; +id Denom(?k) = exp_(?k,-1); +.sort +Print; +B InvGamma,Alfas,pi,Mu0; +.end +'''.format(par1_rules) + +form_file_name = '/tmp/FORM_Code.frm' +with open(form_file_name,'w') as f: + f.write(code) + +""" +Run FORM, read the result back in python and execute SymPy simplifications +""" + +form_res = subprocess.run(["form","-f",form_file_name],capture_output=True,text=True).stdout +form_res=form_res.replace('\n','') +form_res=form_res.replace(' ','') +form_res = form_res.split(';')[:-1] + +#Prepare SymPy variables +InvGamma = sy.Function('InvGamma') +MuFactor = sy.Function('MuFactor') +Col = sy.Function('Col') +Denom = sy.Function('Denom') +m = sy.Function('m') +eps = sy.Symbol('eps') +a,b = map(sy.Wild, 'ab') +alphas = sy.Symbol('alphas') +ep,pi,mu = sy.symbols('ep pi mu') +mom = dict() +masses = dict() +for p in proc.all_particles: + mom[p.id]=sy.Symbol('p{0}'.format(p.id)) + if p.isQCD and p.isMassive: + masses[p.id] = sy.Symbol(p.mass) + + +eq_dict = dict() +for eq in form_res: + eq_name = re.search(r"^(.*)=",eq).group(0)[:-1] + eq_dict[eq_name] = [] + + eq = sy.together(sy.sympify(eq.replace(eq_name+'=',''))) + eq = eq.replace(InvGamma(a), 1/sy.gamma(a)) + eq = eq.replace(Denom(a), 1/a) + eq = eq.replace(m(a), lambda a: masses[a]) + + series = sy.series(eq,eps,n=0) + + eq_dict[eq_name].append(eq) + eq_dict[eq_name].append(series.coeff(eps**-1).simplify()) + eq_dict[eq_name].append(series.coeff(eps**-2).simplify()) diff --git a/pyDipole/__init__.py b/pyDipole/__init__.py @@ -0,0 +1,2 @@ +from .particles import Particle, particles +from .process import Process diff --git a/pyDipole/particles.py b/pyDipole/particles.py @@ -0,0 +1,62 @@ +__all__ = [ + 'Particle', + 'particles', + ] +""" +Style: name: [mass(string),isQCD(bool)] +""" + +particle_table = { + 'u' : ['0' , True], + 'ubar' : ['0' , True], + 't' : ['mt', True], + 'tbar' : ['mt', True], + 'e' : ['0' , False], + 'ebar' : ['0' , False], + 'g' : ['0' , True] + } + +par_id = 1 + + +class Particle(object): + def __init__(self,name): + self.name = name + try: + self.mass = particle_table[name][0] + self.isQCD = particle_table[name][1] + self.isMassive = False if self.mass == '0' else True + except KeyError: + raise KeyError("particle {} is not defined".format(name)) + global par_id + self.id = par_id + par_id = par_id + 1 + + @property + def name(self): + return self.__name; + + @name.setter + def name(self, name): + if not isinstance(name,str): + raise TypeError("Particle name should be a string") + else: + self.__name = name + + def __str__(self): + return '{0}({1})'.format(self.__class__.__name__, self.__dict__) + + def __setattr__(self,name,value): + super(Particle,self).__setattr__(name,value) + +def particles(name): + if not isinstance(name, str): + raise TypeError("Particle names should be given as a space separated string") + else: + particles = name.split() + output = tuple() + for p in particles: + particle=Particle(p) + output = output + (particle,) + + return output diff --git a/pyDipole/process.py b/pyDipole/process.py @@ -0,0 +1,31 @@ +from .particles import Particle + +__all__ = [ + 'Process', + ] + +class Process(object): + def __init__(self, initial, final, mF_list=[]): + """ + initial and final should be a list of initial and final state particle + names for the Born process. + For the NLO correction all possible gluon emissions will be assumed. + + particle name should be given as a string + """ + self.all_particles = [] + self.mF_list = [] + for par in initial: + p = Particle(par) + setattr(p,'initial', True) + self.all_particles.append(p) + + for par in final: + p = Particle(par) + setattr(p,'initial', False) + self.all_particles.append(p) + + for par in final: + p = Particle(par) + setattr(p,'initial', False) + self.mF_list.append(p)