Skip to main content

neopdf/
uncertainty.rs

1use numpy::{PyArray1, PyArrayMethods};
2use pyo3::prelude::*;
3
4/// Python wrapper for the NeoPDF `Uncertainty` struct.
5#[pyclass(name = "Uncertainty")]
6#[derive(Clone, Debug)]
7pub struct PyUncertainty {
8    /// Central value.
9    #[pyo3(get)]
10    pub central: f64,
11    /// Negative error (absolute value).
12    #[pyo3(get)]
13    pub errminus: f64,
14    /// Positive error (absolute value).
15    #[pyo3(get)]
16    pub errplus: f64,
17}
18
19#[pymethods]
20impl PyUncertainty {
21    fn __repr__(&self) -> String {
22        format!(
23            "Uncertainty(central={}, errminus={}, errplus={})",
24            self.central, self.errminus, self.errplus
25        )
26    }
27
28    /// The symmetric error, defined as the average of `errminus` and `errplus`.
29    #[must_use]
30    pub fn errsymm(&self) -> f64 {
31        (self.errminus + self.errplus) / 2.0
32    }
33}
34
35/// Compute PDF uncertainty from per-member values.
36///
37/// # Errors
38///
39/// Raises an error if the computation of the uncertainty fails.
40///
41/// Parameters
42/// ----------
43/// values : numpy.ndarray
44///     1D array of values for all members, with element 0 being the central member
45///     (best-fit or average) and the following elements corresponding to error
46///     replicas / eigenvectors, matching the LHAPDF/NeoPDF convention.
47/// `error_type` : str
48///     String describing the error type, typically taken from the metadata
49///     `ErrorType` field (e.g. ``"replicas"``, ``"hessian"``, ``"symmhessian"``
50///     or ``"asymhessian"``).
51/// `error_conf_level` : float, optional
52///     Confidence level (in %) at which the PDF's error members were constructed.
53/// cl : float
54///     Two-sided confidence level in percent (e.g. 68.2689 for 1σ).
55/// alternative : bool
56///     If ``True``, replica sets use a quantile-based (asymmetric) interval
57///     instead of the standard deviation.
58///
59/// Returns
60/// -------
61/// Uncertainty
62///     A small object with attributes ``central``, ``errminus`` and ``errplus``.
63#[pyfunction]
64#[pyo3(name = "uncertainty")]
65#[pyo3(signature = (values, error_type, error_conf_level=None, cl=68.268_949_213_708_58, alternative=false))]
66pub fn py_uncertainty<'py>(
67    _py: Python<'py>,
68    values: &Bound<'py, PyArray1<f64>>,
69    error_type: &str,
70    error_conf_level: Option<f64>,
71    cl: f64,
72    alternative: bool,
73) -> PyResult<PyUncertainty> {
74    let slice = unsafe { values.as_slice()? };
75    let unc = neopdf::uncertainty::uncertainty(
76        slice,
77        error_type,
78        error_conf_level.unwrap_or(neopdf::uncertainty::CL_1_SIGMA),
79        cl,
80        alternative,
81    )
82    .map_err(pyo3::exceptions::PyValueError::new_err)?;
83
84    Ok(PyUncertainty {
85        central: unc.central,
86        errminus: unc.errminus,
87        errplus: unc.errplus,
88    })
89}
90
91/// Register the `uncertainty` submodule on the parent Python module.
92///
93/// # Errors
94///
95/// TODO
96pub fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
97    let m = PyModule::new(parent_module.py(), "uncertainty")?;
98    m.setattr(
99        pyo3::intern!(m.py(), "__doc__"),
100        "PDF set uncertainty utilities.",
101    )?;
102    pyo3::py_run!(
103        parent_module.py(),
104        m,
105        "import sys; sys.modules['neopdf.uncertainty'] = m"
106    );
107    m.add("CL_1_SIGMA", neopdf::uncertainty::CL_1_SIGMA)?;
108    m.add("CL_2_SIGMA", neopdf::uncertainty::CL_2_SIGMA)?;
109    m.add("CL_3_SIGMA", neopdf::uncertainty::CL_3_SIGMA)?;
110    m.add("CL_90", neopdf::uncertainty::CL_90)?;
111    m.add("CL_95", neopdf::uncertainty::CL_95)?;
112    m.add_class::<PyUncertainty>()?;
113    m.add_function(wrap_pyfunction!(py_uncertainty, &m)?)?;
114    parent_module.add_submodule(&m)
115}