Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/actions/cache-test-data/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions maintainer/download-test-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
16 changes: 8 additions & 8 deletions pineappl_cli/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f64>, usize, Vec<bool>)> {
// 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))
Expand All @@ -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<f64>, usize, Vec<bool>)> {
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"))
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
178 changes: 80 additions & 98 deletions pineappl_cli/src/export/applgrid.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -95,7 +96,7 @@ fn reconstruct_subgrid_params(grid: &Grid, order: usize, bin: usize) -> Result<V
pub fn convert_into_applgrid(
grid: &mut Grid,
output: &Path,
discard_non_matching_scales: bool,
discard_non_matching_values: bool,
) -> Result<(UniquePtr<grid>, Vec<bool>)> {
let dim = grid.bwfl().dimensions();

Expand Down Expand Up @@ -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<_>> = 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::<Result<_>>()?;

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())
Expand All @@ -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::<Result<_>>()?;
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::<Result<_>>()?;
.unwrap()
} else {
Vec::new()
},
];

let appl_idx: Vec<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::<Result<_>>()
}
).collect::<Result<_>>()?;

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,
);
}
Expand Down
Loading
Loading