#![warn(clippy::all, clippy::cargo, clippy::nursery, clippy::pedantic)]
#![warn(missing_docs)]
mod error;
mod ffi;
#[cfg(feature = "managed")]
mod manager;
mod unmanaged;
#[cfg(not(feature = "managed"))]
mod manager {
pub use super::unmanaged::*;
}
use cxx::{CxxVector, UniquePtr, let_cxx_string};
use std::fmt::{self, Formatter};
pub use error::{Error, Result};
pub use ffi::PdfUncertainty;
pub const CL_1_SIGMA: f64 = 68.268_949_213_708_58;
#[must_use]
pub fn lookup_pdf(lhaid: i32) -> Option<(String, i32)> {
manager::pdf_name_and_member_via_lhaid(lhaid)
}
pub fn set_verbosity(verbosity: i32) {
manager::set_verbosity(verbosity);
}
#[must_use]
pub fn verbosity() -> i32 {
manager::verbosity()
}
pub struct Pdf {
ptr: UniquePtr<ffi::PDF>,
}
impl fmt::Debug for Pdf {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Pdf")
.field("lhaid", &self.ptr.lhapdfID())
.finish()
}
}
impl Pdf {
pub fn with_lhaid(lhaid: i32) -> Result<Self> {
let Some((setname, member)) = lookup_pdf(lhaid) else {
return Err(Error::General(format!(
"did not find PDF with LHAID = {lhaid}"
)));
};
Self::with_setname_and_member(&setname, member)
}
pub fn with_setname_and_member(setname: &str, member: i32) -> Result<Self> {
manager::pdf_with_setname_and_member(setname, member).map(|ptr| Self { ptr })
}
pub fn with_setname_and_nmem(setname_nmem: &str) -> Result<Self> {
let (setname, member) = setname_nmem.split_once('/').map_or(
Ok::<_, Error>((setname_nmem, 0)),
|(setname, nmem)| {
Ok((
setname,
nmem.parse().map_err(|err| {
{
Error::General(format!(
"problem while parsing member index = {nmem}: '{err}'"
))
}
})?,
))
},
)?;
Self::with_setname_and_member(setname, member)
}
#[must_use]
pub fn xfx_q2(&self, id: i32, x: f64, q2: f64) -> f64 {
self.ptr.xfxQ2(id, x, q2).unwrap()
}
#[must_use]
pub fn alphas_q2(&self, q2: f64) -> f64 {
self.ptr.alphasQ2(q2).unwrap()
}
#[must_use]
pub fn set(&self) -> PdfSet {
let_cxx_string!(setname = "");
ffi::pdf_setname(&self.ptr, setname.as_mut());
let setname = setname.to_str().unwrap_or_else(|_| unreachable!());
PdfSet::new(setname).unwrap_or_else(|_| unreachable!())
}
#[must_use]
pub fn x_min(&mut self) -> f64 {
self.ptr.pin_mut().xMin()
}
#[must_use]
pub fn x_max(&mut self) -> f64 {
self.ptr.pin_mut().xMax()
}
pub fn set_force_positive(&mut self, mode: i32) {
self.ptr.pin_mut().setForcePositive(mode);
}
#[must_use]
pub fn force_positive(&mut self) -> i32 {
self.ptr.pin_mut().forcePositive()
}
#[must_use]
pub fn flavors(&self) -> Vec<i32> {
self.ptr.flavors().iter().copied().collect()
}
pub fn set_flavors(&mut self, flavors: &[i32]) {
let mut vector = CxxVector::new();
flavors
.iter()
.for_each(|&flavor| vector.pin_mut().push(flavor));
self.ptr.pin_mut().setFlavors(&vector);
}
}
unsafe impl Send for Pdf {}
unsafe impl Sync for Pdf {}
pub struct PdfSet {
ptr: UniquePtr<ffi::PDFSet>,
}
impl fmt::Debug for PdfSet {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("PdfSet")
.field("lhaid", &self.ptr.lhapdfID())
.finish()
}
}
impl PdfSet {
pub fn new(setname: &str) -> Result<Self> {
manager::pdfset_new(setname).map(|ptr| Self { ptr })
}
#[must_use]
pub fn entry(&self, key: &str) -> Option<String> {
let_cxx_string!(cxx_key = key);
if self.ptr.has_key(&cxx_key) {
Some(self.ptr.get_entry(&cxx_key).to_string_lossy().into_owned())
} else {
None
}
}
#[must_use]
pub fn error_type(&self) -> String {
let_cxx_string!(string = "");
ffi::get_pdfset_error_type(&self.ptr, string.as_mut());
string.to_string_lossy().into_owned()
}
pub fn mk_pdfs(&self) -> Result<Vec<Pdf>> {
let setname = self.name();
(0..i32::try_from(self.ptr.size()).unwrap_or_else(|_| unreachable!()))
.map(|member| Pdf::with_setname_and_member(&setname, member))
.collect::<Result<Vec<_>>>()
}
#[must_use]
pub fn name(&self) -> String {
let_cxx_string!(setname = "");
ffi::pdfset_setname(&self.ptr, setname.as_mut());
let setname = setname.to_str().unwrap_or_else(|_| unreachable!());
setname.to_owned()
}
pub fn uncertainty(
&self,
values: &[f64],
cl: f64,
alternative: bool,
) -> Result<PdfUncertainty> {
Ok(ffi::pdf_uncertainty(&self.ptr, values, cl, alternative)?)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn set_verbosity() {
super::set_verbosity(0);
assert_eq!(verbosity(), 0);
}
#[test]
fn check_lookup_pdf() {
assert!(matches!(lookup_pdf(324900), Some((name, member))
if (name == "NNPDF31_nlo_as_0118_luxqed") && (member == 0)));
assert!(matches!(lookup_pdf(324901), Some((name, member))
if (name == "NNPDF31_nlo_as_0118_luxqed") && (member == 1)));
assert!(matches!(lookup_pdf(-1), None));
}
#[test]
fn debug_pdf() -> Result<()> {
let pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?;
assert_eq!(format!("{:?}", pdf), "Pdf { lhaid: 324900 }");
Ok(())
}
#[test]
fn check_pdf() -> Result<()> {
let mut pdf_0 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?;
let mut pdf_1 = Pdf::with_lhaid(324900)?;
let value_0 = pdf_0.xfx_q2(2, 0.5, 90.0 * 90.0);
let value_1 = pdf_1.xfx_q2(2, 0.5, 90.0 * 90.0);
assert_ne!(value_0, 0.0);
assert_eq!(value_0, value_1);
let value_0 = pdf_0.alphas_q2(90.0 * 90.0);
let value_1 = pdf_1.alphas_q2(90.0 * 90.0);
assert_ne!(value_0, 0.0);
assert_eq!(value_0, value_1);
assert_eq!(
Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 10000)
.unwrap_err()
.to_string(),
"PDF NNPDF31_nlo_as_0118_luxqed/10000 is out of the member range of set NNPDF31_nlo_as_0118_luxqed"
);
assert_eq!(
Pdf::with_lhaid(0).unwrap_err().to_string(),
"did not find PDF with LHAID = 0"
);
assert_eq!(pdf_0.x_min(), 1e-9);
assert_eq!(pdf_0.x_max(), 1.0);
assert_eq!(pdf_1.x_min(), 1e-9);
assert_eq!(pdf_1.x_max(), 1.0);
Ok(())
}
#[test]
fn check_setname_and_nmem() -> Result<()> {
let pdf_0 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 1)?;
let pdf_1 = Pdf::with_setname_and_nmem("NNPDF31_nlo_as_0118_luxqed/1")?;
let value_0 = pdf_0.xfx_q2(2, 0.5, 90.0 * 90.0);
let value_1 = pdf_1.xfx_q2(2, 0.5, 90.0 * 90.0);
assert_ne!(value_0, 0.0);
assert_eq!(value_0, value_1);
let value_0 = pdf_0.alphas_q2(90.0 * 90.0);
let value_1 = pdf_1.alphas_q2(90.0 * 90.0);
assert_ne!(value_0, 0.0);
assert_eq!(value_0, value_1);
assert_eq!(
Pdf::with_setname_and_nmem("foobar/0")
.unwrap_err()
.to_string(),
"Info file not found for PDF set 'foobar'"
);
assert_eq!(
Pdf::with_setname_and_nmem("NNPDF31_nlo_as_0118_luxqed/x")
.unwrap_err()
.to_string(),
"problem while parsing member index = x: 'invalid digit found in string'"
);
Ok(())
}
#[test]
fn check_pdf_set() -> Result<()> {
let pdf_set = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
assert!(matches!(pdf_set.entry("Particle"), Some(value) if value == "2212"));
assert!(matches!(pdf_set.entry("Flavors"), Some(value)
if value == "[-5, -4, -3, -2, -1, 21, 1, 2, 3, 4, 5, 22]"));
assert_eq!(pdf_set.entry("idontexist"), None);
assert_eq!(pdf_set.error_type(), "replicas");
assert_eq!(pdf_set.name(), "NNPDF31_nlo_as_0118_luxqed");
assert_eq!(
PdfSet::new("IDontExist").unwrap_err().to_string(),
"Info file not found for PDF set 'IDontExist'"
);
assert_eq!(pdf_set.mk_pdfs().unwrap().len(), 101);
let uncertainty = pdf_set.uncertainty(&[0.0; 101], 68.268949213709, false)?;
assert_eq!(uncertainty.central, 0.0);
assert_eq!(uncertainty.central, 0.0);
assert_eq!(uncertainty.errplus, 0.0);
assert_eq!(uncertainty.errminus, 0.0);
assert_eq!(uncertainty.errsymm, 0.0);
assert_eq!(uncertainty.errplus_pdf, 0.0);
assert_eq!(uncertainty.errminus_pdf, 0.0);
assert_eq!(uncertainty.errsymm_pdf, 0.0);
assert_eq!(uncertainty.err_par, 0.0);
Ok(())
}
#[test]
fn debug_pdf_set() -> Result<()> {
let pdf_set = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
assert_eq!(format!("{:?}", pdf_set), "PdfSet { lhaid: 324900 }");
Ok(())
}
#[test]
fn check_pdf_pdfset() -> Result<()> {
let pdf_set0 = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
let pdf_set1 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?.set();
assert_eq!(pdf_set0.entry("Particle"), pdf_set1.entry("Particle"));
assert_eq!(pdf_set0.entry("NumMembers"), pdf_set1.entry("NumMembers"));
Ok(())
}
#[test]
fn force_positive() -> Result<()> {
let mut pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 1)?;
assert_eq!(pdf.force_positive(), 0);
pdf.set_force_positive(1);
assert_eq!(pdf.force_positive(), 1);
Ok(())
}
#[test]
fn set_flavors() {
let mut pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0).unwrap();
assert_eq!(pdf.flavors(), &[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21, 22]);
pdf.set_flavors(&[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21]);
assert_eq!(pdf.flavors(), &[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21]);
}
#[test]
fn download_pdf_set() {
let _ = Pdf::with_setname_and_member("CT10", 0).unwrap();
}
}