From 31be4f533e5909c4d7de9d4bbd819c7de892a425 Mon Sep 17 00:00:00 2001
From: enocera <emanueleroberto.nocera@unito.it>
Date: Thu, 14 Jul 2022 16:58:21 +0100
Subject: [PATCH] Implemented function to compute asymptotic exponents

---
 .../asy_exponents/asy_exponents_plots.yaml    |  42 +++
 .../asy_exponents/asy_exponents_table.yaml    |  28 ++
 validphys2/src/validphys/app.py               |   1 +
 validphys2/src/validphys/asy_exponents.py     | 266 ++++++++++++++++++
 validphys2/src/validphys/pdfbases.py          |  22 ++
 5 files changed, 359 insertions(+)
 create mode 100644 validphys2/examples/asy_exponents/asy_exponents_plots.yaml
 create mode 100644 validphys2/examples/asy_exponents/asy_exponents_table.yaml
 create mode 100644 validphys2/src/validphys/asy_exponents.py

diff --git a/validphys2/examples/asy_exponents/asy_exponents_plots.yaml b/validphys2/examples/asy_exponents/asy_exponents_plots.yaml
new file mode 100644
index 0000000000..1a94fd35f3
--- /dev/null
+++ b/validphys2/examples/asy_exponents/asy_exponents_plots.yaml
@@ -0,0 +1,42 @@
+meta:
+    author: Emanuele R. Nocera
+    title: Asymptotic Exponents
+    keywords: [asy_exp]
+
+pdfs:
+  - {id: "NNPDF40_nnlo_as_01180", label: "NNPDF4.0"}
+
+Q: 5000 # GeV
+
+alpha:
+  xmin: 1e-5
+  xmax: 1e-3
+  ybottom: 0.0
+  ytop: 1.0
+  basis: flavour
+  npoints: 70
+
+beta:
+  xmin: 0.6
+  xmax: 0.9
+  ybottom: 0
+  ytop: 10
+  basis: flavour
+  npoints: 70
+
+show_mc_errors: False
+
+template_text: |
+  ---
+  title: Asymptotic exponents
+  keywords: [asy_exp]
+  ---
+  Asymptotic exponents plots
+  -----------
+  {@alpha plot_alpha_asy@}
+  {@beta plot_beta_asy@}
+  -----------
+
+actions_:
+  - report(main=true)
+  
\ No newline at end of file
diff --git a/validphys2/examples/asy_exponents/asy_exponents_table.yaml b/validphys2/examples/asy_exponents/asy_exponents_table.yaml
new file mode 100644
index 0000000000..91baf90219
--- /dev/null
+++ b/validphys2/examples/asy_exponents/asy_exponents_table.yaml
@@ -0,0 +1,28 @@
+meta:
+    author: Emanuele R. Nocera
+    title: Asymptotic Exponents
+    keywords: [asy_exp]
+
+pdf: NNPDF40_nnlo_as_01180
+
+Q: 5000 # GeV
+
+x_alpha: 1e-5
+x_beta:  0.90
+npoints: 70
+
+basis: flavour
+
+template_text: |
+  ---
+  title: Asymptotic exponents
+  keywords: [asy_exp]
+  ---
+  Asymptotic exponents
+  -----------
+  {@asymptotic_exponents_table@}
+  -----------
+
+actions_:
+  - report(main=true)
+  
\ No newline at end of file
diff --git a/validphys2/src/validphys/app.py b/validphys2/src/validphys/app.py
index 614022a9c7..4d40699df6 100644
--- a/validphys2/src/validphys/app.py
+++ b/validphys2/src/validphys/app.py
@@ -37,6 +37,7 @@ providers = [
     "validphys.correlations",
     "validphys.chi2grids",
     "validphys.eff_exponents",
+    "validphys.asy_exponents",    
     "validphys.paramfits.dataops",
     "validphys.paramfits.plots",
     "validphys.theorycovariance.construction",
diff --git a/validphys2/src/validphys/asy_exponents.py b/validphys2/src/validphys/asy_exponents.py
new file mode 100644
index 0000000000..4add5e82b2
--- /dev/null
+++ b/validphys2/src/validphys/asy_exponents.py
@@ -0,0 +1,266 @@
+# -*- coding: utf-8 -*-
+"""
+Tools for computing and plotting effective exponents.
+"""
+from __future__ import generator_stop
+
+import logging
+import numbers
+import random
+import warnings
+
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+
+from reportengine import collect
+from reportengine.compat import yaml
+from reportengine.figure import figuregen
+from reportengine.floatformatting import format_number, significant_digits, format_error_value_columns
+from reportengine.table import table
+
+from validphys.checks import check_positive, check_pdf_normalize_to, make_argcheck, check_xlimits
+from validphys.core import PDF, FitSpec
+from validphys.pdfbases import check_basis, Basis
+from validphys.pdfplots import BandPDFPlotter, PDFPlotter
+
+import validphys.pdfgrids as pdfgrids
+
+from findiff import FinDiff
+
+log = logging.getLogger(__name__)
+
+INTERNAL_LINESTYLE = ['-.', ':']
+INTERNAL_COLOR = plt.rcParams['axes.prop_cycle'].by_key()["color"]
+
+@check_positive('Q')
+@make_argcheck(check_basis)
+@check_xlimits
+def alpha_asy(pdf: PDF, *,
+              xmin: numbers.Real = 1e-6,
+              xmax: numbers.Real = 1e-3,
+              npoints: int = 100,
+              Q: numbers.Real = 1.65,
+              basis: (str, Basis),
+              flavours: (list, tuple, type(None)) = None):
+    """Returns a list of xplotting_grids containing the value of the asymptotic
+    exponent alpha, as defined by the first relationship in Eq. (4) of 
+    [arXiv:1604.00024], at the specified value of Q (in GeV), in the interval [xmin, xmax].
+
+    basis: Is one of the bases defined in pdfbases.py. This includes 'flavour'
+    and 'evolution'.
+
+    flavours: A set of elements from the basis.
+    If None, the defaults for that basis will be selected.
+
+    npoints: the number of sub-intervals in the range [xmin, xmax] on which the 
+    derivative is computed.
+    """
+    #Loading the filter map of the fit/PDF
+    checked = check_basis(basis, flavours)
+    basis = checked['basis']
+    flavours = checked['flavours']
+
+    if npoints == 2:
+        xGrid = np.array([xmin, xmax])
+    else:
+        xGrid = pdfgrids.xgrid(xmin, xmax, 'log', npoints)
+
+    pdfGrid = pdfgrids.xplotting_grid(
+        pdf, Q, xgrid=xGrid, basis=basis, flavours=flavours)
+    pdfGrid_values = pdfGrid.grid_values.data
+    # NOTE: without this I get "setting an array element with a sequence"
+    xGrid = pdfGrid.xgrid
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore', RuntimeWarning)
+        dx = np.log(xGrid[1]) - np.log(xGrid[0])
+        d_dx = FinDiff(2,dx,acc=4)
+        alphaGrid_values = -np.log(abs(pdfGrid_values))
+        alphaGrid_values = d_dx(alphaGrid_values)
+        alphaGrid_values[alphaGrid_values == -np.inf] = np.nan  # when PDF_i =0
+        alphaGrid = pdfGrid.copy_grid(grid_values=pdf.stats_class(alphaGrid_values))
+        
+    return alphaGrid
+
+@check_positive('Q')
+@make_argcheck(check_basis)
+@check_xlimits
+def beta_asy(pdf, *,
+             xmin: numbers.Real = 0.6,
+             xmax: numbers.Real = 0.9,
+             npoints: int = 100,
+             Q: numbers.Real = 1.65,
+             basis: (str, Basis),
+             flavours: (list, tuple, type(None)) = None):
+    """Returns a list of xplotting_grids containing the value of the asymptotic
+    exponent beta, as defined by the second relationship in Eq. (4) of 
+    [arXiv:1604.00024], at the specified value of Q (in GeV), in the interval [xmin, xmax].
+
+    basis: Is one of the bases defined in pdfbases.py. This includes 'flavour'
+    and 'evolution'.
+
+    flavours: A set of elements from the basis.
+    If None, the defaults for that basis will be selected.
+
+    npoints: the number of sub-intervals in the range [xmin, xmax] on which the 
+    derivative is computed.
+    """
+    checked = check_basis(basis, flavours)
+    basis = checked['basis']
+    flavours = checked['flavours']
+
+    if npoints == 2:
+        xGrid = np.array([xmin, xmax])
+    else:
+        xGrid = pdfgrids.xgrid(xmin, xmax, 'linear', npoints)
+
+
+    pdfGrid = pdfgrids.xplotting_grid(
+        pdf, Q, xgrid=xGrid, basis=basis, flavours=flavours)
+    pdfGrid_values = pdfGrid.grid_values.data
+    # NOTE: without this I get "setting an array element with a sequence"
+    xGrid = pdfGrid.xgrid
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore', RuntimeWarning)
+        dx = xGrid[1] - xGrid[0]
+        d_dx = FinDiff(2,dx,acc=4)
+        betaGrid_values = np.log(abs(pdfGrid_values))
+        betaGrid_values = (xGrid - 1.) * d_dx(betaGrid_values)
+        betaGrid_values[betaGrid_values == -np.inf] = np.nan  # when PDF_i =0
+        betaGrid = pdfGrid.copy_grid(grid_values=pdf.stats_class(betaGrid_values))
+
+    return betaGrid
+
+class AsyExponentPlotter(PDFPlotter):
+    """ Class inherenting from BandPDFPlotter, changing title and ylabel to reflect the effective
+    exponent being plotted.
+    """
+
+    def __init__(self, exponent, *args,  **kwargs):
+        self.exponent = exponent
+        super().__init__(*args, **kwargs)
+
+    def get_title(self, parton_name):
+        return fr"$\{self.exponent}_a$ for ${parton_name}$ at {format_number(self.Q, 3)} GeV"
+
+    def get_ylabel(self, parton_name):
+        if self.normalize_to is not None:
+            return "Ratio to {}".format(self.normalize_pdf.label)
+        else:
+            return fr"$\{self.exponent}_a$ for ${parton_name}$"
+
+class AsyExponentBandPlotter(BandPDFPlotter, AsyExponentPlotter):
+    def __init__(self, exponent, *args,  **kwargs):
+        super().__init__(exponent, *args,  **kwargs)
+
+alpha_asy_pdfs = collect('alpha_asy', ('pdfs',))
+
+@figuregen
+@check_pdf_normalize_to
+def plot_alpha_asy(
+        pdfs, alpha_asy_pdfs, pdfs_alpha_lines,
+        normalize_to: (int, str, type(None)) = None,
+        ybottom=None, ytop=None):
+    """ Plots the alpha asymptotic exponent """
+    yield from AsyExponentBandPlotter(
+        'alpha',
+        pdfs,
+        alpha_asy_pdfs,
+        'log',
+        normalize_to,
+        ybottom,
+        ytop,)    
+        
+beta_asy_pdfs = collect('beta_asy', ('pdfs',))
+
+@figuregen
+@check_pdf_normalize_to
+def plot_beta_asy(
+        pdfs, beta_asy_pdfs, pdfs_beta_lines,
+        normalize_to: (int, str, type(None)) = None,
+        ybottom=None, ytop=None):
+    """ Plots the beta asymptotic exponent """
+    yield from AsyExponentBandPlotter(
+        'beta',
+        pdfs,
+        beta_asy_pdfs,
+        'linear',
+        normalize_to,
+        ybottom,
+        ytop,)
+
+@table
+@make_argcheck(check_basis)
+def asymptotic_exponents_table(
+    pdf: PDF,
+    *,
+    x_alpha: numbers.Real = 1e-6,
+    x_beta: numbers.Real = 0.90,
+    Q: numbers.Real = 1.65,   
+    basis:(str, Basis),
+    flavours: (list, tuple, type(None)) = None,
+    npoints=100,):
+    """ Returns a table with the values of the asymptotic exponents alpha and beta, as defined 
+    in Eq. (4) of [arXiv:1604.00024], at the specified value of x and Q.
+
+    basis: Is one of the bases defined in pdfbases.py. This includes 'flavour'
+    and 'evolution'.
+
+    flavours: A set of elements from the basis.
+    If None, the defaults for that basis will be selected.
+
+    npoints: the number of sub-intervals in the range [xmin, xmax] on which the 
+    derivative is computed.
+    """
+
+    alpha_a = alpha_asy(
+        pdf,
+        xmin=x_alpha,
+        xmax=1e-3,
+        npoints=npoints,
+        Q=Q,
+        basis=basis,
+        flavours=flavours)
+    
+    beta_a = beta_asy(
+        pdf,
+        xmin=0.60,
+        xmax=x_beta,
+        npoints=npoints,
+        Q=Q,
+        basis=basis,
+        flavours=flavours)
+
+    alphastats = alpha_a.grid_values
+    betastats = beta_a.grid_values
+
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore', RuntimeWarning)
+
+        alpha_cv = alphastats.central_value()
+        beta_cv = betastats.central_value()
+        
+        alpha_er = alphastats.std_error()
+        beta_er = betastats.std_error()
+
+        alpha_68 = alphastats.errorbar68()
+        beta_68 = betastats.errorbar68()
+    
+    flavours_label = []
+    asy_exp_mean   = []
+    asy_exp_err    = []
+    asy_exp_min    = []
+    asy_exp_max    = []
+
+    for (j, fl) in enumerate(flavours):
+        asy_exp_mean.extend((alpha_cv[j,0],beta_cv[j,-1]))
+        asy_exp_err.extend((alpha_er[j,0],beta_er[j,-1]))
+        asy_exp_min.extend((alpha_68[0][j,0],beta_68[0][j,-1]))
+        asy_exp_max.extend((alpha_68[1][j,0],beta_68[1][j,-1]))
+        flavours_label.append(f"${basis.elementlabel(fl)}$")
+
+    asy_exp_data = {"mean": asy_exp_mean, "std": asy_exp_err, "min(68% CL)": asy_exp_min, "max(68% CL)": asy_exp_max}
+    ind = pd.MultiIndex.from_product([flavours_label, [r"$\alpha$", r"$\beta$"]])
+
+    df = pd.DataFrame(asy_exp_data, index=ind, columns=["mean","std","min(68% CL)","max(68% CL)"])
+    return df
diff --git a/validphys2/src/validphys/pdfbases.py b/validphys2/src/validphys/pdfbases.py
index 89321f098c..6154b59325 100644
--- a/validphys2/src/validphys/pdfbases.py
+++ b/validphys2/src/validphys/pdfbases.py
@@ -601,6 +601,28 @@ r'\bar{d}': {'dbar':1},
 'c': {'c':1},
 })
 
+@scalar_function_transformation(label="u_V")
+def u_valence(func, xmat, qmat):
+    gv = func([2, -2], xmat, qmat)
+    u = gv[:, [0], ...]
+    ubar = gv[:, [1], ...]
+    return u - ubar
+
+@scalar_function_transformation(label="d_V")
+def d_valence(func, xmat, qmat):
+    gv = func([1, -1], xmat, qmat)
+    d = gv[:, [0], ...]
+    dbar = gv[:, [1], ...]
+    return d - dbar
+
+@scalar_function_transformation(label="S")
+def total_sea(func, xmat, qmat):
+    gv = func([3, -2, -1, -3], xmat, qmat)
+    s = gv[:, [0], ...]
+    ubar = gv[:, [1], ...]
+    dbar = gv[:, [2], ...]
+    sbar = gv[:, [3], ...]
+    return 2.*(ubar + dbar) + s + sbar
 
 @scalar_function_transformation(label="u/d")
 def ud_ratio(func, xmat, qmat):
-- 
GitLab