diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 147c70a9..59ad2c5d 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -36,6 +36,7 @@ jobs: uses: redhat-actions/buildah-build@v2 with: image: pineappl-ci + tags: latest containerfiles: maintainer/Containerfile context: maintainer/ oci: true diff --git a/maintainer/Containerfile b/maintainer/Containerfile index b9094e64..d8e7fef1 100644 --- a/maintainer/Containerfile +++ b/maintainer/Containerfile @@ -9,6 +9,7 @@ ENV APPL_IGRID_DIR="/usr/local/src/applgrid/src" # the last version is the default Rust version used in the container # as long as we're using `persist-doctests` in the `Rust` workflow we need nightly as default +# see also https://github.com/rust-lang/rust/issues/56925 ARG RUST_V="1.80.1 1.91.1 nightly-2026-03-10" ENV CARGO_HOME="/usr/local/cargo" diff --git a/maintainer/build-container.sh b/maintainer/build-container.sh index f2a03f39..a1cf26a7 100755 --- a/maintainer/build-container.sh +++ b/maintainer/build-container.sh @@ -3,11 +3,13 @@ set -euo pipefail pkgs=( + automake build-essential curl gfortran git libssl-dev + libtool openssl pkg-config ) diff --git a/maintainer/install-cli-dependencies.sh b/maintainer/install-cli-dependencies.sh index 91e2e0f9..d4153543 100755 --- a/maintainer/install-cli-dependencies.sh +++ b/maintainer/install-cli-dependencies.sh @@ -1,7 +1,8 @@ #!/bin/bash APPLGRID_V=1.6.36 -FASTNLO_V=2.5.0-2826 +FASTNLO_V=2.6 +FASTNLO_R=69d87cf4 LHAPDF_V=6.5.4 ZLIB_V=1.3.1 @@ -15,9 +16,12 @@ urls=( "https://lhapdf.hepforge.org/downloads/?f=LHAPDF-${LHAPDF_V}.tar.gz" "https://www.zlib.net/fossils/zlib-${ZLIB_V}.tar.gz" "https://applgrid.hepforge.org/downloads/applgrid-${APPLGRID_V}.tgz" - "https://fastnlo.hepforge.org/code/v25/fastnlo_toolkit-${FASTNLO_V}.tar.gz" + # "https://fastnlo.hepforge.org/code/v25/fastnlo_toolkit-${FASTNLO_V}.tar.gz" ) +# download fastNLO from Git to support PineAPPL -> fastNLO export +git clone https://gitlab.etp.kit.edu/qcd-public/fastNLO.git + for url in "${urls[@]}"; do curl -fsSL "${url}" | tar xzf - done @@ -83,7 +87,10 @@ cp src/*.h "${APPL_IGRID_DIR}" cd .. # install fastNLO -cd "fastnlo_toolkit-${FASTNLO_V}" +cd fastNLO +git checkout "${FASTNLO_R}" +cd "v${FASTNLO_V}/toolkit" +autoreconf -fi if [[ ${static} == yes ]]; then # compile static libraries with PIC to make statically linking PineAPPL's CLI work ./configure --prefix=/usr/local/ --disable-shared --with-pic=yes diff --git a/pineappl_cli/Cargo.toml b/pineappl_cli/Cargo.toml index 65e8041e..968af90a 100644 --- a/pineappl_cli/Cargo.toml +++ b/pineappl_cli/Cargo.toml @@ -54,6 +54,6 @@ rustc-args = [ "--cfg feature=\"docs-only\"" ] [features] applgrid = ["dep:cxx", "dep:pineappl_applgrid"] evolve = ["dep:base64", "dep:either", "dep:tar", "dep:lz4_flex", "dep:ndarray-npy", "dep:serde", "dep:serde_yaml"] -fastnlo = ["dep:pineappl_fastnlo"] +fastnlo = ["dep:cxx", "dep:pineappl_fastnlo"] fktable = ["dep:flate2", "dep:tar"] static = ["lhapdf/static", "pineappl/static", "pineappl_applgrid?/static", "pineappl_fastnlo?/static"] diff --git a/pineappl_cli/src/export.rs b/pineappl_cli/src/export.rs index 8a2ad659..0cfb8376 100644 --- a/pineappl_cli/src/export.rs +++ b/pineappl_cli/src/export.rs @@ -12,6 +12,9 @@ use std::process::ExitCode; #[cfg(feature = "applgrid")] mod applgrid; +#[cfg(feature = "fastnlo")] +mod fastnlo; + #[cfg(feature = "applgrid")] fn convert_into_applgrid( output: &Path, @@ -42,23 +45,113 @@ fn convert_into_applgrid( )) } +#[cfg(feature = "fastnlo")] +fn convert_into_fastnlo( + output: &Path, + grid: &Grid, + fun_names: &ConvFuns, + scales: usize, + discard_non_matching_scales: bool, +) -> Result<(&'static str, Vec, usize, Vec)> { + use pineappl_fastnlo::ffi; + + // TODO: other cases NYI + assert_eq!(scales, 1); + + // TODO: convert this into an error? + assert_eq!(fun_names.lhapdf_names.len(), 1); + + // this creates a file, but doesn't give us an object that we can convolve with a function + let order_mask = fastnlo::convert_into_fastnlo(grid, output, discard_non_matching_scales)?; + + // so load this file, giving the right PDF set + let mut file = ffi::make_fastnlo_lhapdf_with_name_file_set( + output.to_str().unwrap(), + &fun_names.lhapdf_names[0], + // UNWRAP: this shouldn't be negative or overflow + fun_names.members[0].unwrap_or(0).try_into().unwrap(), + ); + + let mut reader = ffi::downcast_lhapdf_to_reader_mut(file.as_mut().unwrap()); + + // fastNLO does not support a fragmentation scale + let unpermuted_results: Vec<_> = helpers::SCALES_VECTOR_REN_FAC[0..scales] + .iter() + .map(|&(xir, xif, _)| { + if !reader.as_mut().SetScaleFactorsMuRMuF(xir, xif) { + return None; + } + reader.as_mut().CalcCrossSection(); + Some(ffi::GetCrossSection(reader.as_mut(), false)) + }) + .take_while(Option::is_some) + .map(Option::unwrap) + .collect(); + + assert!(matches!(unpermuted_results.len(), 1 | 3 | 7 | 9)); + + let bins = unpermuted_results[0].len(); + + let results: Vec<_> = (0..bins) + .flat_map(|bin| unpermuted_results.iter().map(move |r| r[bin])) + .collect(); + + Ok(("fastNLO", results, scales, order_mask)) +} + +#[cfg(not(feature = "fastnlo"))] +fn convert_into_fastnlo( + _: &Path, + _: &Grid, + _: &ConvFuns, + _: usize, + _: bool, +) -> Result<(&'static str, Vec, usize, Vec)> { + Err(anyhow!( + "you need to install `pineappl` with feature `fastnlo`" + )) +} + fn convert_into_grid( output: &Path, grid: &mut Grid, conv_funs: &mut [Pdf], + fun_names: &ConvFuns, scales: usize, discard_non_matching_scales: 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); + if let Some(extension) = output.extension() { + if extension == "appl" || extension == "root" { + return convert_into_applgrid( + output, + grid, + conv_funs, + scales, + discard_non_matching_scales, + ); + } else if extension == "tab" + || (extension == "gz" + && output + .with_extension("") + .extension() + .map_or(false, |ext| ext == "tab")) + { + return convert_into_fastnlo( + output, + grid, + fun_names, + scales, + discard_non_matching_scales, + ); + } } - Err(anyhow!("could not detect file format")) + Err(anyhow!( + "file extension must be one of: .appl, .root, .tab or .tab.gz" + )) } -/// Converts PineAPPL grids to APPLgrid files. +/// Converts PineAPPL grids to APPLgrid/fastNLO files. #[derive(Parser)] pub struct Opts { /// Path to the input grid. @@ -103,6 +196,7 @@ impl Subcommand for Opts { &self.output, &mut grid, &mut conv_funs, + &self.conv_funs, self.scales, self.discard_non_matching_scales, )?; diff --git a/pineappl_cli/src/export/fastnlo.rs b/pineappl_cli/src/export/fastnlo.rs new file mode 100644 index 00000000..cb7568e0 --- /dev/null +++ b/pineappl_cli/src/export/fastnlo.rs @@ -0,0 +1,106 @@ +use anyhow::{Result, bail}; +use float_cmp::assert_approx_eq; +use pineappl::boc::Order; +use pineappl::grid::Grid; +use pineappl_fastnlo::ffi; +use std::path::Path; + +pub fn convert_into_fastnlo( + grid: &Grid, + output: &Path, + _discard_non_matching_scales: bool, +) -> Result> { + let bwfl = grid.bwfl(); + let dim = bwfl.dimensions(); + + if dim > 3 { + bail!( + "grid has {} dimensions, but fastNLO only supports up to three-dimensional distributions", + dim + ); + } + + let bins = bwfl.bins(); + let left_bin_limits: Vec> = bins + .iter() + .map(|bin| bin.limits().iter().map(|&(l, _)| l).collect()) + .collect(); + let right_bin_limits: Vec> = bins + .iter() + .map(|bin| bin.limits().iter().map(|&(_, r)| r).collect()) + .collect(); + let normalizations = bwfl.normalizations(); + + let order_mask = Order::create_mask(grid.orders(), 3, 0, false); + let orders_with_mask: Vec<_> = grid + .orders() + .iter() + .cloned() + .zip(order_mask.iter().copied()) + .collect(); + let lo_alphas = orders_with_mask + .iter() + .filter_map(|&(Order { alphas, .. }, keep)| keep.then_some(alphas)) + .min() + // UNWRAP: this will fail for `Grid` with no orders, but this shouldn't happen + .unwrap(); + //let loops = orders_with_mask + // .iter() + // .filter_map(|&(Order { alphas, .. }, keep)| keep.then_some(alphas)) + // .max() + // .unwrap() + // - lo_alphas; + + let convolutions: Vec = grid.convolutions().iter().map(|conv| conv.pid()).collect(); + + // TODO: lift this restriction + assert_eq!(grid.convolutions().len(), 2); + + let channels: Vec> = grid + .channels() + .iter() + .map(|channel| { + channel + .entry() + .iter() + .map(|&(ref pids, factor)| { + assert_approx_eq!(f64, factor, 1.0, ulps = 4); + ffi::pair_int_int { + first: pids[0], + second: pids[1], + } + }) + .collect() + }) + .collect(); + + //for (fnlo_order, order) in order_mask + // .iter() + // .enumerate() + // .filter_map(|(index, keep)| keep.then_some(index)) + // .enumerate() + //{} + + ffi::make_fastnlo_create( + // UNWRAP: negative numbers and overflow should not happen + lo_alphas.try_into().unwrap(), + &left_bin_limits, + &right_bin_limits, + &normalizations, + // TODO: calculate channels for each order separately + // UNWRAP: negative numbers and overflow should not happen + channels.len().try_into().unwrap(), + // UNWRAP: negative numbers and overflow should not happen + channels.len().try_into().unwrap(), + // UNWRAP: negative numbers and overflow should not happen + channels.len().try_into().unwrap(), + &convolutions, + &channels, + &output + .to_str() + // TODO: decide what to do in case of an error + .unwrap(), + ); + + Ok(order_mask) +} diff --git a/pineappl_cli/tests/export.rs b/pineappl_cli/tests/export.rs index bd25e451..53716f4f 100644 --- a/pineappl_cli/tests/export.rs +++ b/pineappl_cli/tests/export.rs @@ -5,7 +5,7 @@ use assert_cmd::Command; #[cfg(feature = "applgrid")] use assert_fs::NamedTempFile; -const HELP_STR: &str = "Converts PineAPPL grids to APPLgrid files +const HELP_STR: &str = "Converts PineAPPL grids to APPLgrid/fastNLO files Usage: pineappl export [OPTIONS] diff --git a/pineappl_cli/tests/main.rs b/pineappl_cli/tests/main.rs index 8f6b2e34..67144d56 100644 --- a/pineappl_cli/tests/main.rs +++ b/pineappl_cli/tests/main.rs @@ -12,7 +12,7 @@ Commands: convolve Convolutes a PineAPPL grid with a PDF set diff Compares the numerical content of two grids with each other evolve Evolve a grid with an evolution kernel operator to an FK table - export Converts PineAPPL grids to APPLgrid files + export Converts PineAPPL grids to APPLgrid/fastNLO files help Display a manpage for selected subcommands import Converts APPLgrid/fastNLO/FastKernel files to PineAPPL grids merge Merges one or more PineAPPL grids together diff --git a/pineappl_fastnlo/src/fastnlo.cpp b/pineappl_fastnlo/src/fastnlo.cpp index 7cda9649..73154afd 100644 --- a/pineappl_fastnlo/src/fastnlo.cpp +++ b/pineappl_fastnlo/src/fastnlo.cpp @@ -1,6 +1,7 @@ #include "pineappl_fastnlo/src/fastnlo.hpp" #include +#include #include #include @@ -65,6 +66,174 @@ std::unique_ptr make_fastnlo_lhapdf_with_name_file_set( return std::unique_ptr(new fastNLOLHAPDF(arg0, arg1, PDFSet)); } +void make_fastnlo_create( + int alphas_lo, + rust::Slice const> left_bin_limits, + rust::Slice const> right_bin_limits, + rust::Slice normalizations, + int lo_channels, + int nlo_channels, + int nnlo_channels, + rust::Slice convolutions, + rust::Slice const> channels, + rust::Str filename +) { + assert(left_bin_limits.size() == right_bin_limits.size()); + auto const bins = left_bin_limits.size(); + assert(bins == normalizations.size()); + assert(bins > 0); + auto const dimensions = left_bin_limits.at(0).size(); + assert(dimensions > 0); + assert(convolutions.size() <= 2); + assert(convolutions.size() >= 1); + + std::vector> bin_limits(dimensions); + + // TODO: check if this is the right ordering + for (std::size_t i = 0; i != dimensions; ++i) { + assert(left_bin_limits.at(i).size() == dimensions); + assert(right_bin_limits.at(i).size() == dimensions); + + //bin_limits.at(i).resize(2 * limits); + + //for (std::size_t j = 0; j != limits; ++j) { + // bin_limits.at(i).at(2 * j + 0) = left_bin_limits.at(j).at(i); + // bin_limits.at(i).at(2 * j + 1) = right_bin_limits.at(j).at(i); + //} + bin_limits.at(i).resize(bins + 1); + bin_limits.at(i).at(0) = left_bin_limits.at(0).front(); + + for (std::size_t j = 0; j != bins; ++j) { + bin_limits.at(i).at(j + 1) = right_bin_limits.at(j).at(i); + } + } + + fastNLO::GeneratorConstants gconst; + // TODO: add PineAPPL's version number + gconst.Name = "PineAPPL-fastNLO interface"; + + fastNLO::ProcessConstants pconst; + pconst.LeadingOrder = alphas_lo; + pconst.NPDF = convolutions.size(); + pconst.NSubProcessesLO = lo_channels; + pconst.NSubProcessesNLO = nlo_channels; + pconst.NSubProcessesNNLO = nnlo_channels; + + if (convolutions.size() == 1) { + pconst.IPDFdef1 = 2; + } else { + pconst.IPDFdef1 = 3; + } + + // TODO: is this the correct value to set the linear combinations ourselves? + pconst.IPDFdef2 = 0; + pconst.IPDFdef3LO = 2; + pconst.IPDFdef3NLO = 0; + pconst.IPDFdef3NNLO = 0; + + if (convolutions.size() == 1) { + // TODO: not yet implemented + assert(false); + } else { + pconst.NPDFDim = 2; + } + + std::vector>> linear_combinations(channels.size()); + for (std::size_t i = 0; i != channels.size(); ++i) { + std::vector> entries(channels.at(i).size()); + for (std::size_t j = 0; j != channels.at(i).size(); ++j) { + auto const first = channels.at(i).at(j).first; + auto const second = channels.at(i).at(j).second; + + // TODO: has the gluon id `0`? + assert( first >= -6 && first <= 6 ); + assert( second >= -6 && first <= 6 ); + + entries.at(j) = std::make_pair(first, second); + } + linear_combinations.at(i) = entries; + } + pconst.PDFCoeffLO = linear_combinations; + + fastNLO::ScenarioConstants sconst; + sconst.DifferentialDimension = dimensions; + sconst.DimensionIsDifferential = std::vector(dimensions, 0); + sconst.CalculateBinSize = false; + sconst.BinSize = std::vector(normalizations.begin(), normalizations.end()); + + switch (sconst.DifferentialDimension) { + case 1: + sconst.SingleDifferentialBinning = bin_limits.at(0); + break; + + case 2: + sconst.DoubleDifferentialBinning = bin_limits; + break; + + case 3: + sconst.TripleDifferentialBinning = bin_limits; + break; + + default: + // ASSERT: there are no or too many dimensions, which fastNLO doesn't support + assert(false); + } + sconst.FlexibleScaleTable = true; + + if (convolutions.size() == 1) { + sconst.PDF1 = convolutions.at(0); + // TODO: do we leave PDF2 unchanged (set to 'proton') for DIS? + } else { + sconst.PDF1 = convolutions.at(0); + sconst.PDF2 = convolutions.at(1); + } + + sconst.ReadBinningFromSteering = true; + sconst.IgnoreWarmupBinningCheck = true; + sconst.X_NNodeCounting = "NodesPerBin"; + sconst.Mu1_NNodeCounting = "NodesPerBin"; + sconst.Mu2_NNodeCounting = "NodesPerBin"; + sconst.OutputFilename = static_cast (filename); + + // TODO: implement compression. Since fastNLO adds '.gz' to the filename for us, we must always + // omit the suffix + assert( sconst.OutputFilename.substr(sconst.OutputFilename.size() - 3, 3) != ".gz" ); + sconst.OutputCompression = false; + + fastNLO::WarmupConstants wconst(sconst); + + // leave `wconst.Binning` empty, otherwise it's being checked and I don't know what it's + // supposed to contain + // wconst.Binning.resize(bins, std::vector{}); + + // these values are probably irrelevant but must nevertheless given + wconst.Values.resize(bins, std::vector{ + // bin index + 0, + // x-min + 2e-7, + // x-max + 1.0, + // scale1-min + 10.0, + // scale1-max + 100.0, + // scale2-min + 10.0, + // scale2-max + 100.0 + }); + for (std::size_t i = 0; i != wconst.Values.size(); ++i) { + wconst.Values.at(i).at(0) = static_cast (i); + } + // wconst.headerValues = ; + + fastNLOCreate table(gconst, pconst, sconst, wconst); + + table.SetNumberOfEvents(1.0); + table.WriteTable(); +} + rust::Vec GetCrossSection(fastNLOReader& reader, bool lNorm) { return std_vector_to_rust_vec(reader.GetCrossSection(lNorm)); diff --git a/pineappl_fastnlo/src/fastnlo.hpp b/pineappl_fastnlo/src/fastnlo.hpp index 832e8809..0f2cc638 100644 --- a/pineappl_fastnlo/src/fastnlo.hpp +++ b/pineappl_fastnlo/src/fastnlo.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,19 @@ std::unique_ptr make_fastnlo_lhapdf_with_name_file_set( int PDFSet ); +void make_fastnlo_create( + int alphas_lo, + rust::Slice const> left_bin_limits, + rust::Slice const> right_bin_limits, + rust::Slice normalizations, + int lo_channels, + int nlo_channels, + int nnlo_channels, + rust::Slice convolutions, + rust::Slice const> channels, + rust::Str filename +); + rust::Vec CalcPDFLinearCombination( fastNLOPDFLinearCombinations const& lc, fastNLOCoeffAddBase const& base, diff --git a/pineappl_fastnlo/src/lib.rs b/pineappl_fastnlo/src/lib.rs index 27bca84a..89122e03 100644 --- a/pineappl_fastnlo/src/lib.rs +++ b/pineappl_fastnlo/src/lib.rs @@ -189,6 +189,19 @@ pub mod ffi { _: &str, _: i32, ) -> UniquePtr; + + fn make_fastnlo_create( + alphas_lo: i32, + left_bin_limits: &[Vec], + right_bin_limits: &[Vec], + normalizations: &[f64], + lo_channels: i32, + nlo_channels: i32, + nnlo_channels: i32, + convolutions: &[i32], + channels: &[Vec], + filename: &str, + ); } }