diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11ee1e506..8b88dfbb8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: # Run the formatter. - id: ruff-format - repo: https://github.com/PyCQA/docformatter - rev: v1.7.5 + rev: "master" hooks: - id: docformatter additional_dependencies: [tomli] diff --git a/crates/eko/src/lib.rs b/crates/eko/src/lib.rs index a6c5fa9ae..ea42fb1e9 100644 --- a/crates/eko/src/lib.rs +++ b/crates/eko/src/lib.rs @@ -14,6 +14,7 @@ struct RawCmplx { } /// Map tensors to c-ordered list +/// (res is a vector with dim order_qcd filled with DIMxDIM matrices) fn unravel(res: Vec<[[Complex; DIM]; DIM]>, order_qcd: usize) -> RawCmplx { let mut target = RawCmplx { re: Vec::::new(), @@ -30,6 +31,46 @@ fn unravel(res: Vec<[[Complex; DIM]; DIM]>, order_qcd: us target } +/// Map tensors to c-ordered list in the QED singlet and valence case +/// (res is a matrix with dim order_qcd x order_qed filled with DIMxDIM matrices) +fn unravel_qed( + res: Vec; DIM]; DIM]>>, + order_qcd: usize, + order_qed: usize, +) -> RawCmplx { + let mut target = RawCmplx { + re: Vec::::new(), + im: Vec::::new(), + }; + for obj_ in res.iter().take(order_qcd) { + for obj in obj_.iter().take(order_qed) { + for col in obj.iter().take(DIM) { + for el in col.iter().take(DIM) { + target.re.push(el.re); + target.im.push(el.im); + } + } + } + } + target +} + +/// Map tensors to c-ordered list in the QED non-singlet case +/// (res is a matrix with dim order_qcd x order_qed filled with complex numbers) +fn unravel_qed_ns(res: Vec>>, order_qcd: usize, order_qed: usize) -> RawCmplx { + let mut target = RawCmplx { + re: Vec::::new(), + im: Vec::::new(), + }; + for col in res.iter().take(order_qcd) { + for el in col.iter().take(order_qed) { + target.re.push(el.re); + target.im.push(el.im); + } + } + target +} + /// QCD intergration kernel inside quad. /// /// # Safety @@ -37,7 +78,14 @@ fn unravel(res: Vec<[[Complex; DIM]; DIM]>, order_qcd: us #[no_mangle] pub unsafe extern "C" fn rust_quad_ker_qcd(u: f64, rargs: *mut c_void) -> f64 { let args = *(rargs as *mut QuadQCDargs); - let is_singlet = (100 == args.mode0) || (21 == args.mode0) || (90 == 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(); @@ -70,14 +118,41 @@ pub unsafe extern "C" fn rust_quad_ker_qcd(u: f64, rargs: *mut c_void) -> f64 { ); } } else if is_singlet { - 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, - }; - raw = unravel( - gamma_singlet_qcd(args.order_qcd, &mut c, args.nf), - args.order_qcd, - ); + if args.order_qed > 0 { + let gamma_singlet_qed = + ekore::anomalous_dimensions::unpolarized::spacelike::gamma_singlet_qed; + raw = unravel_qed( + gamma_singlet_qed(args.order_qcd, args.order_qed, &mut c, args.nf), + args.order_qcd, + args.order_qed, + ); + } 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, + }; + raw = unravel( + gamma_singlet_qcd(args.order_qcd, &mut c, args.nf), + args.order_qcd, + ); + } + } else if args.order_qed > 0 { + if is_qed_valence { + let gamma_valence_qed = + ekore::anomalous_dimensions::unpolarized::spacelike::gamma_valence_qed; + raw = unravel_qed( + gamma_valence_qed(args.order_qcd, args.order_qed, &mut c, args.nf), + args.order_qcd, + args.order_qed, + ); + } else { + let gamma_ns_qed = ekore::anomalous_dimensions::unpolarized::spacelike::gamma_ns_qed; + raw = unravel_qed_ns( + gamma_ns_qed(args.order_qcd, args.order_qed, args.mode0, &mut c, args.nf), + args.order_qcd, + args.order_qed, + ); + } } else { // we can not do 1D let gamma_ns_qcd = match args.is_polarized { @@ -100,6 +175,7 @@ pub unsafe extern "C" fn rust_quad_ker_qcd(u: f64, rargs: *mut c_void) -> f64 { jac.re, jac.im, args.order_qcd, + args.order_qed, is_singlet, args.mode0, args.mode1, @@ -118,6 +194,15 @@ pub unsafe extern "C" fn rust_quad_ker_qcd(u: f64, rargs: *mut c_void) -> f64 { 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, ) } @@ -130,6 +215,7 @@ type PyQuadKerQCDT = unsafe extern "C" fn( f64, f64, usize, + usize, bool, u16, u16, @@ -148,6 +234,14 @@ type PyQuadKerQCDT = unsafe extern "C" fn( u8, bool, f64, + *const f64, + u8, + f64, + f64, + *const f64, + u8, + u8, + bool, ) -> f64; /// Additional integration parameters @@ -156,6 +250,7 @@ type PyQuadKerQCDT = unsafe extern "C" fn( #[derive(Clone, Copy)] pub struct QuadQCDargs { pub order_qcd: usize, + pub order_qed: usize, pub mode0: u16, pub mode1: u16, pub is_polarized: bool, @@ -177,6 +272,15 @@ pub struct QuadQCDargs { pub is_threshold: bool, pub is_ome: bool, pub Lsv: f64, + // additional param required for QED + pub as_list: *const f64, + pub as_list_len: u8, + pub mu2_from: f64, + pub mu2_to: f64, + pub a_half: *const f64, + pub a_half_x: u8, + pub a_half_y: u8, + pub alphaem_running: bool, } /// Empty placeholder function for python callback. @@ -191,6 +295,7 @@ pub unsafe extern "C" fn my_py( _re_jac: f64, _im_jac: f64, _order_qcd: usize, + _order_qed: usize, _is_singlet: bool, _mode0: u16, _mode1: u16, @@ -209,6 +314,14 @@ pub unsafe extern "C" fn my_py( _sv_mode_num: u8, _is_threshold: bool, _lsv: f64, + _as_list: *const f64, + _as_list_len: u8, + _mu2_from: f64, + _mu2_to: f64, + _a_half: *const f64, + _a_half_x: u8, + _a_half_y: u8, + _alphaem_running: bool, ) -> f64 { 0. } @@ -224,6 +337,7 @@ pub unsafe extern "C" fn my_py( pub unsafe extern "C" fn empty_qcd_args() -> QuadQCDargs { QuadQCDargs { order_qcd: 0, + order_qed: 0, mode0: 0, mode1: 0, is_polarized: false, @@ -245,5 +359,13 @@ pub unsafe extern "C" fn empty_qcd_args() -> QuadQCDargs { is_threshold: false, is_ome: false, Lsv: 0., + as_list: [].as_ptr(), + as_list_len: 0, + mu2_from: 0., + mu2_to: 0., + a_half: [].as_ptr(), + a_half_x: 0, + a_half_y: 0, + alphaem_running: false, } } diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs index dec73bf04..38479b89f 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike.rs @@ -1,10 +1,14 @@ //! The unpolarized, space-like anomalous dimensions at various couplings power. -use crate::constants::{PID_NSM, PID_NSP, PID_NSV}; +use crate::constants::{ + ed2, eu2, PID_NSM, PID_NSM_ED2, PID_NSM_EU2, PID_NSP, PID_NSP_ED2, PID_NSP_EU2, PID_NSV, +}; use crate::harmonics::cache::Cache; use num::complex::Complex; use num::Zero; +pub mod aem1; pub mod as1; +pub mod as1aem1; pub mod as2; pub mod as3; @@ -55,3 +59,168 @@ pub fn gamma_singlet_qcd(order_qcd: usize, c: &mut Cache, nf: u8) -> Vec<[[Compl } gamma_S } + +/// Compute the tower of the QED non-singlet anomalous dimensions. +pub fn gamma_ns_qed( + order_qcd: usize, + order_qed: usize, + mode: u16, + c: &mut Cache, + nf: u8, +) -> Vec>> { + let col = vec![Complex::::zero(); order_qcd + 1]; + let mut gamma_ns = vec![col; order_qed + 1]; + gamma_ns[1][0] = as1::gamma_ns(c, nf); + gamma_ns[0][1] = choose_ns_as_aem1(mode, c, nf); + gamma_ns[1][1] = choose_ns_as_as1aem1(mode, c, nf); + gamma_ns +} + +pub fn choose_ns_as_aem1(mode: u16, c: &mut Cache, nf: u8) -> Complex { + match mode { + PID_NSP_EU2 | PID_NSM_EU2 => eu2 * aem1::gamma_ns(c, nf), + PID_NSP_ED2 | PID_NSM_ED2 => ed2 * aem1::gamma_ns(c, nf), + _ => panic!("Unkown non-singlet sector element"), + } +} + +pub fn choose_ns_as_as1aem1(mode: u16, c: &mut Cache, nf: u8) -> Complex { + match mode { + PID_NSP_EU2 => eu2 * as1aem1::gamma_nsp(c, nf), + PID_NSP_ED2 => ed2 * as1aem1::gamma_nsp(c, nf), + PID_NSM_EU2 => eu2 * as1aem1::gamma_nsm(c, nf), + PID_NSM_ED2 => ed2 * as1aem1::gamma_nsm(c, nf), + _ => panic!("Unkown non-singlet sector element"), + } +} + +pub fn gamma_singlet_qed( + order_qcd: usize, + order_qed: usize, + c: &mut Cache, + nf: u8, +) -> Vec; 4]; 4]>> { + let col = vec![ + [[ + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + Complex::::zero() + ]; 4]; + order_qcd + 1 + ]; + + let mut gamma_s = vec![col; order_qed + 1]; + + gamma_s[1][0] = as1::gamma_singlet_qed(c, nf); + gamma_s[0][1] = aem1::gamma_singlet(c, nf); + gamma_s[1][1] = as1aem1::gamma_singlet(c, nf); + gamma_s +} + +/// Compute the grid of the QED valence anomalous dimensions matrices +pub fn gamma_valence_qed( + order_qcd: usize, + order_qed: usize, + c: &mut Cache, + nf: u8, +) -> Vec; 2]; 2]>> { + let col = vec![[[Complex::::zero(), Complex::::zero(),]; 2]; order_qcd + 1]; + + let mut gamma_v = vec![col; order_qed + 1]; + gamma_v[1][0] = as1::gamma_valence_qed(c, nf); + gamma_v[0][1] = aem1::gamma_valence(c, nf); + gamma_v[1][1] = as1aem1::gamma_valence(c, nf); + gamma_v +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_approx_eq_cmplx, cmplx}; + use num::complex::Complex; + use num::Zero; + + #[test] + fn gamma_ns() { + const NF: u8 = 3; + const N: Complex = cmplx!(1., 0.); + let mut c = Cache::new(N); + assert_approx_eq_cmplx!( + f64, + gamma_ns_qcd(3, PID_NSP, &mut c, NF)[0], + cmplx!(0., 0.), + epsilon = 1e-14 + ); + + for i in [0, 1] { + assert_approx_eq_cmplx!( + f64, + gamma_ns_qcd(2, PID_NSM, &mut c, NF)[i], + cmplx!(0., 0.), + epsilon = 2e-6 + ); + } + + for i in 0..3 { + assert_approx_eq_cmplx!( + f64, + gamma_ns_qcd(3, PID_NSM, &mut c, NF)[i], + cmplx!(0., 0.), + epsilon = 2e-4 + ); + } + + for i in 0..3 { + assert_approx_eq_cmplx!( + f64, + gamma_ns_qcd(3, PID_NSV, &mut c, NF)[i], + cmplx!(0., 0.), + epsilon = 8e-4 + ); + } + } + + #[test] + fn test_gamma_ns_qed() { + const NF: u8 = 3; + const N: Complex = cmplx!(1., 0.); + let mut c = Cache::new(N); + + for i in [0, 1] { + for j in [0, 1] { + assert_approx_eq_cmplx!( + f64, + gamma_ns_qed(1, 1, PID_NSM_EU2, &mut c, NF)[i][j], + cmplx!(0., 0.), + epsilon = 1e-5 + ); + } + } + + for i in [0, 1] { + for j in [0, 1] { + assert_approx_eq_cmplx!( + f64, + gamma_ns_qed(1, 1, PID_NSM_ED2, &mut c, NF)[i][j], + cmplx!(0., 0.), + epsilon = 1e-5 + ); + } + } + + assert_approx_eq_cmplx!( + f64, + gamma_ns_qed(1, 1, PID_NSP_EU2, &mut c, NF)[0][1], + cmplx!(0., 0.), + epsilon = 1e-5 + ); + + assert_approx_eq_cmplx!( + f64, + gamma_ns_qed(1, 1, PID_NSP_ED2, &mut c, NF)[0][1], + cmplx!(0., 0.), + epsilon = 1e-5 + ); + } +} diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs new file mode 100644 index 000000000..bc70ed97f --- /dev/null +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/aem1.rs @@ -0,0 +1,130 @@ +//! |LO| |QED|. +use num::complex::Complex; +use num::Zero; + +use crate::constants::{ed2, eu2, uplike_flavors, ChargeCombinations, CF, NC, TR}; +use crate::harmonics::cache::Cache; + +use crate::anomalous_dimensions::unpolarized::spacelike::as1; + +/// Compute the leading-order photon-quark anomalous dimension. +/// +/// Implements Eq. (2.5) of +pub fn gamma_phq(c: &mut Cache, nf: u8) -> Complex { + as1::gamma_gq(c, nf) / CF +} + +/// Compute the leading-order quark-photon anomalous dimension. +/// +/// Implements Eq. (2.5) of +pub fn gamma_qph(c: &mut Cache, nf: u8) -> Complex { + as1::gamma_qg(c, nf) / TR * (NC as f64) +} + +/// Compute the leading-order photon-photon anomalous dimension. +/// +/// Implements Eq. (2.5) of +pub fn gamma_phph(_c: &mut Cache, nf: u8) -> Complex { + let nu = uplike_flavors(nf); + let nd = nf - nu; + (4.0 / 3.0 * (NC as f64) * ((nu as f64) * eu2 + (nd as f64) * ed2)).into() +} + +/// Compute the leading-order non-singlet QED anomalous dimension +/// +/// Implements Eq. (2.5) of +pub fn gamma_ns(c: &mut Cache, nf: u8) -> Complex { + as1::gamma_ns(c, nf) / CF +} + +/// Compute the leading-order singlet QED anomalous dimension matrix +/// +/// Implements Eq. (2.5) of +pub fn gamma_singlet(c: &mut Cache, nf: u8) -> [[Complex; 4]; 4] { + let cc = ChargeCombinations { nf }; + + let gamma_ph_q = gamma_phq(c, nf); + let gamma_q_ph = gamma_qph(c, nf); + let gamma_nonsinglet = gamma_ns(c, nf); + + [ + [ + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + ], + [ + Complex::::zero(), + gamma_phph(c, nf), + cc.e2avg() * gamma_ph_q, + cc.vue2m() * gamma_ph_q, + ], + [ + Complex::::zero(), + cc.e2avg() * gamma_q_ph, + cc.e2avg() * gamma_nonsinglet, + cc.vue2m() * gamma_nonsinglet, + ], + [ + Complex::::zero(), + cc.vde2m() * gamma_q_ph, + cc.vde2m() * gamma_nonsinglet, + cc.e2delta() * gamma_nonsinglet, + ], + ] +} + +/// Compute the leading-order valence QED anomalous dimension matrix +/// +/// Implements Eq. (2.5) of +pub fn gamma_valence(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { + let cc = ChargeCombinations { nf }; + + [ + [cc.e2avg() * gamma_ns(c, nf), cc.vue2m() * gamma_ns(c, nf)], + [cc.vde2m() * gamma_ns(c, nf), cc.e2delta() * gamma_ns(c, nf)], + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_approx_eq_cmplx, cmplx}; + use num::complex::Complex; + use num::Zero; + const NF: u8 = 5; + + #[test] + fn number_conservation() { + const N: Complex = cmplx!(1., 0.); + let mut c = Cache::new(N); + let me = gamma_ns(&mut c, NF); + assert_approx_eq_cmplx!(f64, me, Complex::zero(), epsilon = 1e-12); + } + + #[test] + fn quark_momentum_conservation() { + const N: Complex = cmplx!(2., 0.); + let mut c = Cache::new(N); + let me = gamma_ns(&mut c, NF) + gamma_phq(&mut c, NF); + assert_approx_eq_cmplx!(f64, me, Complex::zero(), epsilon = 1e-12); + } + + #[test] + fn photon_momentum_conservation() { + const N: Complex = cmplx!(2., 0.); + let mut c = Cache::new(N); + + for nf in 2..7 { + let nu = uplike_flavors(nf); + let nd = nf - nu; + assert_approx_eq_cmplx!( + f64, + eu2 * gamma_qph(&mut c, nu) + ed2 * gamma_qph(&mut c, nd) + gamma_phph(&mut c, nf), + cmplx!(0., 0.), + epsilon = 2e-6 + ); + } + } +} diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs index 6d25e1c51..bd2c67045 100644 --- a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1.rs @@ -1,6 +1,7 @@ //! |LO| |QCD|. use num::complex::Complex; +use num::Zero; use crate::constants::{CA, CF, TR}; use crate::harmonics::cache::{Cache, K}; @@ -52,6 +53,46 @@ pub fn gamma_singlet(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { ] } +/// Compute the leading-order singlet anomalous dimension matrix +/// for the unified evolution basis. +pub fn gamma_singlet_qed(c: &mut Cache, nf: u8) -> [[Complex; 4]; 4] { + [ + [ + gamma_gg(c, nf), + Complex::::zero(), + gamma_gq(c, nf), + Complex::::zero(), + ], + [ + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + ], + [ + gamma_qg(c, nf), + Complex::::zero(), + gamma_ns(c, nf), + Complex::::zero(), + ], + [ + Complex::::zero(), + Complex::::zero(), + Complex::::zero(), + gamma_ns(c, nf), + ], + ] +} + +/// Compute the leading-order valence anomalous dimension matrix +/// for the unified evolution basis. +pub fn gamma_valence_qed(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { + [ + [gamma_ns(c, nf), Complex::::zero()], + [Complex::::zero(), gamma_ns(c, nf)], + ] +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs new file mode 100644 index 000000000..546acca85 --- /dev/null +++ b/crates/ekore/src/anomalous_dimensions/unpolarized/spacelike/as1aem1.rs @@ -0,0 +1,305 @@ +//! The $O(a_s^1a_{em}^1)$ Altarelli-Parisi splitting kernels. +use crate::cmplx; +use num::complex::Complex; + +use crate::constants::{ + ed2, eu2, uplike_flavors, ChargeCombinations, CA, CF, NC, TR, ZETA2, ZETA3, +}; +use crate::harmonics::cache::{Cache, K}; + +/// Compute the $O(a_s^1a_{em}^1)$ photon-quark anomalous dimension. +/// +/// Implements Eq. (36) of +pub fn gamma_phq(c: &mut Cache, _nf: u8) -> Complex { + let N = c.n(); + let S1 = c.get(K::S1); + let S2 = c.get(K::S2); + + #[rustfmt::skip] + let tmp_const = + 2.0 + * ( + -4.0 + - 12.0 * N + - N.powu(2) + + 28.0 * N.powu(3) + + 43.0 * N.powu(4) + + 30.0 * N.powu(5) + + 12.0 * N.powu(6) + ) / ((-1.0 + N) * N.powu(3) * (1.0 + N).powu(3)); + + #[rustfmt::skip] + let tmp_S1 = -4.0 + * (10.0 + 27.0 * N + 25.0 * N.powu(2) + 13.0 * N.powu(3) + 5.0 * N.powu(4)) + / ((-1.0 + N) * N * (1.0 + N).powu(3)); + + let tmp_S12 = 4.0 * (2.0 + N + N.powu(2)) / ((-1.0 + N) * N * (1.0 + N)); + let tmp_S2 = 4.0 * (2.0 + N + N.powu(2)) / ((-1.0 + N) * N * (1.0 + N)); + + CF * (tmp_const + tmp_S1 * S1 + tmp_S12 * S1.powu(2) + tmp_S2 * S2) +} + +/// Compute the $O(a_s^1a_{em}^1)$ quark-photon anomalous dimension. +/// +/// Implements Eq. (26) of +pub fn gamma_qph(c: &mut Cache, nf: u8) -> Complex { + let N = c.n(); + let S1 = c.get(K::S1); + let S2 = c.get(K::S2); + + #[rustfmt::skip] + let tmp_const = -2.0 + * (4.0 + + 8.0 * N + + 25.0 * N.powu(2) + + 51.0 * N.powu(3) + + 36.0 * N.powu(4) + + 15.0 * N.powu(5) + + 5.0 * N.powu(6)) + / (N.powu(3) * (1.0 + N).powu(3) * (2.0 + N)); + + let tmp_S1 = 8.0 / N.powu(2); + let tmp_S12 = -4.0 * (2.0 + N + N.powu(2)) / (N * (1.0 + N) * (2.0 + N)); + let tmp_S2 = 4.0 * (2.0 + N + N.powu(2)) / (N * (1.0 + N) * (2.0 + N)); + + 2.0 * (nf as f64) * CA * CF * (tmp_const + tmp_S1 * S1 + tmp_S12 * S1.powu(2) + tmp_S2 * S2) +} + +/// Compute the $O(a_s^1a_{em}^1)$ gluon-photon anomalous dimension. +/// +/// Implements Eq. (27) of +pub fn gamma_gph(c: &mut Cache, _nf: u8) -> Complex { + let N = c.n(); + CF * CA + * (8.0 * (-4.0 + N * (-4.0 + N * (-5.0 + N * (-10.0 + N + 2.0 * N.powu(2) * (2.0 + N)))))) + / (N.powu(3) * (1.0 + N).powu(3) * (-2.0 + N + N.powu(2))) +} + +/// Compute the $O(a_s^1a_{em}^1)$ photon-gluon anomalous dimension. +/// +/// Implements Eq. (30) of +pub fn gamma_phg(c: &mut Cache, nf: u8) -> Complex { + TR / CF / CA * (NC as f64) * gamma_gph(c, nf) +} + +/// Compute the $O(a_s^1a_{em}^1)$ quark-gluon singlet anomalous dimension. +/// +/// Implements Eq. (29) of +pub fn gamma_qg(c: &mut Cache, nf: u8) -> Complex { + TR / CF / CA * (NC as f64) * gamma_qph(c, nf) +} + +/// Compute the $O(a_s^1a_{em}^1)$ gluon-quark singlet anomalous dimension. +/// +/// Implements Eq. (35) of +pub fn gamma_gq(c: &mut Cache, nf: u8) -> Complex { + gamma_phq(c, nf) +} + +/// Compute the $O(a_s^1a_{em}^1)$ photon-photon singlet anomalous dimension. +/// +/// Implements Eq. (28) of +pub fn gamma_phph(_c: &mut Cache, nf: u8) -> Complex { + let nu = uplike_flavors(nf); + let nd = nf - nu; + cmplx!(4.0 * CF * CA * ((nu as f64) * eu2 + (nd as f64) * ed2), 0.) +} + +/// Compute the $O(a_s^1a_{em}^1)$ gluon-gluon singlet anomalous dimension. +/// +/// Implements Eq. (31) of +pub fn gamma_gg(_c: &mut Cache, _nf: u8) -> Complex { + cmplx!(4.0 * TR * (NC as f64), 0.) +} + +/// Compute the $O(a_s^1a_{em}^1)$ singlet-like non singlet anomalous dimension. +/// +/// Implements Eqs. (33-34) of +pub fn gamma_nsp(c: &mut Cache, _nf: u8) -> Complex { + let N = c.n(); + let S1 = c.get(K::S1); + let S2 = c.get(K::S2); + let S3 = c.get(K::S3); + let S1h = c.get(K::S1h); + let S2h = c.get(K::S2h); + let S3h = c.get(K::S3h); + let S1p1h = c.get(K::S1ph); + let S2p1h = c.get(K::S2ph); + let S3p1h = c.get(K::S3ph); + + let g3N = c.get(K::G3); + let g3Np2 = c.get(K::G3p2); + + #[rustfmt::skip] + let result = 32.0 * ZETA2 * S1h - 32.0 * ZETA2 * S1p1h + + 8.0 / (N + N.powu(2)) * S2h + - 4.0 * S3h + (24.0 + 16.0 / (N + N.powu(2))) * S2 + - 32.0 * S3 - 8.0 / (N + N.powu(2)) * S2p1h + + S1 * (16.0 * (3.0 / N.powu(2) - 3.0 / (1.0 + N).powu(2) + 2.0 * ZETA2) - 16.0 * S2h + - 32.0 * S2 + 16.0 * S2p1h ) + + (-8.0 + N * (-32.0 + N * ( -8.0 - 3.0 * N * (3.0 + N) * (3.0 + N.powu(2)) - 48.0 * (1.0 + N).powu(2) * ZETA2))) + / (N.powu(3) * (1.0 + N).powu(3)) + + 32.0 * (g3N + g3Np2) + 4.0 * S3p1h - 16.0 * ZETA3; + + CF * result +} + +/// Compute the $O(a_s^1a_{em}^1)$ valence-like non singlet anomalous dimension. +/// +/// Implements Eqs. (33-34) of +pub fn gamma_nsm(c: &mut Cache, _nf: u8) -> Complex { + let N = c.n(); + let S1 = c.get(K::S1); + let S2 = c.get(K::S2); + let S3 = c.get(K::S3); + let S1h = c.get(K::S1h); + let S2h = c.get(K::S2h); + let S3h = c.get(K::S3h); + let S1p1h = c.get(K::S1ph); + let S2p1h = c.get(K::S2ph); + let S3p1h = c.get(K::S3ph); + let g3N = c.get(K::G3); + let g3Np2 = c.get(K::G3p2); + + #[rustfmt::skip] + let result = + -32.0 * ZETA2 * S1h + - 8.0 / (N + N.powu(2)) * S2h + + (24.0 + 16.0 / (N + N.powu(2))) * S2 + + 8.0 / (N + N.powu(2)) * S2p1h + + S1 + * ( + 16.0 * (-1.0 / N.powu(2) + 1.0 / (1.0 + N).powu(2) + 2.0 * ZETA2) + + 16.0 * S2h + - 32.0 * S2 + - 16.0 * S2p1h + ) + + ( + 72.0 + + N + * ( + 96.0 + - 3.0 * N * (8.0 + 3.0 * N * (3.0 + N) * (3.0 + N.powu(2))) + + 48.0 * N * (1.0 + N).powu(2) * ZETA2 + ) + ) + / (3.0 * N.powu(3) * (1.0 + N).powu(3)) + - 32.0 * (g3N + g3Np2) + + 32.0 * ZETA2 * S1p1h + + 4.0 * S3h + - 32.0 * S3 + - 4.0 * S3p1h + - 16.0 * ZETA3; + + CF * result +} + +/// Compute the $O(a_s^1a_{em}^1)$ singlet sector. +pub fn gamma_singlet(c: &mut Cache, nf: u8) -> [[Complex; 4]; 4] { + let cc = ChargeCombinations { nf }; + let e2_tot = nf as f64 * cc.e2avg(); + + [ + [ + e2_tot * gamma_gg(c, nf), + e2_tot * gamma_gph(c, nf), + cc.e2avg() * gamma_gq(c, nf), + cc.vue2m() * gamma_gq(c, nf), + ], + [ + e2_tot * gamma_phg(c, nf), + gamma_phph(c, nf), + cc.e2avg() * gamma_phq(c, nf), + cc.vue2m() * gamma_phq(c, nf), + ], + [ + cc.e2avg() * gamma_qg(c, nf), + cc.e2avg() * gamma_qph(c, nf), + cc.e2avg() * gamma_nsp(c, nf), + cc.vue2m() * gamma_nsp(c, nf), + ], + [ + cc.vde2m() * gamma_qg(c, nf), + cc.vde2m() * gamma_qph(c, nf), + cc.vde2m() * gamma_nsp(c, nf), + cc.e2delta() * gamma_nsp(c, nf), + ], + ] +} + +/// Compute the $O(a_s^1a_{em}^1)$ valence sector. +pub fn gamma_valence(c: &mut Cache, nf: u8) -> [[Complex; 2]; 2] { + let cc = ChargeCombinations { nf }; + [ + [cc.e2avg() * gamma_nsm(c, nf), cc.vue2m() * gamma_nsm(c, nf)], + [ + cc.vde2m() * gamma_nsm(c, nf) * gamma_nsm(c, nf), + cc.e2delta() * gamma_nsm(c, nf), + ], + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_approx_eq_cmplx, cmplx}; + use num::complex::Complex; + use num::Zero; + const NF: u8 = 5; + + #[test] + fn number_conservation() { + const N: Complex = cmplx!(1., 0.); + let mut c = Cache::new(N); + let me = gamma_nsm(&mut c, NF); + assert_approx_eq_cmplx!(f64, me, Complex::zero(), epsilon = 1e-4); + } + + #[test] + fn gluon_momentum_conservation() { + const N: Complex = cmplx!(2., 0.); + let mut c = Cache::new(N); + + for nf in 2..7 { + let nu = uplike_flavors(nf); + let nd = nf - nu; + assert_approx_eq_cmplx!( + f64, + eu2 * gamma_qg(&mut c, nu) + + ed2 * gamma_qg(&mut c, nd) + + (nu as f64 * eu2 + nd as f64 * ed2) * gamma_phg(&mut c, nf) + + (nu as f64 * eu2 + nd as f64 * ed2) * gamma_gg(&mut c, nf), + cmplx!(0., 0.), + epsilon = 1e-14 + ); + } + } + + #[test] + fn photon_momentum_conservation() { + const N: Complex = cmplx!(2., 0.); + let mut c = Cache::new(N); + + for nf in 2..7 { + let nu = uplike_flavors(nf); + let nd = nf - nu; + assert_approx_eq_cmplx!( + f64, + eu2 * gamma_qph(&mut c, nu) + + ed2 * gamma_qph(&mut c, nd) + + gamma_phph(&mut c, nf) + + (nu as f64 * eu2 + nd as f64 * ed2) * gamma_gph(&mut c, nf), + cmplx!(0., 0.), + epsilon = 1e-14 + ); + } + } + + #[test] + fn quark_momentum_conservation() { + const N: Complex = cmplx!(2., 0.); + let mut c = Cache::new(N); + let me = gamma_nsp(&mut c, NF) + gamma_gq(&mut c, NF) + gamma_phq(&mut c, NF); + assert_approx_eq_cmplx!(f64, me, Complex::zero(), epsilon = 1e-4); + } +} diff --git a/crates/ekore/src/constants.rs b/crates/ekore/src/constants.rs index 8cb90eb1f..61cc0d9e0 100644 --- a/crates/ekore/src/constants.rs +++ b/crates/ekore/src/constants.rs @@ -1,4 +1,5 @@ //! Global constants. +use std::unimplemented; /// The number of colors. /// @@ -20,6 +21,16 @@ pub const CA: f64 = NC as f64; /// Defaults to $C_F = \frac{N_C^2-1}{2N_C} = 4/3$. pub const CF: f64 = ((NC * NC - 1) as f64) / ((2 * NC) as f64); +/// Up quark charge square. +/// +/// Defaults to $e_u^2 = 4./9$ +pub const eu2: f64 = 4. / 9.; + +/// Down quark charge square. +/// +/// Defaults to $e_d^2 = 1./9$ +pub const ed2: f64 = 1. / 9.; + /// Riemann zeta function at z = 2. /// /// $\zeta(2) = \pi^2 / 6$. @@ -41,3 +52,50 @@ pub const PID_NSM: u16 = 10201; /// non-singlet all-valence |PID|. pub const PID_NSV: u16 = 10200; + +/// QED |PID|. Need to give sensible names +pub const PID_NSP_EU2: u16 = 10102; + +pub const PID_NSP_ED2: u16 = 10103; + +pub const PID_NSM_EU2: u16 = 10202; + +pub const PID_NSM_ED2: u16 = 10203; + +/// compute the number of up flavors +pub fn uplike_flavors(nf: u8) -> u8 { + if nf > 6 { + unimplemented!("Selected nf is not implemented") + } + nf / 2 +} + +pub struct ChargeCombinations { + pub nf: u8, +} + +impl ChargeCombinations { + pub fn nu(&self) -> u8 { + self.nf / 2 + } + + pub fn nd(&self) -> u8 { + self.nf - self.nu() + } + + pub fn e2avg(&self) -> f64 { + (self.nu() as f64 * eu2 + self.nd() as f64 * ed2) / (self.nf as f64) + } + + pub fn vue2m(&self) -> f64 { + self.nu() as f64 / (self.nf as f64) * (eu2 - ed2) + } + + pub fn vde2m(&self) -> f64 { + self.nd() as f64 / (self.nf as f64) * (eu2 - ed2) + } + + pub fn e2delta(&self) -> f64 { + self.vde2m() - self.vue2m() + self.e2avg() + } +} diff --git a/crates/ekore/src/harmonics/cache.rs b/crates/ekore/src/harmonics/cache.rs index da0f4800b..a9dc6ad25 100644 --- a/crates/ekore/src/harmonics/cache.rs +++ b/crates/ekore/src/harmonics/cache.rs @@ -46,6 +46,12 @@ pub enum K { Sm21e, /// $S_{-2,1}(N)$ odd moments Sm21o, + /// recursive harmonics + S1ph, + S2ph, + S3ph, + S1p2, + G3p2, } /// Hold all cached values. @@ -90,6 +96,7 @@ impl Cache { K::S2mh => w2::S2((self.n - 1.) / 2.), K::S3mh => w3::S3((self.n - 1.) / 2.), K::G3 => g_functions::g3(self.n, self.get(K::S1)), + K::G3p2 => g_functions::g3(self.n + 2., self.get(K::S1p2)), K::Sm1e => w1::Sm1e(self.get(K::S1), self.get(K::S1h)), K::Sm1o => w1::Sm1o(self.get(K::S1), self.get(K::S1mh)), K::Sm2e => w2::Sm2e(self.get(K::S2), self.get(K::S2h)), @@ -98,6 +105,10 @@ impl Cache { K::Sm3o => w3::Sm3o(self.get(K::S3), self.get(K::S3mh)), K::Sm21e => w3::Sm21e(self.n, self.get(K::S1), self.get(K::Sm1e)), K::Sm21o => w3::Sm21o(self.n, self.get(K::S1), self.get(K::Sm1o)), + K::S1ph => recursive_harmonic_sum(self.get(K::S1mh), (self.n - 1.) / 2., 1, 1), + K::S2ph => recursive_harmonic_sum(self.get(K::S2mh), (self.n - 1.) / 2., 1, 2), + K::S3ph => recursive_harmonic_sum(self.get(K::S3mh), (self.n - 1.) / 2., 1, 3), + K::S1p2 => recursive_harmonic_sum(self.get(K::S1), self.n, 2, 1), }; // insert self.m.insert(k, val); diff --git a/src/eko/evolution_operator/__init__.py.patch b/src/eko/evolution_operator/__init__.py.patch index a4951e687..41ddb9f9d 100644 --- a/src/eko/evolution_operator/__init__.py.patch +++ b/src/eko/evolution_operator/__init__.py.patch @@ -1,5 +1,5 @@ diff --git a/src/eko/evolution_operator/__init__.py b/src/eko/evolution_operator/__init__.py -index bd1b19d6..f543f7bc 100644 +index bd1b19d6..de87651c 100644 --- a/src/eko/evolution_operator/__init__.py +++ b/src/eko/evolution_operator/__init__.py @@ -3,16 +3,16 @@ r"""Contains the central operator classes. @@ -21,11 +21,12 @@ index bd1b19d6..f543f7bc 100644 import ekore.anomalous_dimensions.polarized.space_like as ad_ps import ekore.anomalous_dimensions.unpolarized.space_like as ad_us -@@ -32,91 +32,10 @@ from ..matchings import Segment, lepton_number +@@ -32,91 +32,11 @@ from ..matchings import Segment, lepton_number from ..member import OpMember from ..scale_variations import expanded as sv_expanded from ..scale_variations import exponentiated as sv_exponentiated +from .quad_ker import cb_quad_ker_qcd ++from .quad_ker import cb_quad_ker_qed logger = logging.getLogger(__name__) @@ -114,7 +115,7 @@ index bd1b19d6..f543f7bc 100644 spec = [ ("is_singlet", nb.boolean), ("is_QEDsinglet", nb.boolean), -@@ -188,421 +107,6 @@ class QuadKerBase: +@@ -188,422 +108,6 @@ class QuadKerBase: return self.path.prefactor * pj * self.path.jac @@ -533,9 +534,10 @@ index bd1b19d6..f543f7bc 100644 - ) - return ker - - +- OpMembers = Dict[OperatorLabel, OpMember] """Map of all operators.""" + @@ -792,49 +296,6 @@ class Operator(sv.ScaleVariationModeMixin): """Return the evolution method.""" return ev_method(EvolutionMethod(self.config["method"])) @@ -586,7 +588,7 @@ index bd1b19d6..f543f7bc 100644 def initialize_op_members(self): """Init all operators with the identity or zeros.""" eye = OpMember( -@@ -857,10 +318,14 @@ class Operator(sv.ScaleVariationModeMixin): +@@ -857,10 +318,17 @@ class Operator(sv.ScaleVariationModeMixin): else: self.op_members[n] = zero.copy() @@ -598,14 +600,17 @@ index bd1b19d6..f543f7bc 100644 + """Adjust integration config.""" + cfg.as1 = self.as_list[1] + cfg.as0 = self.as_list[0] -+ cfg.py = ekors.ffi.cast("void *", cb_quad_ker_qcd.address) ++ if self.order[1] == 0: ++ cfg.py = ekors.ffi.cast("void *", cb_quad_ker_qcd.address) ++ else: ++ cfg.py = ekors.ffi.cast("void *", cb_quad_ker_qed.address) + cfg.method_num = self.ev_method + + def run_op_integration(self, log_grid): """Run the integration for each grid point. Parameters -@@ -875,18 +339,53 @@ class Operator(sv.ScaleVariationModeMixin): +@@ -875,18 +343,75 @@ class Operator(sv.ScaleVariationModeMixin): """ column = [] k, logx = log_grid @@ -615,6 +620,7 @@ index bd1b19d6..f543f7bc 100644 + # start preparing C arguments + cfg = ekors.lib.empty_qcd_args() + cfg.order_qcd = self.order[0] ++ cfg.order_qed = self.order[1] + cfg.is_polarized = self.config["polarized"] + cfg.is_time_like = self.config["time_like"] + cfg.nf = self.nf @@ -625,6 +631,27 @@ index bd1b19d6..f543f7bc 100644 + cfg.ev_op_max_order_qcd = self.config["ev_op_max_order"][0] + cfg.sv_mode_num = self.sv_mode + cfg.is_threshold = self.is_threshold ++ cfg.mu2_from = self.q2_from ++ cfg.mu2_to = self.q2_to ++ cfg.alphaem_running=self.alphaem_running ++ ++ # prepare as_list for c ++ as_list_len = self.as_list.shape[0] ++ as_list_ffi = ekors.ffi.new(f"double[{as_list_len}]", self.as_list.tolist()) ++ cfg.as_list = as_list_ffi ++ cfg.as_list_len = as_list_len ++ ++ # prepare a_half for c ++ a_half_x = self.a_half_list.shape[0] ++ a_half_y = self.a_half_list.shape[1] ++ a_half_len = a_half_x * a_half_y ++ a_half_ffi = ekors.ffi.new( ++ f"double[{a_half_len}]", self.a_half_list.flatten().tolist() ++ ) ++ cfg.a_half = a_half_ffi ++ cfg.a_half_x = a_half_x ++ cfg.a_half_y = a_half_y ++ + self.update_cfg(cfg) + # iterate basis functions diff --git a/src/eko/evolution_operator/quad_ker.py b/src/eko/evolution_operator/quad_ker.py index 087f6bbb7..aaedb7374 100644 --- a/src/eko/evolution_operator/quad_ker.py +++ b/src/eko/evolution_operator/quad_ker.py @@ -9,7 +9,11 @@ from .. import scale_variations as sv from ..io.types import InversionMethod from ..kernels import non_singlet as ns +from ..kernels import non_singlet_qed as qed_ns from ..kernels import singlet as s +from ..kernels import singlet_qed as qed_s +from ..kernels import valence_qed as qed_v +from ..matchings import lepton_number from ..scale_variations import expanded as sv_expanded from ..scale_variations import exponentiated as sv_exponentiated @@ -47,6 +51,7 @@ def select_singlet_element(ker, mode0, mode1): nb.types.double, # re_jac nb.types.double, # im_jac nb.types.uintc, # order_qcd + nb.types.uintc, # order_qed nb.types.bool_, # is_singlet nb.types.uintc, # mode0 nb.types.uintc, # mode1 @@ -65,6 +70,14 @@ def select_singlet_element(ker, mode0, mode1): nb.types.uintc, # sv_mode_num nb.types.bool_, # is_threshold nb.types.double, # Lsv + nb.types.CPointer(nb.types.double), # as_list + nb.types.uintc, # as_list_len + nb.types.double, # mu2_from + nb.types.double, # mu2_to + nb.types.CPointer(nb.types.double), # a_half + nb.types.uintc, # a_half_x + nb.types.uintc, # a_half_y + nb.types.bool_, # alphaem_running ) @@ -81,6 +94,7 @@ def cb_quad_ker_qcd( re_jac, im_jac, order_qcd, + _order_qed, is_singlet, mode0, mode1, @@ -99,6 +113,14 @@ def cb_quad_ker_qcd( sv_mode, is_threshold, Lsv, + _as_list, + _as_list_len, + _mu2_from, + _mu2_to, + _a_half, + _a_half_x, + _a_half_y, + _alphaem_running, ): """C Callback inside integration kernel.""" # recover complex variables @@ -225,6 +247,7 @@ def cb_quad_ker_ome( re_jac, im_jac, order_qcd, + _order_qed, is_singlet, mode0, mode1, @@ -243,6 +266,14 @@ def cb_quad_ker_ome( sv_mode, _is_threshold, Lsv, + _as_list, + _as_list_len, + _mu2_from, + _mu2_to, + _a_half, + _a_half_x, + _a_half_y, + _alphaem_running, ): """C Callback inside integration kernel.""" # recover complex variables @@ -280,210 +311,219 @@ def cb_quad_ker_ome( return np.real(res) -# from ..kernels import singlet_qed as qed_s -# from ..kernels import non_singlet_qed as qed_ns -# from ..kernels import valence_qed as qed_v - -# @nb.njit(cache=True) -# def select_QEDsinglet_element(ker, mode0, mode1): -# """Select element of the QEDsinglet matrix. - -# Parameters -# ---------- -# ker : numpy.ndarray -# QEDsinglet integration kernel -# mode0 : int -# id for first sector element -# mode1 : int -# id for second sector element -# Returns -# ------- -# ker : complex -# QEDsinglet integration kernel element -# """ -# if mode0 == 21: -# index1 = 0 -# elif mode0 == 22: -# index1 = 1 -# elif mode0 == 100: -# index1 = 2 -# else: -# index1 = 3 -# if mode1 == 21: -# index2 = 0 -# elif mode1 == 22: -# index2 = 1 -# elif mode1 == 100: -# index2 = 2 -# else: -# index2 = 3 -# return ker[index1, index2] - - -# @nb.njit(cache=True) -# def select_QEDvalence_element(ker, mode0, mode1): -# """ -# Select element of the QEDvalence matrix. - -# Parameters -# ---------- -# ker : numpy.ndarray -# QEDvalence integration kernel -# mode0 : int -# id for first sector element -# mode1 : int -# id for second sector element -# Returns -# ------- -# ker : complex -# QEDvalence integration kernel element -# """ -# index1 = 0 if mode0 == 10200 else 1 -# index2 = 0 if mode1 == 10200 else 1 -# return ker[index1, index2] - - -# @nb.njit(cache=True) -# def quad_ker_qed( -# ker_base, -# order, -# mode0, -# mode1, -# method, -# as_list, -# mu2_from, -# mu2_to, -# a_half, -# alphaem_running, -# nf, -# L, -# ev_op_iterations, -# ev_op_max_order, -# sv_mode, -# is_threshold, -# ): -# """Raw evolution kernel inside quad. - -# Parameters -# ---------- -# ker_base : QuadKerBase -# quad argument -# order : int -# perturbation order -# mode0: int -# pid for first sector element -# mode1 : int -# pid for second sector element -# method : str -# method -# as1 : float -# target coupling value -# as0 : float -# initial coupling value -# mu2_from : float -# initial value of mu2 -# mu2_from : float -# final value of mu2 -# aem_list : list -# list of electromagnetic coupling values -# alphaem_running : bool -# whether alphaem is running or not -# nf : int -# number of active flavors -# L : float -# logarithm of the squared ratio of factorization and renormalization scale -# ev_op_iterations : int -# number of evolution steps -# ev_op_max_order : int -# perturbative expansion order of U -# sv_mode: int, `enum.IntEnum` -# scale variation mode, see `eko.scale_variations.Modes` -# is_threshold : boolean -# is this an itermediate threshold operator? - -# Returns -# ------- -# float -# evaluated integration kernel -# """ -# # compute the actual evolution kernel for QEDxQCD -# if ker_base.is_QEDsinglet: -# gamma_s = ad_us.gamma_singlet_qed(order, ker_base.n, nf) -# # scale var exponentiated is directly applied on gamma -# if sv_mode == sv.Modes.exponentiated: -# gamma_s = sv.exponentiated.gamma_variation_qed( -# gamma_s, order, nf, L, alphaem_running -# ) -# ker = qed_s.dispatcher( -# order, -# method, -# gamma_s, -# as_list, -# a_half, -# nf, -# ev_op_iterations, -# ev_op_max_order, -# ) -# # scale var expanded is applied on the kernel -# # TODO : in this way a_half[-1][1] is the aem value computed in -# # the middle point of the last step. Instead we want aem computed in mu2_final. -# # However the distance between the two is very small and affects only the running aem -# if sv_mode == sv.Modes.expanded and not is_threshold: -# ker = np.ascontiguousarray( -# sv.expanded.singlet_variation_qed( -# gamma_s, as_list[-1], a_half[-1][1], alphaem_running, order, nf, L -# ) -# ) @ np.ascontiguousarray(ker) -# ker = select_QEDsinglet_element(ker, mode0, mode1) -# elif ker_base.is_QEDvalence: -# gamma_v = ad_us.gamma_valence_qed(order, ker_base.n, nf) -# # scale var exponentiated is directly applied on gamma -# if sv_mode == sv.Modes.exponentiated: -# gamma_v = sv.exponentiated.gamma_variation_qed( -# gamma_v, order, nf, L, alphaem_running -# ) -# ker = qed_v.dispatcher( -# order, -# method, -# gamma_v, -# as_list, -# a_half, -# nf, -# ev_op_iterations, -# ev_op_max_order, -# ) -# # scale var expanded is applied on the kernel -# if sv_mode == sv.Modes.expanded and not is_threshold: -# ker = np.ascontiguousarray( -# sv.expanded.valence_variation_qed( -# gamma_v, as_list[-1], a_half[-1][1], alphaem_running, order, nf, L -# ) -# ) @ np.ascontiguousarray(ker) -# ker = select_QEDvalence_element(ker, mode0, mode1) -# else: -# gamma_ns = ad_us.gamma_ns_qed(order, mode0, ker_base.n, nf) -# # scale var exponentiated is directly applied on gamma -# if sv_mode == sv.Modes.exponentiated: -# gamma_ns = sv.exponentiated.gamma_variation_qed( -# gamma_ns, order, nf, L, alphaem_running -# ) -# ker = qed_ns.dispatcher( -# order, -# method, -# gamma_ns, -# as_list, -# a_half[:, 1], -# alphaem_running, -# nf, -# ev_op_iterations, -# mu2_from, -# mu2_to, -# ) -# if sv_mode == sv.Modes.expanded and not is_threshold: -# ker = ( -# sv.expanded.non_singlet_variation_qed( -# gamma_ns, as_list[-1], a_half[-1][1], alphaem_running, order, nf, L -# ) -# * ker -# ) -# return ker +@nb.njit(cache=True) +def select_QEDsinglet_element(ker, mode0, mode1): + """Select element of the QEDsinglet matrix. + + Parameters + ---------- + ker : numpy.ndarray + QEDsinglet integration kernel + mode0 : int + id for first sector element + mode1 : int + id for second sector element + Returns + ------- + ker : complex + QEDsinglet integration kernel element + """ + if mode0 == 21: + index1 = 0 + elif mode0 == 22: + index1 = 1 + elif mode0 == 100: + index1 = 2 + else: + index1 = 3 + if mode1 == 21: + index2 = 0 + elif mode1 == 22: + index2 = 1 + elif mode1 == 100: + index2 = 2 + else: + index2 = 3 + return ker[index1, index2] + + +@nb.njit(cache=True) +def select_QEDvalence_element(ker, mode0, mode1): + """Select element of the QEDvalence matrix. + + Parameters + ---------- + ker : numpy.ndarray + QEDvalence integration kernel + mode0 : int + id for first sector element + mode1 : int + id for second sector element + Returns + ------- + ker : complex + QEDvalence integration kernel element + """ + index1 = 0 if mode0 == 10200 else 1 + index2 = 0 if mode1 == 10200 else 1 + return ker[index1, index2] + + +@nb.cfunc( + CB_SIGNATURE, + cache=True, + nopython=True, +) +def cb_quad_ker_qed( + re_gamma_raw, + im_gamma_raw, + re_n, + im_n, + re_jac, + im_jac, + order_qcd, + order_qed, + is_singlet, + mode0, + mode1, + nf, + is_log, + logx, + areas_raw, + areas_x, + areas_y, + L, + ev_method, + as1, + as0, + ev_op_iterations, + ev_op_max_order_qcd, + sv_mode, + is_threshold, + Lsv, + as_list_raw, + as_list_len, + mu2_from, + mu2_to, + a_half_raw, + a_half_x, + a_half_y, + alphaem_running, +): + """C Callback inside integration kernel.""" + # recover complex variables + n = re_n + im_n * 1j + jac = re_jac + im_jac * 1j + # compute basis functions + areas = nb.carray(areas_raw, (areas_x, areas_y)) + pj = interpolation.evaluate_grid(n, is_log, logx, areas) + order = (order_qcd, order_qed) + ev_op_max_order = (ev_op_max_order_qcd, order_qed) + is_valence = (mode0 == 10200) or (mode0 == 10204) + + as_list = nb.carray(as_list_raw, as_list_len) + a_half = nb.carray(a_half_raw, (a_half_x, a_half_y)) + + if is_singlet: + # reconstruct singlet matrices + re_gamma_singlet = nb.carray(re_gamma_raw, (order_qcd, order_qed, 4, 4)) + im_gamma_singlet = nb.carray(im_gamma_raw, (order_qcd, order_qed, 4, 4)) + gamma_singlet = re_gamma_singlet + im_gamma_singlet * 1j + + # scale var exponentiated is directly applied on gamma + if sv_mode == sv.Modes.exponentiated: + gamma_singlet = sv.exponentiated.gamma_variation_qed( + gamma_singlet, order, nf, lepton_number(mu2_to), L, alphaem_running + ) + + ker = qed_s.dispatcher( + order, + ev_method, + gamma_singlet, + as_list, + a_half, + nf, + ev_op_iterations, + ev_op_max_order, + ) + if sv_mode == sv.Modes.expanded and not is_threshold: + ker = np.ascontiguousarray( + sv.expanded.singlet_variation_qed( + gamma_singlet, + as_list[-1], + a_half[-1][1], + alphaem_running, + order, + nf, + L, + ) + ) @ np.ascontiguousarray(ker) + ker = select_QEDsinglet_element(ker, mode0, mode1) + + elif is_valence: + # reconstruct valence matrices + re_gamma_valence = nb.carray(re_gamma_raw, (order_qcd, order_qed, 2, 2)) + im_gamma_valence = nb.carray(im_gamma_raw, (order_qcd, order_qed, 2, 2)) + gamma_valence = re_gamma_valence + im_gamma_valence * 1j + + if sv_mode == sv.Modes.exponentiated: + gamma_valence = sv.exponentiated.gamma_variation_qed( + gamma_valence, order, nf, lepton_number(mu2_to), L, alphaem_running + ) + ker = qed_v.dispatcher( + order, + ev_method, + gamma_valence, + as_list, + a_half, + nf, + ev_op_iterations, + ev_op_max_order, + ) + # scale var expanded is applied on the kernel + if sv_mode == sv.Modes.expanded and not is_threshold: + ker = np.ascontiguousarray( + sv.expanded.valence_variation_qed( + gamma_valence, + as_list[-1], + a_half[-1][1], + alphaem_running, + order, + nf, + L, + ) + ) @ np.ascontiguousarray(ker) + ker = select_QEDvalence_element(ker, mode0, mode1) + + else: + # construct non-singlet matrices + re_gamma_ns = nb.carray(re_gamma_raw, (order_qcd, order_qed)) + im_gamma_ns = nb.carray(im_gamma_raw, (order_qcd, order_qed)) + gamma_ns = re_gamma_ns + im_gamma_ns * 1j + if sv_mode == sv.Modes.exponentiated: + gamma_ns = sv_exponentiated.gamma_variation_qed( + gamma_ns, order, nf, lepton_number(mu2_to), L, alphaem_running + ) + # construct eko + ker = qed_ns.dispatcher( + order, + ev_method, + gamma_ns, + as_list, + a_half[:, 1], + alphaem_running, + nf, + ev_op_iterations, + mu2_from, + mu2_to, + ) + if sv_mode == sv.Modes.expanded and not is_threshold: + ker = ( + sv_expanded.non_singlet_variation_qed( + gamma_ns, as_list[-1], a_half[-1][1], alphaem_running, order, nf, L + ) + * ker + ) + # recombine everything + res = ker * pj * jac + return np.real(res) diff --git a/src/eko/kernels/non_singlet.py b/src/eko/kernels/non_singlet.py index 1d25d5845..0e8352dda 100644 --- a/src/eko/kernels/non_singlet.py +++ b/src/eko/kernels/non_singlet.py @@ -375,7 +375,7 @@ def dispatcher(order, method, gamma_ns, a1, a0, nf, ev_op_iterations): # pylint return eko_ordered_truncated( gamma_ns, a1, a0, betalist, order, ev_op_iterations ) - if method is EvoMethods.TRUNCATED: + if method == EvoMethods.TRUNCATED: return eko_truncated(gamma_ns, a1, a0, betalist, order, ev_op_iterations) # NLO diff --git a/src/eko/kernels/singlet.py b/src/eko/kernels/singlet.py index 96f02a972..2ce7bce21 100644 --- a/src/eko/kernels/singlet.py +++ b/src/eko/kernels/singlet.py @@ -613,7 +613,7 @@ def dispatcher( # pylint: disable=too-many-return-statements # Common method for NLO and NNLO if method in [EvoMethods.ITERATE_EXACT, EvoMethods.ITERATE_EXPANDED]: return eko_iterate(gamma_singlet, a1, a0, betalist, order, ev_op_iterations) - if method is EvoMethods.PERTURBATIVE_EXACT: + if method == EvoMethods.PERTURBATIVE_EXACT: return eko_perturbative( gamma_singlet, a1, @@ -624,7 +624,7 @@ def dispatcher( # pylint: disable=too-many-return-statements ev_op_max_order, True, ) - if method is EvoMethods.PERTURBATIVE_EXPANDED: + if method == EvoMethods.PERTURBATIVE_EXPANDED: return eko_perturbative( gamma_singlet, a1, @@ -638,13 +638,13 @@ def dispatcher( # pylint: disable=too-many-return-statements if method in [EvoMethods.TRUNCATED, EvoMethods.ORDERED_TRUNCATED]: return eko_truncated(gamma_singlet, a1, a0, betalist, order, ev_op_iterations) # These methods are scattered for nlo and nnlo - if method is EvoMethods.DECOMPOSE_EXACT: + if method == EvoMethods.DECOMPOSE_EXACT: if order[0] == 2: return nlo_decompose_exact(gamma_singlet, a1, a0, betalist) if order[0] == 3: return nnlo_decompose_exact(gamma_singlet, a1, a0, betalist) return n3lo_decompose_exact(gamma_singlet, a1, a0, nf) - if method is EvoMethods.DECOMPOSE_EXPANDED: + if method == EvoMethods.DECOMPOSE_EXPANDED: if order[0] == 2: return nlo_decompose_expanded(gamma_singlet, a1, a0, betalist) if order[0] == 3: diff --git a/src/eko/kernels/singlet_qed.py b/src/eko/kernels/singlet_qed.py index d63a2f1ac..0a5716df8 100644 --- a/src/eko/kernels/singlet_qed.py +++ b/src/eko/kernels/singlet_qed.py @@ -97,7 +97,7 @@ def dispatcher( e_s : numpy.ndarray singlet EKO """ - if method is EvoMethods.ITERATE_EXACT: + if method == EvoMethods.ITERATE_EXACT: return eko_iterate( gamma_singlet, as_list, a_half, nf, order, ev_op_iterations, 4 ) diff --git a/src/eko/kernels/valence_qed.py b/src/eko/kernels/valence_qed.py index 8b83e1917..b5da158b4 100644 --- a/src/eko/kernels/valence_qed.py +++ b/src/eko/kernels/valence_qed.py @@ -45,7 +45,7 @@ def dispatcher( e_v : numpy.ndarray singlet EKO """ - if method is EvoMethods.ITERATE_EXACT: + if method == EvoMethods.ITERATE_EXACT: return eko_iterate( gamma_valence, as_list, a_half, nf, order, ev_op_iterations, 2 )