Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added further `LHAPDF` compatibility layers ([#93](https://github.com/QCDLab/neopdf/pull/93))

## [0.3.3] - 12/05/2026

### Added
Expand Down
15 changes: 15 additions & 0 deletions docs/design-and-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ into diverse computational workflows:
Key functions like `xfxQ2()`, `alphasQ2()`, and `mkPDF()` maintain the same signatures as
LHAPDF, ensuring compatibility with existing analysis codes.

The following symbols have an exact match with their LHAPDF counterparts.

*Module-level:*
`setVerbosity()`, `verbosity()`, `getPDFSet()`, `mkPDF()`, `mkPDFs()`,
`availablePDFSets()`, `paths()`, `setPaths()`, `pathsAppend()`, `pathsPrepend()`.

*`PDFSet` class:*
`name`, `size`, `description`, `errorType`, `lhapdfID`, `mkPDF()`, `mkPDFs()`, `info()`.

*`PDF` class:*
`xfxQ()`, `xfxQ2()`, `alphasQ()`, `alphasQ2()`, `flavors()`, `hasFlavor()`,
`inRangeX()`, `inRangeQ()`, `inRangeQ2()`, `inRangeXQ()`, `inRangeXQ2()`,
`memberID`, `lhapdfID`, `orderQCD`, `xMin`, `xMax`, `q2Min`, `q2Max`,
`quarkMass()`, `quarkThreshold()`, `description`, `set`.

This compatibility is crucial for the physics community, as it allows for immediate adoption
without requiring extensive code rewrites or validation efforts.

Expand Down
24 changes: 12 additions & 12 deletions neopdf/src/interpolator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,18 @@ impl InterpolationConfig {

match dims {
(false, false, false, false, false) => Self::TwoD,
(true, false, false, false, false) => Self::ThreeDNucleons,
(false, true, false, false, false) => Self::ThreeDAlphas,
(false, false, true, false, false) => Self::ThreeDXi,
(false, false, false, true, false) => Self::ThreeDDelta,
(false, false, false, false, true) => Self::ThreeDKt,
(true, true, false, false, false) => Self::FourDNucleonsAlphas,
(true, false, false, false, true) => Self::FourDNucleonsKt,
(false, true, false, false, true) => Self::FourDAlphasKt,
(false, false, true, true, false) => Self::FourDXiDelta,
(false, false, true, true, true) => Self::FiveD,
(true, false, true, true, true) => Self::SixD,
(true, true, true, true, true) => Self::SevenD,
(true, false, false, false, false) => Self::ThreeDNucleons,
(false, true, false, false, false) => Self::ThreeDAlphas,
(false, false, true, false, false) => Self::ThreeDXi,
(false, false, false, true, false) => Self::ThreeDDelta,
(false, false, false, false, true ) => Self::ThreeDKt,
(true, true, false, false, false) => Self::FourDNucleonsAlphas,
(true, false, false, false, true ) => Self::FourDNucleonsKt,
(false, true, false, false, true ) => Self::FourDAlphasKt,
(false, false, true, true, false) => Self::FourDXiDelta,
(false, false, true, true, true ) => Self::FiveD,
(true, false, true, true, true ) => Self::SixD,
(true, true, true, true, true ) => Self::SevenD,
_ => panic!(
"Unsupported dimension combination: nucleons={}, alphas={}, xis={}, deltas={}, kts={}",
n_nucleons, n_alphas, n_xis, n_deltas, n_kts
Expand Down
106 changes: 105 additions & 1 deletion neopdf_pyapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
#![allow(unsafe_op_in_unsafe_fn)]

use pyo3::prelude::*;
use std::sync::atomic::{AtomicI32, Ordering};

use crate::pdf::{PyLoaderMethod, PyPDF, PyPDFSet};
use ::neopdf::manage::ManageData;

/// Python bindings for the `converter` module.
pub mod converter;
Expand All @@ -21,10 +25,99 @@ pub mod uncertainty;
/// Python bindings for the `writer` module.
pub mod writer;

static VERBOSITY: AtomicI32 = AtomicI32::new(0);

/// Set the `NeoPDF` verbosity level (no-op, exists for LHAPDF API compatibility).
#[pyfunction]
#[pyo3(name = "setVerbosity")]
fn set_verbosity(level: i32) {
VERBOSITY.store(level, Ordering::Relaxed);
}

/// Return the current verbosity level.
#[pyfunction]
fn verbosity() -> i32 {
VERBOSITY.load(Ordering::Relaxed)
}

/// Return a `PDFSet` object for the named set (LHAPDF-compatible).
#[pyfunction]
#[pyo3(name = "getPDFSet")]
fn get_pdf_set(setname: &str) -> PyPDFSet {
PyPDFSet::new(setname)
}

/// Load a single PDF member by set name and member index (LHAPDF-compatible).
#[pyfunction]
#[pyo3(name = "mkPDF")]
#[pyo3(signature = (pdf_name, member = 0))]
fn mk_pdf(pdf_name: &str, member: usize) -> PyPDF {
PyPDF::new(pdf_name, member)
}

/// Load all members of a PDF set (LHAPDF-compatible).
#[pyfunction]
#[pyo3(name = "mkPDFs")]
fn mk_pdfs(pdf_name: &str) -> Vec<PyPDF> {
PyPDF::mkpdfs(pdf_name, &PyLoaderMethod::Parallel)
}

/// Return a list of PDF set names available in the current data path.
#[pyfunction]
#[pyo3(name = "availablePDFSets")]
fn available_pdf_sets() -> Vec<String> {
let data_path = ManageData::get_data_path();
let mut sets = Vec::new();
if let Ok(entries) = std::fs::read_dir(&data_path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
let name = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
if path.join(format!("{name}.info")).exists() {
sets.push(name);
}
}
}
}
sets.sort();
sets
}

/// Return the list of data paths searched for PDF sets.
#[pyfunction]
fn paths() -> Vec<String> {
vec![ManageData::get_data_path().to_string_lossy().into_owned()]
}

/// No-op: path management is controlled via the `NEOPDF_DATA_PATH` env var.
#[pyfunction]
#[pyo3(name = "setPaths")]
fn set_paths(_paths: Vec<String>) {}

/// No-op: path management is controlled via the `NEOPDF_DATA_PATH` env var.
#[pyfunction]
#[pyo3(name = "pathsAppend")]
fn paths_append(_path: String) {}

/// No-op: path management is controlled via the `NEOPDF_DATA_PATH` env var.
#[pyfunction]
#[pyo3(name = "pathsPrepend")]
fn paths_prepend(_path: String) {}

/// Return the NeoPDF version string (LHAPDF-compatible callable).
#[pyfunction]
fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}

/// `PyO3` Python module that contains all exposed classes from Rust.
#[pymodule]
fn neopdf(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("version", env!("CARGO_PKG_VERSION"))?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
pdf::register(m)?;
metadata::register(m)?;
converter::register(m)?;
Expand All @@ -33,5 +126,16 @@ fn neopdf(m: &Bound<'_, PyModule>) -> PyResult<()> {
parser::register(m)?;
writer::register(m)?;
uncertainty::register(m)?;
m.add_function(wrap_pyfunction!(set_verbosity, m)?)?;
m.add_function(wrap_pyfunction!(verbosity, m)?)?;
m.add_function(wrap_pyfunction!(get_pdf_set, m)?)?;
m.add_function(wrap_pyfunction!(mk_pdf, m)?)?;
m.add_function(wrap_pyfunction!(mk_pdfs, m)?)?;
m.add_function(wrap_pyfunction!(available_pdf_sets, m)?)?;
m.add_function(wrap_pyfunction!(paths, m)?)?;
m.add_function(wrap_pyfunction!(set_paths, m)?)?;
m.add_function(wrap_pyfunction!(paths_append, m)?)?;
m.add_function(wrap_pyfunction!(paths_prepend, m)?)?;
m.add_function(wrap_pyfunction!(version, m)?)?;
Ok(())
}
2 changes: 1 addition & 1 deletion neopdf_pyapi/src/manage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;

/// Python wrapper for the `PdfSetFormat` enum.
#[pyclass(name = "PdfSetFormat")]
#[pyclass(from_py_object, name = "PdfSetFormat")]
#[derive(Clone)]
pub enum PyPdfSetFormat {
/// LHAPDF format (standard PDF set format used by LHAPDF).
Expand Down
8 changes: 4 additions & 4 deletions neopdf_pyapi/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use pyo3::prelude::*;
use neopdf::metadata::{InterpolatorType, MetaData, SetType};

/// The type of the set.
#[pyclass(eq, eq_int, name = "SetType")]
#[pyclass(eq, eq_int, from_py_object, name = "SetType")]
#[derive(Clone, PartialEq, Eq)]
pub enum PySetType {
/// Parton Distribution Function.
Expand Down Expand Up @@ -31,7 +31,7 @@ impl From<&PySetType> for SetType {
}

/// The interpolation method used for the grid.
#[pyclass(eq, eq_int, name = "InterpolatorType")]
#[pyclass(eq, eq_int, from_py_object, name = "InterpolatorType")]
#[derive(Clone, PartialEq, Eq)]
pub enum PyInterpolatorType {
/// Bilinear interpolation strategy.
Expand Down Expand Up @@ -83,7 +83,7 @@ impl From<&PyInterpolatorType> for InterpolatorType {
}

/// Physical Parameters of the PDF set.
#[pyclass(name = "PhysicsParameters")]
#[pyclass(from_py_object, name = "PhysicsParameters")]
#[derive(Debug, Clone)]
pub struct PyPhysicsParameters {
pub(crate) flavor_scheme: String,
Expand Down Expand Up @@ -198,7 +198,7 @@ impl Default for PyPhysicsParameters {
}

/// Grid metadata.
#[pyclass(name = "MetaData")]
#[pyclass(from_py_object, name = "MetaData")]
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct PyMetaData {
Expand Down
Loading
Loading