From 12c59ffe43bd0c561cd04f420e6b78a06dfb7749 Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 11:42:35 +0530 Subject: [PATCH 1/8] Init mock C++ library --- extras/gsoc/libome/README.md | 8 ++++++++ extras/gsoc/libome/ome.cpp | 33 +++++++++++++++++++++++++++++++++ extras/gsoc/libome/ome.h | 26 ++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 extras/gsoc/libome/README.md create mode 100644 extras/gsoc/libome/ome.cpp create mode 100644 extras/gsoc/libome/ome.h diff --git a/extras/gsoc/libome/README.md b/extras/gsoc/libome/README.md new file mode 100644 index 000000000..0048962d9 --- /dev/null +++ b/extras/gsoc/libome/README.md @@ -0,0 +1,8 @@ +# Mock libome for Mellin N-space OMEs + +This directory contains a mock C++ library exposing a C ABI for $\mathcal{O}(\alpha_s^3)$ (N3LO) space-like unpolarized Operator Matrix Elements in Mellin $N$-space. + +## Interface + +The C ABI declarations are provided in `ome.h`. +Currently, the functions return dummy complex values (`{0.0, 0.0}`) for testing Rust FFI integration. diff --git a/extras/gsoc/libome/ome.cpp b/extras/gsoc/libome/ome.cpp new file mode 100644 index 000000000..1b85d9bcc --- /dev/null +++ b/extras/gsoc/libome/ome.cpp @@ -0,0 +1,33 @@ +#include "ome.h" + +extern "C" { + +ome_complex_t ome_as3_Agg(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_Agq(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_Aqg(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_AHg(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_AHq(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_AqqPS(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/) { + return {0.0, 0.0}; +} + +ome_complex_t ome_as3_AqqNS(ome_complex_t /*n*/, unsigned int /*nf*/, double /*L*/, int /*eta*/) { + return {0.0, 0.0}; +} + +} diff --git a/extras/gsoc/libome/ome.h b/extras/gsoc/libome/ome.h new file mode 100644 index 000000000..4a7293ff6 --- /dev/null +++ b/extras/gsoc/libome/ome.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief C-compatible double-precision complex number. + */ +typedef struct { + double re; + double im; +} ome_complex_t; + +// Space-like unpolarized OMEs at O(as^3) in Mellin N-space +ome_complex_t ome_as3_Agg(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_Agq(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_Aqg(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_AHg(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_AHq(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_AqqPS(ome_complex_t n, unsigned int nf, double L); +ome_complex_t ome_as3_AqqNS(ome_complex_t n, unsigned int nf, double L, int eta); + +#ifdef __cplusplus +} +#endif From e4d8595517cc798dbff1f96fdff5d77ebfb8fd9c Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 11:44:35 +0530 Subject: [PATCH 2/8] Added build.rs --- Cargo.lock | 23 +++++++++++++++++++++++ Cargo.toml | 1 + crates/ekore/Cargo.toml | 3 +++ crates/ekore/build.rs | 17 +++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 crates/ekore/build.rs diff --git a/Cargo.lock b/Cargo.lock index 9d12b7b3d..80c30fab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -227,6 +237,7 @@ name = "ekore" version = "0.0.1" dependencies = [ "GSL", + "cc", "float-cmp 0.9.0", "num", ] @@ -285,6 +296,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + [[package]] name = "flate2" version = "1.1.0" @@ -876,6 +893,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "syn" version = "2.0.98" diff --git a/Cargo.toml b/Cargo.toml index 3716c60e7..362706259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ yaml-rust2 = "0.8" # Dev dependencies assert_fs = "1.1.2" +cc = "1.0" float-cmp = "0.9.0" GSL = "7.0" predicates = "3.1.2" diff --git a/crates/ekore/Cargo.toml b/crates/ekore/Cargo.toml index faab10ab6..0dc9e245c 100644 --- a/crates/ekore/Cargo.toml +++ b/crates/ekore/Cargo.toml @@ -20,6 +20,9 @@ rustdoc-args = ["--html-in-header", "doc-header.html"] [dependencies] num.workspace = true +[build-dependencies] +cc.workspace = true + [dev-dependencies] float-cmp.workspace = true GSL.workspace = true diff --git a/crates/ekore/build.rs b/crates/ekore/build.rs new file mode 100644 index 000000000..8006be32c --- /dev/null +++ b/crates/ekore/build.rs @@ -0,0 +1,17 @@ +//! Build script to compile the mock `libome` C++ library from `extras/gsoc/libome` +//! and link it statically into `ekore`. + +use std::path::PathBuf; + +fn main() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let libome_dir = manifest_dir.join("../../extras/gsoc/libome"); + + println!("cargo:rerun-if-changed={}", libome_dir.display()); + + cc::Build::new() + .cpp(true) + .include(&libome_dir) + .file(libome_dir.join("ome.cpp")) + .compile("ome"); +} From 77867b3adaf5e4a1333f47813ae0a339b85777ca Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 11:46:04 +0530 Subject: [PATCH 3/8] Added as3.rs --- .../unpolarized/spacelike/as3.rs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs new file mode 100644 index 000000000..5912d042d --- /dev/null +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs @@ -0,0 +1,92 @@ +//! |N3LO| unpolarized, space-like |OME| via external `libome` C ABI. + +use num::Zero; +use num::complex::Complex; + +use crate::harmonics::cache::Cache; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct OmeComplex { + re: f64, + im: f64, +} + +impl From> for OmeComplex { + fn from(c: Complex) -> Self { + Self { re: c.re, im: c.im } + } +} + +impl From for Complex { + fn from(c: OmeComplex) -> Self { + Complex::new(c.re, c.im) + } +} + +unsafe extern "C" { + fn ome_as3_Agg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_Agq(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_Aqg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_AHg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_AHq(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_AqqPS(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; + fn ome_as3_AqqNS(n: OmeComplex, nf: u32, L: f64, eta: i32) -> OmeComplex; +} + +/// Compute the |N3LO| singlet |OME|. +pub(super) fn A_singlet(c: &mut Cache, nf: u8, L: f64) -> [[Complex; 3]; 3] { + let n: OmeComplex = c.n().into(); + let nf_u32 = u32::from(nf); + + let a_gg = unsafe { Complex::from(ome_as3_Agg(n, nf_u32, L)) }; + let a_gq = unsafe { Complex::from(ome_as3_Agq(n, nf_u32, L)) }; + let a_qg = unsafe { Complex::from(ome_as3_Aqg(n, nf_u32, L)) }; + let a_hg = unsafe { Complex::from(ome_as3_AHg(n, nf_u32, L)) }; + let a_hq = unsafe { Complex::from(ome_as3_AHq(n, nf_u32, L)) }; + let a_qq_ps = unsafe { Complex::from(ome_as3_AqqPS(n, nf_u32, L)) }; + let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_u32, L, 1)) }; + + [ + [a_gg, a_gq, Complex::::zero()], + [a_qg, a_qq_ps + a_qq_ns, Complex::::zero()], + [a_hg, a_hq, Complex::::zero()], + ] +} + +/// Compute the |N3LO| non-singlet |OME|. +pub(super) fn A_ns(c: &mut Cache, nf: u8, L: f64) -> [[Complex; 2]; 2] { + let n: OmeComplex = c.n().into(); + let nf_u32 = u32::from(nf); + let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_u32, L, -1)) }; + + [ + [a_qq_ns, Complex::::zero()], + [Complex::::zero(), Complex::::zero()], + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cmplx; + + #[test] + fn test_as3_calls() { + const NF: u8 = 4; + let n = cmplx!(2.0, 1.5); + let mut c = Cache::new(n); + let a_s = A_singlet(&mut c, NF, 0.0); + for row in a_s.iter() { + for entry in row.iter() { + assert_eq!(*entry, Complex::zero()); + } + } + let a_ns = A_ns(&mut c, NF, 0.0); + for row in a_ns.iter() { + for entry in row.iter() { + assert_eq!(*entry, Complex::zero()); + } + } + } +} From 9e8ff3cbb5b00ae0162a939f94bfa9f083adbfca Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 11:46:28 +0530 Subject: [PATCH 4/8] Updated spacelike.rs fn and tests --- .../unpolarized/spacelike.rs | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs index 2dfa0f000..413608857 100644 --- a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike.rs @@ -5,6 +5,7 @@ use num::Zero; use num::complex::Complex; mod as1; mod as2; +mod as3; /// Compute the tower of the unpolarized, space-like singlet |OME|. /// @@ -12,13 +13,14 @@ mod as2; /// using `nf` light flavors and `L`, logarithm of the squared ratio of factorization scale and mass /// $\ln(\mu_F^2 / m^2)$. /// -/// Returns an array of shape `(MAX_ORDER_QCD - 2, 3, 3)`. Only the first `matching_order_qcd` +/// Returns an array of shape `(MAX_ORDER_QCD - 1, 3, 3)`. Only the first `matching_order_qcd` /// entries along the outer axis are filled; remaining slots are zero. /// /// # Available perturbative orders: /// /// - $a_s^1$ [\[Ball:2015tna\]](crate::bib::Ball2015tna) [\[Buza:1996wv\]](crate::bib::Buza1996wv) /// - $a_s^2$ [\[Buza:1996wv\]](crate::bib::Buza1996wv) [\[Bierenbaum:2009zt\]](crate::bib::Bierenbaum2009zt) +/// - $a_s^3$ (via external `libome`) /// /// # Panics /// @@ -28,11 +30,11 @@ pub fn A_singlet( cache: &mut Cache, nf: u8, L: f64, -) -> [[[Complex; 3]; 3]; MAX_ORDER_QCD - 2] { - if matching_order_qcd >= 3 { - panic!("OME beyond NLO is not yet implemented"); +) -> [[[Complex; 3]; 3]; MAX_ORDER_QCD - 1] { + if matching_order_qcd >= 4 { + panic!("OME beyond N3LO is not yet implemented"); } - let mut A_s = [[[Complex::::zero(); 3]; 3]; MAX_ORDER_QCD - 2]; + let mut A_s = [[[Complex::::zero(); 3]; 3]; MAX_ORDER_QCD - 1]; if matching_order_qcd >= 1 { A_s[0] = as1::A_singlet(cache, nf, L); } @@ -40,6 +42,9 @@ pub fn A_singlet( // TODO recover MSbar mass A_s[1] = as2::A_singlet(cache, nf, L, false); } + if matching_order_qcd >= 3 { + A_s[2] = as3::A_singlet(cache, nf, L); + } A_s } @@ -49,13 +54,14 @@ pub fn A_singlet( /// using `nf` light flavors and `L`, logarithm of the squared ratio of factorization scale and mass /// $\ln(\mu_F^2 / m^2)$. /// -/// Returns an array of shape `(MAX_ORDER_QCD - 2, 2, 2)`. Only the first `matching_order_qcd` +/// Returns an array of shape `(MAX_ORDER_QCD - 1, 2, 2)`. Only the first `matching_order_qcd` /// entries along the outer axis are filled; remaining slots are zero. /// /// # Available perturbative orders: /// /// - $a_s^1$ [\[Ball:2015tna\]](crate::bib::Ball2015tna) /// - $a_s^2$ [\[Buza:1996wv\]](crate::bib::Buza1996wv) +/// - $a_s^3$ (via external `libome`) /// /// # Panics /// @@ -65,17 +71,20 @@ pub fn A_non_singlet( cache: &mut Cache, nf: u8, L: f64, -) -> [[[Complex; 2]; 2]; MAX_ORDER_QCD - 2] { - if matching_order_qcd >= 3 { - panic!("OME beyond NLO is not yet implemented"); +) -> [[[Complex; 2]; 2]; MAX_ORDER_QCD - 1] { + if matching_order_qcd >= 4 { + panic!("OME beyond N3LO is not yet implemented"); } - let mut A_ns = [[[Complex::::zero(); 2]; 2]; MAX_ORDER_QCD - 2]; + let mut A_ns = [[[Complex::::zero(); 2]; 2]; MAX_ORDER_QCD - 1]; if matching_order_qcd >= 1 { A_ns[0] = as1::A_ns(cache, nf, L); } if matching_order_qcd >= 2 { A_ns[1] = as2::A_ns(cache, nf, L); } + if matching_order_qcd >= 3 { + A_ns[2] = as3::A_ns(cache, nf, L); + } A_ns } @@ -90,10 +99,10 @@ mod tests { const NF: u8 = 4; const N: Complex = cmplx!(0., 1.); const L: f64 = 0.0; - for matching_order_qcd in 1..=2usize { + for matching_order_qcd in 1..=3usize { let mut cache = Cache::new(N); let a_s = A_singlet(matching_order_qcd, &mut cache, NF, L); - assert_eq!(a_s.len(), MAX_ORDER_QCD - 2); + assert_eq!(a_s.len(), MAX_ORDER_QCD - 1); assert_eq!(a_s[0].len(), 3); assert_eq!(a_s[0][0].len(), 3); // slots beyond order_qcd must be zero @@ -101,7 +110,7 @@ mod tests { assert_approx_eq_cmplx_2d!(f64, item, [[cmplx!(0., 0.); 3]; 3], 3); } let a_ns = A_non_singlet(matching_order_qcd, &mut cache, NF, L); - assert_eq!(a_ns.len(), MAX_ORDER_QCD - 2); + assert_eq!(a_ns.len(), MAX_ORDER_QCD - 1); assert_eq!(a_ns[0].len(), 2); assert_eq!(a_ns[0][0].len(), 2); // slots beyond order_qcd must be zero @@ -118,11 +127,13 @@ mod tests { const N: Complex = cmplx!(1., 0.); const L: f64 = 0.0; let mut cache = Cache::new(N); - let a_ns = A_non_singlet(2, &mut cache, NF, L); + let a_ns = A_non_singlet(3, &mut cache, NF, L); // LO assert_approx_eq_cmplx_2d!(f64, a_ns[0], [[cmplx!(0., 0.); 2]; 2], 2, epsilon = 1e-14); // NNLO assert_approx_eq_cmplx_2d!(f64, a_ns[1], [[cmplx!(0., 0.); 2]; 2], 2, epsilon = 1e-14); + // N3LO + assert_approx_eq_cmplx_2d!(f64, a_ns[2], [[cmplx!(0., 0.); 2]; 2], 2, epsilon = 1e-14); } #[test] @@ -132,7 +143,7 @@ mod tests { const N: Complex = cmplx!(2., 0.); const L: f64 = 100.; let mut cache = Cache::new(N); - let a_s = A_singlet(2, &mut cache, NF, L); + let a_s = A_singlet(3, &mut cache, NF, L); // LO assert_approx_eq_cmplx!( f64, @@ -158,25 +169,27 @@ mod tests { Complex::zero(), epsilon = 1e-11 ); + // N3LO + assert_approx_eq_cmplx_2d!(f64, a_s[2], [[cmplx!(0., 0.); 3]; 3], 3, epsilon = 1e-14); } #[test] - #[should_panic(expected = "OME beyond NLO is not yet implemented")] - fn test_a_singlet_order3_panics() { + #[should_panic(expected = "OME beyond N3LO is not yet implemented")] + fn test_a_singlet_order4_panics() { const NF: u8 = 4; const N: Complex = cmplx!(1.234, 0.); const L: f64 = 0.0; let mut cache = Cache::new(N); - A_singlet(3, &mut cache, NF, L); + A_singlet(4, &mut cache, NF, L); } #[test] - #[should_panic(expected = "OME beyond NLO is not yet implemented")] - fn test_a_non_singlet_order3_panics() { + #[should_panic(expected = "OME beyond N3LO is not yet implemented")] + fn test_a_non_singlet_order4_panics() { const NF: u8 = 4; const N: Complex = cmplx!(1.234, 0.); const L: f64 = 0.0; let mut cache = Cache::new(N); - A_non_singlet(3, &mut cache, NF, L); + A_non_singlet(4, &mut cache, NF, L); } } From 9c6b85cffe418e728c2012133e0947238324f77f Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 11:48:21 +0530 Subject: [PATCH 5/8] Updated ekore_capi and ekore_py --- crates/ekore_capi/src/ome_us.rs | 16 ++++++------- crates/ekore_capi/tests/c/test_ome_us.c | 30 ++++++++++++++++--------- crates/ekore_py/src/ome_us.rs | 8 +++---- crates/ekore_py/tests/test_ome_us.py | 19 +++++++++++----- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/crates/ekore_capi/src/ome_us.rs b/crates/ekore_capi/src/ome_us.rs index 09aeb388d..76304e1af 100644 --- a/crates/ekore_capi/src/ome_us.rs +++ b/crates/ekore_capi/src/ome_us.rs @@ -7,14 +7,14 @@ use ekore::operator_matrix_elements::unpolarized::spacelike; /// Required length of `result` for [`ome_us_A_singlet`] at the given `matching_order_qcd`. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (must be < 3). +/// * `matching_order_qcd`: The QCD matching order (must be < 4). /// /// # Returns /// * Returns the required buffer size (number of `ComplexF64` elements). /// * Returns `0` if `matching_order_qcd` is out of the supported range. #[unsafe(no_mangle)] pub extern "C" fn ome_us_A_singlet_result_len(matching_order_qcd: usize) -> usize { - result_len_body!(matching_order_qcd >= 3, matching_order_qcd * 9) + result_len_body!(matching_order_qcd >= 4, matching_order_qcd * 9) } /// Compute the tower of the singlet |OME|. @@ -25,7 +25,7 @@ pub extern "C" fn ome_us_A_singlet_result_len(matching_order_qcd: usize) -> usiz /// * The `result` buffer must have a capacity of at least `matching_order_qcd * 9` elements. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (supported range: < 3). +/// * `matching_order_qcd`: The QCD matching order (supported range: < 4). /// * `cache`: Pointer to the harmonic cache. /// * `nf`: Number of active flavors. /// * `L`: The logarithm parameter. @@ -44,7 +44,7 @@ pub unsafe extern "C" fn ome_us_A_singlet( nf, L, result, - matching_order_qcd >= 3, + matching_order_qcd >= 4, spacelike::A_singlet, 3, 9 @@ -54,14 +54,14 @@ pub unsafe extern "C" fn ome_us_A_singlet( /// Required length of `result` for [`ome_us_A_non_singlet`] at the given `matching_order_qcd`. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (must be < 3). +/// * `matching_order_qcd`: The QCD matching order (must be < 4). /// /// # Returns /// * Returns the required buffer size (number of `ComplexF64` elements). /// * Returns `0` if `matching_order_qcd` is out of the supported range. #[unsafe(no_mangle)] pub extern "C" fn ome_us_A_non_singlet_result_len(matching_order_qcd: usize) -> usize { - result_len_body!(matching_order_qcd >= 3, matching_order_qcd * 4) + result_len_body!(matching_order_qcd >= 4, matching_order_qcd * 4) } /// Compute the tower of the non-singlet |OME|. @@ -72,7 +72,7 @@ pub extern "C" fn ome_us_A_non_singlet_result_len(matching_order_qcd: usize) -> /// * The `result` buffer must have a capacity of at least `matching_order_qcd * 4` elements. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (supported range: < 3). +/// * `matching_order_qcd`: The QCD matching order (supported range: < 4). /// * `cache`: Pointer to the harmonic cache. /// * `nf`: Number of active flavors. /// * `L`: The logarithm parameter. @@ -91,7 +91,7 @@ pub unsafe extern "C" fn ome_us_A_non_singlet( nf, L, result, - matching_order_qcd >= 3, + matching_order_qcd >= 4, spacelike::A_non_singlet, 2, 4 diff --git a/crates/ekore_capi/tests/c/test_ome_us.c b/crates/ekore_capi/tests/c/test_ome_us.c index 699688c59..f48c05114 100644 --- a/crates/ekore_capi/tests/c/test_ome_us.c +++ b/crates/ekore_capi/tests/c/test_ome_us.c @@ -10,12 +10,14 @@ static int test_lengths(void) fail |= check_len("ome_us_A_singlet_result_len(0)", ome_us_A_singlet_result_len(0), 0); fail |= check_len("ome_us_A_singlet_result_len(1)", ome_us_A_singlet_result_len(1), 9); fail |= check_len("ome_us_A_singlet_result_len(2)", ome_us_A_singlet_result_len(2), 18); - fail |= check_len("ome_us_A_singlet_result_len(3)", ome_us_A_singlet_result_len(3), 0); + fail |= check_len("ome_us_A_singlet_result_len(3)", ome_us_A_singlet_result_len(3), 27); + fail |= check_len("ome_us_A_singlet_result_len(4)", ome_us_A_singlet_result_len(4), 0); fail |= check_len("ome_us_A_non_singlet_result_len(0)", ome_us_A_non_singlet_result_len(0), 0); fail |= check_len("ome_us_A_non_singlet_result_len(1)", ome_us_A_non_singlet_result_len(1), 4); fail |= check_len("ome_us_A_non_singlet_result_len(2)", ome_us_A_non_singlet_result_len(2), 8); - fail |= check_len("ome_us_A_non_singlet_result_len(3)", ome_us_A_non_singlet_result_len(3), 0); + fail |= check_len("ome_us_A_non_singlet_result_len(3)", ome_us_A_non_singlet_result_len(3), 12); + fail |= check_len("ome_us_A_non_singlet_result_len(4)", ome_us_A_non_singlet_result_len(4), 0); if (!fail) printf("PASS test_lengths\n"); return fail; @@ -28,10 +30,10 @@ static int test_a_non_singlet(void) const double L = 0.0; Cache *c = cache_new(1.0, 0.0); - const size_t len = ome_us_A_non_singlet_result_len(2); + const size_t len = ome_us_A_non_singlet_result_len(3); ComplexF64 a[len]; - ome_us_A_non_singlet(2, c, nf, L, a); + ome_us_A_non_singlet(3, c, nf, L, a); /* LO */ for (int k = 0; k < 4; k++) @@ -39,6 +41,9 @@ static int test_a_non_singlet(void) /* NNLO */ for (int k = 4; k < 8; k++) fail |= check("a_non_singlet", "NNLO", a[k].re, a[k].im, 0., 0., 1e-14); + /* N3LO */ + for (int k = 8; k < 12; k++) + fail |= check("a_non_singlet", "N3LO", a[k].re, a[k].im, 0., 0., 1e-14); cache_delete(c); if (!fail) printf("PASS test_a_non_singlet\n"); @@ -52,10 +57,10 @@ static int test_a_singlet(void) const double L = 100.0; Cache *c = cache_new(2.0, 0.0); - const size_t len = ome_us_A_singlet_result_len(2); + const size_t len = ome_us_A_singlet_result_len(3); ComplexF64 a[len]; - ome_us_A_singlet(2, c, nf, L, a); + ome_us_A_singlet(3, c, nf, L, a); /* LO */ fail |= check("a_singlet", "LO col2 sum", @@ -69,11 +74,16 @@ static int test_a_singlet(void) fail |= check("a_singlet", "NNLO col1 sum", a[10].re + a[13].re + a[16].re, a[10].im + a[13].im + a[16].im, 0., 0., 1e-11); + /* N3LO */ + for (int k = 18; k < 27; k++) + fail |= check("a_singlet", "N3LO", a[k].re, a[k].im, 0., 0., 1e-14); + cache_delete(c); if (!fail) printf("PASS test_a_singlet\n"); return fail; } + static int test_guards(void) { int fail = 0; @@ -84,14 +94,14 @@ static int test_guards(void) ComplexF64 a_s[9]; for (int i = 0; i < 9; i++) a_s[i] = sentinel; - ome_us_A_singlet(3, c, nf, L, a_s); + ome_us_A_singlet(4, c, nf, L, a_s); for (int i = 0; i < 9; i++) - fail |= check("guards", "A_singlet order3 untouched", a_s[i].re, a_s[i].im, sentinel.re, sentinel.im, 0.); + fail |= check("guards", "A_singlet order4 untouched", a_s[i].re, a_s[i].im, sentinel.re, sentinel.im, 0.); ComplexF64 a_ns[4] = {sentinel, sentinel, sentinel, sentinel}; - ome_us_A_non_singlet(3, c, nf, L, a_ns); + ome_us_A_non_singlet(4, c, nf, L, a_ns); for (int i = 0; i < 4; i++) - fail |= check("guards", "A_non_singlet order3 untouched", a_ns[i].re, a_ns[i].im, sentinel.re, sentinel.im, 0.); + fail |= check("guards", "A_non_singlet order4 untouched", a_ns[i].re, a_ns[i].im, sentinel.re, sentinel.im, 0.); cache_delete(c); if (!fail) printf("PASS test_guards\n"); diff --git a/crates/ekore_py/src/ome_us.rs b/crates/ekore_py/src/ome_us.rs index 58c02b33c..a969fd072 100644 --- a/crates/ekore_py/src/ome_us.rs +++ b/crates/ekore_py/src/ome_us.rs @@ -12,7 +12,7 @@ use crate::cache::Cache; /// Compute the tower of the singlet |OME|. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (supported range: < 3). +/// * `matching_order_qcd`: The QCD matching order (supported range: < 4). /// * `cache`: Harmonic sums cache. /// * `nf`: Number of active flavors. /// * `L`: The logarithm parameter. @@ -34,7 +34,7 @@ pub fn A_singlet<'py>( cache, nf, L, - matching_order_qcd >= 3, + matching_order_qcd >= 4, spacelike::A_singlet, 3 ) @@ -43,7 +43,7 @@ pub fn A_singlet<'py>( /// Compute the tower of the non-singlet |OME|. /// /// # Parameters -/// * `matching_order_qcd`: The QCD matching order (supported range: < 3). +/// * `matching_order_qcd`: The QCD matching order (supported range: < 4). /// * `cache`: Harmonic sums cache. /// * `nf`: Number of active flavors. /// * `L`: The logarithm parameter. @@ -65,7 +65,7 @@ pub fn A_non_singlet<'py>( cache, nf, L, - matching_order_qcd >= 3, + matching_order_qcd >= 4, spacelike::A_non_singlet, 2 ) diff --git a/crates/ekore_py/tests/test_ome_us.py b/crates/ekore_py/tests/test_ome_us.py index 21359e24c..b1a3c7b07 100644 --- a/crates/ekore_py/tests/test_ome_us.py +++ b/crates/ekore_py/tests/test_ome_us.py @@ -5,7 +5,8 @@ @pytest.mark.parametrize( - "order, expected_shape", [(0, (0, 3, 3)), (1, (1, 3, 3)), (2, (2, 3, 3))] + "order, expected_shape", + [(0, (0, 3, 3)), (1, (1, 3, 3)), (2, (2, 3, 3)), (3, (3, 3, 3))], ) def test_a_singlet_shape(order, expected_shape): c = Cache(0.0 + 0.0j) @@ -15,7 +16,8 @@ def test_a_singlet_shape(order, expected_shape): @pytest.mark.parametrize( - "order, expected_shape", [(0, (0, 2, 2)), (1, (1, 2, 2)), (2, (2, 2, 2))] + "order, expected_shape", + [(0, (0, 2, 2)), (1, (1, 2, 2)), (2, (2, 2, 2)), (3, (3, 2, 2))], ) def test_a_non_singlet_shape(order, expected_shape): c = Cache(0.0 + 0.0j) @@ -29,12 +31,14 @@ def test_a_non_singlet(): L = 0.0 c = Cache(1.0 + 0.0j) - a = ome_us.A_non_singlet(2, c, nf, L) + a = ome_us.A_non_singlet(3, c, nf, L) # LO assert_allclose(a[0], np.zeros((2, 2)), atol=1e-14) # NNLO assert_allclose(a[1], np.zeros((2, 2)), atol=1e-14) + # N3LO + assert_allclose(a[2], np.zeros((2, 2)), atol=1e-14) def test_a_singlet(): @@ -42,7 +46,7 @@ def test_a_singlet(): L = 100.0 c = Cache(2.0 + 0.0j) - a = ome_us.A_singlet(2, c, nf, L) + a = ome_us.A_singlet(3, c, nf, L) # LO assert_allclose(a[0, 0, 2] + a[0, 1, 2] + a[0, 2, 2], 0.0, atol=1e-10) @@ -52,6 +56,9 @@ def test_a_singlet(): assert_allclose(a[1, 0, 0] + a[1, 1, 0] + a[1, 2, 0], 0.0, atol=2e-6) assert_allclose(a[1, 0, 1] + a[1, 1, 1] + a[1, 2, 1], 0.0, atol=1e-11) + # N3LO + assert_allclose(a[2], np.zeros((3, 3)), atol=1e-14) + def test_guards_invalid_order_raises(): nf = 4 @@ -59,6 +66,6 @@ def test_guards_invalid_order_raises(): c = Cache(1.234 + 0.0j) with pytest.raises(ValueError): - ome_us.A_singlet(3, c, nf, L) + ome_us.A_singlet(4, c, nf, L) with pytest.raises(ValueError): - ome_us.A_non_singlet(3, c, nf, L) + ome_us.A_non_singlet(4, c, nf, L) From 5f1b591e8bd7d458724ed6ff117a39275385faec Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 12:27:54 +0530 Subject: [PATCH 6/8] Updated ffi types --- .../unpolarized/spacelike/as3.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs index 5912d042d..6a52469f4 100644 --- a/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs +++ b/crates/ekore/src/operator_matrix_elements/unpolarized/spacelike/as3.rs @@ -1,5 +1,7 @@ //! |N3LO| unpolarized, space-like |OME| via external `libome` C ABI. +use std::ffi::{c_int, c_uint}; + use num::Zero; use num::complex::Complex; @@ -25,27 +27,27 @@ impl From for Complex { } unsafe extern "C" { - fn ome_as3_Agg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_Agq(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_Aqg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_AHg(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_AHq(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_AqqPS(n: OmeComplex, nf: u32, L: f64) -> OmeComplex; - fn ome_as3_AqqNS(n: OmeComplex, nf: u32, L: f64, eta: i32) -> OmeComplex; + fn ome_as3_Agg(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_Agq(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_Aqg(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_AHg(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_AHq(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_AqqPS(n: OmeComplex, nf: c_uint, L: f64) -> OmeComplex; + fn ome_as3_AqqNS(n: OmeComplex, nf: c_uint, L: f64, eta: c_int) -> OmeComplex; } /// Compute the |N3LO| singlet |OME|. pub(super) fn A_singlet(c: &mut Cache, nf: u8, L: f64) -> [[Complex; 3]; 3] { let n: OmeComplex = c.n().into(); - let nf_u32 = u32::from(nf); + let nf_c = c_uint::from(nf); - let a_gg = unsafe { Complex::from(ome_as3_Agg(n, nf_u32, L)) }; - let a_gq = unsafe { Complex::from(ome_as3_Agq(n, nf_u32, L)) }; - let a_qg = unsafe { Complex::from(ome_as3_Aqg(n, nf_u32, L)) }; - let a_hg = unsafe { Complex::from(ome_as3_AHg(n, nf_u32, L)) }; - let a_hq = unsafe { Complex::from(ome_as3_AHq(n, nf_u32, L)) }; - let a_qq_ps = unsafe { Complex::from(ome_as3_AqqPS(n, nf_u32, L)) }; - let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_u32, L, 1)) }; + let a_gg = unsafe { Complex::from(ome_as3_Agg(n, nf_c, L)) }; + let a_gq = unsafe { Complex::from(ome_as3_Agq(n, nf_c, L)) }; + let a_qg = unsafe { Complex::from(ome_as3_Aqg(n, nf_c, L)) }; + let a_hg = unsafe { Complex::from(ome_as3_AHg(n, nf_c, L)) }; + let a_hq = unsafe { Complex::from(ome_as3_AHq(n, nf_c, L)) }; + let a_qq_ps = unsafe { Complex::from(ome_as3_AqqPS(n, nf_c, L)) }; + let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_c, L, 1 as c_int)) }; [ [a_gg, a_gq, Complex::::zero()], @@ -57,8 +59,8 @@ pub(super) fn A_singlet(c: &mut Cache, nf: u8, L: f64) -> [[Complex; 3]; 3] /// Compute the |N3LO| non-singlet |OME|. pub(super) fn A_ns(c: &mut Cache, nf: u8, L: f64) -> [[Complex; 2]; 2] { let n: OmeComplex = c.n().into(); - let nf_u32 = u32::from(nf); - let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_u32, L, -1)) }; + let nf_c = c_uint::from(nf); + let a_qq_ns = unsafe { Complex::from(ome_as3_AqqNS(n, nf_c, L, -1 as c_int)) }; [ [a_qq_ns, Complex::::zero()], From 56a9123481e450700bc52fb74207a43ff39c7225 Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Sat, 29 Aug 2026 12:28:15 +0530 Subject: [PATCH 7/8] Updated build.rs --- crates/ekore/build.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/ekore/build.rs b/crates/ekore/build.rs index 8006be32c..f71a7bafc 100644 --- a/crates/ekore/build.rs +++ b/crates/ekore/build.rs @@ -7,10 +7,19 @@ fn main() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let libome_dir = manifest_dir.join("../../extras/gsoc/libome"); - println!("cargo:rerun-if-changed={}", libome_dir.display()); + println!( + "cargo:rerun-if-changed={}", + libome_dir.join("ome.cpp").display() + ); + println!( + "cargo:rerun-if-changed={}", + libome_dir.join("ome.h").display() + ); cc::Build::new() .cpp(true) + .flag_if_supported("-std=c++11") + .flag_if_supported("/std:c++14") .include(&libome_dir) .file(libome_dir.join("ome.cpp")) .compile("ome"); From 0a3369d98a55ac6ced918fd1aebc06378e1e0702 Mon Sep 17 00:00:00 2001 From: AkshatRai07 Date: Thu, 3 Sep 2026 17:15:34 +0530 Subject: [PATCH 8/8] Added gitlab/libome in README --- extras/gsoc/libome/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extras/gsoc/libome/README.md b/extras/gsoc/libome/README.md index 0048962d9..85f8d251a 100644 --- a/extras/gsoc/libome/README.md +++ b/extras/gsoc/libome/README.md @@ -6,3 +6,12 @@ This directory contains a mock C++ library exposing a C ABI for $\mathcal{O}(\al The C ABI declarations are provided in `ome.h`. Currently, the functions return dummy complex values (`{0.0, 0.0}`) for testing Rust FFI integration. + +## Future Integration + +In the future, this mock library is intended to be replaced with the upstream implementation from [libome](https://gitlab.com/libome/libome). + +Integration can be done either by: + +1. **Dynamic Linking**: Linking directly against a pre-installed `libome.so`. +2. **Git Submodule**: Adding the upstream `libome` repository as a submodule and compiling it via CMake in `crates/ekore/build.rs`.