Skip to main content

neopdf/
metadata.rs

1use pyo3::prelude::*;
2
3use neopdf::metadata::{InterpolatorType, MetaData, SetType};
4
5/// The type of the set.
6#[pyclass(eq, eq_int, name = "SetType")]
7#[derive(Clone, PartialEq, Eq)]
8pub enum PySetType {
9    /// Parton Distribution Function.
10    SpaceLike,
11    /// Fragmentation Function.
12    TimeLike,
13}
14
15impl From<&SetType> for PySetType {
16    fn from(set_type: &SetType) -> Self {
17        match set_type {
18            SetType::SpaceLike => Self::SpaceLike,
19            SetType::TimeLike => Self::TimeLike,
20        }
21    }
22}
23
24impl From<&PySetType> for SetType {
25    fn from(set_type: &PySetType) -> Self {
26        match set_type {
27            PySetType::SpaceLike => Self::SpaceLike,
28            PySetType::TimeLike => Self::TimeLike,
29        }
30    }
31}
32
33/// The interpolation method used for the grid.
34#[pyclass(eq, eq_int, name = "InterpolatorType")]
35#[derive(Clone, PartialEq, Eq)]
36pub enum PyInterpolatorType {
37    /// Bilinear interpolation strategy.
38    Bilinear,
39    /// Bilinear logarithmic interpolation strategy.
40    LogBilinear,
41    /// Bicubic logarithmic interpolation strategy.
42    LogBicubic,
43    /// Tricubic logarithmic interpolation strategy.
44    LogTricubic,
45    /// Linear interpolation for N-dimensional data.
46    NDLinear,
47    /// Chebyshev logarithmic interpolation strategy.
48    LogChebyshev,
49    /// Four-dimensional cubic logarithmic interpolation strategy.
50    LogFourCubic,
51    /// Five-dimensional cubic logarithmic interpolation strategy.
52    LogFiveCubic,
53}
54
55impl From<&InterpolatorType> for PyInterpolatorType {
56    fn from(basis: &InterpolatorType) -> Self {
57        match basis {
58            InterpolatorType::Bilinear => Self::Bilinear,
59            InterpolatorType::LogBilinear => Self::LogBilinear,
60            InterpolatorType::LogBicubic => Self::LogBicubic,
61            InterpolatorType::LogTricubic => Self::LogTricubic,
62            InterpolatorType::InterpNDLinear => Self::NDLinear,
63            InterpolatorType::LogChebyshev => Self::LogChebyshev,
64            InterpolatorType::LogFourCubic => Self::LogFourCubic,
65            InterpolatorType::LogFiveCubic => Self::LogFiveCubic,
66        }
67    }
68}
69
70impl From<&PyInterpolatorType> for InterpolatorType {
71    fn from(basis: &PyInterpolatorType) -> Self {
72        match basis {
73            PyInterpolatorType::Bilinear => Self::Bilinear,
74            PyInterpolatorType::LogBilinear => Self::LogBilinear,
75            PyInterpolatorType::LogBicubic => Self::LogBicubic,
76            PyInterpolatorType::LogTricubic => Self::LogTricubic,
77            PyInterpolatorType::NDLinear => Self::InterpNDLinear,
78            PyInterpolatorType::LogChebyshev => Self::LogChebyshev,
79            PyInterpolatorType::LogFourCubic => Self::LogFourCubic,
80            PyInterpolatorType::LogFiveCubic => Self::LogFiveCubic,
81        }
82    }
83}
84
85/// Physical Parameters of the PDF set.
86#[pyclass(name = "PhysicsParameters")]
87#[derive(Debug, Clone)]
88pub struct PyPhysicsParameters {
89    pub(crate) flavor_scheme: String,
90    pub(crate) order_qcd: u32,
91    pub(crate) alphas_order_qcd: u32,
92    pub(crate) m_w: f64,
93    pub(crate) m_z: f64,
94    pub(crate) m_up: f64,
95    pub(crate) m_down: f64,
96    pub(crate) m_strange: f64,
97    pub(crate) m_charm: f64,
98    pub(crate) m_bottom: f64,
99    pub(crate) m_top: f64,
100    pub(crate) alphas_type: String,
101    pub(crate) number_flavors: u32,
102}
103
104#[pymethods]
105impl PyPhysicsParameters {
106    /// Constructor for `PyPhysicsParameters`.
107    #[new]
108    #[must_use]
109    #[allow(clippy::too_many_arguments)]
110    #[pyo3(signature = (
111        flavor_scheme = "None".to_string(),
112        order_qcd = 0,
113        alphas_order_qcd = 0,
114        m_w = 0.0,
115        m_z = 0.0,
116        m_up = 0.0,
117        m_down = 0.0,
118        m_strange = 0.0,
119        m_charm = 0.0,
120        m_bottom = 0.0,
121        m_top = 0.0,
122        alphas_type = "None".to_string(),
123        number_flavors = 0,
124    ))]
125    pub const fn new(
126        flavor_scheme: String,
127        order_qcd: u32,
128        alphas_order_qcd: u32,
129        m_w: f64,
130        m_z: f64,
131        m_up: f64,
132        m_down: f64,
133        m_strange: f64,
134        m_charm: f64,
135        m_bottom: f64,
136        m_top: f64,
137        alphas_type: String,
138        number_flavors: u32,
139    ) -> Self {
140        Self {
141            flavor_scheme,
142            order_qcd,
143            alphas_order_qcd,
144            m_w,
145            m_z,
146            m_up,
147            m_down,
148            m_strange,
149            m_charm,
150            m_bottom,
151            m_top,
152            alphas_type,
153            number_flavors,
154        }
155    }
156
157    /// Convert to Python dictionary.
158    ///
159    /// # Errors
160    ///
161    /// Raises an error if the values are not Python compatible.
162    pub fn to_dict(&self, py: Python) -> PyResult<Py<PyAny>> {
163        let dict = pyo3::types::PyDict::new(py);
164        dict.set_item("flavor_scheme", &self.flavor_scheme)?;
165        dict.set_item("order_qcd", self.order_qcd)?;
166        dict.set_item("alphas_order_qcd", self.alphas_order_qcd)?;
167        dict.set_item("m_w", self.m_w)?;
168        dict.set_item("m_z", self.m_z)?;
169        dict.set_item("m_up", self.m_up)?;
170        dict.set_item("m_down", self.m_down)?;
171        dict.set_item("m_strange", self.m_strange)?;
172        dict.set_item("m_charm", self.m_charm)?;
173        dict.set_item("m_bottom", self.m_bottom)?;
174        dict.set_item("m_top", self.m_top)?;
175
176        Ok(dict.into())
177    }
178}
179
180impl Default for PyPhysicsParameters {
181    fn default() -> Self {
182        Self {
183            flavor_scheme: String::new(),
184            order_qcd: 0,
185            alphas_order_qcd: 0,
186            m_w: 0.0,
187            m_z: 0.0,
188            m_up: 0.0,
189            m_down: 0.0,
190            m_strange: 0.0,
191            m_charm: 0.0,
192            m_bottom: 0.0,
193            m_top: 0.0,
194            alphas_type: String::new(),
195            number_flavors: 0,
196        }
197    }
198}
199
200/// Grid metadata.
201#[pyclass(name = "MetaData")]
202#[derive(Debug, Clone)]
203#[repr(transparent)]
204pub struct PyMetaData {
205    pub(crate) meta: MetaData,
206}
207
208#[pymethods]
209impl PyMetaData {
210    /// Constructor for `PyMetaData`.
211    #[new]
212    #[must_use]
213    #[allow(clippy::too_many_arguments)]
214    #[allow(clippy::needless_pass_by_value)]
215    #[pyo3(signature = (
216        set_desc,
217        set_index,
218        num_members,
219        x_min,
220        x_max,
221        q_min,
222        q_max,
223        xsi_min,
224        xsi_max,
225        delta_min,
226        delta_max,
227        flavors,
228        format,
229        alphas_q_values = vec![],
230        alphas_vals = vec![],
231        polarised = false,
232        set_type = PySetType::SpaceLike,
233        interpolator_type = PyInterpolatorType::LogBicubic,
234        error_type = "replicas".to_string(),
235        hadron_pid = 2212,
236        phys_params = PyPhysicsParameters::default(),
237    ))]
238    pub fn new(
239        set_desc: String,
240        set_index: u32,
241        num_members: u32,
242        x_min: f64,
243        x_max: f64,
244        q_min: f64,
245        q_max: f64,
246        xsi_min: f64,
247        xsi_max: f64,
248        delta_min: f64,
249        delta_max: f64,
250        flavors: Vec<i32>,
251        format: String,
252        alphas_q_values: Vec<f64>,
253        alphas_vals: Vec<f64>,
254        polarised: bool,
255        set_type: PySetType,
256        interpolator_type: PyInterpolatorType,
257        error_type: String,
258        hadron_pid: i32,
259        phys_params: PyPhysicsParameters,
260    ) -> Self {
261        Self {
262            meta: MetaData {
263                set_desc,
264                set_index,
265                num_members,
266                x_min,
267                x_max,
268                q_min,
269                q_max,
270                flavors,
271                format,
272                alphas_q_values,
273                alphas_vals,
274                polarised,
275                set_type: SetType::from(&set_type),
276                interpolator_type: InterpolatorType::from(&interpolator_type),
277                error_type,
278                hadron_pid,
279                git_version: String::new(), // placeholder to be overwritten
280                code_version: String::new(), // placeholder to be overwritten
281                flavor_scheme: phys_params.flavor_scheme,
282                order_qcd: phys_params.order_qcd,
283                alphas_order_qcd: phys_params.alphas_order_qcd,
284                m_w: phys_params.m_w,
285                m_z: phys_params.m_z,
286                m_up: phys_params.m_up,
287                m_down: phys_params.m_down,
288                m_strange: phys_params.m_strange,
289                m_charm: phys_params.m_charm,
290                m_bottom: phys_params.m_bottom,
291                m_top: phys_params.m_top,
292                alphas_type: phys_params.alphas_type,
293                number_flavors: phys_params.number_flavors,
294                // New V2 fields with defaults
295                xi_min: xsi_min,
296                xi_max: xsi_max,
297                delta_min,
298                delta_max,
299            },
300        }
301    }
302
303    /// Convert to Python dictionary
304    ///
305    /// # Errors
306    ///
307    /// Raises an erro if the values are not Python compatible.
308    pub fn to_dict(&self, py: Python) -> PyResult<Py<PyAny>> {
309        let dict = pyo3::types::PyDict::new(py);
310
311        let set_type = match &self.meta.set_type {
312            SetType::SpaceLike => "PDF",
313            SetType::TimeLike => "FragFn",
314        };
315
316        let interpolator_type = match &self.meta.interpolator_type {
317            InterpolatorType::Bilinear => "Bilinear",
318            InterpolatorType::LogBilinear => "LogBilinear",
319            InterpolatorType::LogBicubic => "LogBicubic",
320            InterpolatorType::LogTricubic => "LogTricubic",
321            InterpolatorType::InterpNDLinear => "NDLinear",
322            InterpolatorType::LogChebyshev => "LogChebyshev",
323            InterpolatorType::LogFourCubic => "LogFourCubic",
324            InterpolatorType::LogFiveCubic => "LogFiveCubic",
325        };
326
327        dict.set_item("set_desc", &self.meta.set_desc)?;
328        dict.set_item("set_index", self.meta.set_index)?;
329        dict.set_item("num_members", self.meta.num_members)?;
330        dict.set_item("x_min", self.meta.x_min)?;
331        dict.set_item("x_max", self.meta.x_max)?;
332        dict.set_item("q_min", self.meta.q_min)?;
333        dict.set_item("q_max", self.meta.q_max)?;
334        dict.set_item("flavors", &self.meta.flavors)?;
335        dict.set_item("format", &self.meta.format)?;
336        dict.set_item("alphas_q_values", &self.meta.alphas_q_values)?;
337        dict.set_item("alphas_vals", &self.meta.alphas_vals)?;
338        dict.set_item("polarised", self.meta.polarised)?;
339        dict.set_item("set_type", set_type)?;
340        dict.set_item("interpolator_type", interpolator_type)?;
341        dict.set_item("error_type", &self.meta.error_type)?;
342        dict.set_item("hadron_pid", self.meta.hadron_pid)?;
343        dict.set_item("git_version", &self.meta.git_version)?;
344        dict.set_item("code_version", &self.meta.code_version)?;
345        dict.set_item("flavor_scheme", &self.meta.flavor_scheme)?;
346        dict.set_item("order_qcd", self.meta.order_qcd)?;
347        dict.set_item("alphas_order_qcd", self.meta.alphas_order_qcd)?;
348        dict.set_item("m_w", self.meta.m_w)?;
349        dict.set_item("m_z", self.meta.m_z)?;
350        dict.set_item("m_up", self.meta.m_up)?;
351        dict.set_item("m_down", self.meta.m_down)?;
352        dict.set_item("m_strange", self.meta.m_strange)?;
353        dict.set_item("m_charm", self.meta.m_charm)?;
354        dict.set_item("m_bottom", self.meta.m_bottom)?;
355        dict.set_item("m_top", self.meta.m_top)?;
356
357        Ok(dict.into())
358    }
359
360    /// The description of the set.
361    #[must_use]
362    pub const fn set_desc(&self) -> &String {
363        &self.meta.set_desc
364    }
365
366    /// The index of the grid.
367    #[must_use]
368    pub const fn set_index(&self) -> u32 {
369        self.meta.set_index
370    }
371
372    /// The number of sets in the grid.
373    #[must_use]
374    pub const fn number_sets(&self) -> u32 {
375        self.meta.num_members
376    }
377
378    /// The minimum value of `x` in the grid.
379    #[must_use]
380    pub const fn x_min(&self) -> f64 {
381        self.meta.x_min
382    }
383
384    /// The maximum value of `x` in the grid.
385    #[must_use]
386    pub const fn x_max(&self) -> f64 {
387        self.meta.x_max
388    }
389
390    /// The minimum value of `q` in the grid.
391    #[must_use]
392    pub const fn q_min(&self) -> f64 {
393        self.meta.q_min
394    }
395
396    /// The maximum value of `q` in the grid.
397    #[must_use]
398    pub const fn q_max(&self) -> f64 {
399        self.meta.q_max
400    }
401
402    /// The minimum value of `xi` in the grid.
403    #[must_use]
404    pub const fn xi_min(&self) -> f64 {
405        self.meta.xi_min
406    }
407
408    /// The maximum value of `xi` in the grid.
409    #[must_use]
410    pub const fn xi_max(&self) -> f64 {
411        self.meta.xi_max
412    }
413
414    /// The minimum value of `delta` in the grid.
415    #[must_use]
416    pub const fn delta_min(&self) -> f64 {
417        self.meta.delta_min
418    }
419
420    /// The maximum value of `delta` in the grid.
421    #[must_use]
422    pub const fn delta_max(&self) -> f64 {
423        self.meta.delta_max
424    }
425
426    /// The particle IDs of the grid.
427    #[must_use]
428    pub const fn pids(&self) -> &Vec<i32> {
429        &self.meta.flavors
430    }
431
432    /// The format of the grid.
433    #[must_use]
434    pub const fn format(&self) -> &String {
435        &self.meta.format
436    }
437
438    /// The values of `q` for the running of the strong coupling constant.
439    #[must_use]
440    pub const fn alphas_q(&self) -> &Vec<f64> {
441        &self.meta.alphas_q_values
442    }
443
444    /// The values of the running of the strong coupling constant.
445    #[must_use]
446    pub const fn alphas_values(&self) -> &Vec<f64> {
447        &self.meta.alphas_vals
448    }
449
450    /// Whether the grid is polarised.
451    #[must_use]
452    pub const fn is_polarised(&self) -> bool {
453        self.meta.polarised
454    }
455
456    /// The type of the set.
457    #[must_use]
458    pub fn set_type(&self) -> PySetType {
459        PySetType::from(&self.meta.set_type)
460    }
461
462    /// The interpolation method used for the grid.
463    #[must_use]
464    pub fn interpolator_type(&self) -> PyInterpolatorType {
465        PyInterpolatorType::from(&self.meta.interpolator_type)
466    }
467
468    /// The type of error.
469    #[must_use]
470    pub const fn error_type(&self) -> &String {
471        &self.meta.error_type
472    }
473
474    /// The hadron PID.
475    #[must_use]
476    pub const fn hadron_pid(&self) -> i32 {
477        self.meta.hadron_pid
478    }
479}
480
481/// Registers the `metadata` submodule with the parent Python module.
482///
483/// Parameters
484/// ----------
485/// `parent_module` : pyo3.Bound[pyo3.types.PyModule]
486///     The parent Python module to which the `metadata` submodule will be added.
487///
488/// Returns
489/// -------
490/// pyo3.PyResult<()>
491///     `Ok(())` if the registration is successful, or an error if the submodule
492///     cannot be created or added.
493///
494/// # Errors
495///
496/// Raises an error if the (sub)module is not found or cannot be registered.
497pub fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
498    let m = PyModule::new(parent_module.py(), "metadata")?;
499    m.setattr(pyo3::intern!(m.py(), "__doc__"), "Interface for PDF.")?;
500    pyo3::py_run!(
501        parent_module.py(),
502        m,
503        "import sys; sys.modules['neopdf.metadata'] = m"
504    );
505    m.add_class::<PySetType>()?;
506    m.add_class::<PyInterpolatorType>()?;
507    m.add_class::<PyPhysicsParameters>()?;
508    m.add_class::<PyMetaData>()?;
509    parent_module.add_submodule(&m)
510}