diff --git a/.github/actions/cache-test-data/action.yml b/.github/actions/cache-test-data/action.yml index 855d6704..0e1fa8f2 100644 --- a/.github/actions/cache-test-data/action.yml +++ b/.github/actions/cache-test-data/action.yml @@ -10,7 +10,7 @@ runs: uses: actions/cache@v4 with: path: test-data - key: test-data-v30 + key: test-data-v32 - name: Download test data if cache miss if: steps.cache.outputs.cache-hit != 'true' run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a801e0..ed9bb60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ Starting with this version, PineAPPL has an official logo! - instead of depending on a previous version of the PineAPPL crate, a new crate `pineappl_v0` is now responsible for loading files with file version `v0` - raised dependency on `pyo3` to 0.27, which drops support for PyPy 3.9 and 3.10 +- renamed the switch `--discard-non-matching-scales` to + `--discard-non-matching-values`, which now also discards momentum fraction + values unavailable to APPLgrid. Both `--discard-non-matching-scales` and the + new `--discard-non-matching-momenta` are aliases to the new option name to + guarantee backwards compatibility of the CLI. ## [1.3.3] - 01/03/2026 diff --git a/maintainer/download-test-data.sh b/maintainer/download-test-data.sh index 63cab140..f033a4c7 100755 --- a/maintainer/download-test-data.sh +++ b/maintainer/download-test-data.sh @@ -44,6 +44,7 @@ files=( 'https://data.nnpdf.science/pineappl/test-data/LHCB_WP_7TEV_opt.pineappl.lz4' 'https://data.nnpdf.science/pineappl/test-data/LHCB_WP_7TEV_v2.tar' 'https://data.nnpdf.science/pineappl/test-data/LHCB_WP_7TEV_v2_xif_2.tar' + 'https://data.nnpdf.science/pineappl/test-data/nnlo.ak5_ptj-bin63.pineappl.lz4' 'https://data.nnpdf.science/pineappl/test-data/NJetEvents_0-0-2.tab.gz' 'https://data.nnpdf.science/pineappl/test-data/NNPDF_POS_F2D_40.pineappl.lz4' 'https://data.nnpdf.science/pineappl/test-data/NUTEV_CC_NU_FE_SIGMARED.pineappl.lz4' diff --git a/pineappl_cli/src/export.rs b/pineappl_cli/src/export.rs index 8a2ad659..47e4a42e 100644 --- a/pineappl_cli/src/export.rs +++ b/pineappl_cli/src/export.rs @@ -18,12 +18,12 @@ fn convert_into_applgrid( grid: &mut Grid, conv_funs: &mut [Pdf], _: usize, - discard_non_matching_scales: bool, + discard_non_matching_values: bool, ) -> Result<(&'static str, Vec, usize, Vec)> { // TODO: check also scale-varied results let (mut applgrid, order_mask) = - applgrid::convert_into_applgrid(grid, output, discard_non_matching_scales)?; + applgrid::convert_into_applgrid(grid, output, discard_non_matching_values)?; let results = applgrid::convolve_applgrid(applgrid.pin_mut(), conv_funs); Ok(("APPLgrid", results, 1, order_mask)) @@ -47,12 +47,12 @@ fn convert_into_grid( grid: &mut Grid, conv_funs: &mut [Pdf], scales: usize, - discard_non_matching_scales: bool, + discard_non_matching_values: bool, ) -> Result<(&'static str, Vec, usize, Vec)> { if let Some(extension) = output.extension() && (extension == "appl" || extension == "root") { - return convert_into_applgrid(output, grid, conv_funs, scales, discard_non_matching_scales); + return convert_into_applgrid(output, grid, conv_funs, scales, discard_non_matching_values); } Err(anyhow!("could not detect file format")) @@ -72,9 +72,9 @@ pub struct Opts { /// Relative threshold between the table and the converted grid when comparison fails. #[arg(default_value = "1e-10", long)] accuracy: f64, - /// Discard non-matching scales that would otherwise lead to panics. - #[arg(long)] - discard_non_matching_scales: bool, + /// Discard non-matching scales and momentum fractions that would otherwise fail the export. + #[arg(long, aliases = ["discard-non-matching-scales", "discard-non-matching-momentum"])] + discard_non_matching_values: bool, /// Set the number of scale variations to compare with if they are available. #[arg( default_value_t = 7, @@ -104,7 +104,7 @@ impl Subcommand for Opts { &mut grid, &mut conv_funs, self.scales, - self.discard_non_matching_scales, + self.discard_non_matching_values, )?; for Order { diff --git a/pineappl_cli/src/export/applgrid.rs b/pineappl_cli/src/export/applgrid.rs index a1ac190a..8e4d17e2 100644 --- a/pineappl_cli/src/export/applgrid.rs +++ b/pineappl_cli/src/export/applgrid.rs @@ -1,6 +1,7 @@ use anyhow::{Result, bail}; use cxx::{UniquePtr, let_cxx_string}; use float_cmp::approx_eq; +use itertools::izip; use lhapdf::Pdf; use ndarray::{Axis, s}; use pineappl::boc::{Channel, Kinematics, Order}; @@ -95,7 +96,7 @@ fn reconstruct_subgrid_params(grid: &Grid, order: usize, bin: usize) -> Result Result<(UniquePtr, Vec)> { let dim = grid.bwfl().dimensions(); @@ -261,47 +262,31 @@ pub fn convert_into_applgrid( grid.channels().len().try_into().unwrap(), grid.convolutions().len() == 1, ); - let appl_q2: Vec<_> = (0..igrid.Ntau()).map(|i| igrid.getQ2(i)).collect(); - let appl_x1: Vec<_> = (0..igrid.Ny1()).map(|i| igrid.getx1(i)).collect(); - let appl_x2: Vec<_> = (0..igrid.Ny2()).map(|i| igrid.getx2(i)).collect(); + let appl_grids: Vec> = vec![ + (0..igrid.Ntau()).map(|i| igrid.getQ2(i)).collect(), + (0..igrid.Ny1()).map(|i| igrid.getx1(i)).collect(), + (0..igrid.Ny2()).map(|i| igrid.getx2(i)).collect(), + ]; for (channel, subgrid) in subgrids .iter() .enumerate() .filter(|(_, subgrid)| !subgrid.is_empty()) { - let appl_q2_idx: Vec<_> = grid.scales().fac.calc(&subgrid.node_values(), grid.kinematics()) - .iter() - .map(|&fac| { - appl_q2 - .iter() - .position(|&x| subgrid::node_value_eq(x, fac)) - .map_or_else( - || { - if discard_non_matching_scales { - Ok(-1) - } else { - bail!( - "factorization scale muf2 = {fac} not found in APPLgrid", - ) - } - }, - |idx| Ok(idx.try_into().unwrap()), - ) - }) - .collect::>()?; - - let (x1_grid, x2_grid) = if convolutions == 2 { - ( - grid.kinematics() - .iter() - .zip(subgrid.node_values()) - .find_map(|(kin, node_values)| { - matches!(kin, &Kinematics::X(idx) if idx == 0) - .then_some(node_values) - }) - // TODO: convert this into an error - .unwrap(), + let grids = vec![ + grid.scales() + .fac + .calc(&subgrid.node_values(), grid.kinematics()) + .into_owned(), + grid.kinematics() + .iter() + .zip(subgrid.node_values()) + .find_map(|(kin, node_values)| { + matches!(kin, &Kinematics::X(idx) if idx == 0).then_some(node_values) + }) + // TODO: convert this into an error + .unwrap(), + if convolutions == 2 { grid.kinematics() .iter() .zip(subgrid.node_values()) @@ -310,78 +295,75 @@ pub fn convert_into_applgrid( .then_some(node_values) }) // TODO: convert this into an error - .unwrap(), - ) - } else { - ( - grid.kinematics() - .iter() - .zip(subgrid.node_values()) - .find_map(|(kin, node_values)| { - matches!(kin, &Kinematics::X(idx) if idx == 0) - .then_some(node_values) - }) - // TODO: convert this into an error - .unwrap(), - Vec::new(), - ) - }; - - let appl_x1_idx: Vec<_> = x1_grid - .iter() - .map(|&x1| { - appl_x1 - .iter() - .position(|&x| subgrid::node_value_eq(x, x1)) - .map_or_else( - || bail!("momentum fraction x1 = {x1} not found in APPLgrid"), - |idx| Ok(idx.try_into().unwrap()), - ) - }) - .collect::>()?; - let appl_x2_idx: Vec<_> = x2_grid - .iter() - .map(|&x2| { - appl_x2 - .iter() - .position(|&x| subgrid::node_value_eq(x, x2)) - .map_or_else( - || bail!("momentum fraction x2 = {x2} not found in APPLgrid"), - |idx| Ok(idx.try_into().unwrap()), - ) - }) - .collect::>()?; + .unwrap() + } else { + Vec::new() + }, + ]; + + let appl_idx: Vec> = izip!(&grids, &appl_grids, ["factorization scale muf2", "momentum fraction x1", "momentum fraction x2"]) + .map(|(grid, appl_grid, label)| { + grid + .iter() + .map(|&value| { + appl_grid + .iter() + .position(|&appl_value| subgrid::node_value_eq(appl_value, value)) + .map_or_else( + || { + if discard_non_matching_values { + Ok(-1) + } else { + bail!("{label} = {value} not found in APPLgrid; try exporting with `--discard-non-matching-values`") + } + }, + |idx| Ok(idx.try_into().unwrap()), + ) + }) + .collect::>() + } + ).collect::>()?; let mut weightgrid = ffi::igrid_weightgrid(igrid.pin_mut(), channel); - for (indices, value) in subgrid.indexed_iter() { + 'looop: for (indices, value) in subgrid.indexed_iter() { // TODO: here we assume that all X are consecutive starting from the second // element and are in ascending order - let iq2 = indices[0]; - let appl_q2_idx = appl_q2_idx[iq2]; - - if appl_q2_idx == -1 { - if value != 0.0 { - println!( - "WARNING: discarding non-matching scale muf2 = {}", - grid.scales() - .fac - .calc(&subgrid.node_values(), grid.kinematics())[iq2] - ); - } - - continue; - } - ffi::sparse_matrix_set( - weightgrid.as_mut(), - appl_q2_idx, - appl_x1_idx[indices[1]], + let appl_indices = [ + appl_idx[0][indices[0]], + appl_idx[1][indices[1]], if convolutions == 2 { - appl_x2_idx[indices[2]] + appl_idx[2][indices[2]] } else { 0 }, + ]; + + for (&appl_index, grid, &index, label) in izip!( + &appl_indices, + &grids, + &indices, + ["scale muf2", "momentum fraction x1", "momentum fraction x2"] + ) { + if appl_index == -1 { + if value != 0.0 { + println!( + "WARNING: discarding non-matching {label} = {} in subgrid {:?}", + grid[index], + (order, bin, channel) + ); + } + + continue 'looop; + } + } + + ffi::sparse_matrix_set( + weightgrid.as_mut(), + appl_indices[0], + appl_indices[1], + appl_indices[2], factor * value, ); } diff --git a/pineappl_cli/tests/export.rs b/pineappl_cli/tests/export.rs index bd25e451..a00068c3 100644 --- a/pineappl_cli/tests/export.rs +++ b/pineappl_cli/tests/export.rs @@ -16,7 +16,7 @@ Arguments: Options: --accuracy Relative threshold between the table and the converted grid when comparison fails [default: 1e-10] - --discard-non-matching-scales Discard non-matching scales that would otherwise lead to panics + --discard-non-matching-values Discard non-matching scales and momentum fractions that would otherwise fail the export -s, --scales Set the number of scale variations to compare with if they are available [default: 7] [possible values: 1, 3, 7, 9] --digits-abs Set the number of fractional digits shown for absolute numbers [default: 7] --digits-rel Set the number of fractional digits shown for relative numbers [default: 7] @@ -132,6 +132,94 @@ fn export_applgrid() { .stdout(predicates::str::ends_with(EXPORT_APPLGRID_STR)); } +#[cfg(feature = "applgrid")] +const EXPORT_NNLO_AK5_PTJ_STR: &str = "b APPLgrid PineAPPL rel. diff +-+------------+------------+------------ +0 6.7833493e-4 6.7833993e-4 7.3775731e-6 +"; + +#[cfg(feature = "applgrid")] +const EXPORT_NNLO_AK5_PTJ_NO_DISCARD_FAILS_STR: &str = "Error: factorization scale muf2 = 46548084.443279915 not found in APPLgrid; try exporting with `--discard-non-matching-values` +"; + +#[test] +#[cfg(feature = "applgrid")] +fn export_nnlo_ak5_ptj_discard_non_matching_values() { + let output = NamedTempFile::new("nnlo.ak5_ptj.appl").unwrap(); + + Command::cargo_bin("pineappl") + .unwrap() + .args([ + "export", + "--discard-non-matching-values", + "--accuracy=1e-5", + "../test-data/nnlo.ak5_ptj-bin63.pineappl.lz4", + output.path().to_str().unwrap(), + "NNPDF31_nlo_as_0118_luxqed", + ]) + .assert() + .success() + .stdout(predicates::str::ends_with(EXPORT_NNLO_AK5_PTJ_STR)); +} + +#[test] +#[cfg(feature = "applgrid")] +fn export_nnlo_ak5_ptj_discard_non_matching_scales_alias() { + let output = NamedTempFile::new("nnlo.ak5_ptj.appl").unwrap(); + + Command::cargo_bin("pineappl") + .unwrap() + .args([ + "export", + "--discard-non-matching-scales", + "--accuracy=1e-5", + "../test-data/nnlo.ak5_ptj-bin63.pineappl.lz4", + output.path().to_str().unwrap(), + "NNPDF31_nlo_as_0118_luxqed", + ]) + .assert() + .success() + .stdout(predicates::str::ends_with(EXPORT_NNLO_AK5_PTJ_STR)); +} + +#[test] +#[cfg(feature = "applgrid")] +fn export_nnlo_ak5_ptj_discard_non_matching_momentum_alias() { + let output = NamedTempFile::new("nnlo.ak5_ptj.appl").unwrap(); + + Command::cargo_bin("pineappl") + .unwrap() + .args([ + "export", + "--discard-non-matching-momentum", + "--accuracy=1e-5", + "../test-data/nnlo.ak5_ptj-bin63.pineappl.lz4", + output.path().to_str().unwrap(), + "NNPDF31_nlo_as_0118_luxqed", + ]) + .assert() + .success() + .stdout(predicates::str::ends_with(EXPORT_NNLO_AK5_PTJ_STR)); +} + +#[test] +#[cfg(feature = "applgrid")] +fn export_nnlo_ak5_ptj_no_discard_fails() { + let output = NamedTempFile::new("converted.appl").unwrap(); + + Command::cargo_bin("pineappl") + .unwrap() + .args([ + "export", + "../test-data/nnlo.ak5_ptj-bin63.pineappl.lz4", + output.path().to_str().unwrap(), + "NNPDF31_nlo_as_0118_luxqed", + ]) + .assert() + .failure() + .stderr(EXPORT_NNLO_AK5_PTJ_NO_DISCARD_FAILS_STR); +} + #[test] #[cfg(feature = "applgrid")] fn export_dis_applgrid() {