From 03800df256175f37efb57e5e852601579a19191f Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 14:21:40 +0300 Subject: [PATCH 01/11] Add packed_array::clear_if_empty --- pineappl/src/packed_array.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index f93c544d1..28ea72c0b 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -90,6 +90,18 @@ impl PackedArray { .map(|(indices, entry)| (indices, *entry)) } + /// Clear array, if it is empty. + /// + /// See https://github.com/NNPDF/pineappl/issues/338 and https://github.com/NNPDF/pineappl/commit/591cdcfa434ef7028dbbdc5f8da2ab83b273029c. + /// Return value indicates, whether it was cleared + pub fn clear_if_empty(&mut self) -> bool { + if self.indexed_iter().count() == 0 { + self.clear(); + return true; + } + false + } + /// TODO /// /// # Panics @@ -998,6 +1010,28 @@ mod tests { assert_eq!(array.explicit_zeros(), 0); } + #[test] + fn clear_if_empty() { + let mut array = PackedArray::new(vec![40, 50, 50]); + + array[[0,0,0]] = 1; + + assert!(!array.is_empty()); + + // setting the default value does not clear the array on it's own ... + + array[[0,0,0]] = 0; + + assert!(!array.is_empty()); + assert_eq!(array.indexed_iter().count(),0); + + // ... one needs to make that explicitly + array.clear_if_empty(); + + assert!(array.is_empty()); + assert_eq!(array.indexed_iter().count(),0); + } + #[test] fn from_ndarray() { let mut ndarray = Array3::zeros((2, 50, 50)); From a8999e88942f2992e7af1459b4813da95d3caf03 Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 14:23:34 +0300 Subject: [PATCH 02/11] Improve packed_array::clear_if_empty test --- pineappl/src/packed_array.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index 28ea72c0b..1b334faef 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -1014,20 +1014,19 @@ mod tests { fn clear_if_empty() { let mut array = PackedArray::new(vec![40, 50, 50]); + // set something, which is not nothing array[[0,0,0]] = 1; - + assert!(!array.is_empty()); + array.clear_if_empty(); assert!(!array.is_empty()); // setting the default value does not clear the array on it's own ... - array[[0,0,0]] = 0; - assert!(!array.is_empty()); assert_eq!(array.indexed_iter().count(),0); // ... one needs to make that explicitly array.clear_if_empty(); - assert!(array.is_empty()); assert_eq!(array.indexed_iter().count(),0); } From 6acdb154efd6cae0d7af43f3a799a62c1788d669 Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 14:33:21 +0300 Subject: [PATCH 03/11] Improve packed_array::clear_if_empty test further --- pineappl/src/packed_array.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index 1b334faef..f6ba69e9b 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -1017,8 +1017,9 @@ mod tests { // set something, which is not nothing array[[0,0,0]] = 1; assert!(!array.is_empty()); - array.clear_if_empty(); + let must_be_false = array.clear_if_empty(); assert!(!array.is_empty()); + assert!(!must_be_false); // setting the default value does not clear the array on it's own ... array[[0,0,0]] = 0; @@ -1026,8 +1027,9 @@ mod tests { assert_eq!(array.indexed_iter().count(),0); // ... one needs to make that explicitly - array.clear_if_empty(); + let must_be_true = array.clear_if_empty(); assert!(array.is_empty()); + assert!(must_be_true); assert_eq!(array.indexed_iter().count(),0); } From 1090f7872dcdf36bf3e100002c42e68ff09dd79a Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 14:50:03 +0300 Subject: [PATCH 04/11] Bubble repair to Grid and Subgrid --- pineappl/src/grid.rs | 9 +++++++++ pineappl/src/subgrid.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/pineappl/src/grid.rs b/pineappl/src/grid.rs index 48d2d84e4..8cf5ea365 100644 --- a/pineappl/src/grid.rs +++ b/pineappl/src/grid.rs @@ -628,6 +628,15 @@ impl Grid { .for_each(|subgrid| subgrid.scale(factor)); } + /// Repair grid. + pub fn repair(&mut self) -> bool { + let mut has_repaired = false; + self.subgrids + .iter_mut() + .for_each(|subgrid| has_repaired |= subgrid.repair()); + has_repaired + } + /// Scales each subgrid by a factor which is the product of the given values `alphas`, `alpha`, /// `logxir`, and `logxif`, each raised to the corresponding powers for each subgrid. In /// addition, every subgrid is scaled by a factor `global` independently of its order. diff --git a/pineappl/src/subgrid.rs b/pineappl/src/subgrid.rs index 799bb51ae..6fab7edf7 100644 --- a/pineappl/src/subgrid.rs +++ b/pineappl/src/subgrid.rs @@ -67,6 +67,8 @@ impl Subgrid for EmptySubgridV1 { } fn optimize_nodes(&mut self) {} + + fn repair(&mut self) -> bool { false } } /// TODO @@ -89,6 +91,8 @@ impl Subgrid for ImportSubgridV1 { self.array.is_empty() } + fn repair(&mut self) -> bool { self.array.clear_if_empty() } + fn merge_impl(&mut self, other: &SubgridEnum, transpose: Option<(usize, usize)>) { let lhs_node_values = self.node_values(); let mut rhs_node_values = other.node_values(); @@ -275,6 +279,8 @@ impl Subgrid for InterpSubgridV1 { self.array.is_empty() } + fn repair(&mut self) -> bool { self.array.clear_if_empty() } + fn shape(&self) -> &[usize] { self.array.shape() } @@ -485,6 +491,9 @@ pub trait Subgrid { /// TODO fn optimize_nodes(&mut self); + + /// Repair subgrid if necessary. + fn repair (&mut self) -> bool; } /// Type to iterate over the non-zero contents of a subgrid. The tuple contains the indices of the From f856cd871afc2ba44d5e19e658dba3f38893fcab Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 14:52:59 +0300 Subject: [PATCH 05/11] Run cargo fmt manually --- pineappl/src/packed_array.rs | 10 +++++----- pineappl/src/subgrid.rs | 14 ++++++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index f6ba69e9b..ab88a69b6 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -91,7 +91,7 @@ impl PackedArray { } /// Clear array, if it is empty. - /// + /// /// See https://github.com/NNPDF/pineappl/issues/338 and https://github.com/NNPDF/pineappl/commit/591cdcfa434ef7028dbbdc5f8da2ab83b273029c. /// Return value indicates, whether it was cleared pub fn clear_if_empty(&mut self) -> bool { @@ -1015,22 +1015,22 @@ mod tests { let mut array = PackedArray::new(vec![40, 50, 50]); // set something, which is not nothing - array[[0,0,0]] = 1; + array[[0, 0, 0]] = 1; assert!(!array.is_empty()); let must_be_false = array.clear_if_empty(); assert!(!array.is_empty()); assert!(!must_be_false); // setting the default value does not clear the array on it's own ... - array[[0,0,0]] = 0; + array[[0, 0, 0]] = 0; assert!(!array.is_empty()); - assert_eq!(array.indexed_iter().count(),0); + assert_eq!(array.indexed_iter().count(), 0); // ... one needs to make that explicitly let must_be_true = array.clear_if_empty(); assert!(array.is_empty()); assert!(must_be_true); - assert_eq!(array.indexed_iter().count(),0); + assert_eq!(array.indexed_iter().count(), 0); } #[test] diff --git a/pineappl/src/subgrid.rs b/pineappl/src/subgrid.rs index 6fab7edf7..2370f9802 100644 --- a/pineappl/src/subgrid.rs +++ b/pineappl/src/subgrid.rs @@ -68,7 +68,9 @@ impl Subgrid for EmptySubgridV1 { fn optimize_nodes(&mut self) {} - fn repair(&mut self) -> bool { false } + fn repair(&mut self) -> bool { + false + } } /// TODO @@ -91,7 +93,9 @@ impl Subgrid for ImportSubgridV1 { self.array.is_empty() } - fn repair(&mut self) -> bool { self.array.clear_if_empty() } + fn repair(&mut self) -> bool { + self.array.clear_if_empty() + } fn merge_impl(&mut self, other: &SubgridEnum, transpose: Option<(usize, usize)>) { let lhs_node_values = self.node_values(); @@ -279,7 +283,9 @@ impl Subgrid for InterpSubgridV1 { self.array.is_empty() } - fn repair(&mut self) -> bool { self.array.clear_if_empty() } + fn repair(&mut self) -> bool { + self.array.clear_if_empty() + } fn shape(&self) -> &[usize] { self.array.shape() @@ -493,7 +499,7 @@ pub trait Subgrid { fn optimize_nodes(&mut self); /// Repair subgrid if necessary. - fn repair (&mut self) -> bool; + fn repair(&mut self) -> bool; } /// Type to iterate over the non-zero contents of a subgrid. The tuple contains the indices of the From 0e4fcab57985562403499862d88345d34a284c77 Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 15:33:21 +0300 Subject: [PATCH 06/11] Add more repair tests and improve implementation --- pineappl/src/packed_array.rs | 18 ++++++++++------ pineappl/src/subgrid.rs | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index ab88a69b6..a85d5a29c 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -93,9 +93,11 @@ impl PackedArray { /// Clear array, if it is empty. /// /// See https://github.com/NNPDF/pineappl/issues/338 and https://github.com/NNPDF/pineappl/commit/591cdcfa434ef7028dbbdc5f8da2ab83b273029c. - /// Return value indicates, whether it was cleared + /// Return value indicates, whether it was cleared (now) pub fn clear_if_empty(&mut self) -> bool { - if self.indexed_iter().count() == 0 { + // 1) are there actually elements? + // 2) some of them might be default (and filtered away) + if self.lengths.len() > 0 && self.indexed_iter().count() == 0 { self.clear(); return true; } @@ -1012,14 +1014,18 @@ mod tests { #[test] fn clear_if_empty() { + // create an empty array let mut array = PackedArray::new(vec![40, 50, 50]); + let was_repaired = array.clear_if_empty(); + assert!(array.is_empty()); + assert!(!was_repaired); // set something, which is not nothing array[[0, 0, 0]] = 1; assert!(!array.is_empty()); - let must_be_false = array.clear_if_empty(); + let was_repaired = array.clear_if_empty(); assert!(!array.is_empty()); - assert!(!must_be_false); + assert!(!was_repaired); // setting the default value does not clear the array on it's own ... array[[0, 0, 0]] = 0; @@ -1027,9 +1033,9 @@ mod tests { assert_eq!(array.indexed_iter().count(), 0); // ... one needs to make that explicitly - let must_be_true = array.clear_if_empty(); + let was_repaired = array.clear_if_empty(); assert!(array.is_empty()); - assert!(must_be_true); + assert!(was_repaired); assert_eq!(array.indexed_iter().count(), 0); } diff --git a/pineappl/src/subgrid.rs b/pineappl/src/subgrid.rs index 2370f9802..dd7f7e623 100644 --- a/pineappl/src/subgrid.rs +++ b/pineappl/src/subgrid.rs @@ -712,6 +712,30 @@ mod tests { ); } + #[test] + fn import_subgrid_v1_repair() { + let x = vec![ + 0.015625, 0.03125, 0.0625, 0.125, 0.1875, 0.25, 0.375, 0.5, 0.75, 1.0, + ]; + // create empty grid + let mut grid1: SubgridEnum = ImportSubgridV1::new( + PackedArray::new(vec![1, 10, 10]), + vec![vec![0.0], x.clone(), x.clone()], + ) + .into(); + assert_eq!(grid1.node_values(), vec![vec![0.0], x.clone(), x.clone()]); + assert!(grid1.is_empty()); + // set a default value + if let SubgridEnum::ImportSubgridV1(ref mut x) = grid1 { + x.array[[0, 1, 2]] = 0.0; + } + assert!(!grid1.is_empty()); + // now fix + let has_repaired = grid1.repair(); + assert!(grid1.is_empty()); + assert!(has_repaired); + } + #[test] fn interp_subgrid_v1_fill_zero() { let interps = v0::default_interps(false, 2); @@ -799,4 +823,20 @@ mod tests { assert_eq!(subgrid.shape(), [23, 1, 1]); } + + #[test] + fn interp_subgrid_v1_repair() { + let interps = v0::default_interps(false, 2); + let mut subgrid = InterpSubgridV1::new(&interps); + // Fill something + subgrid.fill(&interps, &[1000.0, 0.5, 0.5], 1.0); + assert!(!subgrid.is_empty()); + // The multiply with 0 trick is still possible at f856cd871afc2ba44d5e19e658dba3f38893fcab + subgrid.scale(0.); + assert!(subgrid.is_empty()); + // now fix + let has_repaired = subgrid.repair(); + assert!(subgrid.is_empty()); + assert!(!has_repaired); + } } From 5f746581793de3742dd858fed34217076761d23b Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 16:05:18 +0300 Subject: [PATCH 07/11] Add Grid::repair test --- pineappl/src/grid.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pineappl/src/grid.rs b/pineappl/src/grid.rs index 8cf5ea365..c5b03a6a2 100644 --- a/pineappl/src/grid.rs +++ b/pineappl/src/grid.rs @@ -1609,6 +1609,39 @@ mod tests { assert_eq!(grid.orders().len(), 1); } + #[test] + fn grid_repair() { + use super::super::packed_array::PackedArray; + // create emtpy grid + let mut grid = Grid::new( + BinsWithFillLimits::from_fill_limits([0.0, 0.25, 0.5, 0.75, 1.0].to_vec()).unwrap(), + vec![Order::new(0, 2, 0, 0, 0)], + vec![channel![1.0 * (2, 2) + 1.0 * (4, 4)]], + PidBasis::Pdg, + vec![Conv::new(ConvType::UnpolPDF, 2212); 2], + v0::default_interps(false, 2), + vec![Kinematics::Scale(0), Kinematics::X(0), Kinematics::X(1)], + Scales { + ren: ScaleFuncForm::Scale(0), + fac: ScaleFuncForm::Scale(0), + frg: ScaleFuncForm::NoScale, + }, + ); + let was_reparied = grid.repair(); + assert!(!was_reparied); + // insert nothing + let x = vec![ + 0.015625, 0.03125, 0.0625, 0.125, 0.1875, 0.25, 0.375, 0.5, 0.75, 1.0, + ]; + let mut ar = PackedArray::new(vec![1, 10, 10]); + ar[[0, 0, 0]] = 0.; + let sg: SubgridEnum = + ImportSubgridV1::new(ar, vec![vec![0.0], x.clone(), x.clone()]).into(); + grid.subgrids_mut()[[0, 0, 0]] = sg; + let was_reparied = grid.repair(); + assert!(was_reparied); + } + #[test] fn grid_merge_orders() { let mut grid = Grid::new( From ea8c6927c89712f793e3eadf481f58889cd9c1e8 Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 8 Aug 2025 16:11:59 +0300 Subject: [PATCH 08/11] Fix typo --- pineappl/src/grid.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pineappl/src/grid.rs b/pineappl/src/grid.rs index c5b03a6a2..14e478433 100644 --- a/pineappl/src/grid.rs +++ b/pineappl/src/grid.rs @@ -1627,8 +1627,8 @@ mod tests { frg: ScaleFuncForm::NoScale, }, ); - let was_reparied = grid.repair(); - assert!(!was_reparied); + let was_repaired = grid.repair(); + assert!(!was_repaired); // insert nothing let x = vec![ 0.015625, 0.03125, 0.0625, 0.125, 0.1875, 0.25, 0.375, 0.5, 0.75, 1.0, @@ -1638,8 +1638,8 @@ mod tests { let sg: SubgridEnum = ImportSubgridV1::new(ar, vec![vec![0.0], x.clone(), x.clone()]).into(); grid.subgrids_mut()[[0, 0, 0]] = sg; - let was_reparied = grid.repair(); - assert!(was_reparied); + let was_repaired = grid.repair(); + assert!(was_repaired); } #[test] From 5533f4cda552b44ac4c3f2d4ac3ca3994c3cc923 Mon Sep 17 00:00:00 2001 From: Christopher Schwan Date: Tue, 12 Aug 2025 09:12:40 +0200 Subject: [PATCH 09/11] Add method `Grid::repair` --- CHANGELOG.md | 5 ++++ pineappl/src/grid.rs | 23 +++++++++++---- pineappl/src/packed_array.rs | 41 --------------------------- pineappl/src/subgrid.rs | 55 ------------------------------------ 4 files changed, 22 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4994e0660..df16e01a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- added `Grid::repair` to repair bugs that survived by writing bugged grids to + disk, for example + ## [1.1.0] - 08/07/2025 ### Added diff --git a/pineappl/src/grid.rs b/pineappl/src/grid.rs index 14e478433..157ed73fe 100644 --- a/pineappl/src/grid.rs +++ b/pineappl/src/grid.rs @@ -628,13 +628,24 @@ impl Grid { .for_each(|subgrid| subgrid.scale(factor)); } - /// Repair grid. + /// Repair the grid if it was written by bugged versions to disk. + /// + /// Returns `true` if this operations did anything. Currently, this scans for these problems: + /// - pub fn repair(&mut self) -> bool { - let mut has_repaired = false; - self.subgrids - .iter_mut() - .for_each(|subgrid| has_repaired |= subgrid.repair()); - has_repaired + let mut repaired = false; + + for subgrid in &mut self.subgrids { + // if the subgrid states it isn't empty and also doesn't return any elements it's + // broken; we need to fix that to avoid + if !subgrid.is_empty() && subgrid.indexed_iter().count() == 0 { + *subgrid = EmptySubgridV1.into(); + + repaired = true; + } + } + + repaired } /// Scales each subgrid by a factor which is the product of the given values `alphas`, `alpha`, diff --git a/pineappl/src/packed_array.rs b/pineappl/src/packed_array.rs index a85d5a29c..f93c544d1 100644 --- a/pineappl/src/packed_array.rs +++ b/pineappl/src/packed_array.rs @@ -90,20 +90,6 @@ impl PackedArray { .map(|(indices, entry)| (indices, *entry)) } - /// Clear array, if it is empty. - /// - /// See https://github.com/NNPDF/pineappl/issues/338 and https://github.com/NNPDF/pineappl/commit/591cdcfa434ef7028dbbdc5f8da2ab83b273029c. - /// Return value indicates, whether it was cleared (now) - pub fn clear_if_empty(&mut self) -> bool { - // 1) are there actually elements? - // 2) some of them might be default (and filtered away) - if self.lengths.len() > 0 && self.indexed_iter().count() == 0 { - self.clear(); - return true; - } - false - } - /// TODO /// /// # Panics @@ -1012,33 +998,6 @@ mod tests { assert_eq!(array.explicit_zeros(), 0); } - #[test] - fn clear_if_empty() { - // create an empty array - let mut array = PackedArray::new(vec![40, 50, 50]); - let was_repaired = array.clear_if_empty(); - assert!(array.is_empty()); - assert!(!was_repaired); - - // set something, which is not nothing - array[[0, 0, 0]] = 1; - assert!(!array.is_empty()); - let was_repaired = array.clear_if_empty(); - assert!(!array.is_empty()); - assert!(!was_repaired); - - // setting the default value does not clear the array on it's own ... - array[[0, 0, 0]] = 0; - assert!(!array.is_empty()); - assert_eq!(array.indexed_iter().count(), 0); - - // ... one needs to make that explicitly - let was_repaired = array.clear_if_empty(); - assert!(array.is_empty()); - assert!(was_repaired); - assert_eq!(array.indexed_iter().count(), 0); - } - #[test] fn from_ndarray() { let mut ndarray = Array3::zeros((2, 50, 50)); diff --git a/pineappl/src/subgrid.rs b/pineappl/src/subgrid.rs index dd7f7e623..799bb51ae 100644 --- a/pineappl/src/subgrid.rs +++ b/pineappl/src/subgrid.rs @@ -67,10 +67,6 @@ impl Subgrid for EmptySubgridV1 { } fn optimize_nodes(&mut self) {} - - fn repair(&mut self) -> bool { - false - } } /// TODO @@ -93,10 +89,6 @@ impl Subgrid for ImportSubgridV1 { self.array.is_empty() } - fn repair(&mut self) -> bool { - self.array.clear_if_empty() - } - fn merge_impl(&mut self, other: &SubgridEnum, transpose: Option<(usize, usize)>) { let lhs_node_values = self.node_values(); let mut rhs_node_values = other.node_values(); @@ -283,10 +275,6 @@ impl Subgrid for InterpSubgridV1 { self.array.is_empty() } - fn repair(&mut self) -> bool { - self.array.clear_if_empty() - } - fn shape(&self) -> &[usize] { self.array.shape() } @@ -497,9 +485,6 @@ pub trait Subgrid { /// TODO fn optimize_nodes(&mut self); - - /// Repair subgrid if necessary. - fn repair(&mut self) -> bool; } /// Type to iterate over the non-zero contents of a subgrid. The tuple contains the indices of the @@ -712,30 +697,6 @@ mod tests { ); } - #[test] - fn import_subgrid_v1_repair() { - let x = vec![ - 0.015625, 0.03125, 0.0625, 0.125, 0.1875, 0.25, 0.375, 0.5, 0.75, 1.0, - ]; - // create empty grid - let mut grid1: SubgridEnum = ImportSubgridV1::new( - PackedArray::new(vec![1, 10, 10]), - vec![vec![0.0], x.clone(), x.clone()], - ) - .into(); - assert_eq!(grid1.node_values(), vec![vec![0.0], x.clone(), x.clone()]); - assert!(grid1.is_empty()); - // set a default value - if let SubgridEnum::ImportSubgridV1(ref mut x) = grid1 { - x.array[[0, 1, 2]] = 0.0; - } - assert!(!grid1.is_empty()); - // now fix - let has_repaired = grid1.repair(); - assert!(grid1.is_empty()); - assert!(has_repaired); - } - #[test] fn interp_subgrid_v1_fill_zero() { let interps = v0::default_interps(false, 2); @@ -823,20 +784,4 @@ mod tests { assert_eq!(subgrid.shape(), [23, 1, 1]); } - - #[test] - fn interp_subgrid_v1_repair() { - let interps = v0::default_interps(false, 2); - let mut subgrid = InterpSubgridV1::new(&interps); - // Fill something - subgrid.fill(&interps, &[1000.0, 0.5, 0.5], 1.0); - assert!(!subgrid.is_empty()); - // The multiply with 0 trick is still possible at f856cd871afc2ba44d5e19e658dba3f38893fcab - subgrid.scale(0.); - assert!(subgrid.is_empty()); - // now fix - let has_repaired = subgrid.repair(); - assert!(subgrid.is_empty()); - assert!(!has_repaired); - } } From 829626de7e0d97a2cd0f03eb11c7b2bcaf6c04d9 Mon Sep 17 00:00:00 2001 From: Christopher Schwan Date: Tue, 12 Aug 2025 09:48:54 +0200 Subject: [PATCH 10/11] Add `--repair` switch to the CLI's `write` --- CHANGELOG.md | 3 ++- pineappl_cli/src/write.rs | 19 ++++++++++++++++++- pineappl_cli/tests/write.rs | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df16e01a9..414e7eeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - added `Grid::repair` to repair bugs that survived by writing bugged grids to - disk, for example + disk, for example . The CLI + offers this functionality via `pineappl write --repair` ## [1.1.0] - 08/07/2025 diff --git a/pineappl_cli/src/write.rs b/pineappl_cli/src/write.rs index 89303e1ef..57dfdea5a 100644 --- a/pineappl_cli/src/write.rs +++ b/pineappl_cli/src/write.rs @@ -41,6 +41,7 @@ enum OpsArg { MulBinNorm(f64), Optimize(bool), OptimizeFkTable(FkAssumptions), + Repair(bool), RewriteChannel((usize, Channel)), RewriteOrder((usize, Order)), RotatePidBasis(PidBasis), @@ -85,7 +86,7 @@ impl FromArgMatches for MoreArgs { }); } } - "merge_channel_factors" | "optimize" | "split_channels" | "upgrade" => { + "merge_channel_factors" | "optimize" | "repair" | "split_channels" | "upgrade" => { let arguments: Vec> = matches .remove_occurrences(&id) .unwrap() @@ -98,6 +99,7 @@ impl FromArgMatches for MoreArgs { args[index] = Some(match id.as_str() { "merge_channel_factors" => OpsArg::MergeChannelFactors(arg[0]), "optimize" => OpsArg::Optimize(arg[0]), + "repair" => OpsArg::Repair(arg[0]), "split_channels" => OpsArg::SplitChannels(arg[0]), "upgrade" => OpsArg::Upgrade(arg[0]), _ => unreachable!(), @@ -394,6 +396,17 @@ impl Args for MoreArgs { .try_map(|s| s.parse::()), ), ) + .arg( + Arg::new("repair") + .action(ArgAction::Append) + .default_missing_value("true") + .help("Repair bugs saved in the grid") + .long("repair") + .num_args(0..=1) + .require_equals(true) + .value_name("ENABLE") + .value_parser(clap::value_parser!(bool)), + ) .arg( Arg::new("rewrite_channel") .action(ArgAction::Append) @@ -583,6 +596,9 @@ impl Subcommand for Opts { // UNWRAP: this cannot fail because we only modify the normalizations .unwrap(); } + OpsArg::Repair(true) => { + grid.repair(); + } OpsArg::RewriteChannel((index, new_channel)) => { // TODO: check that `index` is valid grid.channels_mut()[*index] = new_channel.clone(); @@ -618,6 +634,7 @@ impl Subcommand for Opts { OpsArg::SplitChannels(true) => grid.split_channels(), OpsArg::Upgrade(true) => grid.upgrade(), OpsArg::MergeChannelFactors(false) + | OpsArg::Repair(false) | OpsArg::Optimize(false) | OpsArg::SplitChannels(false) | OpsArg::Upgrade(false) => {} diff --git a/pineappl_cli/tests/write.rs b/pineappl_cli/tests/write.rs index 44ac9c2e1..f15e56890 100644 --- a/pineappl_cli/tests/write.rs +++ b/pineappl_cli/tests/write.rs @@ -24,6 +24,7 @@ Options: --mul-bin-norm Multiply all bin normalizations with the given factor --optimize[=] Optimize internal data structure to minimize memory and disk usage [possible values: true, false] --optimize-fk-table Optimize internal data structure of an FkTable to minimize memory and disk usage [possible values: Nf6Ind, Nf6Sym, Nf5Ind, Nf5Sym, Nf4Ind, Nf4Sym, Nf3Ind, Nf3Sym] + --repair[=] Repair bugs saved in the grid [possible values: true, false] --rewrite-channel Rewrite the definition of the channel with index IDX --rewrite-order Rewrite the definition of the order with index IDX --rotate-pid-basis Rotate the PID basis for this grid [possible values: PDG, EVOL] @@ -876,6 +877,23 @@ fn multiple_arguments() { .stdout(MULTIPLE_ARGUMENTS_STR); } +#[test] +fn repair() { + let output = NamedTempFile::new("repaired-grid.pineappl.lz4").unwrap(); + + Command::cargo_bin("pineappl") + .unwrap() + .args([ + "write", + "--repair", + "../test-data/LHCB_WP_7TEV_opt.pineappl.lz4", + output.path().to_str().unwrap(), + ]) + .assert() + .success() + .stdout(""); +} + #[test] fn rewrite_channels() { let output = NamedTempFile::new("ckm_channels.pineappl.lz4").unwrap(); From 1b27a25fbe1389ddfbbbff5198dc2ef36ee26fcd Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Mon, 18 Aug 2025 11:35:24 +0300 Subject: [PATCH 11/11] Expose Grid::repair to Python --- CHANGELOG.md | 3 ++- pineappl_py/src/grid.rs | 5 +++++ pineappl_py/tests/test_grid.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 414e7eeb8..d6dced007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added `Grid::repair` to repair bugs that survived by writing bugged grids to disk, for example . The CLI - offers this functionality via `pineappl write --repair` + offers this functionality via `pineappl write --repair` and it can also be + accessed via Python ## [1.1.0] - 08/07/2025 diff --git a/pineappl_py/src/grid.rs b/pineappl_py/src/grid.rs index 72a59e860..8135df414 100644 --- a/pineappl_py/src/grid.rs +++ b/pineappl_py/src/grid.rs @@ -811,6 +811,11 @@ impl PyGrid { pub fn split_channels(&mut self) { self.grid.split_channels(); } + + /// Repair the grid if it was written by bugged versions to disk. + pub fn repair(&mut self) -> bool { + self.grid.repair() + } } /// Register submodule in parent. diff --git a/pineappl_py/tests/test_grid.py b/pineappl_py/tests/test_grid.py index 376431c23..02deb561b 100644 --- a/pineappl_py/tests/test_grid.py +++ b/pineappl_py/tests/test_grid.py @@ -190,6 +190,7 @@ def test_set_subgrid(self, fake_grids): node_values=[np.array([90.0]), xs], ) g.set_subgrid(0, 0, 0, subgrid.into()) + assert not g.repair() xs = np.linspace(0.1, 1, 2) Q2s = np.linspace(10, 20, 2)