3. Overview

pySecDec consists of several modules that provide functions and classes for specific purposes. In this overview, we present only the most important aspects of selected modules. These are exactly the modules necessary to set up the algebraic computation of a Feynman loop integral requisite for the numerical evaluation. For detailed instruction of a specific function or class, please be referred to the reference guide.

3.1. The Algebra Module

The algebra module implements a very basic computer algebra system. pySecDec uses both sympy and numpy. Although sympy in principle provides everything we need, it is way too slow for typical applications. That is because sympy is completely written in python without making use of any precompiled functions. pySecDec’s algebra module uses the in general faster numpy function wherever possible.

3.1.1. Polynomials

Since sector decomposition is an algorithm that acts on polynomials, we start with the key class Polynomial. As the name suggests, the Polynomial class is a container for multivariate polynomials, i.e. functions of the form:

\sum_i C_i {\prod_j { x_{j}^{\alpha_{ij}} }}

A multivariate polynomial is completely determined by its coefficients C_i and the exponents \alpha_{ij}. The Polynomial class stores these in two arrays:

>>> from pySecDec.algebra import Polynomial
>>> poly = Polynomial([[1,0], [0,2]], ['A', 'B'])
>>> poly
 + (A)*x0 + (B)*x1**2
>>> poly.expolist
array([[1, 0],
       [0, 2]])
>>> poly.coeffs
array([A, B], dtype=object)

It is also possible to instantiate the Polynomial by its algebraic representation:

>>> poly2 = Polynomial.from_expression('A*x0 + B*x1**2', ['x0','x1'])
>>> poly2
 + (A)*x0 + (B)*x1**2
>>> poly2.expolist
array([[1, 0],
       [0, 2]])
>>> poly2.coeffs
array([A, B], dtype=object)

Note that the second argument of Polynomial.from_expression() defines the variables x_j.

Within the Polynomial class, basic operations are implemented:

>>> poly + 1
 + (1) + (B)*x1**2 + (A)*x0
>>> 2 * poly
 + (2*A)*x0 + (2*B)*x1**2
>>> poly + poly
 + (2*B)*x1**2 + (2*A)*x0
>>> poly * poly
 + (B**2)*x1**4 + (2*A*B)*x0*x1**2 + (A**2)*x0**2
>>> poly ** 2
 + (B**2)*x1**4 + (2*A*B)*x0*x1**2 + (A**2)*x0**2

3.1.2. General Expressions

In order to perform the pySecDec.subtraction and pySecDec.expansion, we have to introduce more complex algebraic constructs.

General expressions can be entered in a straightforward way:

>>> from pySecDec.algebra import Expression
>>> log_of_x = Expression('log(x)', ['x'])
>>> log_of_x
log( + (1)*x)

All expressions in the context of this algebra module are based on extending or combining the Polynomials introduced above. In the example above, log_of_x is a LogOfPolynomial, which is a derived class from Polynomial:

>>> type(log_of_x)
<class 'pySecDec.algebra.LogOfPolynomial'>
>>> isinstance(log_of_x, Polynomial)
True
>>> log_of_x.expolist
array([[1]])
>>> log_of_x.coeffs
array([1], dtype=object)

We have seen an extension to the Polynomial class, now let us consider a combination:

>>> more_complex_expression = log_of_x * log_of_x
>>> more_complex_expression
(log( + (1)*x)) * (log( + (1)*x))

We just introduced the Product of two LogOfPolynomials:

>>> type(more_complex_expression)
<class 'pySecDec.algebra.Product'>

As suggested before, the Product combines two Polynomials. They are accessible through the factors:

>>> more_complex_expression.factors[0]
log( + (1)*x)
>>> more_complex_expression.factors[1]
log( + (1)*x)
>>> type(more_complex_expression.factors[0])
<class 'pySecDec.algebra.LogOfPolynomial'>
>>> type(more_complex_expression.factors[1])
<class 'pySecDec.algebra.LogOfPolynomial'>

Important

When working with this algebra module, it is important to understand that everything is based on the class Polynomial.

To emphasize the importance of the above statement, consider the following code:

>>> expression1 = Expression('x*y', ['x', 'y'])
>>> expression2 = Expression('x*y', ['x'])
>>> type(expression1)
<class 'pySecDec.algebra.Polynomial'>
>>> type(expression2)
<class 'pySecDec.algebra.Polynomial'>
>>> expression1
 + (1)*x*y
>>> expression2
 + (y)*x

Although expression1 and expression2 are mathematically identical, they are treated differently by the algebra module. In expression1, both, x and y, are considered as variables of the Polynomial. In contrast, y is treated as coefficient in expression2:

>>> expression1.expolist
array([[1, 1]])
>>> expression1.coeffs
array([1], dtype=object)
>>> expression2.expolist
array([[1]])
>>> expression2.coeffs
array([y], dtype=object)

The second argument of the function Expression controls how the variables are distributed among the coefficients and the variables in the underlying Polynomials. Keep that in mind in order to avoid confusion. One can always check which symbols are considered as variables by asking for the symbols:

>>> expression1.symbols
[x, y]
>>> expression2.symbols
[x]

3.2. Feynman Parametrization of Loop Integrals

The primary purpose of pySecDec is the numerical computation of loop integrals as they arise in fixed order calculations in quantum field theories.

The conventions of pySecDec are fixed as follows: a Feynman graph G^{\mu_1 \ldots \mu_R}_{l_1 \ldots l_R} in D dimensions at L loops with R loop momenta in the numerator and N propagators, where the propagators can have arbitrary, not necessarily integer powers \nu_j, is considered to have the following representation in momentum space,

\begin{align}
 G &= \int\prod\limits_{l=1}^{L} \mathrm{d}^D\kappa_l\;
 \frac{k_{l_1}^{\mu_1} \cdots k_{l_R}^{\mu_R}}
 {\prod\limits_{j=1}^{N} P_{j}^{\nu_j}(\{k\},\{p\},m_j^2)}, \nonumber \\
 \mathrm{d}^D\kappa_l&=\frac{\mu^{4-D}}{i\pi^{\frac{D}{2}}}\,\mathrm{d}^D k_l\;,\;
 P_j(\{k\},\{p\},m_j^2)=(q_j^2-m_j^2+i\delta)\;, \nonumber
 \end{align}

where the q_j are linear combinations of external momenta p_i and loop momenta k_l.

In the first step of our approach, the loop integral is converted from the momentum representation to the Feynman parameter representation, see for example [Hei08] (Chapter 3).

The module pySecDec.loop_integral implements exactly that conversion. The most basic use is to calculate the first and the second Symanzik polynomial U and F, respectively, from the propagators of a loop integral.

3.2.1. One Loop Bubble

To calculate U and F of the one loop bubble, type the following commands:

>>> from pySecDec.loop_integral import LoopIntegralFromPropagators
>>> propagators = ['k**2', '(k - p)**2']
>>> loop_momenta = ['k']
>>> one_loop_bubble = LoopIntegralFromPropagators(propagators, loop_momenta)
>>> one_loop_bubble.U
 + (1)*x0 + (1)*x1
>>> one_loop_bubble.F
 + (-p**2)*x0*x1

The example above among other useful features is also stated in the full documenation of LoopIntegralFromPropagators() in the reference guide.

3.2.2. Two Loop Planar Box with Numerator

Consider the propagators of the two loop planar box:

>>> propagators = ['k1**2','(k1+p2)**2',
...                '(k1-p1)**2','(k1-k2)**2',
...                '(k2+p2)**2','(k2-p1)**2',
...                '(k2+p2+p3)**2']
>>> loop_momenta = ['k1','k2']

We could now instantiate the LoopIntegral just like before. However, let us consider an additional numerator:

>>> numerator = 'k1(mu)*k1(mu) + 2*k1(mu)*p3(mu) + p3(mu)*p3(mu)' # (k1 + p3) ** 2

In order to unambiguously define the loop integral, we must state which symbols denote the Lorentz_indices (just mu in this case here) and the external_momenta:

>>> external_momenta = ['p1','p2','p3','p4']
>>> Lorentz_indices=['mu']

With that, we can Feynman parametrize the two loop box with a numerator:

>>> box = LoopIntegralFromPropagators(propagators, loop_momenta, external_momenta,
...                                     numerator=numerator, Lorentz_indices=Lorentz_indices)
>>> box.U
 + (1)*x3*x6 + (1)*x3*x5 + (1)*x3*x4 + (1)*x2*x6 + (1)*x2*x5 + (1)*x2*x4 + (1)*x2*x3 + (1)*x1*x6 + (1)*x1*x5 + (1)*x1*x4 + (1)*x1*x3 + (1)*x0*x6 + (1)*x0*x5 + (1)*x0*x4 + (1)*x0*x3
>>> box.F
 + (-p1**2 - 2*p1*p2 - 2*p1*p3 - p2**2 - 2*p2*p3 - p3**2)*x3*x5*x6 + (-p3**2)*x3*x4*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x3*x4*x5 + (-p1**2 - 2*p1*p2 - 2*p1*p3 - p2**2 - 2*p2*p3 - p3**2)*x2*x5*x6 + (-p3**2)*x2*x4*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x2*x4*x5 + (-p1**2 - 2*p1*p2 - 2*p1*p3 - p2**2 - 2*p2*p3 - p3**2)*x2*x3*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x2*x3*x4 + (-p1**2 - 2*p1*p2 - 2*p1*p3 - p2**2 - 2*p2*p3 - p3**2)*x1*x5*x6 + (-p3**2)*x1*x4*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x4*x5 + (-p3**2)*x1*x3*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x3*x5 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x2*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x2*x5 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x2*x4 + (-p1**2 - 2*p1*p2 - p2**2)*x1*x2*x3 + (-p1**2 - 2*p1*p2 - 2*p1*p3 - p2**2 - 2*p2*p3 - p3**2)*x0*x5*x6 + (-p3**2)*x0*x4*x6 + (-p1**2 - 2*p1*p2 - p2**2)*x0*x4*x5 + (-p2**2 - 2*p2*p3 - p3**2)*x0*x3*x6 + (-p1**2)*x0*x3*x5 + (-p2**2)*x0*x3*x4 + (-p1**2)*x0*x2*x6 + (-p1**2)*x0*x2*x5 + (-p1**2)*x0*x2*x4 + (-p1**2)*x0*x2*x3 + (-p2**2)*x0*x1*x6 + (-p2**2)*x0*x1*x5 + (-p2**2)*x0*x1*x4 + (-p2**2)*x0*x1*x3
>>> box.numerator
 + (2*eps*p3(mu)**2 + 2*p3(mu)**2)*U**2 + (eps - 2)*x6*F + (eps - 2)*x5*F + (eps - 2)*x4*F + (eps - 2)*x3*F + (-4*eps*p2(mu)*p3(mu) - 4*eps*p3(mu)**2 - 4*p2(mu)*p3(mu) - 4*p3(mu)**2)*x3*x6*U + (4*eps*p1(mu)*p3(mu) + 4*p1(mu)*p3(mu))*x3*x5*U + (-4*eps*p2(mu)*p3(mu) - 4*p2(mu)*p3(mu))*x3*x4*U + (2*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) + 2*eps*p3(mu)**2 + 2*p2(mu)**2 + 4*p2(mu)*p3(mu) + 2*p3(mu)**2)*x3**2*x6**2 + (-4*eps*p1(mu)*p2(mu) - 4*eps*p1(mu)*p3(mu) - 4*p1(mu)*p2(mu) - 4*p1(mu)*p3(mu))*x3**2*x5*x6 + (2*eps*p1(mu)**2 + 2*p1(mu)**2)*x3**2*x5**2 + (4*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) + 4*p2(mu)**2 + 4*p2(mu)*p3(mu))*x3**2*x4*x6 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x3**2*x4*x5 + (2*eps*p2(mu)**2 + 2*p2(mu)**2)*x3**2*x4**2 + (4*eps*p1(mu)*p3(mu) + 4*p1(mu)*p3(mu))*x2*x6*U + (4*eps*p1(mu)*p3(mu) + 4*p1(mu)*p3(mu))*x2*x5*U + (4*eps*p1(mu)*p3(mu) + 4*p1(mu)*p3(mu))*x2*x4*U + (4*eps*p1(mu)*p3(mu) + 4*p1(mu)*p3(mu))*x2*x3*U + (-4*eps*p1(mu)*p2(mu) - 4*eps*p1(mu)*p3(mu) - 4*p1(mu)*p2(mu) - 4*p1(mu)*p3(mu))*x2*x3*x6**2 + (4*eps*p1(mu)**2 - 4*eps*p1(mu)*p2(mu) - 4*eps*p1(mu)*p3(mu) + 4*p1(mu)**2 - 4*p1(mu)*p2(mu) - 4*p1(mu)*p3(mu))*x2*x3*x5*x6 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2*x3*x5**2 + (-8*eps*p1(mu)*p2(mu) - 4*eps*p1(mu)*p3(mu) - 8*p1(mu)*p2(mu) - 4*p1(mu)*p3(mu))*x2*x3*x4*x6 + (4*eps*p1(mu)**2 - 4*eps*p1(mu)*p2(mu) + 4*p1(mu)**2 - 4*p1(mu)*p2(mu))*x2*x3*x4*x5 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x2*x3*x4**2 + (-4*eps*p1(mu)*p2(mu) - 4*eps*p1(mu)*p3(mu) - 4*p1(mu)*p2(mu) - 4*p1(mu)*p3(mu))*x2*x3**2*x6 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2*x3**2*x5 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x2*x3**2*x4 + (2*eps*p1(mu)**2 + 2*p1(mu)**2)*x2**2*x6**2 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x5*x6 + (2*eps*p1(mu)**2 + 2*p1(mu)**2)*x2**2*x5**2 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x4*x6 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x4*x5 + (2*eps*p1(mu)**2 + 2*p1(mu)**2)*x2**2*x4**2 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x3*x6 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x3*x5 + (4*eps*p1(mu)**2 + 4*p1(mu)**2)*x2**2*x3*x4 + (2*eps*p1(mu)**2 + 2*p1(mu)**2)*x2**2*x3**2 + (-4*eps*p2(mu)*p3(mu) - 4*p2(mu)*p3(mu))*x1*x6*U + (-4*eps*p2(mu)*p3(mu) - 4*p2(mu)*p3(mu))*x1*x5*U + (-4*eps*p2(mu)*p3(mu) - 4*p2(mu)*p3(mu))*x1*x4*U + (-4*eps*p2(mu)*p3(mu) - 4*p2(mu)*p3(mu))*x1*x3*U + (4*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) + 4*p2(mu)**2 + 4*p2(mu)*p3(mu))*x1*x3*x6**2 + (-4*eps*p1(mu)*p2(mu) + 4*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) - 4*p1(mu)*p2(mu) + 4*p2(mu)**2 + 4*p2(mu)*p3(mu))*x1*x3*x5*x6 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x3*x5**2 + (8*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) + 8*p2(mu)**2 + 4*p2(mu)*p3(mu))*x1*x3*x4*x6 + (-4*eps*p1(mu)*p2(mu) + 4*eps*p2(mu)**2 - 4*p1(mu)*p2(mu) + 4*p2(mu)**2)*x1*x3*x4*x5 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1*x3*x4**2 + (4*eps*p2(mu)**2 + 4*eps*p2(mu)*p3(mu) + 4*p2(mu)**2 + 4*p2(mu)*p3(mu))*x1*x3**2*x6 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x3**2*x5 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1*x3**2*x4 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x2*x6**2 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x5*x6 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x2*x5**2 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x4*x6 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x4*x5 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x2*x4**2 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x3*x6 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x3*x5 + (-8*eps*p1(mu)*p2(mu) - 8*p1(mu)*p2(mu))*x1*x2*x3*x4 + (-4*eps*p1(mu)*p2(mu) - 4*p1(mu)*p2(mu))*x1*x2*x3**2 + (2*eps*p2(mu)**2 + 2*p2(mu)**2)*x1**2*x6**2 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x5*x6 + (2*eps*p2(mu)**2 + 2*p2(mu)**2)*x1**2*x5**2 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x4*x6 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x4*x5 + (2*eps*p2(mu)**2 + 2*p2(mu)**2)*x1**2*x4**2 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x3*x6 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x3*x5 + (4*eps*p2(mu)**2 + 4*p2(mu)**2)*x1**2*x3*x4 + (2*eps*p2(mu)**2 + 2*p2(mu)**2)*x1**2*x3**2

We can also generate the output in terms of Mandelstam invariants:

>>> replacement_rules = [
...                        ('p1*p1', 0),
...                        ('p2*p2', 0),
...                        ('p3*p3', 0),
...                        ('p4*p4', 0),
...                        ('p1*p2', 's/2'),
...                        ('p2*p3', 't/2'),
...                        ('p1*p3', '-s/2-t/2')
...                     ]
>>> box = LoopIntegralFromPropagators(propagators, loop_momenta, external_momenta,
...                                     numerator=numerator, Lorentz_indices=Lorentz_indices,
...                                     replacement_rules=replacement_rules)
>>> box.U
 + (1)*x3*x6 + (1)*x3*x5 + (1)*x3*x4 + (1)*x2*x6 + (1)*x2*x5 + (1)*x2*x4 + (1)*x2*x3 + (1)*x1*x6 + (1)*x1*x5 + (1)*x1*x4 + (1)*x1*x3 + (1)*x0*x6 + (1)*x0*x5 + (1)*x0*x4 + (1)*x0*x3
>>> box.F
 + (-s)*x3*x4*x5 + (-s)*x2*x4*x5 + (-s)*x2*x3*x4 + (-s)*x1*x4*x5 + (-s)*x1*x3*x5 + (-s)*x1*x2*x6 + (-s)*x1*x2*x5 + (-s)*x1*x2*x4 + (-s)*x1*x2*x3 + (-s)*x0*x4*x5 + (-t)*x0*x3*x6
>>> box.numerator
 + (eps - 2)*x6*F + (eps - 2)*x5*F + (eps - 2)*x4*F + (eps - 2)*x3*F + (-2*eps*t - 2*t)*x3*x6*U + (-2*eps*s - 2*eps*t - 2*s - 2*t)*x3*x5*U + (-2*eps*t - 2*t)*x3*x4*U + (2*eps*t + 2*t)*x3**2*x6**2 + (2*eps*t + 2*t)*x3**2*x5*x6 + (2*eps*t + 2*t)*x3**2*x4*x6 + (-2*eps*s - 2*s)*x3**2*x4*x5 + (-2*eps*s - 2*eps*t - 2*s - 2*t)*x2*x6*U + (-2*eps*s - 2*eps*t - 2*s - 2*t)*x2*x5*U + (-2*eps*s - 2*eps*t - 2*s - 2*t)*x2*x4*U + (-2*eps*s - 2*eps*t - 2*s - 2*t)*x2*x3*U + (2*eps*t + 2*t)*x2*x3*x6**2 + (2*eps*t + 2*t)*x2*x3*x5*x6 + (-2*eps*s + 2*eps*t - 2*s + 2*t)*x2*x3*x4*x6 + (-2*eps*s - 2*s)*x2*x3*x4*x5 + (-2*eps*s - 2*s)*x2*x3*x4**2 + (2*eps*t + 2*t)*x2*x3**2*x6 + (-2*eps*s - 2*s)*x2*x3**2*x4 + (-2*eps*t - 2*t)*x1*x6*U + (-2*eps*t - 2*t)*x1*x5*U + (-2*eps*t - 2*t)*x1*x4*U + (-2*eps*t - 2*t)*x1*x3*U + (2*eps*t + 2*t)*x1*x3*x6**2 + (-2*eps*s + 2*eps*t - 2*s + 2*t)*x1*x3*x5*x6 + (-2*eps*s - 2*s)*x1*x3*x5**2 + (2*eps*t + 2*t)*x1*x3*x4*x6 + (-2*eps*s - 2*s)*x1*x3*x4*x5 + (2*eps*t + 2*t)*x1*x3**2*x6 + (-2*eps*s - 2*s)*x1*x3**2*x5 + (-2*eps*s - 2*s)*x1*x2*x6**2 + (-4*eps*s - 4*s)*x1*x2*x5*x6 + (-2*eps*s - 2*s)*x1*x2*x5**2 + (-4*eps*s - 4*s)*x1*x2*x4*x6 + (-4*eps*s - 4*s)*x1*x2*x4*x5 + (-2*eps*s - 2*s)*x1*x2*x4**2 + (-4*eps*s - 4*s)*x1*x2*x3*x6 + (-4*eps*s - 4*s)*x1*x2*x3*x5 + (-4*eps*s - 4*s)*x1*x2*x3*x4 + (-2*eps*s - 2*s)*x1*x2*x3**2

3.3. Sector Decomposition

The sector decomposition algorithm aims to factorize the polynomials P_i as products of a monomial and a polynomial with nonzero constant term:

P_i( \{x_j\} ) \longmapsto \prod_j x_j^{\alpha_j} \left( const + p_i(\{ x_j \}) \right).

Factorizing polynomials in that way by expoliting integral transformations is the first step in an algorithm for solving dimensionally regulated integrals of the form

\int_0^1 \prod_{i,j} P_i(\{ x_j \})^{\beta_i} ~ dx_j.

The iterative sector decomposition splits the integral and remaps the integration domain until all polynomials P_i in all arising integrals (called sectors) have the desired form const + polynomial. An introduction to the sector decomposition approach can be found in [Hei08].

To demonstrate the pySecDec.decomposition module, we decompose the polynomials

>>> p1 = Polynomial.from_expression('x + A*y', ['x','y','z'])
>>> p2 = Polynomial.from_expression('x + B*y*z', ['x','y','z'])

Let us first focus on the iterative decomposition of p1. In the pySecDec framework, we first have to pack p1 into a Sector:

>>> from pySecDec.decomposition import Sector
>>> initial_sector = Sector([p1])
>>> print(initial_sector)
Sector:
Jacobian= + (1)
cast=[( + (1)) * ( + (1)*x + (A)*y)]
other=[]

We can now run the iterative decomposition and take a look at the decomposed sectors:

>>> from pySecDec.decomposition.iterative import iterative_decomposition
>>> decomposed_sectors = iterative_decomposition(initial_sector)
>>> for sector in decomposed_sectors:
...     print(sector)
...     print('\n')
...
Sector:
Jacobian= + (1)*x
cast=[( + (1)*x) * ( + (1) + (A)*y)]
other=[]


Sector:
Jacobian= + (1)*y
cast=[( + (1)*y) * ( + (1)*x + (A))]
other=[]

The decomposition of p2 needs two iterations and yields three sectors:

>>> initial_sector = Sector([p2])
>>> decomposed_sectors = iterative_decomposition(initial_sector)
>>> for sector in decomposed_sectors:
...     print(sector)
...     print('\n')
...
Sector:
Jacobian= + (1)*x
cast=[( + (1)*x) * ( + (1) + (B)*y*z)]
other=[]


Sector:
Jacobian= + (1)*x*y
cast=[( + (1)*x*y) * ( + (1) + (B)*z)]
other=[]


Sector:
Jacobian= + (1)*y*z
cast=[( + (1)*y*z) * ( + (1)*x + (B))]
other=[]

Note that we declared z as a variable for sector p1 evne though it does not depend on it. This declaration is necessary if we want to simultaneously decompose p1 and p2:

>>> initial_sector = Sector([p1, p2])
>>> decomposed_sectors = iterative_decomposition(initial_sector)
>>> for sector in decomposed_sectors:
...      print(sector)
...      print('\n')
...
Sector:
Jacobian= + (1)*x
cast=[( + (1)*x) * ( + (1) + (A)*y), ( + (1)*x) * ( + (1) + (B)*y*z)]
other=[]


Sector:
Jacobian= + (1)*x*y
cast=[( + (1)*y) * ( + (1)*x + (A)), ( + (1)*x*y) * ( + (1) + (B)*z)]
other=[]


Sector:
Jacobian= + (1)*y*z
cast=[( + (1)*y) * ( + (1)*x*z + (A)), ( + (1)*y*z) * ( + (1)*x + (B))]
other=[]

We just fully decomposed p1 and p2. In some cases, one may want to bring one polynomial, say p1, into standard form, but not neccessarily the other. For that purpose, the Sector can take a second argument. In the following code example, we bring p1 into standard form, apply all transformations to p2 as well, but stop before p2 is fully decomposed:

>>> initial_sector = Sector([p1], [p2])
>>> decomposed_sectors = iterative_decomposition(initial_sector)
>>> for sector in decomposed_sectors:
...      print(sector)
...      print('\n')
...
Sector:
Jacobian= + (1)*x
cast=[( + (1)*x) * ( + (1) + (A)*y)]
other=[ + (1)*x + (B)*x*y*z]


Sector:
Jacobian= + (1)*y
cast=[( + (1)*y) * ( + (1)*x + (A))]
other=[ + (1)*x*y + (B)*y*z]

3.4. Subtraction

In the subtraction, we want to perform those integrations that lead to \epsilon divergencies. The master formula for one integration variables is

\int_0^1 {x^{(a - b \epsilon)} \mathcal{I} (x, \epsilon) dx} =
\sum_{p=0}^{|a|-1}
    {
        \frac{1}{a + p + 1 - b \epsilon}
        \frac{\mathcal{I}^{(p)} (0, \epsilon)}{p!} +
        \int_0^1
        {
            x^{(a - b \epsilon)}
            R(x, \epsilon) dx
        }
    }

where \mathcal{I}^{(p)} is denotes the p-th derivative of \mathcal{I} with respect to x. The equation above effectively defines the remainder term R. All terms on the right hand side of the equation above are constructed to be free of divergencies. For more details and the generalization to multiple variables, we refer the reader to [Hei08]. In the following, we show how to use the implementation in pySecDec.

To initialize the subtraction, we first define a factorized expression of the form x^{(-1 - b_x \epsilon)} y^{(-2 - b_y \epsilon)} \mathcal{I} (x, y, \epsilon):

>>> from pySecDec.algebra import Expression
>>> symbols = ['x','y','eps']
>>> x_monomial = Expression('x**(-1 - b_x*eps)', symbols)
>>> y_monomial = Expression('y**(-2 - b_y*eps)', symbols)
>>> cal_I = Expression('cal_I(x, y, eps)', symbols)

We must pack the monomials into a pySecDec.algebra.Product:

>>> from pySecDec.algebra import Product
>>> monomials = Product(x_monomial, y_monomial)

Although this seems to be to complete input according to the equation above, we are still missing a structure to store poles in. The function pySecDec.subtraction.integrate_pole_part() is designed to return an iterable of the same type as the input. That is particularly important since the output of the subtraction of one variable is the input for the subtraction of the next variable. We will see this iteration later. Initially, we do not have poles yet, therefore we define a one of the required type:

>>> from pySecDec.algebra import Pow
>>> import numpy as np
>>> polynomial_one = Polynomial(np.zeros([1,len(symbols)], dtype=int), np.array([1]), symbols, copy=False)
>>> pole_part_initializer = Pow(polynomial_one, -polynomial_one)

pole_part_initializer is of type pySecDec.algebra.Pow and has -polynomial_one in the exponent. We initialize the base with polynomial_one; i.e. a one packed into a polynomial. The function pySecDec.subtraction.integrate_pole_part() populates the base with factors of b\epsilon when poles arise.

We are now ready to build the subtraction_initializer - the pySecDec.algebra.Product to be passed into pySecDec.subtraction.integrate_pole_part().

>>> from pySecDec.subtraction import integrate_pole_part
>>> subtraction_initializer = Product(monomials, pole_part_initializer, cal_I)
>>> x_subtracted = integrate_pole_part(subtraction_initializer, 0)

The second argument of pySecDec.subtraction.integrate_pole_part() specifies to which variable we want to apply the master formula, here we choose x. First, remember that the x monomial is a dimensionally regulated x^-1. Therefore, the sum collapses to only one term and we have two terms in total. Each term corresponds to one entry in the list x_subtracted:

>>> len(x_subtracted)
2

x_subtracted has the same structure as our input. The first factor of each term stores the remaining monomials:

>>> x_subtracted[0].factors[0]
(( + (1))**( + (-b_x)*eps + (-1))) * (( + (1)*y)**( + (-b_y)*eps + (-2)))
>>> x_subtracted[1].factors[0]
(( + (1)*x)**( + (-b_x)*eps + (-1))) * (( + (1)*y)**( + (-b_y)*eps + (-2)))

The second factor stores the \epsilon poles. There is an epsilon pole in the first term, but still none in the second:

>>> x_subtracted[0].factors[1]
( + (-b_x)*eps) ** ( + (-1))
>>> x_subtracted[1].factors[1]
( + (1)) ** ( + (-1))

The last factor catches everything that is not covered by the first two fields:

>>> x_subtracted[0].factors[2]
(cal_I( + (0), + (1)*y, + (1)*eps))
>>> x_subtracted[1].factors[2]
(cal_I( + (1)*x, + (1)*y, + (1)*eps)) + (( + (-1)) * (cal_I( + (0), + (1)*y, + (1)*eps)))

We have now performed the subtraction for x. Because in and output have a similar structure, we can easily perform the subtraction for y as well:

>>> x_and_y_subtracted = []
>>> for s in x_subtracted:
...     x_and_y_subtracted.extend( integrate_pole_part(s,1) )

Alternatively, we can directly instruct pySecDec.subtraction.integrate_pole_part() to perform both subtractions:

>>> alternative_x_and_y_subtracted = integrate_pole_part(subtraction_initializer,0,1)

In both cases, the result is a list of the terms appearing on the right hand side of the master equation.

3.5. Expansion

The purpose of the expansion module is, as the name suggests, to provide routines to perform a series expansion. The module basically implements two routines - the Taylor expansion (pySecDec.expansion.expand_Taylor()) and an expansion of polyrational functions supporting singularities in the expansion variable (pySecDec.expansion.expand_singular()).

3.5.1. Taylor Expansion

The function pySecDec.expansion.expand_Taylor() implements the ordinary Taylor expansion. It takes an algebraic expression (in the sense of the algebra module, the index of the expansion variable and the order to which the expression shall be expanded:

>>> from pySecDec.algebra import Expression
>>> from pySecDec.expansion import expand_Taylor
>>> expression = Expression('x**eps', ['eps'])
>>> expand_Taylor(expression, 0, 2).simplify()
 + (1) + (log( + (x)))*eps + ((log( + (x))) * (log( + (x))) * ( + (1/2)))*eps**2

It is also possible to expand an expression in multiple variables simultaneously:

>>> expression = Expression('x**(eps + alpha)', ['eps', 'alpha'])
>>> expand_Taylor(expression, [0,1], [2,0]).simplify()
 + (1) + (log( + (x)))*eps + ((log( + (x))) * (log( + (x))) * ( + (1/2)))*eps**2

The command above instructs pySecDec.expansion.expand_Taylor() to expand the expression to the second order in the variable indexed 0 (eps) and to the zeroth order in the variable indexed 1 (alpha).

3.5.2. Laurent Expansion

pySecDec.expansion.expand_singular() Laurent expands polyrational functions.

Its input is more restrictive than for the Taylor expansion. It expects a Product where the factors are either Polynomials or ExponentiatedPolynomials with exponent = -1:

>>> from pySecDec.expansion import expand_singular
>>> expression = Expression('1/(eps + alpha)', ['eps', 'alpha']).simplify()
>>> expand_singular(expression, 0, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pcl340a/sjahn/Projects/pySecDec/pySecDec/expansion.py", line 241, in expand_singular
    return _expand_and_flatten(product, indices, orders, _expand_singular_step)
  File "/home/pcl340a/sjahn/Projects/pySecDec/pySecDec/expansion.py", line 209, in _expand_and_flatten
    expansion = recursive_expansion(expression, indices, orders)
  File "/home/pcl340a/sjahn/Projects/pySecDec/pySecDec/expansion.py", line 198, in recursive_expansion
    expansion = expansion_one_variable(expression, index, order)
  File "/home/pcl340a/sjahn/Projects/pySecDec/pySecDec/expansion.py", line 82, in _expand_singular_step
    raise TypeError('`product` must be a `Product`')
TypeError: `product` must be a `Product`
>>> expression # ``expression`` is indeed a polyrational function.
( + (1)*alpha + (1)*eps)**(-1)
>>> type(expression) # It is just not packed in a ``Product`` as ``expand_singular`` expects.
<class 'pySecDec.algebra.ExponentiatedPolynomial'>
>>> from pySecDec.algebra import Product
>>> expression = Product(expression)
>>> expand_singular(expression, 0, 1)
 + (( + (1)) * (( + (1)*alpha)**(-1))) + (( + (-1)) * (( + (1)*alpha**2)**(-1)))*eps

Like in the Taylor expansion, we can expand simultaneously in multiple parameters. Note, however, that the result of the Laurent expansion depends on the ordering of the expansion variables. The second argument of pySecDec.expansion.expand_singular() determines the order of the expansion:

>>> expression = Expression('1/(2*eps) * 1/(eps + alpha)', ['eps', 'alpha']).simplify()
>>> eps_first = expand_singular(expression, [0,1], [1,1])
>>> eps_first
 + (( + (1/2)) * (( + (1))**(-1)))*eps**-1*alpha**-1 + (( + (-1/2)) * (( + (1))**(-1)))*alpha**-2 + (( + (1)) * (( + (2))**(-1)))*eps*alpha**-3
>>> alpha_first = expand_singular(expression, [1,0], [1,1])
>>> alpha_first
 + (( + (1/2)) * (( + (1))**(-1)))*eps**-2 + (( + (-1/2)) * (( + (1))**(-1)))*eps**-3*alpha

The expression printed out by our algebra module are quite messy. In order to obtain nicer output, we can convert these expressions to the slower but more high level sympy:

>>> import sympy as sp
>>> eps_first = expand_singular(expression, [0,1], [1,1])
>>> alpha_first = expand_singular(expression, [1,0], [1,1])
>>> sp.sympify(str(eps_first))
1/(2*alpha*eps) - 1/(2*alpha**2) + eps/(2*alpha**3)
>>> sp.sympify(str(alpha_first))
-alpha/(2*eps**3) + 1/(2*eps**2)