In this example we will calculate symbolically and evaluate
numerically a photon propagator. The physical model is QCD
with generic color group (symbolic Nc
, Ca
, Cf
, etc),
Nf
light quarks, and Nft
heavy quarks of squared mass
mt2
.
To start off, for interactive development it is convenient
to reduce the width of Mathematica formatted output to 65
characters, and force it to clear the history (the Out[]
variables) so that old expressions would not linger on in
the memory.
SetOptions[$Output, PageWidth -> 65]; $HistoryLength = 2;
Load alibrary and the QCD Feynman rules from amodel-qcd.m
.
Get["alibrary.m"]; Get[$Apath <> "/amodel-qcd.m"];
We shall calculate the corrections of order ,
but in principle we can be completely general. Let us define
the number of loops as the NLOOPS
variable, and use it
everywhere below.
NLOOPS = 2;
Generate the diagrams with one incoming photon and one outgoing photon with QGraf.
diagrams = Diagrams[{"A"}, {"A"}, NLOOPS]; Print["Loaded ", diagrams//Length, " diagrams"];
One can now view the graphical representation of these diagrams directly in Mathematica, or export them to LaTeX; please see Displaying diagrams for an example.
Because the (amputated) photon propagator has open Lorentz indices corresponding to the incoming and outgoing photons, we need to project it to scalar values somehow.
If is the propagator, and is the momentum flowing through it, there are two tensor structures that it can be made of:
Because of the Ward identities,
this form is further constrained to just
Then to get the scalar from we can construct a projector as
Here it is in Mathematica notation.
projector = delta[lor[-1], lor[-2]] / (d-1);
Apply Feynman rules to get amplitudes out of diagrams.
amplitudes = diagrams // Map[Amplitude[#] * projector&];
Cleanup scaleless integrals. Some of these show up as propagators with zero momentum, which means that a part of the graph is disconnected from the rest, and thus scaleless. We can set these to zero immediately.
amplitudes2 = amplitudes /. den[0] -> 0 /. momentum[0,_] -> 0; Print["Non-zero amplitudes: ", amplitudes2//Count[Except[0]], " of ", amplitudes2//Length];
In this particular example there is a set of diagrams that are zero by the color factors. For example, those with subdiagrams where a photon turns into a single gluon. In principle we could try to skip these during diagram generation, but we don’t need to. Lets compute color factors instead, and see what turns to zero.
amplitudes3 = amplitudes2 // RunThroughForm[{ "#call colorsum\n" }]; Print["Non-zero amplitudes: ", amplitudes3//Count[Except[0]], " of ", amplitudes3//Length];
Next we want to define the integral families onto which we shall map the integrals, and which will be used in the IBP reduction later.
To this end, start with the set of denominators per diagram.
loopMomenta = diagrams // CaseUnion[l1|l2|l3|l4|l5]; externalMomenta = diagrams // CaseUnion[q|q1|q2|q3|q4|q5|p1|p2|p3|p4|p5]; Print["External momenta: ", externalMomenta]; Print["Loop momenta: ", loopMomenta]; FailUnless[Length[loopMomenta] === NLOOPS]; denominatorSets = amplitudes3 // NormalizeDens // Map[ CaseUnion[_den] /* Select[NotFreeQ[Alternatives@@loopMomenta]] ]; Print["Unique denominator sets: ", denominatorSets // DeleteCases[{}] // Union // Length];
In principle we could define the integral families by the denominator sets above, one family per denominator set. This is not very efficient though, as there are symmetries between those families. It’s best to first eliminate denominator sets that are symmetric to others.
The symmetries manifest most directly in the Feynman parameter space, as permutations of the parameters. In the momenta space this corresponds to loop momenta shifts, and we would like to have a set of momenta shifts that would make symmetric families explicitly identical, or identical to subsets of bigger families, so we could test if a family is symmetric by just asking if the set of denominators a subset of another family.
The tool to compute this momenta mapping is Feynson, and the interface to it is SymmetryMaps.
$Feynson = "feynson"; momentaMaps = SymmetryMaps[denominatorSets, loopMomenta]; Print["Found ", momentaMaps // DeleteCases[{}] // Length, " momenta mappings"]; symmetrizedDenominatorSets = MapThread[ReplaceAll, {denominatorSets, momentaMaps}] // NormalizeDens;
Then, the set of unique supersets of the denominator sets is the set of integral families we need.
{denominatorSupersets, supersetIndices} = UniqueSupertopologyMapping[symmetrizedDenominatorSets]; Print["Total integral families: ", denominatorSupersets//Length];
Let us then construct the IBP basis objects for each unique denominator superset. These objects are just associations storing denominators, and maps from scalar products into the denominators.
Also in the case when the denominator set is not sufficient to form the full linear basis of scalar products, we want to complete it; CompleteIBPBasis will do this for us.
bases = denominatorSupersets // MapIndexed[CompleteIBPBasis[ First[#2], #1 // NormalizeDens // Sort, loopMomenta, externalMomenta, {sp[q,q]->sqrq} ]&];
OK, now that we have the IBP bases, we can convert the amplitudes to them.
One practical thing to start with is to identify the set of sectors (integral family subsets) that correspond to scaleless integrals. This is also done with Feynson.
zeroSectors = ZeroSectors[bases];
Next, just call FORM to do all the tensor summation and conversion to IBP families.
amplitudesB = MapThread[ReplaceAll, {amplitudes3, momentaMaps}] // # * BID^supersetIndices & // RunThroughForm[{ "#call contractmomenta\n", "#call sort(after-contractmomenta)\n", "#call chaincolorT\n", "#call chaingammachain\n", "#call flavorsumwithcharge\n", "#call colorsum\n", "#call sort(after-colorsum)\n", "#call polarizationsum\n", "#call spinsum\n", "#call diractrace\n", "#call contractmomenta\n", FormCallToB[bases], "id mt1^2 = mt2;\n", FormCallZeroSectors[zeroSectors] }] // MapWithProgress[FasterFactor]; FailUnless[FreeQ[amplitudesB, l1|l2|l3|l4]];
Next, lets do the IBP reduction.
We’ll use KiraIBP, a simple interface to IBP with Kira. It is probably too simplistic to work automatically for larger examples, but for this problem it’s ideal.
Note that for best performance IBP reduction should be
performed with one of the mass scales set to 1
. This is
possible because we can always restore the overall power of
that mass scale by dimensional analysys at the end of the
calculation.
For this reason, we will instruct Kira to set mt2
to 1
,
and will do the same replacement in the rest of the amplitude
ourselves.
amplitudesBibp = amplitudesB // ReplaceAll[mt2 -> 1] // KiraIBP[bases, ReplaceByOne->mt2];
The full amplitude is just the sum of the diagram amplitudes.
fullAmplitude = amplitudesBibp // Apply[Plus] // Bracket[#, _B, Factor]&;
A good correctness check is to see if the result is
gauge-independent. Because our QCD Feynman rules use the
gauge and set Xi
=, we can check if there is
any Xi
dependence left: none should remain.
FailUnless[FreeQ[fullAmplitude, Xi]];
We now have the amplitude reduced to master integrals.
Print["Master integral count: ", fullAmplitude // CaseUnion[_B] // Length];
The final step is to insert the values of the masters. Of course the masters here are known analytically, but as an example let us evaluate them numerically with pySecDec.
Although pySecDec was originally designed to evaluate single
integrals, there are optimizations that can be performed when
evaluating whole amplitudes, and pySecDec knows how to apply
those. This is why our strategy will be to give pySecDec the
whole fullAmplitude
, and ask to evaluate it, instead of
asking for each integral separately.
To this end, let’s inspect the variables our amplitude depends on:
Print["Variables in the amplitude: ", fullAmplitude // CaseUnion[_Symbol]]; Print["Functions in the amplitude: ", fullAmplitude // CaseUnion[(h_)[___]:>h]];
Variables like number of quark flavours and SU(N) constants
can be set to numbers to evaluate fullAmplitude
as a single
numerical value, but because the master integrals themselves
do not depend on these variables, we can factor them out and
split fullAmplitude
into a sum of prefactors (that we will
keep symbolically) multiplied by sums of integrals (that we
will evaluate numerically).
fullAmplitudeByPrefactor = fullAmplitude // ReplaceAll[Complex[re_, im_] :> re + im*ImagI] // BracketAssociation[Ca | Cf | Na | Tf | d33 | d44 | Nc | Nf | Nft | gH | gs | Xi | ImagI | _flvsum | _flvsumt]; Print["Symbolic prefactors:"]; fullAmplitudeByPrefactor // Keys // PrintIndexed;
Now we are ready to prepare the pySecDec integration library. Let us ask pySecDec to expand the amplitudes to order .
basesWithoutMt2 = bases // Map[ (Append[#, <|"invariants" -> DeleteCases[#["invariants"], mt2]|>]&) /* Map[ReplaceAll[mt2 -> 1]] ]; SecDecPrepareSum[ "secdectmpdir", basesWithoutMt2, fullAmplitudeByPrefactor // KeyMap[InputForm/*ToString] // Map[ReplaceAll[d->4-2*eps]], Order->2];
After the library is prepared, it needs to be compiled. This
is done by just running the compile.py
script inside:
SafeRun["python3 secdectmpdir/compile.py"];
The compilation here automatically runs in parallel, but it might still take a while. Once it is done, we are ready for integration.
Once we have selected some values for the parameters, we can integrate via SecDecIntegrateSum:
pspoint = { sqrq -> 120/100 }; result = SecDecIntegrateSum[ "secdectmpdir/disteval/sum.json", pspoint, EpsRel -> 10^-3, EpsAbs -> 10^-8];
The integration will automatically run in parallel using all
locally available processors. It can also use the local GPUs,
although for this we would have needed to build the separate
GPU integration libraries during compilation (by setting
the SECDEC_WITH_CUDA_FLAGS
environment variable to e.g.
-gencode arch=compute_80,code=sm_80
, if GPUs with CUDA
“Compute Capability” 8.0 are used).
Once the integration is over, all that is left for us is to read out the results:
Print["Amplitude", pspoint // InputForm, "="] symbolicPrefactors = result["sums"] // Keys; Do[ Print[" +", prefactor, " * ("]; result["sums"][prefactor] // Map[Replace[ {regulators_, value_, error_} :> ( Print[" +", regulators // InputForm, "*("]; Print[" (", value//InputForm, ") +/- "]; Print[" (", error//InputForm, ")"] Print[" )"] ) ]]; Print[" )"]; , {prefactor, symbolicPrefactors}];