use numpy::{IntoPyArray, PyArray2};
use pyo3::prelude::*;
use std::sync::Mutex;
use neopdf::gridpdf::ForcePositive;
use neopdf::pdf::PDF;
use super::gridpdf::PySubGrid;
use super::metadata::PyMetaData;
type LazyType = Result<PDF, Box<dyn std::error::Error>>;
#[pyclass(name = "ForcePositive")]
#[derive(Clone)]
pub enum PyForcePositive {
ClipNegative,
ClipSmall,
NoClipping,
}
impl From<PyForcePositive> for ForcePositive {
fn from(fmt: PyForcePositive) -> Self {
match fmt {
PyForcePositive::ClipNegative => Self::ClipNegative,
PyForcePositive::ClipSmall => Self::ClipSmall,
PyForcePositive::NoClipping => Self::NoClipping,
}
}
}
impl From<&ForcePositive> for PyForcePositive {
fn from(fmt: &ForcePositive) -> Self {
match fmt {
ForcePositive::ClipNegative => Self::ClipNegative,
ForcePositive::ClipSmall => Self::ClipSmall,
ForcePositive::NoClipping => Self::NoClipping,
}
}
}
#[pyclass(name = "LoaderMethod")]
#[derive(Clone)]
pub enum PyLoaderMethod {
Parallel,
Sequential,
}
#[pymethods]
impl PyForcePositive {
fn __eq__(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
fn __hash__(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
std::mem::discriminant(self).hash(&mut hasher);
hasher.finish()
}
}
#[pyclass(name = "GridParams")]
#[derive(Clone)]
pub enum PyGridParams {
A,
AlphaS,
X,
KT,
Q2,
}
#[pyclass(name = "LazyPDFs")]
pub struct PyLazyPDFs {
iter: Mutex<Box<dyn Iterator<Item = LazyType> + Send>>,
}
#[pymethods]
impl PyLazyPDFs {
const fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
#[allow(clippy::needless_pass_by_value)]
fn __next__(slf: PyRefMut<'_, Self>) -> PyResult<Option<PyPDF>> {
let mut iter = slf.iter.lock().unwrap();
match iter.next() {
Some(Ok(pdf)) => Ok(Some(PyPDF { pdf })),
Some(Err(e)) => Err(pyo3::exceptions::PyValueError::new_err(e.to_string())),
None => Ok(None),
}
}
}
#[pyclass(name = "PDF")]
#[repr(transparent)]
pub struct PyPDF {
pub(crate) pdf: PDF,
}
#[pymethods]
#[allow(clippy::doc_markdown)]
impl PyPDF {
#[new]
#[must_use]
#[pyo3(signature = (pdf_name, member = 0))]
pub fn new(pdf_name: &str, member: usize) -> Self {
Self {
pdf: PDF::load(pdf_name, member),
}
}
#[must_use]
#[staticmethod]
#[pyo3(name = "mkPDF")]
#[pyo3(signature = (pdf_name, member = 0))]
pub fn mkpdf(pdf_name: &str, member: usize) -> Self {
Self::new(pdf_name, member)
}
#[must_use]
#[staticmethod]
#[pyo3(name = "mkPDFs")]
#[pyo3(signature = (pdf_name, method = &PyLoaderMethod::Parallel))]
pub fn mkpdfs(pdf_name: &str, method: &PyLoaderMethod) -> Vec<Self> {
let loader_method = match method {
PyLoaderMethod::Parallel => PDF::load_pdfs,
PyLoaderMethod::Sequential => PDF::load_pdfs_seq,
};
loader_method(pdf_name)
.into_iter()
.map(move |pdfobj| Self { pdf: pdfobj })
.collect()
}
#[must_use]
#[staticmethod]
#[pyo3(name = "mkPDFs_lazy")]
pub fn mkpdfs_lazy(pdf_name: &str) -> PyLazyPDFs {
PyLazyPDFs {
iter: Mutex::new(Box::new(PDF::load_pdfs_lazy(pdf_name))),
}
}
#[must_use]
pub fn pids(&self) -> Vec<i32> {
self.pdf.pids().to_vec()
}
#[must_use]
pub fn subgrids(&self) -> Vec<PySubGrid> {
self.pdf
.subgrids()
.iter()
.map(|subgrid| PySubGrid {
subgrid: subgrid.clone(),
})
.collect()
}
#[must_use]
pub fn subgrid_knots(&self, param: &PyGridParams, subgrid_index: usize) -> Vec<f64> {
match param {
PyGridParams::AlphaS => self.pdf.subgrid(subgrid_index).alphas.to_vec(),
PyGridParams::X => self.pdf.subgrid(subgrid_index).xs.to_vec(),
PyGridParams::Q2 => self.pdf.subgrid(subgrid_index).q2s.to_vec(),
PyGridParams::A => self.pdf.subgrid(subgrid_index).nucleons.to_vec(),
PyGridParams::KT => self.pdf.subgrid(subgrid_index).kts.to_vec(),
}
}
pub fn set_force_positive(&mut self, option: PyForcePositive) {
self.pdf.set_force_positive(option.into());
}
#[staticmethod]
#[pyo3(name = "set_force_positive_members")]
#[allow(clippy::needless_pass_by_value)]
pub fn set_force_positive_members(pdfs: Vec<PyRefMut<Self>>, option: PyForcePositive) {
for mut pypdf in pdfs {
pypdf.set_force_positive(option.clone());
}
}
#[must_use]
pub fn is_force_positive(&self) -> PyForcePositive {
self.pdf.is_force_positive().into()
}
#[must_use]
pub fn x_min(&self) -> f64 {
self.pdf.param_ranges().x.min
}
#[must_use]
pub fn x_max(&self) -> f64 {
self.pdf.param_ranges().x.max
}
#[must_use]
pub fn q2_min(&self) -> f64 {
self.pdf.param_ranges().q2.min
}
#[must_use]
pub fn q2_max(&self) -> f64 {
self.pdf.param_ranges().q2.max
}
#[must_use]
pub fn flavour_pids(&self) -> Vec<i32> {
self.pdf.metadata().flavors.clone()
}
#[must_use]
#[pyo3(name = "xfxQ2")]
pub fn xfxq2(&self, id: i32, x: f64, q2: f64) -> f64 {
self.pdf.xfxq2(id, &[x, q2])
}
#[must_use]
#[pyo3(name = "xfxQ2_ND")]
#[allow(clippy::needless_pass_by_value)]
pub fn xfxq2_nd(&self, id: i32, params: Vec<f64>) -> f64 {
self.pdf.xfxq2(id, ¶ms)
}
#[must_use]
#[pyo3(name = "xfxQ2_Chebyshev_batch")]
#[allow(clippy::needless_pass_by_value)]
pub fn xfxq2_cheby_batch(&self, id: i32, params: Vec<Vec<f64>>) -> Vec<f64> {
let slices: Vec<&[f64]> = params.iter().map(Vec::as_slice).collect();
self.pdf.xfxq2_cheby_batch(id, &slices)
}
#[must_use]
#[pyo3(name = "xfxQ2s")]
#[allow(clippy::needless_pass_by_value)]
pub fn xfxq2s<'py>(
&self,
pids: Vec<i32>,
xs: Vec<f64>,
q2s: Vec<f64>,
py: Python<'py>,
) -> Bound<'py, PyArray2<f64>> {
let flatten_points: Vec<Vec<f64>> = xs
.iter()
.flat_map(|&x| q2s.iter().map(move |&q2| vec![x, q2]))
.collect();
let points_interp: Vec<&[f64]> = flatten_points.iter().map(Vec::as_slice).collect();
let slice_points: &[&[f64]] = &points_interp;
self.pdf.xfxq2s(pids, slice_points).into_pyarray(py)
}
#[must_use]
#[pyo3(name = "alphasQ2")]
pub fn alphas_q2(&self, q2: f64) -> f64 {
self.pdf.alphas_q2(q2)
}
#[must_use]
#[pyo3(name = "metadata")]
pub fn metadata(&self) -> PyMetaData {
PyMetaData {
meta: self.pdf.metadata().clone(),
}
}
}
pub fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
let m = PyModule::new(parent_module.py(), "pdf")?;
m.setattr(pyo3::intern!(m.py(), "__doc__"), "Interface for PDF.")?;
pyo3::py_run!(
parent_module.py(),
m,
"import sys; sys.modules['neopdf.pdf'] = m"
);
m.add_class::<PyPDF>()?;
m.add_class::<PyLazyPDFs>()?;
m.add_class::<PyForcePositive>()?;
m.add_class::<PyGridParams>()?;
m.add_class::<PyLoaderMethod>()?;
parent_module.add_submodule(&m)
}