diff --git a/doc/source/development/Benchmarks.rst b/doc/source/development/Benchmarks.rst index bde9f1cce..758bae648 100644 --- a/doc/source/development/Benchmarks.rst +++ b/doc/source/development/Benchmarks.rst @@ -58,7 +58,7 @@ APFEL (and FTDY as well). It has been used by the NNPDF collaboration up to NNPDF4.0 -|APFEL| solves |DGLAP| numerically in x-space up to |NNLO|. QED evolution is also available. +|APFEL| solves |DGLAP| numerically in x-space up to |NNLO|. |QED| evolution is also available. The programs provides 3 different strategies, and in various theory setups (|FNS|, SV, IC ) as shown in the table. As |Eko|, |APFEL| can be interfaced with |lhapdf|. diff --git a/doc/source/refs.bib b/doc/source/refs.bib index 346c499c1..624bcb5a6 100644 --- a/doc/source/refs.bib +++ b/doc/source/refs.bib @@ -447,6 +447,34 @@ @article{Liu:2015fxa year = "2015" } +@article{deFlorian:2015ujt, + author = "de Florian, Daniel and Sborlini, Germ\'an F. R. and Rodrigo, Germ\'an", + title = "{QED corrections to the Altarelli\textendash{}Parisi splitting functions}", + eprint = "1512.00612", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "ICAS-03-15, IFIC-15-81", + doi = "10.1140/epjc/s10052-016-4131-8", + journal = "Eur. Phys. J. C", + volume = "76", + number = "5", + pages = "282", + year = "2016" +} + +@article{deFlorian:2016gvk, + author = "de Florian, Daniel and Sborlini, Germ\'an F. R. and Rodrigo, Germ\'an", + title = "{Two-loop QED corrections to the Altarelli-Parisi splitting functions}", + eprint = "1606.02887", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "ICAS-09-16, IFIC-15-88", + doi = "10.1007/JHEP10(2016)056", + journal = "JHEP", + volume = "10", + pages = "056", + year = "2016" +} @article{Chetyrkin:2017bjc, author = "Chetyrkin, K. G. and Falcioni, G. and Herzog, F. and Vermaseren, J. A. M.", title = "{Five-loop renormalisation of QCD in covariant gauges}", diff --git a/doc/source/shared/abbreviations.rst b/doc/source/shared/abbreviations.rst index 1aff4e212..e7742731e 100644 --- a/doc/source/shared/abbreviations.rst +++ b/doc/source/shared/abbreviations.rst @@ -49,6 +49,12 @@ .. |RGE| replace:: :abbr:`RGE (renormalization group equation)` +.. |QCD| replace:: + :abbr:`QCD (Quantum Chromodynamics)` + +.. |QED| replace:: + :abbr:`QED (Quantum Electrodynamics)` + .. external .. |yadism| replace:: diff --git a/doc/source/theory/FlavorSpace.rst b/doc/source/theory/FlavorSpace.rst index 6882430f4..2baf94f5d 100644 --- a/doc/source/theory/FlavorSpace.rst +++ b/doc/source/theory/FlavorSpace.rst @@ -3,7 +3,7 @@ Flavor Space An |EKO| is a rank-4 operator acting both in Flavor Space :math:`\mathcal F` and momentum fraction space :math:`\mathcal X`. -By Flavor Space :math:`\mathcal F` we mean the 13-dimensional function space that contains +By Flavor Space :math:`\mathcal F` we mean the 14-dimensional function space that contains the different |PDF| flavor. Note, that there is an ambiguity concerning the word "Flavor Basis" which is sometimes referred to as an *abstract* basis in the Flavor Space, but often the specific basis described here below is meant. @@ -11,11 +11,11 @@ in the Flavor Space, but often the specific basis described here below is meant. Flavor Basis ------------ -Here we use the raw quark flavors along with the gluon as they correspond to the +Here we use the raw quark flavors along with the gluon and the photon, as they correspond to the operator in the Lagrange density: .. math :: - \mathcal F = \mathcal F_{fl} = \span(g, u, \bar u, d, \bar d, s, \bar s, c, \bar c, b, \bar b, t, \bar t) + \mathcal F = \mathcal F_{fl} = \span(\gamma, g, u, \bar u, d, \bar d, s, \bar s, c, \bar c, b, \bar b, t, \bar t) - we deliver the :class:`~eko.output.Output` in this basis, although the flavors are slightly differently arranged (Implementation: :data:`here `). @@ -35,17 +35,18 @@ that e.g. in the proton will carry most of the momentum at large x and :math:`q^ sea quark distribution: .. math :: - \mathcal F \sim \mathcal F_{\pm} = \span(g, u^+, u^-, d^+, d^-, s^+, s^-, c^+, c^-, b^+, b^-, t^+, t^-) + \mathcal F \sim \mathcal F_{\pm} = \span(\gamma, g, u^+, u^-, d^+, d^-, s^+, s^-, c^+, c^-, b^+, b^-, t^+, t^-) - this basis is *not* normalized with respect to the canonical Flavor Basis - the basis transformation to the Flavor Basis is implemented in :meth:`~eko.evolution_operator.flavors.rotate_pm_to_flavor` -Evolution Basis ---------------- +QCD Evolution Basis +------------------- As the gluon is flavor-blind it is handy to solve |DGLAP| not in the flavor basis, -but in the Evolution Basis where instead we need to solve a minimal coupled system. +but in the |QCD| Evolution Basis where instead we need to solve a minimal coupled system. +This is the basis in which |DGLAP| equations are solved when only |QCD| corrections are taken into account. The new basis elements can be separated into two major classes: the singlet sector, consisting of the singlet distribution :math:`\Sigma` and the gluon distribution :math:`g`, and the non-singlet sector. The non-singlet sector can be again subdivided into three groups: first the full @@ -66,7 +67,7 @@ The mapping between the Evolution Basis and the +/- Basis is given by T_{15} &= u^+ + d^+ + s^+ - 3 c^+\\ T_{24} &= u^+ + d^+ + s^+ + c^+ - 4 b^+\\ T_{35} &= u^+ + d^+ + s^+ + c^+ + b^+ - 5 t^+\\ - \mathcal F \sim \mathcal F_{ev} &= \span(g, \Sigma, V, V_{3}, V_{8}, V_{15}, V_{24}, V_{35}, T_{3}, T_{8}, T_{15}, T_{24}, T_{35}) + \mathcal F \sim \mathcal F_{ev} &= \span(\gamma, g, \Sigma, V, V_{3}, V_{8}, V_{15}, V_{24}, V_{35}, T_{3}, T_{8}, T_{15}, T_{24}, T_{35}) - the associated numbers to the valence-like and singlet-like non-singlet distributions @@ -75,30 +76,125 @@ The mapping between the Evolution Basis and the +/- Basis is given by - this basis is *not* normalized with respect to the canonical Flavor Basis - the basis transformation from the Flavor Basis is implemented in :data:`~eko.basis_rotation.rotate_flavor_to_evolution` +- the photon is just a spectator and does not couple to anyone -Intrinsic Evolution Bases -------------------------- +Intrinsic QCD Evolution Bases +----------------------------- -However, the Evolution Basis is not yet the most decoupled basis if we consider intrinsic evolution. +However, the |QCD| Evolution Basis is not yet the most decoupled basis if we consider intrinsic evolution. The intrinsic distributions do *not* participate in the |DGLAP| equation but instead evolve with a unity operator: this makes, e.g. :math:`T_{15}` a composite object in a evolution range below the charm mass. Instead, we will keep the non participating distributions here in their :math:`q^\pm` representation. -The Intrinsic Evolution Bases will explicitly depend on the number of light flavors :math:`n_f`. +The Intrinsic |QCD| Evolution Bases will explicitly depend on the number of light flavors :math:`n_f`. For :math:`n_f=3` we define (the other cases are defined analogously): .. math :: - \mathcal F \sim \mathcal F_{iev,3} = \span(g, \Sigma_{(3)}, V_{(3)}, V_3, V_8, T_3, T_8, c^+, c^-, b^+, b^-, t^+, t^-) + \Sigma_{(3)} &= u^+ + d^+ +s^+\\ + V_{(3)} &= u^- + d^- + s^-\\ + \mathcal F \sim \mathcal F_{iev,3} &= \span(\gamma, g, \Sigma_{(3)}, V_{(3)}, V_3, V_8, T_3, T_8, c^+, c^-, b^+, b^-, t^+, t^-) -where we defined :math:`\Sigma_{(3)} = \sum\limits_{j=1}^3 q_j^+` and :math:`V_{(3)} = \sum\limits_{j=1}^3 q_j^-` -(not to be confused with the usual :math:`V_3`). +where :math:`V_{(3)}` is not to be confused with the usual (|QCD| like) :math:`V_3`. -- for :math:`n_f=6` the Intrinsic Evolution Basis coincides with the Evolution Basis: :math:`\mathcal F_{iev,6} = \mathcal F_{ev}` +- for :math:`n_f=6` the Intrinsic |QCD| Evolution Basis coincides with the |QCD| Evolution Basis: :math:`\mathcal F_{iev,6} = \mathcal F_{ev}` - this basis is *not* normalized with respect to the canonical Flavor Basis - the basis transformation from the Flavor Basis is implemented in :meth:`~eko.evolution_operator.flavors.pids_from_intrinsic_evol` - note that for the case of non-intrinsic component the higher elements in :math:`\mathcal F_{ev}` do become linear dependent to other basis vectors (e.g. :math:`\left. T_{15}\right|_{c^+ = 0} = \Sigma`) but are non zero - instead in :math:`\mathcal F_{iev,3}` this direction vanishes +- the photon is just a spectator and does not couple to anyone + + +Unified Evolution Basis +----------------------- + +In presence of |QED| corrections to |DGLAP| evolution equations, +the |QCD| Evolution basis does not decouple the distributions +as it was for the pure |QCD| evolution. + +Defining the following combinations + +.. math :: + \Sigma_u & = u^+ + c^+ + t^+ \\ + \Sigma_d & = d^+ + s^+ + b^+ \\ + V_u & = u^- + c^- + t^- \\ + V_d & = d^- + s^- + b^- \\ + +we have that in this case the |QED| :math:`\otimes` |QCD| evolution basis that performs the maximal decoupling is given by: + +.. math :: + \Sigma &= \Sigma_u + \Sigma_d \\ + \Sigma_{\Delta} &= \Sigma_u - \Sigma_d \\ + V &= V_u + V_d \\ + V_{\Delta} &= V_u - V_d \\ + T_3^u &=u^+ - c^+ \\ + T_8^u &=u^+ + c^+ - 2t^+ \\ + T_3^d &=d^+ - s^+ \\ + T_8^d &=d^+ + s^+ - 2b^+ \\ + V_3^u &=u^- - c^- \\ + V_8^u &=u^- + c^- - 2t^- \\ + V_3^d &=d^- - s^- \\ + V_8^d &=d^- + s^- - 2b^- \\ + \mathcal F \sim \mathcal F_{uni,ev} &= \span(\gamma, g, \Sigma, \Sigma_{\Delta}, V, V_{\Delta}, T_3^u, T_8^u, T_3^d, T_8^d, V_3^u, V_8^u, V_3^d, V_8^d) + + +- this basis is *not* normalized with respect to the canonical Flavor Basis +- The singlet :math:`\Sigma` is just the |QCD| singlet +- The valence :math:`V` is just the |QCD| valence + + +Intrinsic Unified Evolution Basis +--------------------------------- + +Again, we need the generalization to the presence of intrinsic (static) distributions. +As |QED| can distinguish between up-like and down-like flavors the situation is again slightly +more involved. + +For :math:`n_f=3` light flavors we find: + +.. math :: + \Sigma_{(3)} &= u^+ + d^+ + s^+\\ + \Sigma_{\Delta,(3)} &= 2u^+ - d^+ - s^+ \\ + V_{(3)} &= u^- + d^- + s^-\\ + V_{\Delta,(3)} &= 2u^- - d^- - s^-\\ + T_3^d &=d^+ - s^+ \\ + V_3^d &=d^- - s^- \\ + \mathcal F \sim \mathcal F_{uni,iev,3} &= \span(\gamma, g, \Sigma_{(3)}, \Sigma_{\Delta,(3)}, V_{(3)}, V_{\Delta,(3)}, T_3^d, V_3^d, c^+, c^-, b^+, b^-, t^+, t^-) + +For :math:`n_f=4` light flavors we find: + +.. math :: + \Sigma_{(4)} &= u^+ + d^+ + s^+ + c^+\\ + \Sigma_{\Delta,(4)} &= u^+ + c^+ - d^+ - s^+\\ + V_{(4)} &= u^- + d^- + s^- + c^-\\ + V_{\Delta,(4)} &= u^- + c^- - d^- - s^-\\ + T_3^u &=u^+ - c^+ \\ + T_3^d &=d^+ - s^+ \\ + V_3^u &=u^- - c^- \\ + V_3^d &=d^- - s^- \\ + \mathcal F \sim \mathcal F_{uni,iev,4} &= \span(\gamma, g, \Sigma_{(4)}, \Sigma_{\Delta,(4)}, V_{(4)}, V_{\Delta,(4)}, V_3^d, T_3^d, V_3^u, T_3^u, b^+, b^-, t^+, t^-) + +For :math:`n_f=5` light flavors we find: + +.. math :: + \Sigma_{(5)} &= u^+ + d^+ + s^+ + c^+ + b^+\\ + \Sigma_{\Delta,(5)} &= \frac{3}{2}u^+ + \frac{3}{2}c^+ - d^+ -s^+ - b^+\\ + V_{(5)} &= u^- + d^- + s^- + c^- + b^-\\ + V_{\Delta,(5)} &= \frac{3}{2}u^- + \frac{3}{2}c^- - d^- -s^- - b^-\\ + T_3^u &=u^+ - c^+ \\ + T_3^d &=d^+ - s^+ \\ + V_3^u &=u^- - c^- \\ + V_3^d &=d^- - s^- \\ + T_8^d &=d^+ + s^+ - 2b^+ \\ + V_8^d &=d^- + s^- - 2b^- \\ + \mathcal F \sim \mathcal F_{uni,iev,5} &= \span(\gamma, g, \Sigma_{(4)}, \Sigma_{\Delta,(4)}, V_{(4)}, V_{\Delta,(4)}, V_3^d, T_3^d, V_3^u, T_3^u, T_8^d, V_8^d, t^+, t^-) + +For :math:`n_f=6` light flavors the Intrinsic Unified Evolution Basis coincides with the :ref:`theory/FlavorSpace:Unified Evolution Basis`. + +- this basis is *not* normalized with respect to the canonical Flavor Basis +- the basis transformation from the Flavor Basis is implemented in + :meth:`~eko.evolution_operator.flavors.pids_from_intrinsic_evol` +- the factors 3/2 in the definition of :math:`V_{0,(5)}` and :math:`T_{0,(5)}` are needed in order to have an orthogonal basis for :math:`n_f=5` Other Bases ----------- @@ -137,10 +233,10 @@ Operator Anomalous Dimension Basis - this basis can *not* span any threshold but can only be used for a *fixed* number of flavors - all actual computations are done in this basis -Operator Intrinsic Evolution Basis -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Operator Intrinsic QCD Evolution Basis +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- here we mean :ref:`theory/FlavorSpace:Intrinsic Evolution Bases` both in the input and the output space +- here we mean :ref:`theory/FlavorSpace:Intrinsic QCD Evolution Bases` both in the input and the output space - this basis does **not** coincide with the :ref:`theory/FlavorSpace:Operator Anomalous Dimension Basis` as the decision on which operator of that basis is used is a non-trivial decision - see :doc:`Matching` - this basis has :math:`2n_f+ 3 = 15` elements diff --git a/doc/source/theory/Matching.rst b/doc/source/theory/Matching.rst index 91844da3f..b2e2046a1 100644 --- a/doc/source/theory/Matching.rst +++ b/doc/source/theory/Matching.rst @@ -16,13 +16,13 @@ present :math:`\left(\mu_{h}^2 < Q_0^2 < Q_1^2 < \mu_{h+1}^2\right)`, in :doc:`M \tilde{\mathbf{f}}^{(n_f)}(Q^2_1)= \tilde{\mathbf{E}}^{(n_f)}(Q^2_1\leftarrow Q^2_0) \tilde{\mathbf{f}}^{(n_f)}(Q^2_0) The bold font indicates the vector space spanned by the :doc:`flavor space ` and the equations decouple mostly -in the :ref:`Intrinsic Evolution Basis `. +in the :ref:`Intrinsic Evolution Basis `. If a single threshold :math:`\left(\mu_{h-1}^2 < Q_0^2 < \mu_{h}^2 < Q_1^2 < \mu_{h+1}^2\right)` is present we decompose the matching into two independent steps: -first, the true QCD induced |OME| :math:`\mathbf{A}^{(n_f)}(\mu_{h}^2)` that are given by perturbative calculations and expressed in the flavor space, +first, the true |QCD| induced |OME| :math:`\mathbf{A}^{(n_f)}(\mu_{h}^2)` that are given by perturbative calculations and expressed in the flavor space, and, second, the necessary :doc:`flavor space rotation ` :math:`\mathbf{R}^{(n_f)}` to fit the -new :ref:`Intrinsic Evolution Basis `. +new :ref:`Intrinsic Evolution Basis `. We can then denote the solution as .. math :: @@ -38,7 +38,7 @@ The matching matrices :math:`\mathbf{A}^{(n_f)}(\mu_{h+1}^2)` mediate between :m and :math:`\mathcal F_{iev,n_f}^{(n_f+1)}`, i.e. they transform the basis vectors of the :math:`n_f`-flavors space in a :math:`n_f`-flavor scheme to the :math:`(n_f+1)`-flavor scheme. Hence, the supscript refers to the flavor scheme with a smaller number of active flavors. To compute the matrices in a minimal coupled system we decompose the -:ref:`Intrinsic Evolution Basis ` :math:`\mathcal F_{iev,n_f}` into +:ref:`Intrinsic Evolution Basis ` :math:`\mathcal F_{iev,n_f}` into several subspaces (of course irrespective of the |FNS|): .. math :: diff --git a/doc/source/theory/pQCD.rst b/doc/source/theory/pQCD.rst index e07ed1e54..b101c6280 100644 --- a/doc/source/theory/pQCD.rst +++ b/doc/source/theory/pQCD.rst @@ -6,7 +6,7 @@ Strong Coupling Implementation: :class:`~eko.strong_coupling.StrongCoupling`. -We use perturbative QCD with the running coupling +We use perturbative |QCD| with the running coupling :math:`a_s(\mu_R^2) = \alpha_s(\mu_R^2)/(4\pi)` given at 5-loop by :cite:`Herzog:2017ohr,Luthe:2016ima,Baikov:2016tgj,Chetyrkin:2017bjc,Luthe:2017ttg` @@ -47,10 +47,10 @@ In particular, the matching involved in the change from :math:`n_f` to :math:`n_ is presented in equation 3.1 of :cite:`Schroder:2005hy` for |MSbar| masses, while the same expression for POLE masses is reported in Appendix A. -Splitting Functions -------------------- +QCD Splitting Functions +----------------------- -The Altarelli-Parisi splitting kernels can be expanded in powers of the strong +In the case in which only the |QCD| corrections are considered, the Altarelli-Parisi splitting kernels can be expanded in powers of the strong coupling :math:`a_s(\mu^2)` and are given by :cite:`Moch:2004pa,Vogt:2004mw` .. math :: @@ -59,6 +59,24 @@ coupling :math:`a_s(\mu^2)` and are given by :cite:`Moch:2004pa,Vogt:2004mw` Note the additional minus in the definition of :math:`\gamma`. +Unified Splitting Functions +--------------------------- + +When the |QED| corrections are taken into account, |DGLAP| equation take the form + +.. math :: + \mathbf{P}=\mathbf{\tilde{P}}+\mathbf{\bar{P}} + +where :math:`\mathbf{\tilde{P}}` are the usual |QCD| splitting kernels defined in the previous section, +while :math:`\mathbf{\bar{P}}` are given by + +.. math :: + \mathbf{\bar{P}} = \alpha \mathbf{P}^{(0,1)} + \alpha_s \alpha \mathbf{P}^{(1,1)} + + \alpha^2 \mathbf{P}^{(0,2)} + \dots + +The expression of the pure |QED| and of the mixed |QED| :math:`\otimes` |QCD| splitting kernels are given in +:cite:`deFlorian:2015ujt,deFlorian:2016gvk` + Scale Variations ---------------- @@ -83,7 +101,7 @@ corresponds to schemes A and B in :cite:`AbdulKhalek:2019ihb`. Heavy Quark Masses ------------------ -In QCD also the heavy quark masses (:math:`m_{c}, m_{b}, m_{t}`) follow a |RGE| +In |QCD| also the heavy quark masses (:math:`m_{c}, m_{b}, m_{t}`) follow a |RGE| and their values depend on the energy scale at which the quark is probed. Masses do not play any role in a single flavour patch, but are important in |VFNS| when more flavour schemes need to be joined (see :doc:`matching @@ -144,7 +162,7 @@ In doing so EKO takes advantage of the monotony of the |RGE| solution Now, being able to evaluate :math:`a_s(\mu_{h,0}^2)`, there are two ways of solving the previous integral and finally compute the evolved :math:`m_{\overline{MS},h}`. In fact, the function :math:`\gamma_m(a_s)` is the -anomalous QCD mass dimension and, as the :math:`\beta` function, it can be evaluated +anomalous |QCD| mass dimension and, as the :math:`\beta` function, it can be evaluated perturbatively in :math:`a_s` up to :math:`\mathcal{O}(a_s^4)`: .. math :: diff --git a/poetry.lock b/poetry.lock index 27688fccd..5410f9075 100644 --- a/poetry.lock +++ b/poetry.lock @@ -93,28 +93,24 @@ docs = ["Sphinx (>=4.3.2,<5.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)", "sphinxc [[package]] name = "black" -version = "21.12b0" +version = "22.1.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0,<1" +pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = ">=0.2.6,<2.0.0" -typing-extensions = [ - {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, - {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, -] +tomli = ">=1.1.0" +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] @@ -278,7 +274,7 @@ docs = ["sphinx"] [[package]] name = "identify" -version = "2.4.9" +version = "2.4.10" description = "File identification library for Python" category = "dev" optional = false @@ -1120,11 +1116,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.3" +version = "2.0.1" description = "A lil' TOML parser" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "traitlets" @@ -1219,7 +1215,7 @@ mark = ["banana-hep", "sqlalchemy", "pandas", "matplotlib"] [metadata] lock-version = "1.1" python-versions = "^3.8,<3.11" -content-hash = "50fc2db62be958b4a328ba0faaef3b7cc5968dd13811d88e432354238e9f2ef7" +content-hash = "ffe8b44e472a8495bfc6b6bf908ab4f7a1bc1fe68fb55ea1d7378cadc49d958e" [metadata.files] alabaster = [ @@ -1255,8 +1251,29 @@ banana-hep = [ {file = "banana_hep-0.6.0-py3-none-any.whl", hash = "sha256:98f5fac7f874e7f03781c65a1bde9c47b5d4368ab2b66c568f39943fe073a87b"}, ] black = [ - {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, - {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1406,8 +1423,8 @@ greenlet = [ {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] identify = [ - {file = "identify-2.4.9-py2.py3-none-any.whl", hash = "sha256:bff7c4959d68510bc28b99d664b6a623e36c6eadc933f89a4e0a9ddff9b4fee4"}, - {file = "identify-2.4.9.tar.gz", hash = "sha256:e926ae3b3dc142b6a7a9c65433eb14ccac751b724ee255f7c2ed3b5970d764fb"}, + {file = "identify-2.4.10-py2.py3-none-any.whl", hash = "sha256:7d10baf6ba6f1912a0a49f4c1c2c49fa1718765c3a37d72d13b07779567c5b85"}, + {file = "identify-2.4.10.tar.gz", hash = "sha256:e12b2aea3cf108de73ae055c2260783bde6601de09718f6768cf8e9f6f6322a6"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -2089,8 +2106,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, - {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] traitlets = [ {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, diff --git a/pyproject.toml b/pyproject.toml index 99327c360..1766e6d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ pandas = { version = "^1.3.0", optional = true } matplotlib = { version = "^3.5.1", optional = true } [tool.poetry.dev-dependencies] -black = "^21.12b0" +black = "^22.1.0" isort = "^5.10.1" pylint = "^2.12.2" pre-commit = "^2.16.0" diff --git a/src/eko/anomalous_dimensions/lo.py b/src/eko/anomalous_dimensions/lo.py index 39b4398af..847fb4ba5 100644 --- a/src/eko/anomalous_dimensions/lo.py +++ b/src/eko/anomalous_dimensions/lo.py @@ -4,7 +4,7 @@ import numba as nb import numpy as np -from eko import constants +from .. import constants @nb.njit("c16(c16,c16)", cache=True) diff --git a/src/eko/basis_rotation.py b/src/eko/basis_rotation.py index 9647bd16c..0aa256380 100644 --- a/src/eko/basis_rotation.py +++ b/src/eko/basis_rotation.py @@ -34,6 +34,9 @@ ) """String representation of :data:`flavor_basis_pids`.""" + +quark_names = "".join(flavor_basis_names[-6:]) + evol_basis = ( "ph", "S", @@ -168,3 +171,38 @@ def ad_projectors(nf): projs.append(ad_projector(ad, nf)) return np.array(projs) + + +def intrinsic_unified_evol_labels(nf): + """ + Collect all labels in the intrinsic unified evolution basis. + + Parameters + ---------- + nf : int + number of light flavors + + Returns + ------- + labels : list(str) + active distributions + """ + labels = [ + "ph", + "g", + "S", + "V", + "Sdelta", + "Vdelta", + "Td3", + "Vd3", + ] + news = ["u3", "d8", "u8"] + for j in range(nf, 6): + q = quark_names[j] + labels.extend([f"{q}+", f"{q}-"]) + for k in range(3, nf): + new = news[k - 3] + labels.extend([f"T{new}", f"V{new}"]) + + return labels diff --git a/src/eko/beta.py b/src/eko/beta.py index dcdeda6de..f7d9af05d 100644 --- a/src/eko/beta.py +++ b/src/eko/beta.py @@ -7,8 +7,8 @@ import numba as nb -from eko import constants -from eko.anomalous_dimensions.harmonics import zeta3 +from . import constants +from .anomalous_dimensions.harmonics import zeta3 @nb.njit("f8(u1)", cache=True) diff --git a/src/eko/evolution_operator/flavors.py b/src/eko/evolution_operator/flavors.py index 508c09283..ed62fe0ab 100644 --- a/src/eko/evolution_operator/flavors.py +++ b/src/eko/evolution_operator/flavors.py @@ -9,10 +9,8 @@ from .. import basis_rotation as br -quark_names = "duscbt" - -def pids_from_intrinsic_evol(label, nlf, normalize): +def pids_from_intrinsic_evol(label, nf, normalize): r""" Obtain the list of pids with their corresponding weight, that are contributing to ``evol`` @@ -24,13 +22,13 @@ def pids_from_intrinsic_evol(label, nlf, normalize): e.g. in nf=3 :math:`u = \frac 1 6 S + \frac 1 6 V + \ldots` The normalization can only happen here since we're actively cutting out some - flavor (according to ``nlf``). + flavor (according to ``nf``). Parameters ---------- evol : str evolution label - nlf : int + nf : int maximum number of light flavors normalize : bool normalize output @@ -47,7 +45,7 @@ def pids_from_intrinsic_evol(label, nlf, normalize): if is_evol: weights = br.rotate_flavor_to_evolution[evol_idx].copy() for j, pid in enumerate(br.flavor_basis_pids): - if nlf < abs(pid) <= 6: + if nf < abs(pid) <= 6: weights[j] = 0 else: weights = rotate_pm_to_flavor(label) @@ -106,7 +104,7 @@ def rotate_pm_to_flavor(label): if label in ["g", "ph"]: return br.rotate_flavor_to_evolution[br.evol_basis.index(label)].copy() # no it has to be a quark with + or - appended - if label[0] not in quark_names or label[1] not in ["+", "-"]: + if label[0] not in br.quark_names or label[1] not in ["+", "-"]: raise ValueError(f"Invalid pm label: {label}") l = np.zeros(len(br.flavor_basis_pids)) idx = br.flavor_basis_names.index(label[0]) @@ -146,7 +144,7 @@ def rotate_matching(nf, inverse=False): l[f"T{n}.T{n}"] = 1.0 # the new contributions n = nf**2 - 1 # nf is pointing upwards - q = quark_names[nf - 1] + q = br.quark_names[nf - 1] for (tot, oth, qpm) in (("S", f"T{n}", f"{q}+"), ("V", f"V{n}", f"{q}-")): if inverse: l[f"{tot}.{tot}"] = (nf - 1.0) / nf @@ -160,7 +158,7 @@ def rotate_matching(nf, inverse=False): l[f"{oth}.{qpm}"] = -(nf - 1.0) # also higher quarks do not care for k in range(nf + 1, 6 + 1): - q = quark_names[k - 1] + q = br.quark_names[k - 1] for sgn in "+-": l[f"{q}{sgn}.{q}{sgn}"] = 1.0 return l @@ -168,3 +166,68 @@ def rotate_matching(nf, inverse=False): def rotate_matching_inverse(nf): return rotate_matching(nf, True) + + +def pids_from_intrinsic_unified_evol(label, nf, normalize): + r""" + Obtain the list of pids with their corresponding weight, that are contributing to intrinsic + unified evolution. + + Parameters + ---------- + evol : str + evolution label + nf : int + maximum number of light flavors + normalize : bool + normalize output + + Returns + ------- + m : list + """ + if label in ["ph", "g", "S", "V"]: + return pids_from_intrinsic_evol(label, nf, normalize) + if label[0] in br.quark_names[3:]: + weights = rotate_pm_to_flavor(label) + else: + weights = np.array([0.0] * len(br.flavor_basis_pids)) + mapping = { + "d3": {1: 1.0, 3: -1.0}, # T3d = d+ - s+ + "d8": {1: 1.0, 3: 1.0, 5: -2.0}, # T8d = d+ + s+ - 2b+ + "u3": {2: 1.0, 4: -1.0}, # T3u = u+ - c+ + "u8": {2: 1.0, 4: 1.0, 6: -2.0}, # T8u = u+ + c+ - 2t+ + "delta": { + 3: {2: 2.0, 1: -1.0, 3: -1.0}, # Sdelta = 2u+ - d+ -s+ + 4: {2: 1.0, 4: 1.0, 1: -1.0, 3: -1.0}, # Sdelta = u+ + c+ - d+ - s+ + 5: { + 2: 3.0 / 2.0, + 4: 3.0 / 2.0, + 1: -1.0, + 3: -1.0, + 5: -1.0, + }, # Sdelta = 3/2u+ + 3/2c+ - d+ - s+ - b+ + 6: { + 2: 1.0, + 4: 1.0, + 6: 1.0, + 1: -1.0, + 3: -1.0, + 5: -1.0, + }, # Sdelta = u+ + c+ + t+ - d+ -s+ - b+ + }, + } + cur_map = mapping[label[1:]] + if label[1:] == "delta": + cur_map = cur_map[nf] + for q, w in cur_map.items(): + weights[br.flavor_basis_pids.index(q)] = w + weights[br.flavor_basis_pids.index(-q)] = ( + -1 if label[0] == "V" else 1.0 + ) * w + + # normalize? + if normalize: + norm = weights @ weights + weights = weights / norm + return weights diff --git a/src/eko/evolution_operator/grid.py b/src/eko/evolution_operator/grid.py index db5843556..aa9e80b76 100644 --- a/src/eko/evolution_operator/grid.py +++ b/src/eko/evolution_operator/grid.py @@ -11,11 +11,10 @@ import numpy as np -from eko import matching_conditions, member -from eko.evolution_operator import flavors - +from .. import basis_rotation as br +from .. import matching_conditions, member from ..matching_conditions.operator_matrix_element import OperatorMatrixElement -from . import Operator, physical +from . import Operator, flavors, physical logger = logging.getLogger(__name__) @@ -133,7 +132,7 @@ def from_dict( if int(theory_card["IB"]) == 1: intrinsic_range.append(5) config["intrinsic_range"] = intrinsic_range - for hq in flavors.quark_names[3:]: + for hq in br.quark_names[3:]: config[f"m{hq}"] = theory_card[f"m{hq}"] return cls( config, q2_grid, thresholds_config, strong_coupling, interpol_dispatcher diff --git a/src/eko/matching_conditions/__init__.py b/src/eko/matching_conditions/__init__.py index be6554a96..3b7ae3c72 100644 --- a/src/eko/matching_conditions/__init__.py +++ b/src/eko/matching_conditions/__init__.py @@ -3,8 +3,8 @@ This module defines the matching conditions for the |VFNS| evolution. """ +from .. import basis_rotation as br from .. import member -from ..evolution_operator import flavors class MatchingCondition(member.OperatorBase): @@ -58,7 +58,7 @@ def split_ad_to_evol_map( m[f"T{n}.T{n}"] = m["V.V"] # activate the next heavy quark - hq = flavors.quark_names[nf] + hq = br.quark_names[nf] m.update( { f"{hq}-.V": ome_members["NS_Hq"], @@ -71,7 +71,7 @@ def split_ad_to_evol_map( if len(intrinsic_range) != 0: op_id = member.OpMember.id_like(ome_members["NS_qq"]) for intr_fl in intrinsic_range: - ihq = flavors.quark_names[intr_fl - 1] # find name + ihq = br.quark_names[intr_fl - 1] # find name if intr_fl > nf + 1: # keep the higher quarks as they are m[f"{ihq}+.{ihq}+"] = op_id.copy() diff --git a/src/eko/msbar_masses.py b/src/eko/msbar_masses.py index 69732b6e3..6c3813d63 100644 --- a/src/eko/msbar_masses.py +++ b/src/eko/msbar_masses.py @@ -6,8 +6,8 @@ import numpy as np from scipy import integrate, optimize +from .basis_rotation import quark_names from .beta import b, beta -from .evolution_operator.flavors import quark_names from .gamma import gamma from .strong_coupling import StrongCoupling, invert_matching_coeffs diff --git a/tests/test_basis_rotation.py b/tests/test_basis_rotation.py index 1747c0182..c55a49f78 100644 --- a/tests/test_basis_rotation.py +++ b/tests/test_basis_rotation.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import numpy as np +import pytest from eko import basis_rotation as br @@ -41,3 +42,12 @@ def test_ad_projectors(): atol=1e-15, err_msg=f"nf = {nf}", ) + + +def test_intrinsic_unified_evol_labels(): + for nf in range(3, 6 + 1): + labels = br.intrinsic_unified_evol_labels(nf) + assert len(labels) == 14 + # errors + with pytest.raises(IndexError): + br.intrinsic_unified_evol_labels(7) diff --git a/tests/test_ev_op_flavors.py b/tests/test_ev_op_flavors.py index dcd190159..6debd5341 100644 --- a/tests/test_ev_op_flavors.py +++ b/tests/test_ev_op_flavors.py @@ -95,8 +95,8 @@ def test_rotate_matching(): def test_rotate_matching_is_inv(): def replace_names(k): for q in range(4, 6 + 1): - k = k.replace(flavors.quark_names[q - 1] + "+", f"T{q**2-1}").replace( - flavors.quark_names[q - 1] + "-", f"V{q**2-1}" + k = k.replace(br.quark_names[q - 1] + "+", f"T{q**2-1}").replace( + br.quark_names[q - 1] + "-", f"V{q**2-1}" ) return k @@ -112,3 +112,25 @@ def load(m): m = load(flavors.rotate_matching(nf)) minv = load(flavors.rotate_matching_inverse(nf)) np.testing.assert_allclose(m @ minv, np.eye(len(br.evol_basis)), atol=1e-10) + + +def test_pids_from_intrinsic_unified_evol(): + for nf in range(3, 6 + 1): + labels = br.intrinsic_unified_evol_labels(nf) + for lab in labels: + n = flavors.pids_from_intrinsic_unified_evol(lab, nf, True) + for lab2 in labels: + n2 = flavors.pids_from_intrinsic_unified_evol(lab2, nf, False) + if lab == lab2: + np.testing.assert_allclose(n @ n2, 1.0) + else: + np.testing.assert_allclose( + n @ n2, + 0.0, + atol=1e-10, + err_msg=f"{lab} is not orthogonal to {lab2} in nf={nf}", + ) + with pytest.raises(KeyError): + flavors.pids_from_intrinsic_unified_evol("V3", 4, True) + with pytest.raises(KeyError): + flavors.pids_from_intrinsic_unified_evol("T0", 7, True) diff --git a/tests/test_msbar_masses.py b/tests/test_msbar_masses.py index fbe530534..ec77fe302 100644 --- a/tests/test_msbar_masses.py +++ b/tests/test_msbar_masses.py @@ -6,7 +6,7 @@ import pytest from eko import msbar_masses -from eko.evolution_operator.flavors import quark_names +from eko.basis_rotation import quark_names from eko.strong_coupling import StrongCoupling theory_dict = {