diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml new file mode 100644 index 000000000..0e0825ee5 --- /dev/null +++ b/.github/workflows/msrv.yml @@ -0,0 +1,19 @@ +name: MSRV + +on: push + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run check + run: | + # extract the MSRV + MSRV=$(grep '^rust-version ' Cargo.toml | cut -d= -f2- | tr -d ' "') + # enable the MSRV + rustup default "${MSRV}" + cargo check --all-features --all-targets diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f3bbf19e..0830525be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Items without prefix refer to a global change. ## [Unreleased](https://github.com/NNPDF/eko/compare/v0.15.4...HEAD) +### Changed +- rust: Bumped `rust-version` to 1.85.0, `edition` to 2024 ([#545](https://github.com/NNPDF/eko/pull/545)) + ## [0.15.4](https://github.com/NNPDF/eko/compare/v0.15.3...v0.15.4) - 2026-06-24 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 081204a1f..42558f1d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,9 @@ authors = [ description = "Evolution Kernel Operators" readme = "README.md" categories = ["science"] -edition = "2021" +edition = "2024" keywords = ["physics"] license = "GPL-3.0-or-later" repository = "https://github.com/NNPDF/eko" -rust-version = "1.70.0" +rust-version = "1.85.0" version = "0.0.1" diff --git a/crates/dekoder/src/eko.rs b/crates/dekoder/src/eko.rs index 50112e352..3843510ce 100644 --- a/crates/dekoder/src/eko.rs +++ b/crates/dekoder/src/eko.rs @@ -1,6 +1,6 @@ //! Utilities for reading and writing an eko output. -use std::fs::remove_dir_all; use std::fs::File; +use std::fs::remove_dir_all; use std::io::BufWriter; use std::path::PathBuf; use yaml_rust2::Yaml; diff --git a/crates/dekoder/src/inventory.rs b/crates/dekoder/src/inventory.rs index d14210fa0..4824ebe3c 100644 --- a/crates/dekoder/src/inventory.rs +++ b/crates/dekoder/src/inventory.rs @@ -3,7 +3,7 @@ use lz4_flex::frame::FrameDecoder; use ndarray_npy::NpzReader; use std::collections::HashMap; use std::ffi::OsString; -use std::fs::{read_dir, read_to_string, File}; +use std::fs::{File, read_dir, read_to_string}; use std::io::{Cursor, Read}; use std::path::PathBuf; use yaml_rust2::{Yaml, YamlLoader}; @@ -35,7 +35,7 @@ impl TryFrom<&'a Yaml, Error = EKOError>> Inventory { for entry in read_dir(&self.path)? { // is header file? let entry = entry?.path(); - if !entry.extension().is_some_and(|ext| ext == HEADER_EXT) { + if entry.extension().is_none_or(|ext| ext != HEADER_EXT) { continue; } // read diff --git a/crates/dekoder/tests/test_load.rs b/crates/dekoder/tests/test_load.rs index 84de5a047..1178c8427 100644 --- a/crates/dekoder/tests/test_load.rs +++ b/crates/dekoder/tests/test_load.rs @@ -2,7 +2,7 @@ use assert_fs::prelude::*; use predicates::prelude::*; use std::path::PathBuf; -use dekoder::eko::{EvolutionPoint, EKO}; +use dekoder::eko::{EKO, EvolutionPoint}; // assert_fs will clean up the directories for us, // so for the most part we don't need worry about that. diff --git a/crates/eko/src/lib.rs b/crates/eko/src/lib.rs index 531a88674..a5b12dea6 100644 --- a/crates/eko/src/lib.rs +++ b/crates/eko/src/lib.rs @@ -80,178 +80,181 @@ fn unravel_qed_ns( /// /// # Safety /// This is the connection from Python, so we don't know what is on the other side. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn rust_quad_ker(u: f64, rargs: *mut c_void) -> f64 { - let args = *(rargs as *mut QuadArgs); + unsafe { + let args = *(rargs as *mut QuadArgs); - let is_singlet = (100 == args.mode0) - || (21 == args.mode0) - || (90 == args.mode0) - || (22 == args.mode0) - || (101 == args.mode0); + let is_singlet = (100 == args.mode0) + || (21 == args.mode0) + || (90 == args.mode0) + || (22 == args.mode0) + || (101 == args.mode0); - let is_qed_valence = (10200 == args.mode0) || (10204 == args.mode0); - // prepare Mellin stuff - let path = mellin::TalbotPath::new(u, args.logx, is_singlet); - let jac = path.jac() * path.prefactor(); - let mut c = Cache::new(path.n()); - let n3lo_ad_variation: [u8; 7] = std::slice::from_raw_parts(args.n3lo_ad_variation, 7) - .try_into() - .unwrap(); + let is_qed_valence = (10200 == args.mode0) || (10204 == args.mode0); + // prepare Mellin stuff + let path = mellin::TalbotPath::new(u, args.logx, is_singlet); + let jac = path.jac() * path.prefactor(); + let mut c = Cache::new(path.n()); + let n3lo_ad_variation: [u8; 7] = std::slice::from_raw_parts(args.n3lo_ad_variation, 7) + .try_into() + .unwrap(); - let max_buffer_size = (args.order_qcd + 1) * (args.order_qed + 1) * 16; - let out_re = std::slice::from_raw_parts_mut(args.re_gamma, max_buffer_size); - let out_im = std::slice::from_raw_parts_mut(args.im_gamma, max_buffer_size); + let max_buffer_size = (args.order_qcd + 1) * (args.order_qed + 1) * 16; + let out_re = std::slice::from_raw_parts_mut(args.re_gamma, max_buffer_size); + let out_im = std::slice::from_raw_parts_mut(args.im_gamma, max_buffer_size); - if args.is_ome { - if is_singlet { - unravel_qcd( - &ekore::operator_matrix_elements::unpolarized::spacelike::A_singlet( + if args.is_ome { + if is_singlet { + unravel_qcd( + &ekore::operator_matrix_elements::unpolarized::spacelike::A_singlet( + args.order_qcd, + &mut c, + args.nf, + args.L, + ), args.order_qcd, - &mut c, - args.nf, - args.L, - ), - args.order_qcd, - out_re, - out_im, - ); - } else { - unravel_qcd( - &ekore::operator_matrix_elements::unpolarized::spacelike::A_non_singlet( + out_re, + out_im, + ); + } else { + unravel_qcd( + &ekore::operator_matrix_elements::unpolarized::spacelike::A_non_singlet( + args.order_qcd, + &mut c, + args.nf, + args.L, + ), args.order_qcd, - &mut c, - args.nf, - args.L, - ), - args.order_qcd, - out_re, - out_im, - ); - } - } else if is_singlet { - if args.order_qed > 0 { - let gamma_singlet_qed = - ekore::anomalous_dimensions::unpolarized::spacelike::gamma_singlet_qed; - unravel_qed( - &gamma_singlet_qed( + out_re, + out_im, + ); + } + } else if is_singlet { + if args.order_qed > 0 { + let gamma_singlet_qed = + ekore::anomalous_dimensions::unpolarized::spacelike::gamma_singlet_qed; + unravel_qed( + &gamma_singlet_qed( + args.order_qcd, + args.order_qed, + &mut c, + args.nf, + n3lo_ad_variation, + ), args.order_qcd, args.order_qed, - &mut c, - args.nf, - n3lo_ad_variation, - ), - args.order_qcd, - args.order_qed, - out_re, - out_im, - ); - } else { - let gamma_singlet_qcd = match args.is_polarized { - true => ekore::anomalous_dimensions::polarized::spacelike::gamma_singlet_qcd, - false => ekore::anomalous_dimensions::unpolarized::spacelike::gamma_singlet_qcd, - }; - unravel_qcd( - &gamma_singlet_qcd( + out_re, + out_im, + ); + } else { + let gamma_singlet_qcd = match args.is_polarized { + true => ekore::anomalous_dimensions::polarized::spacelike::gamma_singlet_qcd, + false => ekore::anomalous_dimensions::unpolarized::spacelike::gamma_singlet_qcd, + }; + unravel_qcd( + &gamma_singlet_qcd( + args.order_qcd, + &mut c, + args.nf, + n3lo_ad_variation[0..4].try_into().unwrap(), + ), args.order_qcd, - &mut c, - args.nf, - n3lo_ad_variation[0..4].try_into().unwrap(), - ), - args.order_qcd, - out_re, - out_im, - ); - } - } else if args.order_qed > 0 { - if is_qed_valence { - let gamma_valence_qed = - ekore::anomalous_dimensions::unpolarized::spacelike::gamma_valence_qed; - unravel_qed( - &gamma_valence_qed( + out_re, + out_im, + ); + } + } else if args.order_qed > 0 { + if is_qed_valence { + let gamma_valence_qed = + ekore::anomalous_dimensions::unpolarized::spacelike::gamma_valence_qed; + unravel_qed( + &gamma_valence_qed( + args.order_qcd, + args.order_qed, + &mut c, + args.nf, + n3lo_ad_variation[4..7].try_into().unwrap(), + ), args.order_qcd, args.order_qed, - &mut c, - args.nf, - n3lo_ad_variation[4..7].try_into().unwrap(), - ), - args.order_qcd, - args.order_qed, - out_re, - out_im, - ); - } else { - let gamma_ns_qed = ekore::anomalous_dimensions::unpolarized::spacelike::gamma_ns_qed; - unravel_qed_ns( - &gamma_ns_qed( + out_re, + out_im, + ); + } else { + let gamma_ns_qed = + ekore::anomalous_dimensions::unpolarized::spacelike::gamma_ns_qed; + unravel_qed_ns( + &gamma_ns_qed( + args.order_qcd, + args.order_qed, + args.mode0, + &mut c, + args.nf, + n3lo_ad_variation[4..7].try_into().unwrap(), + ), args.order_qcd, args.order_qed, - args.mode0, - &mut c, - args.nf, - n3lo_ad_variation[4..7].try_into().unwrap(), - ), + out_re, + out_im, + ); + } + } else { + let gamma_ns_qcd = match args.is_polarized { + true => ekore::anomalous_dimensions::polarized::spacelike::gamma_ns_qcd, + false => ekore::anomalous_dimensions::unpolarized::spacelike::gamma_ns_qcd, + }; + let res = gamma_ns_qcd( args.order_qcd, - args.order_qed, - out_re, - out_im, + args.mode0, + &mut c, + args.nf, + n3lo_ad_variation[4..7].try_into().unwrap(), ); + for (i, el) in res.iter().take(args.order_qcd).enumerate() { + out_re[i] = el.re; + out_im[i] = el.im; + } } - } else { - let gamma_ns_qcd = match args.is_polarized { - true => ekore::anomalous_dimensions::polarized::spacelike::gamma_ns_qcd, - false => ekore::anomalous_dimensions::unpolarized::spacelike::gamma_ns_qcd, - }; - let res = gamma_ns_qcd( + + // pass on + (args.py)( + args.re_gamma as *const f64, + args.im_gamma as *const f64, + c.n().re, + c.n().im, + jac.re, + jac.im, args.order_qcd, + args.order_qed, + is_singlet, args.mode0, - &mut c, + args.mode1, args.nf, - n3lo_ad_variation[4..7].try_into().unwrap(), - ); - for (i, el) in res.iter().take(args.order_qcd).enumerate() { - out_re[i] = el.re; - out_im[i] = el.im; - } + args.is_log, + args.logx, + args.areas, + args.areas_x, + args.areas_y, + args.method_num, + args.as1, + args.as0, + args.ev_op_iterations, + args.ev_op_max_order_qcd, + args.sv_mode_num, + args.is_threshold, + args.Lsv, + // additional QED params + args.as_list, + args.as_list_len, + args.mu2_from, + args.mu2_to, + args.a_half, + args.a_half_x, + args.a_half_y, + args.alphaem_running, + ) } - - // pass on - (args.py)( - args.re_gamma as *const f64, - args.im_gamma as *const f64, - c.n().re, - c.n().im, - jac.re, - jac.im, - args.order_qcd, - args.order_qed, - is_singlet, - args.mode0, - args.mode1, - args.nf, - args.is_log, - args.logx, - args.areas, - args.areas_x, - args.areas_y, - args.method_num, - args.as1, - args.as0, - args.ev_op_iterations, - args.ev_op_max_order_qcd, - args.sv_mode_num, - args.is_threshold, - args.Lsv, - // additional QED params - args.as_list, - args.as_list_len, - args.mu2_from, - args.mu2_to, - args.a_half, - args.a_half_x, - args.a_half_y, - args.alphaem_running, - ) } /// Python callback signature @@ -384,7 +387,7 @@ pub unsafe extern "C" fn my_py( /// /// # Safety /// This is the connection from and back to Python, so we don't know what is on the other side. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn empty_args() -> QuadArgs { QuadArgs { order_qcd: 0, diff --git a/crates/ekore/src/anomalous_dimensions/polarized/spacelike.rs b/crates/ekore/src/anomalous_dimensions/polarized/spacelike.rs index 178cb7eb9..99c5dd858 100644 --- a/crates/ekore/src/anomalous_dimensions/polarized/spacelike.rs +++ b/crates/ekore/src/anomalous_dimensions/polarized/spacelike.rs @@ -2,8 +2,8 @@ use crate::constants::{MAX_ORDER_QCD, PID_NSM, PID_NSP, PID_NSV}; use crate::harmonics::cache::Cache; -use num::complex::Complex; use num::Zero; +use num::complex::Complex; pub mod as1; pub mod as2; // pub mod as3; diff --git a/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as1.rs b/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as1.rs index d9894009c..1d12335cd 100644 --- a/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as1.rs +++ b/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as1.rs @@ -55,8 +55,8 @@ mod tests { use super::*; use crate::harmonics::cache::Cache; use crate::{assert_approx_eq_cmplx, cmplx}; - use num::complex::Complex; use num::Zero; + use num::complex::Complex; const NF: u8 = 5; #[test] diff --git a/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as2.rs b/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as2.rs index 45460b64e..d29d48932 100644 --- a/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as2.rs +++ b/crates/ekore/src/anomalous_dimensions/polarized/spacelike/as2.rs @@ -132,9 +132,9 @@ mod tests { use super::*; use crate::harmonics::cache::Cache; use crate::{assert_approx_eq_cmplx, cmplx}; + use num::Zero; use num::complex::Complex; use num::traits::Pow; - use num::Zero; use std::f64::consts::PI; const NF: u8 = 5; diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs index b8984a25d..e7e92c3e6 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs @@ -5,8 +5,8 @@ use crate::constants::{ PID_NSP_U, PID_NSV, }; use crate::harmonics::cache::Cache; -use num::complex::Complex; use num::Zero; +use num::complex::Complex; pub mod aem1; pub mod aem2; pub mod as1; diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs index 2cbb04b24..74976c938 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs @@ -1,9 +1,9 @@ //! |LO| |QED|. -use num::complex::Complex; use num::Zero; +use num::complex::Complex; use crate::cmplx; -use crate::constants::{ChargeCombinations, CF, ED2, EU2, NC, TR}; +use crate::constants::{CF, ChargeCombinations, ED2, EU2, NC, TR}; use crate::harmonics::cache::Cache; use crate::anomalous_dimensions::unpolarized::spacelike::as1; @@ -92,8 +92,8 @@ pub fn gamma_valence(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { mod tests { use super::*; use crate::{assert_approx_eq_cmplx, cmplx}; - use num::complex::Complex; use num::Zero; + use num::complex::Complex; const NF: u8 = 5; #[test] diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem2.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem2.rs index 1a39ef6a6..5db88cbed 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem2.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem2.rs @@ -1,8 +1,8 @@ //! |NLO| |QED|. -use num::complex::Complex; use num::Zero; +use num::complex::Complex; -use crate::constants::{ChargeCombinations, CA, CF, ED2, EU2, NC}; +use crate::constants::{CA, CF, ChargeCombinations, ED2, EU2, NC}; use crate::harmonics::cache::{Cache, K}; use crate::anomalous_dimensions::unpolarized::spacelike::as1aem1; @@ -179,8 +179,8 @@ pub fn gamma_valence(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { mod tests { use super::*; use crate::{assert_approx_eq_cmplx, cmplx}; - use num::complex::Complex; use num::Zero; + use num::complex::Complex; #[test] fn number_conservation() { diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs index 6d25e1c51..1b9bb5c65 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs @@ -57,8 +57,8 @@ mod tests { use super::*; use crate::harmonics::cache::Cache; use crate::{assert_approx_eq_cmplx, cmplx}; - use num::complex::Complex; use num::Zero; + use num::complex::Complex; const NF: u8 = 5; #[test] diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs index ac4596edf..487f44965 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs @@ -2,8 +2,8 @@ use crate::cmplx; use num::complex::Complex; -use crate::constants::{ChargeCombinations, CA, CF, ED2, EU2, NC, TR, ZETA2, ZETA3}; -use crate::harmonics::cache::{recursive_harmonic_sum, Cache, K}; +use crate::constants::{CA, CF, ChargeCombinations, ED2, EU2, NC, TR, ZETA2, ZETA3}; +use crate::harmonics::cache::{Cache, K, recursive_harmonic_sum}; use std::f64::consts::PI; /// Compute the photon-quark anomalous dimension. @@ -256,8 +256,8 @@ pub fn gamma_valence(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { mod tests { use super::*; use crate::{assert_approx_eq_cmplx, cmplx}; - use num::complex::Complex; use num::Zero; + use num::complex::Complex; const NF: u8 = 5; #[test] diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as2.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as2.rs index f1d0266d5..df17fecee 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as2.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as2.rs @@ -219,9 +219,9 @@ mod tests { use super::*; use crate::harmonics::cache::Cache; use crate::{assert_approx_eq_cmplx, cmplx}; + use num::Zero; use num::complex::Complex; use num::traits::Pow; - use num::Zero; use std::f64::consts::PI; const NF: u8 = 5; diff --git a/crates/ekore/src/harmonics/cache.rs b/crates/ekore/src/harmonics/cache.rs index 3c1b2049f..36bdb1ff1 100644 --- a/crates/ekore/src/harmonics/cache.rs +++ b/crates/ekore/src/harmonics/cache.rs @@ -1,6 +1,6 @@ //! Cache harmonic sums for given Mellin N. -use num::{complex::Complex, Zero}; +use num::{Zero, complex::Complex}; use crate::harmonics::{g_functions, w1, w2, w3, w4, w5}; diff --git a/crates/ekore/src/harmonics/g_functions.rs b/crates/ekore/src/harmonics/g_functions.rs index 71264a977..2cec4b7f3 100644 --- a/crates/ekore/src/harmonics/g_functions.rs +++ b/crates/ekore/src/harmonics/g_functions.rs @@ -3,7 +3,7 @@ use crate::constants::ZETA2; use crate::harmonics::cache::recursive_harmonic_sum as s; use crate::harmonics::cache::{Cache, K}; -use num::{complex::Complex, Zero}; +use num::{Zero, complex::Complex}; /// Compute the Mellin transform of $\text{Li}_2(x)/(1+x)$. /// diff --git a/crates/ekore/src/harmonics/polygamma.rs b/crates/ekore/src/harmonics/polygamma.rs index c9c504adb..9263bcccf 100644 --- a/crates/ekore/src/harmonics/polygamma.rs +++ b/crates/ekore/src/harmonics/polygamma.rs @@ -1,6 +1,6 @@ //! Tools to compute [polygamma functions](https://en.wikipedia.org/wiki/Polygamma_function). -use num::{complex::Complex, Zero}; +use num::{Zero, complex::Complex}; use std::f64::consts::PI; #[allow(clippy::excessive_precision, clippy::assign_op_pattern)] diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs index fa87d0441..b20125ddd 100644 --- a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs @@ -1,8 +1,8 @@ //! The unpolarized, space-like |OME| at various couplings power. use crate::constants::MAX_ORDER_QCD; use crate::harmonics::cache::Cache; -use num::complex::Complex; use num::Zero; +use num::complex::Complex; pub mod as1; pub mod as2; diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as1.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as1.rs index 960efe43c..e3b1d5a6b 100644 --- a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as1.rs +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as1.rs @@ -1,7 +1,7 @@ //! |NLO| |QCD|. -use num::complex::Complex; use num::Zero; +use num::complex::Complex; use crate::cmplx; use crate::constants::CF; diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as2.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as2.rs index 21b5e7056..d82fcd89c 100644 --- a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as2.rs +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as2.rs @@ -1,8 +1,8 @@ //! |NNLO| |QCD|. +use num::Zero; use num::complex::Complex; use num::traits::Pow; -use num::Zero; use crate::constants::{CA, CF, TR, ZETA2, ZETA3}; use crate::harmonics::cache::{Cache, K};