Quickstart#
This quickstart introduces RMT, Mellin products, one-loop integrals and the dispersive sunset functions. CI executes the examples and checks their output.
To follow along, install the package, or run the same calls without installing anything on the Try it in your browser page.
The functions introduced here take dps=, the number of decimal digits requested, and
return mpmath numbers (mpf for real, mpc for complex
results).
For graph-based calculations beyond the named topologies, see
Massive Euclidean multiloop graphs. That experimental backend returns float64
estimates with statistical errors and uses epsrel/epsabs instead of dps.
For a graph-generated multivariate Gamma series with arbitrary precision,
see Multivariate Ramanujan series for the D=2 sunset, currently restricted to two justified
convergence charts of the massive, unit-power D=2 sunset.
For a method-selection table and three reference-checked graph calculations, see Choose a method and check your first result.
1. Ramanujan’s Master Theorem#
If \(f(x) = \sum_{n\ge 0} \varphi(n)\,(-x)^n/n!\), then, under Hardy’s growth condition on \(\varphi\),
For \(f(x) = (1+x)^{-3}\) the Taylor coefficients give \(\varphi(n) = \Gamma(3+n)/\Gamma(3)\), so the theorem predicts the Beta function \(B(s, 3-s)\):
>>> import mpmath as mp
>>> from ramanujan_qft import ramanujan_master_theorem, mellin_numeric
>>> phi = lambda n: mp.gamma(3 + n) / 2
>>> ramanujan_master_theorem(phi, 1.25)
mpf('0.41652027545234682')
>>> mp.beta(1.25, 1.75)
mpf('0.41652027545234682')
>>> mellin_numeric(lambda x: (1 + x)**-3, 1.25) # direct quadrature, as a check
mpf('0.41652027545234682')
ramanujan_master_theorem evaluates \(\Gamma(s)\,\varphi(-s)\) exactly as
given. It cannot tell whether the function you pass is the continuation of
\(\varphi\) that the theorem requires, so check new uses with mellin_numeric.
See Ramanujan’s Master Theorem.
2. Product integrals by Mellin residues#
product_integral(f, g, s=1) evaluates
\(\int_0^\infty x^{s-1} f(x)\,g(x)\,dx\), where f and g come from a small
atlas of functions with known Mellin transforms: exponential(k)
\(= e^{-kx}\), gaussian(a) \(= e^{-ax^2}\), bessel_j(nu, b) \(= J_\nu(bx)\),
cosine(b), sine(b), inverse_power(m, v) \(= (x+m)^{-v}\) and
gaussian_bessel(a, b, nu) \(= e^{-ax^2} J_\nu(bx)\).
>>> from ramanujan_qft import product_integral, exponential, bessel_j
>>> product_integral(exponential(1), bessel_j(0, 3))
mpf('0.31622776601683794')
>>> 1 / mp.sqrt(10) # closed form
mpf('0.31622776601683794')
The cost hardly depends on the oscillation frequency. Here is \(\int_0^\infty e^{-x} J_0(10^5 x)\,dx = 1/\sqrt{1+10^{10}}\) to 30 digits:
>>> v = product_integral(exponential(1), bessel_j(0, 1e5), dps=30)
>>> mp.nstr(v, 30)
'0.0000099999999995000000000375'
>>> with mp.workdps(30):
... mp.nstr(1 / mp.sqrt(1 + mp.mpf(10)**10), 30)
'0.0000099999999995000000000375'
Pass return_info=True to see which factor was expanded, the error estimate,
the number of terms and the working precision:
>>> product_integral(exponential(1), bessel_j(0, 3), dps=30, return_info=True)[1]
{'method': 'expand exp(-1 x)', 'error_estimate': mpf('1.3611704260536571e-31'), 'terms': 73, 'working_dps': 45}
Refusals. The residue series converge only when the scales of the two
factors are far enough apart. \(\int_0^\infty e^{-x} J_0(x)\,dx = 1/\sqrt{2}\)
exists, but both of its residue series sit on the convergence boundary
(\(\rho = 1\)), so product_integral raises an error instead of returning a
number it cannot verify:
>>> product_integral(exponential(1), bessel_j(0, 1))
Traceback (most recent call last):
...
ramanujan_qft.mellin.SeriesConvergenceError: neither ordering of exp(-1 x) * J_0(1 x) is a convergent residue expansion (the scales are at the convergence boundary rho = 1, or the factors cannot be expanded). Use quadrature or a closed form.
An integral that does not exist for the given s, such as
\(\int_0^\infty \cos x \cos 2x\,dx\), raises ValueError with the range of
Re s where it does exist. See Product integrals by Mellin residue summation.
3. One-loop integrals#
B0 and C0 follow the LoopTools conventions: arguments are squared
masses, the metric is Minkowski (\(p^2 > 0\) is time-like), propagators carry
\(+i0\), and B0 returns the finite part with \(\Delta = 0\) and \(\mu^2 = 1\) by
default (parameters delta and mu2). C0(s, m1sq, m2sq, m3sq) is
\(C_0(0, 0, s; m_1^2, m_2^2, m_3^2)\).
>>> from ramanujan_qft import B0, C0
>>> B0(10, 1, 2)
mpc(real='0.62259207309054199', imag='2.0116008064341786')
>>> C0(10, 1, 2, 3)
mpc(real='-0.42193253167473543', imag='-0.33673226803368017')
LoopTools 2.16 gives 0.622592073090541875 + 2.01160080643417860 i and
-0.421932531674735434 - 0.336732268033680282 i for these two calls
(tests/data/looptools_reference.csv): the results agree to double-precision
round-off. Unlike LoopTools, the same functions go to any precision:
>>> mp.nstr(C0(10, 1, 2, 3, dps=50), 50)
'(-0.42193253167473545355940149447302777724030318471005 - 0.33673226803368019435750959172486639656816178457921j)'
Also available: B0_mob and C0_mob (the equal-mass Method-of-Brackets
forms) and bubble_3d (the finite \(D=3\) bubble). See
One-loop integrals and Conventions.
4. The two-loop sunset#
sunset_im(s, m1sq, m2sq, m3sq) is the imaginary part of the massive sunset.
It vanishes below the three-particle threshold \(s = (m_1+m_2+m_3)^2\):
>>> from ramanujan_qft import sunset_im, sunset_threshold
>>> sunset_threshold(1, 2, 3)
mpf('17.191508225450303')
>>> sunset_im(100, 1, 2, 3)
mpf('-88.184995551555915')
>>> sunset_im(5, 1, 2, 3)
mpf('0.0')
sunset_im takes a fraction of a second. The other two sunset functions,
sunset_subtracted (\(S(s)-S(0)-s\,S'(0)\)) and sunset_d2 (\(S''(s)\)), run
nested quadratures at two working precisions and take 10–30 s per call, so
they are not run on this page. See Two-loop sunset.
5. Precision and printing#
A result carries the precision you requested, but print and repr show it
at mpmath’s global precision, mp.mp.dps, which is 15 by default. Use
mp.nstr(x, n) or a with mp.workdps(n): block to see all the digits:
>>> x = B0(10, 1, 2, dps=40)
>>> print(x)
(0.622592073090542 + 2.01160080643418j)
>>> mp.nstr(x, 40)
'(0.6225920730905419577001843621004278217297 + 2.01160080643417843946062487127894859004j)'
>>> with mp.workdps(40):
... print(x)
(0.6225920730905419577001843621004278217297 + 2.01160080643417843946062487127894859004j)
Inputs are taken as exact numbers. A Python float such as 0.1 is the
binary number nearest to 0.1. To mean the decimal value, pass a string. (An
mpf is binary too: mp.mpf("0.1") made at the default 15 digits is the same
number as the float.)
>>> mp.nstr(B0(0.1, 1, 2, dps=30), 30)
'(-0.374845190552788401789865766787 + 0.0j)'
>>> mp.nstr(B0('0.1', 1, 2, dps=30), 30)
'(-0.374845190552788402429829239118 + 0.0j)'
The one-loop and sunset functions are precision-checked: each is evaluated at two working precisions, which are raised until the results agree to the requested number of digits. Precision and numerical checks explains what this does and does not guarantee.
Next steps#
User guide: each part of the package in detail, with its conventions, limitations and refusals.
Examples: complete example scripts with their output.
API reference: every function and its parameters.
Validation: what the results are checked against.
How ramanujan-qft relates to existing tools: when another tool is the better choice.