1#![warn(clippy::all, clippy::cargo, clippy::nursery, clippy::pedantic)]
2#![warn(missing_docs)]
3
4mod error;
7mod ffi;
8#[cfg(feature = "managed")]
9mod manager;
10mod unmanaged;
11
12#[cfg(not(feature = "managed"))]
13mod manager {
14 pub use super::unmanaged::*;
15}
16
17use cxx::{let_cxx_string, CxxVector, UniquePtr};
18use std::fmt::{self, Formatter};
19
20pub use error::{Error, Result};
21pub use ffi::PdfUncertainty;
22
23pub const CL_1_SIGMA: f64 = 68.268_949_213_708_58;
25
26#[must_use]
28pub fn lookup_pdf(lhaid: i32) -> Option<(String, i32)> {
29 manager::pdf_name_and_member_via_lhaid(lhaid)
30}
31
32pub fn set_verbosity(verbosity: i32) {
34 manager::set_verbosity(verbosity);
35}
36
37#[must_use]
39pub fn verbosity() -> i32 {
40 manager::verbosity()
41}
42
43pub struct Pdf {
45 ptr: UniquePtr<ffi::PDF>,
46}
47
48impl fmt::Debug for Pdf {
49 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
50 f.debug_struct("Pdf")
52 .field("lhaid", &self.ptr.lhapdfID())
53 .finish()
54 }
55}
56
57impl Pdf {
58 pub fn with_lhaid(lhaid: i32) -> Result<Self> {
64 let Some((setname, member)) = lookup_pdf(lhaid) else {
65 return Err(Error::General(format!(
66 "did not find PDF with LHAID = {lhaid}"
67 )));
68 };
69
70 Self::with_setname_and_member(&setname, member)
71 }
72
73 pub fn with_setname_and_member(setname: &str, member: i32) -> Result<Self> {
79 manager::pdf_with_setname_and_member(setname, member).map(|ptr| Self { ptr })
80 }
81
82 pub fn with_setname_and_nmem(setname_nmem: &str) -> Result<Self> {
94 let (setname, member) = setname_nmem.split_once('/').map_or(
95 Ok::<_, Error>((setname_nmem, 0)),
96 |(setname, nmem)| {
97 Ok((
98 setname,
99 nmem.parse().map_err(|err| {
100 {
101 Error::General(format!(
102 "problem while parsing member index = {nmem}: '{err}'"
103 ))
104 }
105 })?,
106 ))
107 },
108 )?;
109
110 Self::with_setname_and_member(setname, member)
111 }
112
113 #[must_use]
119 pub fn xfx_q2(&self, id: i32, x: f64, q2: f64) -> f64 {
120 self.ptr.xfxQ2(id, x, q2).unwrap()
121 }
122
123 #[must_use]
129 pub fn alphas_q2(&self, q2: f64) -> f64 {
130 self.ptr.alphasQ2(q2).unwrap()
131 }
132
133 #[must_use]
135 pub fn set(&self) -> PdfSet {
136 let_cxx_string!(setname = "");
137 ffi::pdf_setname(&self.ptr, setname.as_mut());
138 let setname = setname.to_str().unwrap_or_else(|_| unreachable!());
140
141 PdfSet::new(setname).unwrap_or_else(|_| unreachable!())
143 }
144
145 #[must_use]
147 pub fn x_min(&mut self) -> f64 {
148 self.ptr.pin_mut().xMin()
149 }
150
151 #[must_use]
153 pub fn x_max(&mut self) -> f64 {
154 self.ptr.pin_mut().xMax()
155 }
156
157 pub fn set_force_positive(&mut self, mode: i32) {
159 self.ptr.pin_mut().setForcePositive(mode);
160 }
161
162 #[must_use]
168 pub fn force_positive(&mut self) -> i32 {
169 self.ptr.pin_mut().forcePositive()
170 }
171
172 #[must_use]
174 pub fn flavors(&self) -> Vec<i32> {
175 self.ptr.flavors().iter().copied().collect()
176 }
177
178 pub fn set_flavors(&mut self, flavors: &[i32]) {
180 let mut vector = CxxVector::new();
181
182 flavors
183 .iter()
184 .for_each(|&flavor| vector.pin_mut().push(flavor));
185
186 self.ptr.pin_mut().setFlavors(&vector);
187 }
188}
189
190unsafe impl Send for Pdf {}
191unsafe impl Sync for Pdf {}
192
193pub struct PdfSet {
195 ptr: UniquePtr<ffi::PDFSet>,
196}
197
198impl fmt::Debug for PdfSet {
199 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
200 f.debug_struct("PdfSet")
202 .field("lhaid", &self.ptr.lhapdfID())
203 .finish()
204 }
205}
206
207impl PdfSet {
208 pub fn new(setname: &str) -> Result<Self> {
214 manager::pdfset_new(setname).map(|ptr| Self { ptr })
215 }
216
217 #[must_use]
219 pub fn entry(&self, key: &str) -> Option<String> {
220 let_cxx_string!(cxx_key = key);
221
222 if self.ptr.has_key(&cxx_key) {
223 Some(self.ptr.get_entry(&cxx_key).to_string_lossy().into_owned())
224 } else {
225 None
226 }
227 }
228
229 #[must_use]
231 pub fn error_type(&self) -> String {
232 let_cxx_string!(string = "");
233
234 ffi::get_pdfset_error_type(&self.ptr, string.as_mut());
235 string.to_string_lossy().into_owned()
236 }
237
238 pub fn mk_pdfs(&self) -> Result<Vec<Pdf>> {
244 let setname = self.name();
245
246 (0..i32::try_from(self.ptr.size()).unwrap_or_else(|_| unreachable!()))
249 .map(|member| Pdf::with_setname_and_member(&setname, member))
250 .collect::<Result<Vec<_>>>()
251 }
252
253 #[must_use]
255 pub fn name(&self) -> String {
256 let_cxx_string!(setname = "");
257 ffi::pdfset_setname(&self.ptr, setname.as_mut());
258 let setname = setname.to_str().unwrap_or_else(|_| unreachable!());
260
261 setname.to_owned()
262 }
263
264 pub fn uncertainty(
293 &self,
294 values: &[f64],
295 cl: f64,
296 alternative: bool,
297 ) -> Result<PdfUncertainty> {
298 Ok(ffi::pdf_uncertainty(&self.ptr, values, cl, alternative)?)
299 }
300}
301
302#[cfg(test)]
303mod test {
304 use super::*;
305
306 #[test]
307 fn set_verbosity() {
308 super::set_verbosity(0);
309 assert_eq!(verbosity(), 0);
310 }
311
312 #[test]
313 fn check_lookup_pdf() {
314 assert!(matches!(lookup_pdf(324900), Some((name, member))
315 if (name == "NNPDF31_nlo_as_0118_luxqed") && (member == 0)));
316 assert!(matches!(lookup_pdf(324901), Some((name, member))
317 if (name == "NNPDF31_nlo_as_0118_luxqed") && (member == 1)));
318 assert!(matches!(lookup_pdf(-1), None));
319 }
320
321 #[test]
322 fn debug_pdf() -> Result<()> {
323 let pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?;
324
325 assert_eq!(format!("{:?}", pdf), "Pdf { lhaid: 324900 }");
326
327 Ok(())
328 }
329
330 #[test]
331 fn check_pdf() -> Result<()> {
332 let mut pdf_0 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?;
333 let mut pdf_1 = Pdf::with_lhaid(324900)?;
334
335 let value_0 = pdf_0.xfx_q2(2, 0.5, 90.0 * 90.0);
336 let value_1 = pdf_1.xfx_q2(2, 0.5, 90.0 * 90.0);
337
338 assert_ne!(value_0, 0.0);
339 assert_eq!(value_0, value_1);
340
341 let value_0 = pdf_0.alphas_q2(90.0 * 90.0);
342 let value_1 = pdf_1.alphas_q2(90.0 * 90.0);
343
344 assert_ne!(value_0, 0.0);
345 assert_eq!(value_0, value_1);
346
347 assert_eq!(
348 Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 10000)
349 .unwrap_err()
350 .to_string(),
351 "PDF NNPDF31_nlo_as_0118_luxqed/10000 is out of the member range of set NNPDF31_nlo_as_0118_luxqed"
352 );
353
354 assert_eq!(
355 Pdf::with_lhaid(0).unwrap_err().to_string(),
356 "did not find PDF with LHAID = 0"
357 );
358
359 assert_eq!(pdf_0.x_min(), 1e-9);
360 assert_eq!(pdf_0.x_max(), 1.0);
361 assert_eq!(pdf_1.x_min(), 1e-9);
362 assert_eq!(pdf_1.x_max(), 1.0);
363
364 Ok(())
365 }
366
367 #[test]
368 fn check_setname_and_nmem() -> Result<()> {
369 let pdf_0 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 1)?;
370 let pdf_1 = Pdf::with_setname_and_nmem("NNPDF31_nlo_as_0118_luxqed/1")?;
371
372 let value_0 = pdf_0.xfx_q2(2, 0.5, 90.0 * 90.0);
373 let value_1 = pdf_1.xfx_q2(2, 0.5, 90.0 * 90.0);
374
375 assert_ne!(value_0, 0.0);
376 assert_eq!(value_0, value_1);
377
378 let value_0 = pdf_0.alphas_q2(90.0 * 90.0);
379 let value_1 = pdf_1.alphas_q2(90.0 * 90.0);
380
381 assert_ne!(value_0, 0.0);
382 assert_eq!(value_0, value_1);
383
384 assert_eq!(
385 Pdf::with_setname_and_nmem("foobar/0")
386 .unwrap_err()
387 .to_string(),
388 "Info file not found for PDF set 'foobar'"
389 );
390
391 assert_eq!(
392 Pdf::with_setname_and_nmem("NNPDF31_nlo_as_0118_luxqed/x")
393 .unwrap_err()
394 .to_string(),
395 "problem while parsing member index = x: 'invalid digit found in string'"
396 );
397
398 Ok(())
399 }
400
401 #[test]
402 fn check_pdf_set() -> Result<()> {
403 let pdf_set = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
404
405 assert!(matches!(pdf_set.entry("Particle"), Some(value) if value == "2212"));
406 assert!(matches!(pdf_set.entry("Flavors"), Some(value)
407 if value == "[-5, -4, -3, -2, -1, 21, 1, 2, 3, 4, 5, 22]"));
408 assert_eq!(pdf_set.entry("idontexist"), None);
409
410 assert_eq!(pdf_set.error_type(), "replicas");
411 assert_eq!(pdf_set.name(), "NNPDF31_nlo_as_0118_luxqed");
412
413 assert_eq!(
414 PdfSet::new("IDontExist").unwrap_err().to_string(),
415 "Info file not found for PDF set 'IDontExist'"
416 );
417
418 assert_eq!(pdf_set.mk_pdfs().unwrap().len(), 101);
419
420 let uncertainty = pdf_set.uncertainty(&[0.0; 101], 68.268949213709, false)?;
421
422 assert_eq!(uncertainty.central, 0.0);
423 assert_eq!(uncertainty.central, 0.0);
424 assert_eq!(uncertainty.errplus, 0.0);
425 assert_eq!(uncertainty.errminus, 0.0);
426 assert_eq!(uncertainty.errsymm, 0.0);
427 assert_eq!(uncertainty.errplus_pdf, 0.0);
429 assert_eq!(uncertainty.errminus_pdf, 0.0);
430 assert_eq!(uncertainty.errsymm_pdf, 0.0);
431 assert_eq!(uncertainty.err_par, 0.0);
432
433 Ok(())
434 }
435
436 #[test]
437 fn debug_pdf_set() -> Result<()> {
438 let pdf_set = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
439
440 assert_eq!(format!("{:?}", pdf_set), "PdfSet { lhaid: 324900 }");
441
442 Ok(())
443 }
444
445 #[test]
446 fn check_pdf_pdfset() -> Result<()> {
447 let pdf_set0 = PdfSet::new("NNPDF31_nlo_as_0118_luxqed")?;
448 let pdf_set1 = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0)?.set();
449
450 assert_eq!(pdf_set0.entry("Particle"), pdf_set1.entry("Particle"));
451 assert_eq!(pdf_set0.entry("NumMembers"), pdf_set1.entry("NumMembers"));
452
453 Ok(())
454 }
455
456 #[test]
457 fn force_positive() -> Result<()> {
458 let mut pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 1)?;
459
460 assert_eq!(pdf.force_positive(), 0);
461
462 pdf.set_force_positive(1);
463 assert_eq!(pdf.force_positive(), 1);
464
465 Ok(())
466 }
467
468 #[test]
469 fn set_flavors() {
470 let mut pdf = Pdf::with_setname_and_member("NNPDF31_nlo_as_0118_luxqed", 0).unwrap();
471
472 assert_eq!(pdf.flavors(), &[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21, 22]);
473
474 pdf.set_flavors(&[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21]);
475
476 assert_eq!(pdf.flavors(), &[-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 21]);
477 }
478
479 #[test]
480 fn download_pdf_set() {
481 let _ = Pdf::with_setname_and_member("CT10", 0).unwrap();
482 }
483}