diff --git a/.github/actions/cache-data/action.yml b/.github/actions/cache-data/action.yml index 2894856a..36f7b9ef 100644 --- a/.github/actions/cache-data/action.yml +++ b/.github/actions/cache-data/action.yml @@ -10,7 +10,7 @@ runs: uses: actions/cache@v4 with: path: neopdf-data - key: data-v11 + key: data-v12 - name: Download data if cache miss if: steps.cache-data.outputs.cache-hit != 'true' run: | diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 22120433..f22e33d9 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -34,7 +34,7 @@ jobs: id: bench run: | export NEOPDF_DATA_PATH=${PWD}/neopdf-data - # The awk command filters out the verbose test summary before the benchmark results. + # TODO: remove the version specification cargo bench -p neopdf -- --nocapture | awk '/^test result: ok\./ {p=1; next} p' | tee benchmark_results.txt - name: Post benchmark results as a PR comment diff --git a/.github/workflows/run-python.yml b/.github/workflows/run-python.yml index b1582aa6..da96c914 100644 --- a/.github/workflows/run-python.yml +++ b/.github/workflows/run-python.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/run-rust.yml b/.github/workflows/run-rust.yml index 73f6761d..ee25b5c9 100644 --- a/.github/workflows/run-rust.yml +++ b/.github/workflows/run-rust.yml @@ -12,8 +12,9 @@ env: jobs: build: strategy: + fail-fast: false matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: @@ -37,9 +38,12 @@ jobs: run: | export NEOPDF_DATA_PATH=${PWD}/neopdf-data cargo test --workspace --exclude neopdf_pyapi --exclude neopdf_tmdlib --exclude neopdf_wolfram --no-fail-fast 2> >(tee stderr 1>&2) - sed -i 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' stderr + if [ "${{ matrix.os }}" != "macos-latest" ]; then + sed -i 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' stderr + fi - name: Generate code coverage + if: matrix.os == 'ubuntu-latest' run: | find . -name '*.profraw' -exec $(rustc --print target-libdir)/../bin/llvm-profdata merge -sparse -o neopdf.profdata {} + ( sed -nE 's/[[:space:]]+Running( unittests|) [^[:space:]]+ \(([^)]+)\)/\2/p' stderr && echo target/debug/doctestbins/*/rust_out | tr ' ' "\n" ) | \ @@ -57,6 +61,7 @@ jobs: grep SF lcov.info | sort -u | sed 's/SF://' - name: Upload to codecov.io + if: matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index cc951f3b..a1ce76a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added `LogFourCubic` and `LogFiveCubic` interpolation strategies. +- Added new methods to the Fortran and C/C++ APIs to write and compress grids + with `xi` and `delta` dependence. + +### Changed + +- Breaking change to the Python API for the `PyMetaData` and `PySubgrid`. +- Bump `PyO3` and `numpy` versions to `v0.27` to support Python `3.14`. +- Extended the Grid layout to support GTMDs and GPDs (https://github.com/QCDLab/neopdf/pull/79). + ## [0.2.0] - 06/10/2025 ### Added diff --git a/Cargo.lock b/Cargo.lock index 8909c440..0a4a2623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1361,6 +1361,30 @@ dependencies = [ "serde", ] +[[package]] +name = "neopdf" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b0aaca320ec52a7b70f90e14b5046b194db82fcdf59d0692e539aff111e816" +dependencies = [ + "bincode", + "flate2", + "git-version", + "indicatif", + "itertools 0.13.0", + "lz4_flex", + "ndarray", + "ninterp", + "rayon", + "regex", + "reqwest", + "serde", + "serde_yaml", + "tar", + "tempfile", + "thiserror 1.0.69", +] + [[package]] name = "neopdf" version = "0.2.1-alpha1" @@ -1373,6 +1397,7 @@ dependencies = [ "itertools 0.13.0", "lz4_flex", "ndarray", + "neopdf 0.2.0", "ninterp", "rayon", "regex", @@ -1390,7 +1415,7 @@ version = "0.2.1-alpha1" dependencies = [ "cbindgen", "ndarray", - "neopdf", + "neopdf 0.2.1-alpha1", ] [[package]] @@ -1401,7 +1426,7 @@ dependencies = [ "assert_fs", "clap 4.5.41", "ndarray", - "neopdf", + "neopdf 0.2.1-alpha1", "neopdf_tmdlib", "predicates", "serde", @@ -1415,7 +1440,7 @@ name = "neopdf_pyapi" version = "0.2.1-alpha1" dependencies = [ "ndarray", - "neopdf", + "neopdf 0.2.1-alpha1", "numpy", "pyo3", "thiserror 1.0.69", @@ -1436,7 +1461,7 @@ name = "neopdf_wolfram" version = "0.2.1-alpha1" dependencies = [ "lazy_static", - "neopdf", + "neopdf 0.2.1-alpha1", "parking_lot", "wolfram-library-link", ] @@ -1495,9 +1520,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "numpy" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f1dee9aa8d3f6f8e8b9af3803006101bb3653866ef056d530d53ae68587191" +checksum = "0fa24ffc88cf9d43f7269d6b6a0d0a00010924a8cc90604a21ef9c433b66998d" dependencies = [ "libc", "ndarray", @@ -1710,9 +1735,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +checksum = "37a6df7eab65fc7bee654a421404947e10a0f7085b6951bf2ea395f4659fb0cf" dependencies = [ "indoc", "libc", @@ -1727,19 +1752,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +checksum = "f77d387774f6f6eec64a004eac0ed525aab7fa1966d94b42f743797b3e395afb" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +checksum = "2dd13844a4242793e02df3e2ec093f540d948299a6a77ea9ce7afd8623f542be" dependencies = [ "libc", "pyo3-build-config", @@ -1747,9 +1771,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +checksum = "eaf8f9f1108270b90d3676b8679586385430e5c0bb78bb5f043f95499c821a71" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1759,9 +1783,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +checksum = "70a3b2274450ba5288bc9b8c1b69ff569d1d61189d4bff38f8d22e03d17f932b" dependencies = [ "heck 0.5.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 6acf302d..7e972d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,8 @@ clap = { version = "4.5", features = ["derive"] } terminal_size = "0.3.0" # Python bindings -numpy = "0.25" -pyo3 = { version = "0.25", features = ["extension-module"] } +numpy = "0.27" +pyo3 = { version = "0.27", features = ["extension-module"] } # Build dependencies cbindgen = "0.26.0" diff --git a/README.md b/README.md index 8f912848..ff621524 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,13 @@

- NeoPDF is a fast, reliable, and scalable interpolation library for both collinear - and transverse momentum dependent Parton Distribution Functions with modern features - designed for both present and future hadron collider experiments: + NeoPDF is a fast, reliable, and scalable interpolation library for Non-Perturbative Distribution Functions + with modern features designed for both present and future hadron collider experiments:

+## Supported distributions + +

+ NeoPDF supports generic classes of distributions that are functions of different combinations of the kinematic + variables. Examples of distribution functions are given in the diagram below with their simplified relationships. +

+ +```mermaid +graph TD + +A["**Generalized Tranverse Momentum Distribution**
GTMD(x, ξ, Δ, kT​​, Q²)"] + +A -->|∫ dkT| B["**Generalized Parton Distributions**
GPD(x, ξ, Δ, Q²)"] +A -->|ξ → 0, Δ → 0| C["**Transverse Momentum Distributions**
TMD(x, kT, Q²)"] + +B -->|ξ → 0, Δ → 0| D["**Collinear Parton Distribution Functions**
PDF(x, Q²)"] +C -->|∫ dkT| D + +style A fill:#ffd580,stroke:#b8860b,stroke-width:2px +style B fill:#add8e6,stroke:#1e90ff,stroke-width:2px +style C fill:#98fb98,stroke:#228b22,stroke-width:2px +style D fill:#f08080,stroke:#8b0000,stroke-width:2px +``` + ## Quick Links - [Documentation](https://qcdlab.github.io/neopdf/) | [Rust Crate Documentation](https://docs.rs/neopdf/0.1.1/neopdf/) | [C++ API Reference](https://neopdf.readthedocs.io/en/latest/) diff --git a/neopdf/Cargo.toml b/neopdf/Cargo.toml index ab10c922..b54b9e26 100644 --- a/neopdf/Cargo.toml +++ b/neopdf/Cargo.toml @@ -30,6 +30,9 @@ regex.workspace = true git-version.workspace = true indicatif.workspace = true +# For backward compatibility with v0.2.0 format +neopdf_legacy = { package = "neopdf", version = "0.2.0" } + [dev-dependencies] criterion.workspace = true diff --git a/neopdf/src/alphas.rs b/neopdf/src/alphas.rs index 180e6033..1be0151a 100644 --- a/neopdf/src/alphas.rs +++ b/neopdf/src/alphas.rs @@ -82,12 +82,14 @@ impl AlphaSAnalytic { meta.alphas_order_qcd }; + let (_m_up, _m_down, _m_strange, m_charm, m_bottom, m_top) = meta.quark_masses(); + Ok(Self { qcd_order: alphas_order_qcd, lambda_maps, - mc_sq: meta.m_charm * meta.m_charm, - mb_sq: meta.m_bottom * meta.m_bottom, - mt_sq: meta.m_top * meta.m_top, + mc_sq: m_charm * m_charm, + mb_sq: m_bottom * m_bottom, + mt_sq: m_top * m_top, num_fl: meta.number_flavors, fl_scheme: meta.flavor_scheme.clone(), }) @@ -162,7 +164,7 @@ impl AlphaSAnalytic { let mut tmp = 1.0; if self.qcd_order == 0 { - return 0.118; // _alpha_mz reference value + return 0.118; // `_alpha_mz` reference value } if self.qcd_order > 1 { @@ -202,12 +204,14 @@ pub struct AlphaSInterpol { impl AlphaSInterpol { pub fn from_metadata(meta: &MetaData) -> Result { - let (q_values, alphas_vals): (Vec<_>, Vec<_>) = meta - .alphas_q_values + let alphas_q_values = &meta.alphas_q_values; + let alphas_vals = &meta.alphas_vals; + + let (q_values, alphas_vals_filtered): (Vec<_>, Vec<_>) = alphas_q_values .iter() - .zip(&meta.alphas_vals) + .zip(alphas_vals) .enumerate() - .filter(|(i, (&q, _))| *i == 0 || q != meta.alphas_q_values[i - 1]) + .filter(|(i, (&q, _))| *i == 0 || q != alphas_q_values[i - 1]) .map(|(_, (&q, &alpha))| (q, alpha)) .unzip(); @@ -215,7 +219,7 @@ impl AlphaSInterpol { let interpolator = Interp1D::new( q2_values.into(), - alphas_vals.into(), + alphas_vals_filtered.into(), AlphaSCubicInterpolation, Extrapolate::Error, ) diff --git a/neopdf/src/converter.rs b/neopdf/src/converter.rs index 824bc220..16b67348 100644 --- a/neopdf/src/converter.rs +++ b/neopdf/src/converter.rs @@ -11,7 +11,7 @@ use regex::Regex; use super::gridpdf::GridArray; use super::metadata::{InterpolatorType, MetaData}; use super::parser::LhapdfSet; -use super::subgrid::{ParamRange, SubGrid}; +use super::subgrid::{GridData, ParamRange, SubGrid}; use super::writer::GridArrayCollection; /// Converts an LHAPDF set to the NeoPDF format and writes it to disk. @@ -155,11 +155,19 @@ pub fn combine_lhapdf_npdfs>( xs: xs.clone(), q2s: q2s.clone(), kts: kts.clone(), - grid: concatenated, + xis: subgrids[0].xis.clone(), + deltas: subgrids[0].deltas.clone(), + grid: GridData::Grid6D( + concatenated + .into_dimensionality() + .expect("Failed to convert to 6D"), + ), nucleons, alphas: alphas.clone(), nucleons_range, alphas_range: subgrids[0].alphas_range, + xi_range: subgrids[0].xi_range, + delta_range: subgrids[0].delta_range, kt_range: subgrids[0].kt_range, x_range: subgrids[0].x_range, q2_range: subgrids[0].q2_range, @@ -284,11 +292,19 @@ pub fn combine_lhapdf_alphas>( xs: xs.clone(), q2s: q2s.clone(), kts: kts.clone(), - grid: concatenated, + xis: subgrids[0].xis.clone(), + deltas: subgrids[0].deltas.clone(), + grid: GridData::Grid6D( + concatenated + .into_dimensionality() + .expect("Failed to convert to 6D"), + ), nucleons: nucleons.clone(), alphas, nucleons_range: subgrids[0].nucleons_range, alphas_range, + xi_range: subgrids[0].xi_range, + delta_range: subgrids[0].delta_range, kt_range: subgrids[0].kt_range, x_range: subgrids[0].x_range, q2_range: subgrids[0].q2_range, diff --git a/neopdf/src/gridpdf.rs b/neopdf/src/gridpdf.rs index 501fe8e8..65dc917d 100644 --- a/neopdf/src/gridpdf.rs +++ b/neopdf/src/gridpdf.rs @@ -54,15 +54,29 @@ impl GridArray { let subgrids = subgrid_data .into_iter() .map(|data| { - SubGrid::new( - data.nucleons, - data.alphas, - data.kts, - data.xs, - data.q2s, - nflav, - data.grid_data, - ) + if data.xis.len() > 1 || data.deltas.len() > 1 { + SubGrid::new_8d( + data.nucleons, + data.alphas, + data.xis, + data.deltas, + data.kts, + data.xs, + data.q2s, + nflav, + data.grid_data, + ) + } else { + SubGrid::new( + data.nucleons, + data.alphas, + data.kts, + data.xs, + data.q2s, + nflav, + data.grid_data, + ) + } }) .collect(); @@ -103,7 +117,8 @@ impl GridArray { subgrid_idx: usize, ) -> f64 { let pid_idx = self.pid_index(flavor_id).expect("Invalid flavor ID"); - self.subgrids[subgrid_idx].grid[[nucleon_idx, alpha_idx, pid_idx, kt_idx, x_idx, q2_idx]] + let grid_view = self.subgrids[subgrid_idx].grid.view(); + grid_view[[nucleon_idx, alpha_idx, pid_idx, kt_idx, x_idx, q2_idx]] } /// Finds the index of the subgrid that contains the given point. @@ -169,6 +184,8 @@ impl GridArray { RangeParameters::new( global_range(&self.subgrids, |sg| &sg.nucleons_range), global_range(&self.subgrids, |sg| &sg.alphas_range), + global_range(&self.subgrids, |sg| &sg.xi_range), + global_range(&self.subgrids, |sg| &sg.delta_range), global_range(&self.subgrids, |sg| &sg.kt_range), global_range(&self.subgrids, |sg| &sg.x_range), global_range(&self.subgrids, |sg| &sg.q2_range), @@ -261,7 +278,7 @@ impl GridPDF { (0..knot_array.pids.len()) .map(|pid_idx| { InterpolatorFactory::create( - info.interpolator_type.to_owned(), + info.interpolator_type.clone(), subgrid, pid_idx, ) @@ -297,6 +314,8 @@ impl GridPDF { InterpolatorType::LogBilinear | InterpolatorType::LogBicubic | InterpolatorType::LogTricubic + | InterpolatorType::LogFourCubic + | InterpolatorType::LogFiveCubic | InterpolatorType::LogChebyshev ); @@ -462,6 +481,8 @@ mod tests { nucleons: vec![1.0], alphas: vec![0.118], kts: vec![0.0], + xis: vec![0.0], + deltas: vec![0.0], xs: vec![1.0, 2.0, 3.0], q2s: vec![4.0, 5.0], grid_data: vec![ @@ -471,7 +492,13 @@ mod tests { let flavors = vec![21, 22]; let grid_array = GridArray::new(subgrid_data, flavors); - assert_eq!(grid_array.subgrids[0].grid.shape(), &[1, 1, 2, 1, 3, 2]); + // Grid shape is 6D: [nucleons, alphas, pids, kT, x, Q²] + match &grid_array.subgrids[0].grid { + crate::subgrid::GridData::Grid6D(grid) => { + assert_eq!(grid.shape(), &[1, 1, 2, 1, 3, 2]); + } + _ => std::panic!("Expected 6D grid"), + } assert!(grid_array.find_subgrid(&[1.5, 4.5]).is_some()); } } diff --git a/neopdf/src/interpolator.rs b/neopdf/src/interpolator.rs index 43e886bc..3338c6f4 100644 --- a/neopdf/src/interpolator.rs +++ b/neopdf/src/interpolator.rs @@ -11,7 +11,7 @@ //! Interpolation strategies are defined in `strategy.rs`. //! The [`SubGrid`] struct is defined in `subgrid.rs`. -use ndarray::{s, OwnedRepr}; +use ndarray::{s, IxDyn, OwnedRepr}; use ninterp::data::{InterpData2D, InterpData3D}; use ninterp::error::InterpolateError; use ninterp::interpolator::{ @@ -24,7 +24,8 @@ use ninterp::strategy::Linear; use super::metadata::InterpolatorType; use super::strategy::{ BilinearInterpolation, LogBicubicInterpolation, LogBilinearInterpolation, - LogChebyshevBatchInterpolation, LogChebyshevInterpolation, LogTricubicInterpolation, + LogChebyshevBatchInterpolation, LogChebyshevInterpolation, LogFiveCubicInterpolation, + LogFourCubicInterpolation, LogTricubicInterpolation, }; use super::subgrid::SubGrid; @@ -34,43 +35,82 @@ use super::subgrid::SubGrid; /// dimensions of the PDF grid data. #[derive(Debug, Clone, Copy)] pub enum InterpolationConfig { - /// 2D interpolation, typically in `x` (momentum fraction) and `Q²` (energy scale). + /// 2D interpolation, typically in `x` (momentum fraction) and `Q2` (energy scale). TwoD, /// 3D interpolation, including a dimension for varying nucleon numbers `A`, - /// in addition to `x` and `Q²`. + /// in addition to `x` and `Q2`. ThreeDNucleons, /// 3D interpolation, including a dimension for varying `alpha_s` values, - /// in addition to `x` and `Q²`. + /// in addition to `x` and `Q2`. ThreeDAlphas, + /// 3D interpolation, including a dimension for varying `xi` values, + /// in addition to `x` and `Q2`. + ThreeDXi, + /// 3D interpolation, including a dimension for varying `delta` values, + /// in addition to `x` and `Q2`. + ThreeDDelta, /// 3D interpolation, including a dimension for varying `kT` values, - /// in addition to `x` and `Q²`. + /// in addition to `x` and `Q2`. ThreeDKt, - /// 4D interpolation, covering nucleon numbers `A`, `alpha_s`, `x`, and `Q²`. + /// 4D interpolation, covering nucleon numbers `A`, `alpha_s`, `x`, and `Q2`. FourDNucleonsAlphas, - /// 4D interpolation, covering nucleon numbers `A`, kT, `x`, and `Q²`. + /// 4D interpolation, covering nucleon numbers `A`, kT, `x`, and `Q2`. FourDNucleonsKt, - /// 4D interpolation, covering `alpha_s`, kT, `x`, and `Q²`. + /// 4D interpolation, covering `alpha_s`, kT, `x`, and `Q2`. FourDAlphasKt, - /// 5D interpolation, covering nucleon numbers `A`, `alpha_s`, `kT`, `x`, and `Q²`. + /// 4D interpolation, covering `xi`, `delta`, `x`, and `Q2`. + FourDXiDelta, + /// 5D interpolation, covering `kT`, `xi`, `delta`, `x`, and `Q2`. FiveD, + /// 6D interpolation, covering `A`, `kT`, `xi`, `delta`, `x`, and `Q2`. + SixD, + /// 7D interpolation, covering `A`, `alpha_s`, `xi`, `delta`, `kT`, `x`, and `Q2`. + SevenD, } impl InterpolationConfig { - /// Determines the interpolation configuration from the number of nucleons and alpha_s values. + /// Determines the interpolation configuration from the dimension sizes. /// - /// # Panics + /// # Arguments /// - /// Panics if the combination of `n_nucleons` and `n_alphas` is not supported. - pub fn from_dimensions(n_nucleons: usize, n_alphas: usize, n_kts: usize) -> Self { - match (n_nucleons > 1, n_alphas > 1, n_kts > 1) { - (false, false, false) => Self::TwoD, - (true, false, false) => Self::ThreeDNucleons, - (false, true, false) => Self::ThreeDAlphas, - (false, false, true) => Self::ThreeDKt, - (true, true, false) => Self::FourDNucleonsAlphas, - (true, false, true) => Self::FourDNucleonsKt, - (false, true, true) => Self::FourDAlphasKt, - (true, true, true) => Self::FiveD, + /// * `n_nucleons` - Number of nucleon values + /// * `n_alphas` - Number of `alpha_s` values + /// * `n_xis` - Number of skeweness `xi` values + /// * `n_deltas` - Number of total momentum `delta` values + /// * `n_kts` - Number of transverse momentum `kT` values + pub fn from_dimensions( + n_nucleons: usize, + n_alphas: usize, + n_xis: usize, + n_deltas: usize, + n_kts: usize, + ) -> Self { + let dims = ( + n_nucleons > 1, + n_alphas > 1, + n_xis > 1, + n_deltas > 1, + n_kts > 1, + ); + + match dims { + (false, false, false, false, false) => Self::TwoD, + (true, false, false, false, false) => Self::ThreeDNucleons, + (false, true, false, false, false) => Self::ThreeDAlphas, + (false, false, true, false, false) => Self::ThreeDXi, + (false, false, false, true, false) => Self::ThreeDDelta, + (false, false, false, false, true) => Self::ThreeDKt, + (true, true, false, false, false) => Self::FourDNucleonsAlphas, + (true, false, false, false, true) => Self::FourDNucleonsKt, + (false, true, false, false, true) => Self::FourDAlphasKt, + (false, false, true, true, false) => Self::FourDXiDelta, + (false, false, true, true, true) => Self::FiveD, + (true, false, true, true, true) => Self::SixD, + (true, true, true, true, true) => Self::SevenD, + _ => panic!( + "Unsupported dimension combination: nucleons={}, alphas={}, xis={}, deltas={}, kts={}", + n_nucleons, n_alphas, n_xis, n_deltas, n_kts + ), } } } @@ -167,6 +207,12 @@ impl InterpolatorFactory { InterpolationConfig::ThreeDAlphas => { Self::interpolator_xfxq2_alphas(interp_type, subgrid, pid_index) } + InterpolationConfig::ThreeDXi => { + Self::interpolator_xfxq2_xi(interp_type, subgrid, pid_index) + } + InterpolationConfig::ThreeDDelta => { + Self::interpolator_xfxq2_delta(interp_type, subgrid, pid_index) + } InterpolationConfig::ThreeDKt => { Self::interpolator_xfxq2_kts(interp_type, subgrid, pid_index) } @@ -179,9 +225,18 @@ impl InterpolatorFactory { InterpolationConfig::FourDAlphasKt => { Self::interpolator_xfxq2_alphas_kts(interp_type, subgrid, pid_index) } + InterpolationConfig::FourDXiDelta => { + Self::interpolator_xfxq2_xi_delta(interp_type, subgrid, pid_index) + } InterpolationConfig::FiveD => { Self::interpolator_xfxq2_5dim(interp_type, subgrid, pid_index) } + InterpolationConfig::SixD => { + Self::interpolator_xfxq2_6dim(interp_type, subgrid, pid_index) + } + InterpolationConfig::SevenD => { + Self::interpolator_xfxq2_7dim(interp_type, subgrid, pid_index) + } } } @@ -242,10 +297,8 @@ impl InterpolatorFactory { subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![.., 0, pid_index, 0, .., ..]) - .to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![.., 0, pid_index, 0, .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order((subgrid.nucleons.len(), subgrid.xs.len(), subgrid.q2s.len())) .expect("Failed to reshape 3D data"); @@ -282,10 +335,8 @@ impl InterpolatorFactory { subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![0, .., pid_index, 0, .., ..]) - .to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![0, .., pid_index, 0, .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order((subgrid.alphas.len(), subgrid.xs.len(), subgrid.q2s.len())) .expect("Failed to reshape 3D data"); @@ -322,10 +373,8 @@ impl InterpolatorFactory { subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![0, 0, pid_index, .., .., ..]) - .to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![0, 0, pid_index, .., .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order((subgrid.kts.len(), subgrid.xs.len(), subgrid.q2s.len())) .expect("Failed to reshape 3D data"); @@ -357,15 +406,93 @@ impl InterpolatorFactory { } } - fn interpolator_xfxq2_nucleons_alphas( + fn interpolator_xfxq2_xi( + interp_type: InterpolatorType, + subgrid: &SubGrid, + pid_index: usize, + ) -> Box { + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![0, 0, .., 0, 0, pid_index, .., ..]) + .to_owned(); + let reshaped_data = grid_data + .into_shape_with_order((subgrid.xis.len(), subgrid.xs.len(), subgrid.q2s.len())) + .expect("Failed to reshape 3D data"); + + match interp_type { + InterpolatorType::LogTricubic => Box::new( + Interp3D::new( + subgrid.xis.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + reshaped_data, + LogTricubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 3D interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + Interp3D::new( + subgrid.xis.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + reshaped_data, + LogChebyshevInterpolation::<3>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 3D interpolator"), + ), + _ => panic!("Unsupported 3D xi interpolator: {:?}", interp_type), + } + } + + fn interpolator_xfxq2_delta( interp_type: InterpolatorType, subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![.., .., pid_index, 0, .., ..]) + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![0, 0, 0, .., 0, pid_index, .., ..]) .to_owned(); + let reshaped_data = grid_data + .into_shape_with_order((subgrid.deltas.len(), subgrid.xs.len(), subgrid.q2s.len())) + .expect("Failed to reshape 3D data"); + + match interp_type { + InterpolatorType::LogTricubic => Box::new( + Interp3D::new( + subgrid.deltas.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + reshaped_data, + LogTricubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 3D interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + Interp3D::new( + subgrid.deltas.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + reshaped_data, + LogChebyshevInterpolation::<3>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 3D interpolator"), + ), + _ => panic!("Unsupported 3D delta interpolator: {:?}", interp_type), + } + } + + fn interpolator_xfxq2_nucleons_alphas( + interp_type: InterpolatorType, + subgrid: &SubGrid, + pid_index: usize, + ) -> Box { + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![.., .., pid_index, 0, .., ..]).to_owned(); let coords = vec![ subgrid.nucleons.to_owned(), subgrid.alphas.to_owned(), @@ -386,6 +513,24 @@ impl InterpolatorFactory { InterpND::new(coords, reshaped_data.into_dyn(), Linear, Extrapolate::Clamp) .expect("Failed to create 4D interpolator"), ), + InterpolatorType::LogFourCubic => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogFourCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogFourCubic interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogChebyshevInterpolation::<4>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogChebyshev interpolator"), + ), _ => panic!("Unsupported 4D interpolator: {:?}", interp_type), } } @@ -395,10 +540,8 @@ impl InterpolatorFactory { subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![.., 0, pid_index, .., .., ..]) - .to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![.., 0, pid_index, .., .., ..]).to_owned(); let coords = vec![ subgrid.nucleons.mapv(f64::ln), subgrid.kts.mapv(f64::ln), @@ -419,6 +562,24 @@ impl InterpolatorFactory { InterpND::new(coords, reshaped_data.into_dyn(), Linear, Extrapolate::Clamp) .expect("Failed to create 4D interpolator"), ), + InterpolatorType::LogFourCubic => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogFourCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogFourCubic interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogChebyshevInterpolation::<4>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogChebyshev interpolator"), + ), _ => panic!("Unsupported 4D interpolator: {:?}", interp_type), } } @@ -428,10 +589,8 @@ impl InterpolatorFactory { subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![0, .., pid_index, .., .., ..]) - .to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![0, .., pid_index, .., .., ..]).to_owned(); let coords = vec![ subgrid.alphas.mapv(f64::ln), subgrid.kts.mapv(f64::ln), @@ -452,42 +611,221 @@ impl InterpolatorFactory { InterpND::new(coords, reshaped_data.into_dyn(), Linear, Extrapolate::Clamp) .expect("Failed to create 4D interpolator"), ), + InterpolatorType::LogFourCubic => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogFourCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogFourCubic interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogChebyshevInterpolation::<4>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogChebyshev interpolator"), + ), _ => panic!("Unsupported 4D interpolator: {:?}", interp_type), } } + fn interpolator_xfxq2_xi_delta( + interp_type: InterpolatorType, + subgrid: &SubGrid, + pid_index: usize, + ) -> Box { + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![0, 0, .., .., 0, pid_index, .., ..]) + .to_owned(); + let coords = vec![ + subgrid.xis.mapv(f64::ln), + subgrid.deltas.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + ]; + let reshaped_data = grid_data + .into_shape_with_order(( + subgrid.xis.len(), + subgrid.deltas.len(), + subgrid.xs.len(), + subgrid.q2s.len(), + )) + .expect("Failed to reshape 4D data"); + + match interp_type { + InterpolatorType::InterpNDLinear => Box::new( + InterpND::new(coords, reshaped_data.into_dyn(), Linear, Extrapolate::Clamp) + .expect("Failed to create 4D interpolator"), + ), + InterpolatorType::LogFourCubic => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogFourCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogFourCubic interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogChebyshevInterpolation::<4>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 4D LogChebyshev interpolator"), + ), + _ => panic!("Unsupported 4D xi/delta interpolator: {:?}", interp_type), + } + } + fn interpolator_xfxq2_5dim( interp_type: InterpolatorType, subgrid: &SubGrid, pid_index: usize, ) -> Box { - let grid_data = subgrid - .grid - .slice(s![.., .., pid_index, .., .., ..]) + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![0, 0, .., .., .., pid_index, .., ..]) + .to_owned(); + let coords = vec![ + subgrid.kts.mapv(f64::ln), + subgrid.xis.mapv(f64::ln), + subgrid.deltas.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + ]; + let reshaped_data = grid_data + .into_shape_with_order(( + subgrid.kts.len(), + subgrid.xis.len(), + subgrid.deltas.len(), + subgrid.xs.len(), + subgrid.q2s.len(), + )) + .expect("Failed to reshape 5D data"); + + match interp_type { + InterpolatorType::InterpNDLinear => Box::new( + InterpND::new( + coords.clone(), + reshaped_data.clone().into_dyn(), + Linear, + Extrapolate::Clamp, + ) + .expect("Failed to create 5D interpolator"), + ), + InterpolatorType::LogFiveCubic => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogFiveCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 5D LogFiveCubic interpolator"), + ), + InterpolatorType::LogChebyshev => Box::new( + InterpND::new( + coords, + reshaped_data.into_dyn(), + LogChebyshevInterpolation::<5>::default(), + Extrapolate::Clamp, + ) + .expect("Failed to create 5D LogChebyshev interpolator"), + ), + _ => panic!("Unsupported 5D interpolator: {:?}", interp_type), + } + } + + fn interpolator_xfxq2_6dim( + interp_type: InterpolatorType, + subgrid: &SubGrid, + pid_index: usize, + ) -> Box { + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![.., 0, .., .., .., pid_index, .., ..]) .to_owned(); let coords = vec![ subgrid.nucleons.mapv(f64::ln), - subgrid.alphas.mapv(f64::ln), subgrid.kts.mapv(f64::ln), + subgrid.xis.mapv(f64::ln), + subgrid.deltas.mapv(f64::ln), subgrid.xs.mapv(f64::ln), subgrid.q2s.mapv(f64::ln), ]; let reshaped_data = grid_data .into_shape_with_order(( subgrid.nucleons.len(), - subgrid.alphas.len(), subgrid.kts.len(), + subgrid.xis.len(), + subgrid.deltas.len(), subgrid.xs.len(), subgrid.q2s.len(), )) - .expect("Failed to reshape 5D data"); + .expect("Failed to reshape 6D data"); match interp_type { InterpolatorType::InterpNDLinear => Box::new( InterpND::new(coords, reshaped_data.into_dyn(), Linear, Extrapolate::Clamp) - .expect("Failed to create 5D interpolator"), + .expect("Failed to create 6D interpolator"), ), - _ => panic!("Unsupported 5D interpolator: {:?}", interp_type), + _ => panic!("Unsupported 6D interpolator: {:?}", interp_type), + } + } + + fn interpolator_xfxq2_7dim( + interp_type: InterpolatorType, + subgrid: &SubGrid, + pid_index: usize, + ) -> Box { + let grid_view = subgrid.grid.view(); + let grid_data = grid_view + .slice(s![.., .., .., .., .., pid_index, .., ..]) + .to_owned(); + let coords = vec![ + subgrid.nucleons.mapv(f64::ln), + subgrid.alphas.mapv(f64::ln), + subgrid.xis.mapv(f64::ln), + subgrid.deltas.mapv(f64::ln), + subgrid.kts.mapv(f64::ln), + subgrid.xs.mapv(f64::ln), + subgrid.q2s.mapv(f64::ln), + ]; + + let shape = IxDyn(&[ + subgrid.nucleons.len(), + subgrid.alphas.len(), + subgrid.xis.len(), + subgrid.deltas.len(), + subgrid.kts.len(), + subgrid.xs.len(), + subgrid.q2s.len(), + ]); + let reshaped_data = grid_data + .into_shape_with_order(shape) + .expect("Failed to reshape 7D interpolation data"); + + match interp_type { + InterpolatorType::InterpNDLinear => Box::new( + InterpND::new(coords, reshaped_data.clone(), Linear, Extrapolate::Clamp) + .expect("Failed to create 7D interpolator"), + ), + InterpolatorType::LogFourCubic => Box::new( + InterpND::new( + coords, + reshaped_data, + LogFourCubicInterpolation, + Extrapolate::Clamp, + ) + .expect("Failed to create 7D LogFourCubic interpolator"), + ), + _ => panic!("Unsupported 7D interpolator: {:?}", interp_type), } } @@ -512,7 +850,8 @@ impl InterpolatorFactory { } InterpolationConfig::ThreeDNucleons => { let mut strategy = LogChebyshevBatchInterpolation::<3>::default(); - let grid_data = subgrid.grid.slice(s![.., 0, pid_idx, 0, .., ..]).to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![.., 0, pid_idx, 0, .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order(( @@ -535,7 +874,8 @@ impl InterpolatorFactory { } InterpolationConfig::ThreeDAlphas => { let mut strategy = LogChebyshevBatchInterpolation::<3>::default(); - let grid_data = subgrid.grid.slice(s![0, .., pid_idx, 0, .., ..]).to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![0, .., pid_idx, 0, .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order(( @@ -558,7 +898,8 @@ impl InterpolatorFactory { } InterpolationConfig::ThreeDKt => { let mut strategy = LogChebyshevBatchInterpolation::<3>::default(); - let grid_data = subgrid.grid.slice(s![0, 0, pid_idx, .., .., ..]).to_owned(); + let grid_view = subgrid.grid.view(); + let grid_data = grid_view.slice(s![0, 0, pid_idx, .., .., ..]).to_owned(); let reshaped_data = grid_data .into_shape_with_order((subgrid.kts.len(), subgrid.xs.len(), subgrid.q2s.len())) @@ -632,35 +973,35 @@ mod tests { #[test] fn test_interpolation_config() { assert!(matches!( - InterpolationConfig::from_dimensions(1, 1, 1), + InterpolationConfig::from_dimensions(1, 1, 1, 1, 1), InterpolationConfig::TwoD )); assert!(matches!( - InterpolationConfig::from_dimensions(2, 1, 1), + InterpolationConfig::from_dimensions(2, 1, 1, 1, 1), InterpolationConfig::ThreeDNucleons )); assert!(matches!( - InterpolationConfig::from_dimensions(1, 2, 1), + InterpolationConfig::from_dimensions(1, 2, 1, 1, 1), InterpolationConfig::ThreeDAlphas )); assert!(matches!( - InterpolationConfig::from_dimensions(1, 1, 2), + InterpolationConfig::from_dimensions(1, 1, 1, 1, 2), InterpolationConfig::ThreeDKt )); assert!(matches!( - InterpolationConfig::from_dimensions(2, 2, 1), + InterpolationConfig::from_dimensions(2, 2, 1, 1, 1), InterpolationConfig::FourDNucleonsAlphas )); assert!(matches!( - InterpolationConfig::from_dimensions(2, 1, 2), + InterpolationConfig::from_dimensions(2, 1, 1, 1, 2), InterpolationConfig::FourDNucleonsKt )); assert!(matches!( - InterpolationConfig::from_dimensions(1, 2, 2), + InterpolationConfig::from_dimensions(1, 2, 1, 1, 2), InterpolationConfig::FourDAlphasKt )); assert!(matches!( - InterpolationConfig::from_dimensions(2, 2, 2), + InterpolationConfig::from_dimensions(1, 1, 2, 2, 2), InterpolationConfig::FiveD )); } diff --git a/neopdf/src/metadata.rs b/neopdf/src/metadata.rs index 5b76da9d..190f6983 100644 --- a/neopdf/src/metadata.rs +++ b/neopdf/src/metadata.rs @@ -2,9 +2,7 @@ //! //! It includes the `MetaData` struct (deserialized from .info files), PDF set //! and interpolator type enums, and related utilities for handling PDF set information. -use serde::{Deserialize, Deserializer, Serialize}; -use std::fmt; -use std::ops::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; /// Represents the type of PDF set. #[repr(C)] @@ -17,7 +15,7 @@ pub enum SetType { } /// Represents the type of interpolator used for the PDF. -/// WARNING: When adding elements, always append to the end!!! +/// WARNING: When adding elements, always append at the end!!! #[repr(C)] #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub enum InterpolatorType { @@ -28,14 +26,16 @@ pub enum InterpolatorType { LogTricubic, InterpNDLinear, LogChebyshev, + LogFourCubic, + LogFiveCubic, } -/// Represents the information block of a given set. +/// Represents the information block of a given PDF set. /// /// In order to support LHAPDF formats, the fields here are very much influenced by the /// LHAPDF `.info` file. This struct is generally deserialized from a YAML-like format. #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct MetaDataV1 { +pub struct MetaDataV2 { /// Description of the PDF set. #[serde(rename = "SetDesc")] pub set_desc: String, @@ -129,65 +129,36 @@ pub struct MetaDataV1 { /// Number of active PDF flavors. #[serde(rename = "NumFlavors", default)] pub number_flavors: u32, + /// Minimum xi-value for which the PDF is valid. + #[serde(rename = "XiMin", default)] + pub xi_min: f64, + /// Maximum xi-value for which the PDF is valid. + #[serde(rename = "XiMax", default)] + pub xi_max: f64, + /// Minimum delta-value for which the PDF is valid. + #[serde(rename = "DeltaMin", default)] + pub delta_min: f64, + /// Maximum delta-value for which the PDF is valid. + #[serde(rename = "DeltaMax", default)] + pub delta_max: f64, } -/// Version-aware metadata wrapper that handles serialization compatibility. -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum MetaData { - V1(MetaDataV1), -} - -impl MetaData { - /// Creates a new instance of V1 `MetaData`. - pub fn new_v1(data: MetaDataV1) -> Self { - Self::V1(data) - } - - /// Gets the current version as the latest available version. - pub fn current_v1(data: MetaDataV1) -> Self { - Self::V1(data) - } - - /// Gets the underlying data as the latest version. - pub fn as_latest(&self) -> MetaDataV1 { - match self { - MetaData::V1(data) => data.clone(), - } +impl MetaDataV2 { + /// Helper to get quark masses as a tuple + pub fn quark_masses(&self) -> (f64, f64, f64, f64, f64, f64) { + ( + self.m_up, + self.m_down, + self.m_strange, + self.m_charm, + self.m_bottom, + self.m_top, + ) } } -impl Deref for MetaData { - type Target = MetaDataV1; - - fn deref(&self) -> &Self::Target { - match self { - MetaData::V1(data) => data, - } - } -} - -impl DerefMut for MetaData { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - MetaData::V1(data) => data, - } - } -} - -impl<'de> Deserialize<'de> for MetaData { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let v1 = MetaDataV1::deserialize(deserializer)?; - - Ok(MetaData::V1(v1)) - } -} - -impl fmt::Display for MetaData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl std::fmt::Display for MetaDataV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Set Description: {}", self.set_desc)?; writeln!(f, "Set Index: {}", self.set_index)?; writeln!(f, "Number of Members: {}", self.num_members)?; @@ -216,6 +187,77 @@ impl fmt::Display for MetaData { writeln!(f, "MBottom: {}", self.m_bottom)?; writeln!(f, "MTop: {}", self.m_top)?; writeln!(f, "AlphaS Type: {}", self.alphas_type)?; - writeln!(f, "Number of PDF flavors: {}", self.number_flavors) + writeln!(f, "Number of PDF flavors: {}", self.number_flavors)?; + writeln!(f, "XiMin: {}", self.xi_min)?; + writeln!(f, "XiMax: {}", self.xi_max)?; + writeln!(f, "DeltaMin: {}", self.delta_min)?; + write!(f, "DeltaMax: {}", self.delta_max) + } +} + +/// The following represent the main metadata type for v0.2.1+. +pub type MetaData = MetaDataV2; + +/// Converts from legacy v0.2.0 MetaData to new v0.2.1 format +impl From for MetaData { + fn from(legacy: neopdf_legacy::metadata::MetaData) -> Self { + Self { + set_desc: legacy.set_desc.clone(), + set_index: legacy.set_index, + num_members: legacy.num_members, + x_min: legacy.x_min, + x_max: legacy.x_max, + q_min: legacy.q_min, + q_max: legacy.q_max, + flavors: legacy.flavors.clone(), + format: legacy.format.clone(), + alphas_q_values: legacy.alphas_q_values.clone(), + alphas_vals: legacy.alphas_vals.clone(), + polarised: legacy.polarised, + set_type: match legacy.set_type { + neopdf_legacy::metadata::SetType::SpaceLike => SetType::SpaceLike, + neopdf_legacy::metadata::SetType::TimeLike => SetType::TimeLike, + }, + interpolator_type: match legacy.interpolator_type { + neopdf_legacy::metadata::InterpolatorType::Bilinear => InterpolatorType::Bilinear, + neopdf_legacy::metadata::InterpolatorType::LogBilinear => { + InterpolatorType::LogBilinear + } + neopdf_legacy::metadata::InterpolatorType::LogBicubic => { + InterpolatorType::LogBicubic + } + neopdf_legacy::metadata::InterpolatorType::LogTricubic => { + InterpolatorType::LogTricubic + } + neopdf_legacy::metadata::InterpolatorType::InterpNDLinear => { + InterpolatorType::InterpNDLinear + } + neopdf_legacy::metadata::InterpolatorType::LogChebyshev => { + InterpolatorType::LogChebyshev + } + }, + error_type: legacy.error_type.clone(), + hadron_pid: legacy.hadron_pid, + git_version: legacy.git_version.clone(), + code_version: legacy.code_version.clone(), + flavor_scheme: legacy.flavor_scheme.clone(), + order_qcd: legacy.order_qcd, + alphas_order_qcd: legacy.alphas_order_qcd, + m_w: legacy.m_w, + m_z: legacy.m_z, + m_up: legacy.m_up, + m_down: legacy.m_down, + m_strange: legacy.m_strange, + m_charm: legacy.m_charm, + m_bottom: legacy.m_bottom, + m_top: legacy.m_top, + alphas_type: legacy.alphas_type.clone(), + number_flavors: legacy.number_flavors, + // New V2 fields with defaults + xi_min: 0.0, + xi_max: 0.0, + delta_min: 0.0, + delta_max: 0.0, + } } } diff --git a/neopdf/src/parser.rs b/neopdf/src/parser.rs index 2d94f2be..165e5679 100644 --- a/neopdf/src/parser.rs +++ b/neopdf/src/parser.rs @@ -16,6 +16,8 @@ use super::writer::{GridArrayReader, LazyGridArrayIterator}; pub struct SubgridData { pub nucleons: Vec, pub alphas: Vec, + pub xis: Vec, + pub deltas: Vec, pub kts: Vec, pub xs: Vec, pub q2s: Vec, @@ -207,11 +209,15 @@ impl LhapdfSet { // following values from LHAPDF, their defaults are set to zeros. let nucleons: Vec = vec![0.0]; let alphas: Vec = vec![0.0]; + let xis: Vec = vec![0.0]; + let deltas: Vec = vec![0.0]; let kts: Vec = vec![0.0]; subgrid_data.push(SubgridData { nucleons, alphas, + xis, + deltas, kts, xs, q2s, @@ -240,7 +246,8 @@ impl NeopdfSet { pub fn new(pdf_name: &str) -> Self { let manager = ManageData::new(pdf_name, PdfSetFormat::Neopdf); let neopdf_setpath = manager.set_path(); - let grid_readers = GridArrayReader::from_file(neopdf_setpath).unwrap(); + let grid_readers = GridArrayReader::from_file(neopdf_setpath) + .unwrap_or_else(|e| panic!("Failed to load NeoPDF file: {}", e)); let metadata_info = grid_readers.metadata().as_ref().clone(); Self { diff --git a/neopdf/src/strategy.rs b/neopdf/src/strategy.rs index 8c517ac2..1d1cb0f8 100644 --- a/neopdf/src/strategy.rs +++ b/neopdf/src/strategy.rs @@ -14,10 +14,10 @@ //! All interpolation strategies are designed to work with `ninterp`'s data structures and traits, //! ensuring compatibility and extensibility. -use ndarray::{Array2, Axis, Data, RawDataClone}; -use ninterp::data::{InterpData1D, InterpData2D, InterpData3D}; +use ndarray::{Array2, Axis, Data, IxDyn, RawDataClone}; +use ninterp::data::{InterpData1D, InterpData2D, InterpData3D, InterpDataND}; use ninterp::error::{InterpolateError, ValidateError}; -use ninterp::strategy::traits::{Strategy1D, Strategy2D, Strategy3D}; +use ninterp::strategy::traits::{Strategy1D, Strategy2D, Strategy3D, StrategyND}; use serde::{Deserialize, Serialize}; use std::f64::consts::PI; @@ -693,6 +693,665 @@ where } } +/// Implements four-cubic (4D cubic) interpolation in log space. +/// +/// This strategy extends the LogTricubic interpolation to 4 dimensions, +/// providing cubic Hermite interpolation in log-log-log-log space. +/// It is suitable for 4D PDF grids where all four dimensions benefit from +/// logarithmic scaling and smooth cubic interpolation. +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct LogFourCubicInterpolation; + +impl LogFourCubicInterpolation { + /// Returns the index i such that we can use points [i-1, i, i+1, i+2] for interpolation. + fn find_fourcubic_interval(coords: &[f64], x: f64) -> Result { + // Find the interval [i, i+1] such that coords[i] <= x < coords[i+1] + let i = utils::find_interval_index(coords, x)?; + Ok(i) + } + + /// Calculates the derivative with respect to the first dimension at a given knot. + pub fn calculate_dd0(data: &InterpDataND, idx: &[usize]) -> f64 + where + D: Data + RawDataClone + Clone, + { + let n_knots = data.grid[0].len(); + let coords = data.grid[0].as_slice().unwrap(); + let values = &data.values; + + let i0 = idx[0]; + let del1 = match i0 { + 0 => 0.0, + i => coords[i] - coords[i - 1], + }; + + let del2 = match coords.get(i0 + 1) { + Some(&next) => next - coords[i0], + None => 0.0, + }; + + if i0 != 0 && i0 != n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[0] = i0 - 1; + let mut idx_next = idx.to_vec(); + idx_next[0] = i0 + 1; + + let ldd = (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1; + let rdd = (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2; + (ldd + rdd) / 2.0 + } else if i0 == 0 { + let mut idx_next = idx.to_vec(); + idx_next[0] = i0 + 1; + (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2 + } else if i0 == n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[0] = i0 - 1; + (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1 + } else { + panic!("Should not reach here: Invalid index for derivative calculation."); + } + } + + /// Calculates the derivative with respect to the second dimension at a given knot. + pub fn calculate_dd1(data: &InterpDataND, idx: &[usize]) -> f64 + where + D: Data + RawDataClone + Clone, + { + let n_knots = data.grid[1].len(); + let coords = data.grid[1].as_slice().unwrap(); + let values = &data.values; + + let i1 = idx[1]; + let del1 = match i1 { + 0 => 0.0, + i => coords[i] - coords[i - 1], + }; + + let del2 = match coords.get(i1 + 1) { + Some(&next) => next - coords[i1], + None => 0.0, + }; + + if i1 != 0 && i1 != n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[1] = i1 - 1; + let mut idx_next = idx.to_vec(); + idx_next[1] = i1 + 1; + + let ldd = (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1; + let rdd = (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2; + (ldd + rdd) / 2.0 + } else if i1 == 0 { + let mut idx_next = idx.to_vec(); + idx_next[1] = i1 + 1; + (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2 + } else if i1 == n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[1] = i1 - 1; + (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1 + } else { + panic!("Should not reach here: Invalid index for derivative calculation."); + } + } + + /// Calculates the derivative with respect to the third dimension at a given knot. + pub fn calculate_dd2(data: &InterpDataND, idx: &[usize]) -> f64 + where + D: Data + RawDataClone + Clone, + { + let n_knots = data.grid[2].len(); + let coords = data.grid[2].as_slice().unwrap(); + let values = &data.values; + + let i2 = idx[2]; + let del1 = match i2 { + 0 => 0.0, + i => coords[i] - coords[i - 1], + }; + + let del2 = match coords.get(i2 + 1) { + Some(&next) => next - coords[i2], + None => 0.0, + }; + + if i2 != 0 && i2 != n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[2] = i2 - 1; + let mut idx_next = idx.to_vec(); + idx_next[2] = i2 + 1; + + let ldd = (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1; + let rdd = (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2; + (ldd + rdd) / 2.0 + } else if i2 == 0 { + let mut idx_next = idx.to_vec(); + idx_next[2] = i2 + 1; + (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2 + } else if i2 == n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[2] = i2 - 1; + (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1 + } else { + panic!("Should not reach here: Invalid index for derivative calculation."); + } + } + + /// Calculates the derivative with respect to the fourth dimension at a given knot. + pub fn calculate_dd3(data: &InterpDataND, idx: &[usize]) -> f64 + where + D: Data + RawDataClone + Clone, + { + let n_knots = data.grid[3].len(); + let coords = data.grid[3].as_slice().unwrap(); + let values = &data.values; + + let i3 = idx[3]; + let del1 = match i3 { + 0 => 0.0, + i => coords[i] - coords[i - 1], + }; + + let del2 = match coords.get(i3 + 1) { + Some(&next) => next - coords[i3], + None => 0.0, + }; + + if i3 != 0 && i3 != n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[3] = i3 - 1; + let mut idx_next = idx.to_vec(); + idx_next[3] = i3 + 1; + + let ldd = (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1; + let rdd = (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2; + (ldd + rdd) / 2.0 + } else if i3 == 0 { + let mut idx_next = idx.to_vec(); + idx_next[3] = i3 + 1; + (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2 + } else if i3 == n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[3] = i3 - 1; + (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1 + } else { + panic!("Should not reach here: Invalid index for derivative calculation."); + } + } + + /// Hermite cubic interpolation with derivatives (same as LogTricubic) + fn cubic_interpolate(t: f64, f0: f64, f0_prime: f64, f1: f64, f1_prime: f64) -> f64 { + let t2 = t * t; + let t3 = t2 * t; + + // Hermite basis functions + let h00 = 2.0 * t3 - 3.0 * t2 + 1.0; + let h10 = t3 - 2.0 * t2 + t; + let h01 = -2.0 * t3 + 3.0 * t2; + let h11 = t3 - t2; + + h00 * f0 + h10 * f0_prime + h01 * f1 + h11 * f1_prime + } + + /// Four-cubic Hermite interpolation in 4D + fn hermite_fourcubic_interpolate( + &self, + data: &InterpDataND, + indices: (usize, usize, usize, usize), + coords: (f64, f64, f64, f64), + deltas: (f64, f64, f64, f64), + ) -> f64 + where + D: Data + RawDataClone + Clone, + { + let (i0, i1, i2, i3) = indices; + let (u, v, w, t) = coords; + let (d0, d1, d2, d3) = deltas; + + // Helper closures to access values and derivatives + let get = |o0: usize, o1: usize, o2: usize, o3: usize| { + data.values[IxDyn(&[i0 + o0, i1 + o1, i2 + o2, i3 + o3])] + }; + let dd0 = |o0: usize, o1: usize, o2: usize, o3: usize| { + Self::calculate_dd0(data, &[i0 + o0, i1 + o1, i2 + o2, i3 + o3]) + }; + let dd1 = |o0: usize, o1: usize, o2: usize, o3: usize| { + Self::calculate_dd1(data, &[i0 + o0, i1 + o1, i2 + o2, i3 + o3]) + }; + let dd2 = |o0: usize, o1: usize, o2: usize, o3: usize| { + Self::calculate_dd2(data, &[i0 + o0, i1 + o1, i2 + o2, i3 + o3]) + }; + let dd3 = |o0: usize, o1: usize, o2: usize, o3: usize| { + Self::calculate_dd3(data, &[i0 + o0, i1 + o1, i2 + o2, i3 + o3]) + }; + + // First, interpolate along dimension 0 for all combinations of (dim1, dim2, dim3) + // This creates a 2x2x2 cube of interpolated values and derivatives + let mut interp_dim0 = vec![]; + for &o3 in &[0, 1] { + for &o2 in &[0, 1] { + for &o1 in &[0, 1] { + let (f0, f1) = (get(0, o1, o2, o3), get(1, o1, o2, o3)); + let (deriv0, deriv1) = (dd0(0, o1, o2, o3) * d0, dd0(1, o1, o2, o3) * d0); + let interp_val = Self::cubic_interpolate(u, f0, deriv0, f1, deriv1); + + // Interpolate derivatives for dimension 1 + let (df0, df1) = (dd1(0, o1, o2, o3) * d1, dd1(1, o1, o2, o3) * d1); + let interp_deriv1 = (1.0 - u) * df0 + u * df1; + + interp_dim0.push([interp_val, interp_deriv1]); + } + } + } + + // Now interpolate along dimension 1, creating a 2x2 grid + let mut interp_dim1 = vec![]; + for o3 in 0..2 { + for o2 in 0..2 { + let idx0 = o3 * 4 + o2 * 2; + let (f0, f1) = (interp_dim0[idx0][0], interp_dim0[idx0 + 1][0]); + let (deriv0, deriv1) = (interp_dim0[idx0][1], interp_dim0[idx0 + 1][1]); + let interp_val = Self::cubic_interpolate(v, f0, deriv0, f1, deriv1); + + // Calculate derivative for dimension 2 + let calc_d2_deriv = |o1_offset: usize| { + let (df0, df1) = ( + dd2(0, o1_offset, o2, o3) * d2, + dd2(1, o1_offset, o2, o3) * d2, + ); + (1.0 - u) * df0 + u * df1 + }; + let interp_deriv2 = (1.0 - v) * calc_d2_deriv(0) + v * calc_d2_deriv(1); + + interp_dim1.push([interp_val, interp_deriv2]); + } + } + + // Interpolate along dimension 2, creating a 1x2 vector + let mut interp_dim2 = vec![]; + for o3 in 0..2 { + let idx0 = o3 * 2; + let (f0, f1) = (interp_dim1[idx0][0], interp_dim1[idx0 + 1][0]); + let (deriv0, deriv1) = (interp_dim1[idx0][1], interp_dim1[idx0 + 1][1]); + let interp_val = Self::cubic_interpolate(w, f0, deriv0, f1, deriv1); + + // Calculate derivative for dimension 3 + let calc_d3_deriv = |o2_offset: usize| { + let calc_d3_inner = |o1_offset: usize| { + let (df0, df1) = ( + dd3(0, o1_offset, o2_offset, o3) * d3, + dd3(1, o1_offset, o2_offset, o3) * d3, + ); + (1.0 - u) * df0 + u * df1 + }; + (1.0 - v) * calc_d3_inner(0) + v * calc_d3_inner(1) + }; + let interp_deriv3 = (1.0 - w) * calc_d3_deriv(0) + w * calc_d3_deriv(1); + + interp_dim2.push([interp_val, interp_deriv3]); + } + + // Final interpolation along dimension 3 + let (f0, f1) = (interp_dim2[0][0], interp_dim2[1][0]); + let (deriv0, deriv1) = (interp_dim2[0][1], interp_dim2[1][1]); + Self::cubic_interpolate(t, f0, deriv0, f1, deriv1) + } +} + +impl StrategyND for LogFourCubicInterpolation +where + D: Data + RawDataClone + Clone, +{ + fn init(&mut self, data: &InterpDataND) -> Result<(), ValidateError> { + if data.grid.len() != 4 { + return Err(ValidateError::Other( + "LogFourCubic requires exactly 4 dimensions".to_string(), + )); + } + + for (i, grid) in data.grid.iter().enumerate() { + if grid.len() < 4 { + return Err(ValidateError::Other(format!( + "Need at least 4 grid points in dimension {}, got {}", + i, + grid.len() + ))); + } + } + + Ok(()) + } + + fn interpolate(&self, data: &InterpDataND, point: &[f64]) -> Result { + if point.len() != 4 { + return Err(InterpolateError::Other( + "LogFourCubic requires exactly 4-dimensional point".to_string(), + )); + } + + let [x0, x1, x2, x3] = [point[0], point[1], point[2], point[3]]; + + let coords0 = data.grid[0].as_slice().unwrap(); + let coords1 = data.grid[1].as_slice().unwrap(); + let coords2 = data.grid[2].as_slice().unwrap(); + let coords3 = data.grid[3].as_slice().unwrap(); + + let i0 = Self::find_fourcubic_interval(coords0, x0)?; + let i1 = Self::find_fourcubic_interval(coords1, x1)?; + let i2 = Self::find_fourcubic_interval(coords2, x2)?; + let i3 = Self::find_fourcubic_interval(coords3, x3)?; + + let d0 = coords0[i0 + 1] - coords0[i0]; + let d1 = coords1[i1 + 1] - coords1[i1]; + let d2 = coords2[i2 + 1] - coords2[i2]; + let d3 = coords3[i3 + 1] - coords3[i3]; + + if d0 == 0.0 || d1 == 0.0 || d2 == 0.0 || d3 == 0.0 { + return Err(InterpolateError::Other("Grid spacing is zero".to_string())); + } + + let u = (x0 - coords0[i0]) / d0; + let v = (x1 - coords1[i1]) / d1; + let w = (x2 - coords2[i2]) / d2; + let t = (x3 - coords3[i3]) / d3; + + let result = self.hermite_fourcubic_interpolate( + data, + (i0, i1, i2, i3), + (u, v, w, t), + (d0, d1, d2, d3), + ); + + Ok(result) + } + + fn allow_extrapolate(&self) -> bool { + true + } +} + +/// Implements five-cubic (5D cubic) interpolation in log space. +/// +/// This strategy extends the LogFourCubic interpolation to 5 dimensions, +/// providing cubic Hermite interpolation in log-log-log-log-log space. +/// It is suitable for 5D PDF grids where all five dimensions benefit from +/// logarithmic scaling and smooth cubic interpolation. +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct LogFiveCubicInterpolation; + +impl LogFiveCubicInterpolation { + /// Returns the index i such that we can use points [i-1, i, i+1, i+2] for interpolation. + fn find_fivecubic_interval(coords: &[f64], x: f64) -> Result { + // Find the interval [i, i+1] such that coords[i] <= x < coords[i+1] + let i = utils::find_interval_index(coords, x)?; + Ok(i) + } + + /// Calculates the derivative with respect to dimension k at a given knot. + fn calculate_ddk(data: &InterpDataND, idx: &[usize], dim: usize) -> f64 + where + D: Data + RawDataClone + Clone, + { + let n_knots = data.grid[dim].len(); + let coords = data.grid[dim].as_slice().unwrap(); + let values = &data.values; + + let ik = idx[dim]; + let del1 = match ik { + 0 => 0.0, + i => coords[i] - coords[i - 1], + }; + + let del2 = match coords.get(ik + 1) { + Some(&next) => next - coords[ik], + None => 0.0, + }; + + if ik != 0 && ik != n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[dim] = ik - 1; + let mut idx_next = idx.to_vec(); + idx_next[dim] = ik + 1; + + let ldd = (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1; + let rdd = (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2; + (ldd + rdd) / 2.0 + } else if ik == 0 { + let mut idx_next = idx.to_vec(); + idx_next[dim] = ik + 1; + (values[IxDyn(&idx_next)] - values[IxDyn(idx)]) / del2 + } else if ik == n_knots - 1 { + let mut idx_prev = idx.to_vec(); + idx_prev[dim] = ik - 1; + (values[IxDyn(idx)] - values[IxDyn(&idx_prev)]) / del1 + } else { + panic!("Should not reach here: Invalid index for derivative calculation."); + } + } + + /// Hermite cubic interpolation with derivatives (same as LogTricubic/LogFourCubic) + fn cubic_interpolate(t: f64, f0: f64, f0_prime: f64, f1: f64, f1_prime: f64) -> f64 { + let t2 = t * t; + let t3 = t2 * t; + + // Hermite basis functions + let h00 = 2.0 * t3 - 3.0 * t2 + 1.0; + let h10 = t3 - 2.0 * t2 + t; + let h01 = -2.0 * t3 + 3.0 * t2; + let h11 = t3 - t2; + + h00 * f0 + h10 * f0_prime + h01 * f1 + h11 * f1_prime + } + + /// Five-cubic Hermite interpolation in 5D + fn hermite_fivecubic_interpolate( + &self, + data: &InterpDataND, + indices: (usize, usize, usize, usize, usize), + coords: (f64, f64, f64, f64, f64), + deltas: (f64, f64, f64, f64, f64), + ) -> f64 + where + D: Data + RawDataClone + Clone, + { + let (i0, i1, i2, i3, i4) = indices; + let (u, v, w, t, s) = coords; + let (d0, d1, d2, d3, d4) = deltas; + + // Helper closures to access values and derivatives + let get = |o0: usize, o1: usize, o2: usize, o3: usize, o4: usize| { + data.values[IxDyn(&[i0 + o0, i1 + o1, i2 + o2, i3 + o3, i4 + o4])] + }; + let ddk = |o0: usize, o1: usize, o2: usize, o3: usize, o4: usize, dim: usize| { + Self::calculate_ddk(data, &[i0 + o0, i1 + o1, i2 + o2, i3 + o3, i4 + o4], dim) + }; + + // Interpolate along dimension 0 for all 2^4 = 16 combinations + let mut interp_dim0 = vec![]; + for &o4 in &[0, 1] { + for &o3 in &[0, 1] { + for &o2 in &[0, 1] { + for &o1 in &[0, 1] { + let (f0, f1) = (get(0, o1, o2, o3, o4), get(1, o1, o2, o3, o4)); + let (deriv0, deriv1) = ( + ddk(0, o1, o2, o3, o4, 0) * d0, + ddk(1, o1, o2, o3, o4, 0) * d0, + ); + let interp_val = Self::cubic_interpolate(u, f0, deriv0, f1, deriv1); + + // Interpolate derivatives for dimension 1 + let (df0, df1) = ( + ddk(0, o1, o2, o3, o4, 1) * d1, + ddk(1, o1, o2, o3, o4, 1) * d1, + ); + let interp_deriv1 = (1.0 - u) * df0 + u * df1; + + interp_dim0.push([interp_val, interp_deriv1]); + } + } + } + } + + // Interpolate along dimension 1, creating a 2^3 = 8 element array + let mut interp_dim1 = vec![]; + for o4 in 0..2 { + for o3 in 0..2 { + for o2 in 0..2 { + let idx0 = o4 * 8 + o3 * 4 + o2 * 2; + let (f0, f1) = (interp_dim0[idx0][0], interp_dim0[idx0 + 1][0]); + let (deriv0, deriv1) = (interp_dim0[idx0][1], interp_dim0[idx0 + 1][1]); + let interp_val = Self::cubic_interpolate(v, f0, deriv0, f1, deriv1); + + // Calculate derivative for dimension 2 + let calc_d2_deriv = |o1_offset: usize| { + let (df0, df1) = ( + ddk(0, o1_offset, o2, o3, o4, 2) * d2, + ddk(1, o1_offset, o2, o3, o4, 2) * d2, + ); + (1.0 - u) * df0 + u * df1 + }; + let interp_deriv2 = (1.0 - v) * calc_d2_deriv(0) + v * calc_d2_deriv(1); + + interp_dim1.push([interp_val, interp_deriv2]); + } + } + } + + // Interpolate along dimension 2, creating a 2^2 = 4 element array + let mut interp_dim2 = vec![]; + for o4 in 0..2 { + for o3 in 0..2 { + let idx0 = o4 * 4 + o3 * 2; + let (f0, f1) = (interp_dim1[idx0][0], interp_dim1[idx0 + 1][0]); + let (deriv0, deriv1) = (interp_dim1[idx0][1], interp_dim1[idx0 + 1][1]); + let interp_val = Self::cubic_interpolate(w, f0, deriv0, f1, deriv1); + + // Calculate derivative for dimension 3 + let calc_d3_deriv = |o2_offset: usize| { + let calc_d3_inner = |o1_offset: usize| { + let (df0, df1) = ( + ddk(0, o1_offset, o2_offset, o3, o4, 3) * d3, + ddk(1, o1_offset, o2_offset, o3, o4, 3) * d3, + ); + (1.0 - u) * df0 + u * df1 + }; + (1.0 - v) * calc_d3_inner(0) + v * calc_d3_inner(1) + }; + let interp_deriv3 = (1.0 - w) * calc_d3_deriv(0) + w * calc_d3_deriv(1); + + interp_dim2.push([interp_val, interp_deriv3]); + } + } + + // Interpolate along dimension 3, creating a 2 element array + let mut interp_dim3 = vec![]; + for o4 in 0..2 { + let idx0 = o4 * 2; + let (f0, f1) = (interp_dim2[idx0][0], interp_dim2[idx0 + 1][0]); + let (deriv0, deriv1) = (interp_dim2[idx0][1], interp_dim2[idx0 + 1][1]); + let interp_val = Self::cubic_interpolate(t, f0, deriv0, f1, deriv1); + + // Calculate derivative for dimension 4 + let calc_d4_deriv = |o3_offset: usize| { + let calc_d4_mid = |o2_offset: usize| { + let calc_d4_inner = |o1_offset: usize| { + let (df0, df1) = ( + ddk(0, o1_offset, o2_offset, o3_offset, o4, 4) * d4, + ddk(1, o1_offset, o2_offset, o3_offset, o4, 4) * d4, + ); + (1.0 - u) * df0 + u * df1 + }; + (1.0 - v) * calc_d4_inner(0) + v * calc_d4_inner(1) + }; + (1.0 - w) * calc_d4_mid(0) + w * calc_d4_mid(1) + }; + let interp_deriv4 = (1.0 - t) * calc_d4_deriv(0) + t * calc_d4_deriv(1); + + interp_dim3.push([interp_val, interp_deriv4]); + } + + // Final interpolation along dimension 4 + let (f0, f1) = (interp_dim3[0][0], interp_dim3[1][0]); + let (deriv0, deriv1) = (interp_dim3[0][1], interp_dim3[1][1]); + Self::cubic_interpolate(s, f0, deriv0, f1, deriv1) + } +} + +impl StrategyND for LogFiveCubicInterpolation +where + D: Data + RawDataClone + Clone, +{ + fn init(&mut self, data: &InterpDataND) -> Result<(), ValidateError> { + if data.grid.len() != 5 { + return Err(ValidateError::Other( + "LogFiveCubic requires exactly 5 dimensions".to_string(), + )); + } + + for (i, grid) in data.grid.iter().enumerate() { + if grid.len() < 4 { + return Err(ValidateError::Other(format!( + "Need at least 4 grid points in dimension {}, got {}", + i, + grid.len() + ))); + } + } + + Ok(()) + } + + fn interpolate(&self, data: &InterpDataND, point: &[f64]) -> Result { + if point.len() != 5 { + return Err(InterpolateError::Other( + "LogFiveCubic requires exactly 5-dimensional point".to_string(), + )); + } + + let [x0, x1, x2, x3, x4] = [point[0], point[1], point[2], point[3], point[4]]; + + let coords0 = data.grid[0].as_slice().unwrap(); + let coords1 = data.grid[1].as_slice().unwrap(); + let coords2 = data.grid[2].as_slice().unwrap(); + let coords3 = data.grid[3].as_slice().unwrap(); + let coords4 = data.grid[4].as_slice().unwrap(); + + let i0 = Self::find_fivecubic_interval(coords0, x0)?; + let i1 = Self::find_fivecubic_interval(coords1, x1)?; + let i2 = Self::find_fivecubic_interval(coords2, x2)?; + let i3 = Self::find_fivecubic_interval(coords3, x3)?; + let i4 = Self::find_fivecubic_interval(coords4, x4)?; + + let d0 = coords0[i0 + 1] - coords0[i0]; + let d1 = coords1[i1 + 1] - coords1[i1]; + let d2 = coords2[i2 + 1] - coords2[i2]; + let d3 = coords3[i3 + 1] - coords3[i3]; + let d4 = coords4[i4 + 1] - coords4[i4]; + + if d0 == 0.0 || d1 == 0.0 || d2 == 0.0 || d3 == 0.0 || d4 == 0.0 { + return Err(InterpolateError::Other("Grid spacing is zero".to_string())); + } + + let u = (x0 - coords0[i0]) / d0; + let v = (x1 - coords1[i1]) / d1; + let w = (x2 - coords2[i2]) / d2; + let t = (x3 - coords3[i3]) / d3; + let s = (x4 - coords4[i4]) / d4; + + let result = self.hermite_fivecubic_interpolate( + data, + (i0, i1, i2, i3, i4), + (u, v, w, t, s), + (d0, d1, d2, d3, d4), + ); + + Ok(result) + } + + fn allow_extrapolate(&self) -> bool { + true + } +} + /// Implements cubic interpolation for alpha_s values in log-Q2 space. /// /// This strategy handles the specific extrapolation and interpolation rules @@ -1131,6 +1790,174 @@ where } } +impl StrategyND for LogChebyshevInterpolation<4> +where + D: Data + RawDataClone + Clone, +{ + fn init(&mut self, data: &InterpDataND) -> Result<(), ValidateError> { + if data.grid.len() != 4 { + return Err(ValidateError::Other( + "LogChebyshevInterpolation<4> requires exactly 4 dimensions".to_string(), + )); + } + for dim in 0..4 { + let x_coords = data.grid[dim].as_slice().unwrap(); + let n = x_coords.len(); + if n < 2 { + return Err(ValidateError::Other( + "LogChebyshevInterpolation requires at least 2 grid points per dimension." + .to_string(), + )); + } + self.t_coords[dim] = (0..n) + .map(|j| (PI * (n - 1 - j) as f64 / (n - 1) as f64).cos()) + .collect(); + self.weights[dim] = Self::compute_barycentric_weights(n); + } + Ok(()) + } + + fn interpolate(&self, data: &InterpDataND, point: &[f64]) -> Result { + if point.len() != 4 { + return Err(InterpolateError::Other( + "LogChebyshevInterpolation<4> requires a 4D point".to_string(), + )); + } + let [x, y, z, w] = [point[0], point[1], point[2], point[3]]; + + let x_coords = data.grid[0].as_slice().unwrap(); + let y_coords = data.grid[1].as_slice().unwrap(); + let z_coords = data.grid[2].as_slice().unwrap(); + let w_coords = data.grid[3].as_slice().unwrap(); + + let x_min = *x_coords.first().unwrap(); + let x_max = *x_coords.last().unwrap(); + let y_min = *y_coords.first().unwrap(); + let y_max = *y_coords.last().unwrap(); + let z_min = *z_coords.first().unwrap(); + let z_max = *z_coords.last().unwrap(); + let w_min = *w_coords.first().unwrap(); + let w_max = *w_coords.last().unwrap(); + + let t_x = 2.0 * (x - x_min) / (x_max - x_min) - 1.0; + let t_y = 2.0 * (y - y_min) / (y_max - y_min) - 1.0; + let t_z = 2.0 * (z - z_min) / (z_max - z_min) - 1.0; + let t_w = 2.0 * (w - w_min) / (w_max - w_min) - 1.0; + + let x_coeffs = Self::barycentric_coefficients(t_x, &self.t_coords[0], &self.weights[0]); + let y_coeffs = Self::barycentric_coefficients(t_y, &self.t_coords[1], &self.weights[1]); + let z_coeffs = Self::barycentric_coefficients(t_z, &self.t_coords[2], &self.weights[2]); + let w_coeffs = Self::barycentric_coefficients(t_w, &self.t_coords[3], &self.weights[3]); + + let mut result = 0.0; + for (i, &x_coeff) in x_coeffs.iter().enumerate() { + for (j, &y_coeff) in y_coeffs.iter().enumerate() { + for (k, &z_coeff) in z_coeffs.iter().enumerate() { + for (l, &w_coeff) in w_coeffs.iter().enumerate() { + result += x_coeff * y_coeff * z_coeff * w_coeff * data.values[[i, j, k, l]]; + } + } + } + } + + Ok(result) + } + + fn allow_extrapolate(&self) -> bool { + true + } +} + +impl StrategyND for LogChebyshevInterpolation<5> +where + D: Data + RawDataClone + Clone, +{ + fn init(&mut self, data: &InterpDataND) -> Result<(), ValidateError> { + if data.grid.len() != 5 { + return Err(ValidateError::Other( + "LogChebyshevInterpolation<5> requires exactly 5 dimensions".to_string(), + )); + } + for dim in 0..5 { + let x_coords = data.grid[dim].as_slice().unwrap(); + let n = x_coords.len(); + if n < 2 { + return Err(ValidateError::Other( + "LogChebyshevInterpolation requires at least 2 grid points per dimension." + .to_string(), + )); + } + self.t_coords[dim] = (0..n) + .map(|j| (PI * (n - 1 - j) as f64 / (n - 1) as f64).cos()) + .collect(); + self.weights[dim] = Self::compute_barycentric_weights(n); + } + Ok(()) + } + + fn interpolate(&self, data: &InterpDataND, point: &[f64]) -> Result { + if point.len() != 5 { + return Err(InterpolateError::Other( + "LogChebyshevInterpolation<5> requires a 5D point".to_string(), + )); + } + let [x, y, z, w, v_] = [point[0], point[1], point[2], point[3], point[4]]; + + let x_coords = data.grid[0].as_slice().unwrap(); + let y_coords = data.grid[1].as_slice().unwrap(); + let z_coords = data.grid[2].as_slice().unwrap(); + let w_coords = data.grid[3].as_slice().unwrap(); + let v_coords = data.grid[4].as_slice().unwrap(); + + let x_min = *x_coords.first().unwrap(); + let x_max = *x_coords.last().unwrap(); + let y_min = *y_coords.first().unwrap(); + let y_max = *y_coords.last().unwrap(); + let z_min = *z_coords.first().unwrap(); + let z_max = *z_coords.last().unwrap(); + let w_min = *w_coords.first().unwrap(); + let w_max = *w_coords.last().unwrap(); + let v_min = *v_coords.first().unwrap(); + let v_max = *v_coords.last().unwrap(); + + let t_x = 2.0 * (x - x_min) / (x_max - x_min) - 1.0; + let t_y = 2.0 * (y - y_min) / (y_max - y_min) - 1.0; + let t_z = 2.0 * (z - z_min) / (z_max - z_min) - 1.0; + let t_w = 2.0 * (w - w_min) / (w_max - w_min) - 1.0; + let t_v = 2.0 * (v_ - v_min) / (v_max - v_min) - 1.0; + + let x_coeffs = Self::barycentric_coefficients(t_x, &self.t_coords[0], &self.weights[0]); + let y_coeffs = Self::barycentric_coefficients(t_y, &self.t_coords[1], &self.weights[1]); + let z_coeffs = Self::barycentric_coefficients(t_z, &self.t_coords[2], &self.weights[2]); + let w_coeffs = Self::barycentric_coefficients(t_w, &self.t_coords[3], &self.weights[3]); + let v_coeffs = Self::barycentric_coefficients(t_v, &self.t_coords[4], &self.weights[4]); + + let mut result = 0.0; + for (i, &x_coeff) in x_coeffs.iter().enumerate() { + for (j, &y_coeff) in y_coeffs.iter().enumerate() { + for (k, &z_coeff) in z_coeffs.iter().enumerate() { + for (l, &w_coeff) in w_coeffs.iter().enumerate() { + for (m, &v_coeff) in v_coeffs.iter().enumerate() { + result += x_coeff + * y_coeff + * z_coeff + * w_coeff + * v_coeff + * data.values[[i, j, k, l, m]]; + } + } + } + } + } + + Ok(result) + } + + fn allow_extrapolate(&self) -> bool { + true + } +} + /// Implements a global N-dimensional batch interpolation using Chebyshev polynomials /// with logarithmic coordinate scaling. /// @@ -1435,12 +2262,14 @@ impl LogChebyshevBatchInterpolation<3> { mod tests { use super::*; use itertools::Itertools; - use ndarray::{Array1, Array2, Array3, OwnedRepr}; - use ninterp::data::{InterpData1D, InterpData2D}; + use ndarray::{Array, Array1, Array2, Array3, OwnedRepr}; + use std::f64::consts::PI; + + use ninterp::data::{InterpData1D, InterpData2D, InterpDataND}; use ninterp::interpolator::{Extrapolate, InterpND}; + use ninterp::num_traits::Float; use ninterp::prelude::Interpolator; use ninterp::strategy::Linear; - use std::f64::consts::PI; const EPSILON: f64 = 1e-9; @@ -1974,4 +2803,412 @@ mod tests { assert_close(*res, *exp, EPSILON); } } + + #[test] + fn test_log_fourcubic_interpolation() { + use ndarray::Array4; + use ninterp::data::InterpDataND; + + // Create 4D test grid + let x0_coords = create_logspaced(1e-5, 1e-3, 6); + let x1_coords = create_logspaced(1e2, 1e4, 6); + let x2_coords = [1.0, 5.0, 25.0, 100.0, 150.0, 200.0]; + let x3_coords = [0.5, 1.0, 2.0, 5.0, 10.0, 20.0]; + + // Generate test values: f(x0, x1, x2, x3) = x0 * x1 * x2 * x3 + let values: Vec = x0_coords + .iter() + .cartesian_product(x1_coords.iter()) + .cartesian_product(x2_coords.iter()) + .cartesian_product(x3_coords.iter()) + .map(|(((&a, &b), &c), &d)| a * b * c * d) + .collect(); + + // Transform to log space + let values_ln: Vec = values.iter().map(|val| val.ln()).collect(); + + // Create 4D array + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + ); + let values_array = Array4::from_shape_vec(shape, values_ln.clone()) + .unwrap() + .into_dyn(); + + // Create InterpDataND + let grids = vec![ + x0_coords.iter().map(|v| v.ln()).collect::>().into(), + x1_coords.iter().map(|v| v.ln()).collect::>().into(), + x2_coords.iter().map(|v| v.ln()).collect::>().into(), + x3_coords.iter().map(|v| v.ln()).collect::>().into(), + ]; + let interp_data_ln = InterpDataND::new(grids, values_array).unwrap(); + + // Initialize strategy + let mut strategy = LogFourCubicInterpolation; + strategy.init(&interp_data_ln).unwrap(); + + // Test interpolation at a point + let point: [f64; 4] = [1e-4, 2e3, 25.0, 5.0]; + let log_point = [point[0].ln(), point[1].ln(), point[2].ln(), point[3].ln()]; + let expected: f64 = point.iter().product(); + let result = strategy + .interpolate(&interp_data_ln, &log_point) + .unwrap() + .exp(); + + // Allow for some numerical error in 4D interpolation + let tolerance = 1e-6 * expected.abs(); + assert_close(result, expected, tolerance); + } + + #[test] + fn test_log_fourcubic_grid_point() { + use ndarray::Array4; + use ninterp::data::InterpDataND; + + // Test that interpolation at grid points returns exact values + let x0_coords = [0.1, 0.2, 0.3, 0.4]; + let x1_coords = [1.0, 2.0, 3.0, 4.0]; + let x2_coords = [10.0, 20.0, 30.0, 40.0]; + let x3_coords = [100.0, 200.0, 300.0, 400.0]; + + let values: Vec = x0_coords + .iter() + .cartesian_product(x1_coords.iter()) + .cartesian_product(x2_coords.iter()) + .cartesian_product(x3_coords.iter()) + .map(|(((&a, &b), &c), &d)| a + b + c + d) + .collect(); + + let values_ln: Vec = values.iter().map(|val| val.ln()).collect(); + + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + ); + let values_array = Array4::from_shape_vec(shape, values_ln).unwrap().into_dyn(); + + let grids = vec![ + x0_coords.iter().map(|v| v.ln()).collect::>().into(), + x1_coords.iter().map(|v| v.ln()).collect::>().into(), + x2_coords.iter().map(|v| v.ln()).collect::>().into(), + x3_coords.iter().map(|v| v.ln()).collect::>().into(), + ]; + let interp_data = InterpDataND::new(grids, values_array).unwrap(); + + let mut strategy = LogFourCubicInterpolation; + strategy.init(&interp_data).unwrap(); + + // Test at a grid point (should be exact) + let test_point = [0.2f64.ln(), 2.0f64.ln(), 20.0f64.ln(), 200.0f64.ln()]; + let expected = (0.2f64 + 2.0 + 20.0 + 200.0).ln(); + let result = strategy.interpolate(&interp_data, &test_point).unwrap(); + + assert_close(result, expected, EPSILON); + } + + #[test] + fn test_log_fourcubic_init_validation() { + use ndarray::Array4; + use ninterp::data::InterpDataND; + + // Test that init fails with too few grid points + let x0_coords = vec![0.1, 0.2, 0.3]; // Only 3 points + let x1_coords = vec![1.0, 2.0, 3.0, 4.0]; + let x2_coords = vec![10.0, 20.0, 30.0, 40.0]; + let x3_coords = vec![100.0, 200.0, 300.0, 400.0]; + + let n_total = x0_coords.len() * x1_coords.len() * x2_coords.len() * x3_coords.len(); + let values = vec![1.0; n_total]; + + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + ); + let values_array = Array4::from_shape_vec(shape, values).unwrap().into_dyn(); + + let grids = vec![ + x0_coords.into(), + x1_coords.into(), + x2_coords.into(), + x3_coords.into(), + ]; + let interp_data = InterpDataND::new(grids, values_array).unwrap(); + + let mut strategy = LogFourCubicInterpolation; + let result = strategy.init(&interp_data); + + assert!(result.is_err()); + } + + #[test] + fn test_log_fivecubic_interpolation() { + use ndarray::Array5; + use ninterp::data::InterpDataND; + + // Create 5D test grid + let x0_coords = create_logspaced(1e-5, 1e-3, 5); + let x1_coords = create_logspaced(1e2, 1e4, 5); + let x2_coords = [1.0, 5.0, 25.0, 100.0, 200.0]; + let x3_coords = [0.5, 1.0, 2.0, 5.0, 10.0]; + let x4_coords = [10.0, 20.0, 50.0, 100.0, 200.0]; + + // Generate test values: f(x0, x1, x2, x3, x4) = x0 * x1 * x2 * x3 * x4 + let values: Vec = x0_coords + .iter() + .cartesian_product(x1_coords.iter()) + .cartesian_product(x2_coords.iter()) + .cartesian_product(x3_coords.iter()) + .cartesian_product(x4_coords.iter()) + .map(|((((&a, &b), &c), &d), &e)| a * b * c * d * e) + .collect(); + + // Transform to log space + let values_ln: Vec = values.iter().map(|val| val.ln()).collect(); + + // Create 5D array + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + x4_coords.len(), + ); + let values_array = Array5::from_shape_vec(shape, values_ln.clone()) + .unwrap() + .into_dyn(); + + // Create InterpDataND + let grids = vec![ + x0_coords.iter().map(|v| v.ln()).collect::>().into(), + x1_coords.iter().map(|v| v.ln()).collect::>().into(), + x2_coords.iter().map(|v| v.ln()).collect::>().into(), + x3_coords.iter().map(|v| v.ln()).collect::>().into(), + x4_coords.iter().map(|v| v.ln()).collect::>().into(), + ]; + let interp_data_ln = InterpDataND::new(grids, values_array).unwrap(); + + // Initialize strategy + let mut strategy = LogFiveCubicInterpolation; + strategy.init(&interp_data_ln).unwrap(); + + // Test interpolation at a point + let point: [f64; 5] = [1e-4, 2e3, 25.0, 5.0, 100.0]; + let log_point = [ + point[0].ln(), + point[1].ln(), + point[2].ln(), + point[3].ln(), + point[4].ln(), + ]; + let expected: f64 = point.iter().product(); + let result = strategy + .interpolate(&interp_data_ln, &log_point) + .unwrap() + .exp(); + + // Allow for some numerical error in 5D interpolation + let tolerance = 1e-5 * expected.abs(); + assert_close(result, expected, tolerance); + } + + #[test] + fn test_log_fivecubic_grid_point() { + use ndarray::Array5; + use ninterp::data::InterpDataND; + + // Test that interpolation at grid points returns exact values + let x0_coords = [0.1, 0.2, 0.3, 0.4]; + let x1_coords = [1.0, 2.0, 3.0, 4.0]; + let x2_coords = [10.0, 20.0, 30.0, 40.0]; + let x3_coords = [100.0, 200.0, 300.0, 400.0]; + let x4_coords = [1000.0, 2000.0, 3000.0, 4000.0]; + + let values: Vec = x0_coords + .iter() + .cartesian_product(x1_coords.iter()) + .cartesian_product(x2_coords.iter()) + .cartesian_product(x3_coords.iter()) + .cartesian_product(x4_coords.iter()) + .map(|((((&a, &b), &c), &d), &e)| a + b + c + d + e) + .collect(); + + let values_ln: Vec = values.iter().map(|val| val.ln()).collect(); + + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + x4_coords.len(), + ); + let values_array = Array5::from_shape_vec(shape, values_ln).unwrap().into_dyn(); + + let grids = vec![ + x0_coords.iter().map(|v| v.ln()).collect::>().into(), + x1_coords.iter().map(|v| v.ln()).collect::>().into(), + x2_coords.iter().map(|v| v.ln()).collect::>().into(), + x3_coords.iter().map(|v| v.ln()).collect::>().into(), + x4_coords.iter().map(|v| v.ln()).collect::>().into(), + ]; + let interp_data = InterpDataND::new(grids, values_array).unwrap(); + + let mut strategy = LogFiveCubicInterpolation; + strategy.init(&interp_data).unwrap(); + + // Test at a grid point (should be exact) + let test_point = [ + 0.2f64.ln(), + 2.0f64.ln(), + 20.0f64.ln(), + 200.0f64.ln(), + 2000.0f64.ln(), + ]; + let expected = (0.2f64 + 2.0 + 20.0 + 200.0 + 2000.0).ln(); + let result = strategy.interpolate(&interp_data, &test_point).unwrap(); + + assert_close(result, expected, EPSILON); + } + + #[test] + fn test_log_fivecubic_init_validation() { + use ndarray::Array5; + use ninterp::data::InterpDataND; + + // Test that init fails with too few grid points + let x0_coords = vec![0.1, 0.2, 0.3]; // Only 3 points + let x1_coords = vec![1.0, 2.0, 3.0, 4.0]; + let x2_coords = vec![10.0, 20.0, 30.0, 40.0]; + let x3_coords = vec![100.0, 200.0, 300.0, 400.0]; + let x4_coords = vec![1000.0, 2000.0, 3000.0, 4000.0]; + + let n_total = + x0_coords.len() * x1_coords.len() * x2_coords.len() * x3_coords.len() * x4_coords.len(); + let values = vec![1.0; n_total]; + + let shape = ( + x0_coords.len(), + x1_coords.len(), + x2_coords.len(), + x3_coords.len(), + x4_coords.len(), + ); + let values_array = Array5::from_shape_vec(shape, values).unwrap().into_dyn(); + + let grids = vec![ + x0_coords.into(), + x1_coords.into(), + x2_coords.into(), + x3_coords.into(), + x4_coords.into(), + ]; + let interp_data = InterpDataND::new(grids, values_array).unwrap(); + + let mut strategy = LogFiveCubicInterpolation; + let result = strategy.init(&interp_data); + + assert!(result.is_err()); + } + + #[test] + fn test_log_chebyshev_4d() { + let n = 4; + let x_coords = create_cheby_grid(n, 0.1, 10.0); + let y_coords = create_cheby_grid(n, 0.1, 10.0); + let z_coords = create_cheby_grid(n, 0.1, 10.0); + let w_coords = create_cheby_grid(n, 0.1, 10.0); + + let f_values: Vec = x_coords + .iter() + .cartesian_product(y_coords.iter()) + .cartesian_product(z_coords.iter()) + .cartesian_product(w_coords.iter()) + .map(|(((&x, &y), &z), &w)| x.ln() + y.ln() + z.ln() + w.ln()) + .collect(); + + let shape = ( + x_coords.len(), + y_coords.len(), + z_coords.len(), + w_coords.len(), + ); + let values_array = Array::from_shape_vec(shape, f_values).unwrap().into_dyn(); + + let data = InterpDataND { + grid: vec![ + x_coords.iter().map(|v| v.ln()).collect::>().into(), + y_coords.iter().map(|v| v.ln()).collect::>().into(), + z_coords.iter().map(|v| v.ln()).collect::>().into(), + w_coords.iter().map(|v| v.ln()).collect::>().into(), + ], + values: values_array, + }; + + let mut strategy = LogChebyshevInterpolation::<4>::default(); + strategy.init(&data).unwrap(); + + let point = [2.5, 3.5, 4.5, 5.5]; + let log_point: Vec = point.iter().map(|v| v.ln()).collect(); + let expected = log_point.iter().sum(); + let result = strategy.interpolate(&data, &log_point).unwrap(); + + assert_close(result, expected, EPSILON); + } + + #[test] + fn test_log_chebyshev_5d() { + let n = 4; + let x_coords = create_cheby_grid(n, 0.1, 10.0); + let y_coords = create_cheby_grid(n, 0.1, 10.0); + let z_coords = create_cheby_grid(n, 0.1, 10.0); + let w_coords = create_cheby_grid(n, 0.1, 10.0); + let v_coords = create_cheby_grid(n, 0.1, 10.0); + + let f_values: Vec = x_coords + .iter() + .cartesian_product(y_coords.iter()) + .cartesian_product(z_coords.iter()) + .cartesian_product(w_coords.iter()) + .cartesian_product(v_coords.iter()) + .map(|((((&x, &y), &z), &w), &v)| x.ln() + y.ln() + z.ln() + w.ln() + v.ln()) + .collect(); + + let shape = ( + x_coords.len(), + y_coords.len(), + z_coords.len(), + w_coords.len(), + v_coords.len(), + ); + let values_array = Array::from_shape_vec(shape, f_values).unwrap().into_dyn(); + + let data = InterpDataND { + grid: vec![ + x_coords.iter().map(|v| v.ln()).collect::>().into(), + y_coords.iter().map(|v| v.ln()).collect::>().into(), + z_coords.iter().map(|v| v.ln()).collect::>().into(), + w_coords.iter().map(|v| v.ln()).collect::>().into(), + v_coords.iter().map(|v| v.ln()).collect::>().into(), + ], + values: values_array, + }; + + let mut strategy = LogChebyshevInterpolation::<5>::default(); + strategy.init(&data).unwrap(); + + let point = [2.5, 3.5, 4.5, 5.5, 6.5]; + let log_point: Vec = point.iter().map(|v| v.ln()).collect(); + let expected = log_point.iter().sum(); + let result = strategy.interpolate(&data, &log_point).unwrap(); + + assert_close(result, expected, EPSILON); + } } diff --git a/neopdf/src/subgrid.rs b/neopdf/src/subgrid.rs index 7de78bf6..fd83d09b 100644 --- a/neopdf/src/subgrid.rs +++ b/neopdf/src/subgrid.rs @@ -6,7 +6,7 @@ //! - [`SubGrid`]: Represents a region of phase space with a consistent grid and provides //! methods for subgrid logic. -use ndarray::{s, Array1, Array6, ArrayView2}; +use ndarray::{s, Array1, Array6, ArrayD, ArrayView2, IxDyn}; use serde::{Deserialize, Serialize}; use super::interpolator::InterpolationConfig; @@ -45,12 +45,16 @@ impl ParamRange { } } -/// Represents the parameter ranges for `x` and `q2`. +/// Represents the parameter ranges for all dimensions. pub struct RangeParameters { /// The range for the nucleon numbers `A`. pub nucleons: ParamRange, /// The range for the AlphaS values `as`. pub alphas: ParamRange, + /// The range for the xi values. + pub xi: ParamRange, + /// The range for the delta values. + pub delta: ParamRange, /// The range for the transverse momentum `kT`. pub kt: ParamRange, /// The range for the momentum fraction `x`. @@ -66,12 +70,16 @@ impl RangeParameters { /// /// * `nucleons` - The `ParamRange` for the nuleon numbers `A`. /// * `alphas` - The `ParamRange` for the strong coupling `as`. + /// * `xi` - The `ParamRange` for the xi values. + /// * `delta` - The `ParamRange` for the delta values. /// * `kt` - The `ParamRange` for the transverse momentum `kT`. /// * `x` - The `ParamRange` for the momentum fraction `x`. /// * `q2` - The `ParamRange` for the energy scale `q2`. pub fn new( nucleons: ParamRange, alphas: ParamRange, + xi: ParamRange, + delta: ParamRange, kt: ParamRange, x: ParamRange, q2: ParamRange, @@ -79,6 +87,8 @@ impl RangeParameters { Self { nucleons, alphas, + xi, + delta, kt, x, q2, @@ -86,10 +96,45 @@ impl RangeParameters { } } +/// Enum to hold either 6D or 8D grid data for backward compatibility. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum GridData { + /// 6-dimensional grid data: [nucleons, alphas, pids, kT, x, Q2]. + Grid6D(Array6), + /// 8-dimensional grid data: [nucleons, alphas, xi, delta, kT, pids, x, Q2]. + Grid8D(ArrayD), +} + +impl GridData { + /// Returns a view of the grid data for slicing operations. + pub fn view(&self) -> ndarray::ArrayViewD<'_, f64> { + match self { + GridData::Grid6D(arr) => arr.view().into_dyn(), + GridData::Grid8D(arr) => arr.view(), + } + } + + /// Returns a reference to the 6D grid, panicking if it's 8D. + pub fn as_6d(&self) -> &Array6 { + match self { + GridData::Grid6D(arr) => arr, + GridData::Grid8D(_) => panic!("Cannot convert 8D grid to 6D"), + } + } + + /// Returns a reference to the 8D grid, panicking if it's 6D. + pub fn as_8d(&self) -> &ArrayD { + match self { + GridData::Grid6D(_) => panic!("Cannot convert 6D grid to 8D"), + GridData::Grid8D(arr) => arr, + } + } +} + /// Stores the PDF grid data for a single subgrid. /// /// A subgrid represents a region of the phase space with a consistent -/// grid of `x` and `Q²` values. +/// grid of `x` and `Q2` values. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubGrid { /// Array of `x` values (momentum fraction). @@ -98,8 +143,12 @@ pub struct SubGrid { pub q2s: Array1, /// Array of `kT` values (transverse momentum). pub kts: Array1, - /// 6-dimensional grid data: [nucleons, alphas, pids, kT, x, Q²]. - pub grid: Array6, + /// Array of `xi` values. + pub xis: Array1, + /// Array of `delta` values. + pub deltas: Array1, + /// Grid data (either 6D or 7D for backward compatibility). + pub grid: GridData, /// Array of nucleon number values. pub nucleons: Array1, /// Array of alpha_s values. @@ -108,6 +157,10 @@ pub struct SubGrid { pub nucleons_range: ParamRange, /// The valid range for the `AlphaS` parameter in this subgrid. pub alphas_range: ParamRange, + /// The valid range for the `xi` parameter in this subgrid. + pub xi_range: ParamRange, + /// The valid range for the `delta` parameter in this subgrid. + pub delta_range: ParamRange, /// The valid range for the `kT` parameter in this subgrid. pub kt_range: ParamRange, /// The valid range for the `x` parameter in this subgrid. @@ -117,15 +170,15 @@ pub struct SubGrid { } impl SubGrid { - /// Creates a new `SubGrid` from raw data. + /// Creates a new 6D `SubGrid` from raw data (for backward compatibility). /// /// # Arguments /// /// * `nucleon_numbers` - A vector of nucleon numbers. /// * `alphas_values` - A vector of alpha_s values. /// * `kt_subgrid` - A vector of `kT` values. - /// * `xs` - A vector of `x` values. - /// * `q2s` - A vector of `q2` values. + /// * `x_subgrid` - A vector of `x` values. + /// * `q2_subgrid` - A vector of `q2` values. /// * `nflav` - The number of quark flavors. /// * `grid_data` - A flat vector of grid data points. /// @@ -173,11 +226,93 @@ impl SubGrid { xs: Array1::from_vec(x_subgrid), q2s: Array1::from_vec(q2_subgrid), kts: Array1::from_vec(kt_subgrid), - grid: subgrid, + xis: Array1::from_vec(vec![0.0]), + deltas: Array1::from_vec(vec![0.0]), + grid: GridData::Grid6D(subgrid), + nucleons: Array1::from_vec(nucleon_numbers), + alphas: Array1::from_vec(alphas_values), + nucleons_range: ncs_range, + alphas_range: as_range, + xi_range: ParamRange::new(0.0, 0.0), + delta_range: ParamRange::new(0.0, 0.0), + kt_range: kts_range, + x_range: xs_range, + q2_range: q2s_range, + } + } + + /// Creates a new 8D `SubGrid` from raw data. + /// + /// # Arguments + /// + /// * `nucleon_numbers` - A vector of nucleon numbers. + /// * `alphas_values` - A vector of alpha_s values. + /// * `xi_values` - A vector of xi values. + /// * `delta_values` - A vector of delta values. + /// * `kt_subgrid` - A vector of `kT` values. + /// * `x_subgrid` - A vector of `x` values. + /// * `q2_subgrid` - A vector of `q2` values. + /// * `nflav` - The number of quark flavors. + /// * `grid_data` - A flat vector of grid data points. + /// + /// # Panics + /// + /// Panics if the grid data cannot be reshaped to the expected dimensions. + #[allow(clippy::too_many_arguments)] + pub fn new_8d( + nucleon_numbers: Vec, + alphas_values: Vec, + xi_values: Vec, + delta_values: Vec, + kt_subgrid: Vec, + x_subgrid: Vec, + q2_subgrid: Vec, + nflav: usize, + grid_data: Vec, + ) -> Self { + let xs_range = ParamRange::new(*x_subgrid.first().unwrap(), *x_subgrid.last().unwrap()); + let q2s_range = ParamRange::new(*q2_subgrid.first().unwrap(), *q2_subgrid.last().unwrap()); + let kts_range = ParamRange::new(*kt_subgrid.first().unwrap(), *kt_subgrid.last().unwrap()); + let xis_range = ParamRange::new(*xi_values.first().unwrap(), *xi_values.last().unwrap()); + let deltas_range = ParamRange::new( + *delta_values.first().unwrap(), + *delta_values.last().unwrap(), + ); + let ncs_range = ParamRange::new( + *nucleon_numbers.first().unwrap(), + *nucleon_numbers.last().unwrap(), + ); + let as_range = ParamRange::new( + *alphas_values.first().unwrap(), + *alphas_values.last().unwrap(), + ); + + let shape = IxDyn(&[ + nucleon_numbers.len(), + alphas_values.len(), + xi_values.len(), + delta_values.len(), + kt_subgrid.len(), + nflav, + x_subgrid.len(), + q2_subgrid.len(), + ]); + + let subgrid = ArrayD::from_shape_vec(shape, grid_data).expect("Failed to create 8D grid"); + + Self { + xs: Array1::from_vec(x_subgrid), + q2s: Array1::from_vec(q2_subgrid), + kts: Array1::from_vec(kt_subgrid), + xis: Array1::from_vec(xi_values), + deltas: Array1::from_vec(delta_values), + grid: GridData::Grid8D(subgrid), nucleons: Array1::from_vec(nucleon_numbers), alphas: Array1::from_vec(alphas_values), nucleons_range: ncs_range, alphas_range: as_range, + xi_range: xis_range, + delta_range: deltas_range, kt_range: kts_range, x_range: xs_range, q2_range: q2s_range, @@ -200,15 +335,36 @@ impl SubGrid { InterpolationConfig::TwoD => (2, vec![]), InterpolationConfig::ThreeDNucleons => (3, vec![&self.nucleons_range]), InterpolationConfig::ThreeDAlphas => (3, vec![&self.alphas_range]), + InterpolationConfig::ThreeDXi => (3, vec![&self.xi_range]), + InterpolationConfig::ThreeDDelta => (3, vec![&self.delta_range]), InterpolationConfig::ThreeDKt => (3, vec![&self.kt_range]), InterpolationConfig::FourDNucleonsAlphas => { (4, vec![&self.nucleons_range, &self.alphas_range]) } InterpolationConfig::FourDNucleonsKt => (4, vec![&self.nucleons_range, &self.kt_range]), InterpolationConfig::FourDAlphasKt => (4, vec![&self.alphas_range, &self.kt_range]), - InterpolationConfig::FiveD => ( - 5, - vec![&self.nucleons_range, &self.alphas_range, &self.kt_range], + InterpolationConfig::FourDXiDelta => (4, vec![&self.xi_range, &self.delta_range]), + InterpolationConfig::FiveD => { + (5, vec![&self.kt_range, &self.xi_range, &self.delta_range]) + } + InterpolationConfig::SixD => ( + 6, + vec![ + &self.nucleons_range, + &self.kt_range, + &self.xi_range, + &self.delta_range, + ], + ), + InterpolationConfig::SevenD => ( + 7, + vec![ + &self.nucleons_range, + &self.alphas_range, + &self.xi_range, + &self.delta_range, + &self.kt_range, + ], ), }; @@ -236,27 +392,39 @@ impl SubGrid { /// Gathers the parameter ranges for the subgrid based on its configuration. fn parameter_ranges(&self) -> Vec { - let mut ranges = match self.interpolation_config() { - InterpolationConfig::TwoD => vec![], - InterpolationConfig::ThreeDNucleons => vec![self.nucleons_range], - InterpolationConfig::ThreeDAlphas => vec![self.alphas_range], - InterpolationConfig::ThreeDKt => vec![self.kt_range], - InterpolationConfig::FourDNucleonsAlphas => { - vec![self.nucleons_range, self.alphas_range] - } - InterpolationConfig::FourDNucleonsKt => vec![self.nucleons_range, self.kt_range], - InterpolationConfig::FourDAlphasKt => vec![self.alphas_range, self.kt_range], - InterpolationConfig::FiveD => { - vec![self.nucleons_range, self.alphas_range, self.kt_range] - } - }; + let mut ranges = Vec::new(); + + // Add ranges based on which dimensions are active + if self.nucleons.len() > 1 { + ranges.push(self.nucleons_range); + } + if self.alphas.len() > 1 { + ranges.push(self.alphas_range); + } + if self.xis.len() > 1 { + ranges.push(self.xi_range); + } + if self.deltas.len() > 1 { + ranges.push(self.delta_range); + } + if self.kts.len() > 1 { + ranges.push(self.kt_range); + } + + // Always include x and q2 ranges.extend([self.x_range, self.q2_range]); ranges } /// Gets the interpolation configuration for this subgrid. pub fn interpolation_config(&self) -> InterpolationConfig { - InterpolationConfig::from_dimensions(self.nucleons.len(), self.alphas.len(), self.kts.len()) + InterpolationConfig::from_dimensions( + self.nucleons.len(), + self.alphas.len(), + self.xis.len(), + self.deltas.len(), + self.kts.len(), + ) } /// Gets the parameter ranges for this subgrid. @@ -264,6 +432,8 @@ impl SubGrid { RangeParameters::new( self.nucleons_range, self.alphas_range, + self.xi_range, + self.delta_range, self.kt_range, self.x_range, self.q2_range, @@ -283,10 +453,42 @@ impl SubGrid { /// Panics if called on a subgrid that is not 2D. pub fn grid_slice(&self, pid_index: usize) -> ArrayView2<'_, f64> { match self.interpolation_config() { - InterpolationConfig::TwoD => self.grid.slice(s![0, 0, pid_index, 0, .., ..]), + InterpolationConfig::TwoD => match &self.grid { + GridData::Grid6D(grid) => grid.slice(s![0, 0, pid_index, 0, .., ..]), + GridData::Grid8D(grid) => grid.slice(s![0, 0, 0, 0, 0, pid_index, .., ..]), + }, _ => panic!("grid_slice only valid for 2D interpolation"), } } + + /// Returns a reference to the underlying grid (6D). + /// + /// # Panics + /// + /// Panics if the grid is 8D. + pub fn grid_6d(&self) -> &Array6 { + match &self.grid { + GridData::Grid6D(grid) => grid, + GridData::Grid8D(_) => panic!("Cannot access 8D grid as 6D"), + } + } + + /// Returns a reference to the underlying grid (8D). + /// + /// # Panics + /// + /// Panics if the grid is 6D. + pub fn grid_8d(&self) -> &ArrayD { + match &self.grid { + GridData::Grid6D(_) => panic!("Cannot access 6D grid as 8D"), + GridData::Grid8D(grid) => grid, + } + } + + /// Returns true if this is an 8D grid. + pub fn is_8d(&self) -> bool { + matches!(self.grid, GridData::Grid8D(_)) + } } #[cfg(test)] diff --git a/neopdf/src/writer.rs b/neopdf/src/writer.rs index 06101fa4..0544184a 100644 --- a/neopdf/src/writer.rs +++ b/neopdf/src/writer.rs @@ -29,6 +29,7 @@ use std::sync::Arc; use git_version::git_version; use lz4_flex::frame::{FrameDecoder, FrameEncoder}; +use tempfile::NamedTempFile; use super::gridpdf::GridArray; use super::metadata::MetaData; @@ -76,12 +77,14 @@ impl GridArrayCollection { let buf_writer = BufWriter::new(file); let mut encoder = FrameEncoder::new(buf_writer); - let mut metadata_mut = metadata.as_latest(); - metadata_mut.git_version = GIT_VERSION.to_string(); - metadata_mut.code_version = CODE_VERSION.to_string(); - - let updated_metadata = MetaData::new_v1(metadata_mut); - let metadata_serialized = bincode::serialize(&updated_metadata)?; + let mut metadata_mut = metadata.clone(); + if metadata_mut.git_version.is_empty() || metadata_mut.git_version == "unknown" { + metadata_mut.git_version = GIT_VERSION.to_string(); + } + if metadata_mut.code_version.is_empty() { + metadata_mut.code_version = CODE_VERSION.to_string(); + } + let metadata_serialized = bincode::serialize(&metadata_mut)?; let metadata_size = metadata_serialized.len() as u64; let metadata_size_bytes = bincode::serialize(&metadata_size)?; @@ -129,7 +132,12 @@ impl GridArrayCollection { encoder.write_all(serialized)?; } - encoder.finish()?; + let mut writer = encoder.finish()?; + writer.flush()?; + + // Sync to disk to ensure data is written before returning + writer.get_mut().sync_all()?; + Ok(()) } @@ -235,6 +243,10 @@ pub struct GridArrayReader { impl GridArrayReader { /// Creates a new reader from a file, enabling random access to grid members. /// + /// This method automatically handles backward compatibility with v0.2.0 files. + /// If reading with the current format fails, it falls back to using the legacy + /// loader. + /// /// # Arguments /// /// * `path` - Input file path. @@ -243,6 +255,32 @@ impl GridArrayReader { /// /// A [`GridArrayReader`] instance on success, or an error if reading fails. pub fn from_file>(path: P) -> Result> { + match Self::from_file_v2(path.as_ref()) { + Ok(reader) => Ok(reader), + Err(err) => { + let error_string = format!("{:?}", err); + + if error_string.contains("UnexpectedEof") + || error_string.contains("Eof") + || error_string.contains("Grid is not v2") + { + match Self::from_file_legacy(path.as_ref()) { + Ok(reader) => Ok(reader), + Err(legacy_err) => Err(format!( + "Failed to load PDF with both v0.2.1+ ({}) and v0.2.0 ({}) loaders", + err, legacy_err + ) + .into()), + } + } else { + Err(err) + } + } + } + } + + /// Loads a file using the v0.2.1+ format. + fn from_file_v2(path: &Path) -> Result> { let file = File::open(path)?; let buf_reader = BufReader::new(file); let mut decoder = FrameDecoder::new(buf_reader); @@ -252,7 +290,6 @@ impl GridArrayReader { let mut cursor = std::io::Cursor::new(&data); - // Read metadata let metadata_size: u64 = bincode::deserialize_from(&mut cursor)?; let mut metadata_bytes = vec![0u8; metadata_size as usize]; cursor.read_exact(&mut metadata_bytes)?; @@ -260,10 +297,8 @@ impl GridArrayReader { let shared_metadata = Arc::new(metadata); let count: u64 = bincode::deserialize_from(&mut cursor)?; - // Read offset table size (but don't skip it!) let _offset_table_size: u64 = bincode::deserialize_from(&mut cursor)?; - // Read the actual offsets let mut offsets = Vec::with_capacity(count as usize); for _ in 0..count { let offset: u64 = bincode::deserialize_from(&mut cursor)?; @@ -281,6 +316,78 @@ impl GridArrayReader { }) } + /// Loads a file using the legacy v0.2.0 format and converts it to v0.2.1+ format. + fn from_file_legacy(path: &Path) -> Result> { + let legacy_reader = neopdf_legacy::writer::GridArrayReader::from_file(path)?; + + let legacy_metadata = legacy_reader.metadata(); + let metadata = MetaData::from((**legacy_metadata).clone()); + + let mut new_grids = Vec::new(); + for i in 0..legacy_reader.len() { + let legacy_grid_with_meta = legacy_reader.load_grid(i)?; + let converted_grid = Self::convert_legacy_grid(legacy_grid_with_meta.grid); + new_grids.push(converted_grid); + } + + let temp_file = NamedTempFile::new()?; + let temp_path = temp_file.path(); + + let grid_refs: Vec<&GridArray> = new_grids.iter().collect(); + GridArrayCollection::compress(&grid_refs, &metadata, temp_path)?; + let result = Self::from_file_v2(temp_path)?; + + Ok(result) + } + + /// Converts a legacy GridArray to the new format. + fn convert_legacy_grid(legacy_grid: neopdf_legacy::gridpdf::GridArray) -> GridArray { + use crate::subgrid::{GridData, ParamRange, SubGrid}; + use ndarray::Array1; + + let subgrids: Vec = legacy_grid + .subgrids + .into_iter() + .map(|legacy_subgrid| SubGrid { + xs: legacy_subgrid.xs, + q2s: legacy_subgrid.q2s, + kts: legacy_subgrid.kts, + xis: Array1::from_vec(vec![0.0]), + deltas: Array1::from_vec(vec![0.0]), + grid: GridData::Grid6D(legacy_subgrid.grid), + nucleons: legacy_subgrid.nucleons, + alphas: legacy_subgrid.alphas, + nucleons_range: ParamRange { + min: legacy_subgrid.nucleons_range.min, + max: legacy_subgrid.nucleons_range.max, + }, + alphas_range: ParamRange { + min: legacy_subgrid.alphas_range.min, + max: legacy_subgrid.alphas_range.max, + }, + xi_range: ParamRange::new(0.0, 0.0), + delta_range: ParamRange::new(0.0, 0.0), + kt_range: ParamRange { + min: legacy_subgrid.kt_range.min, + max: legacy_subgrid.kt_range.max, + }, + x_range: ParamRange { + min: legacy_subgrid.x_range.min, + max: legacy_subgrid.x_range.max, + }, + q2_range: ParamRange { + min: legacy_subgrid.q2_range.min, + max: legacy_subgrid.q2_range.max, + }, + }) + .collect(); + + GridArray { + pids: legacy_grid.pids, + subgrids, + } + } + /// Returns the number of grid arrays in the collection. pub fn len(&self) -> usize { self.count as usize @@ -304,8 +411,8 @@ impl GridArrayReader { /// /// # Returns /// - /// The requested [`GridArrayWithMetadata`] on success, or an error if the index is out - /// of bounds or reading fails. + /// The requested [`GridArrayWithMetadata`] on success, or an error if the + /// index is out of bounds or reading fails. pub fn load_grid( &self, index: usize, @@ -385,6 +492,9 @@ impl LazyGridArrayIterator { /// Creates a new lazy iterator from a file path. /// + /// This method automatically handles backward compatibility with v0.2.0 files. + /// If reading with the current format fails, it falls back to using the legacy loader. + /// /// # Arguments /// /// * `path` - Input file path. @@ -393,11 +503,61 @@ impl LazyGridArrayIterator { /// /// A [`LazyGridArrayIterator`] instance on success, or an error if reading fails. pub fn from_file>(path: P) -> Result> { + match Self::from_file_v2(path.as_ref()) { + Ok(iter) => Ok(iter), + Err(e) => { + let error_string = format!("{:?}", e); + + if error_string.contains("UnexpectedEof") + || error_string.contains("Eof") + || error_string.contains("unexpected end of file") + { + match Self::from_file_legacy(path.as_ref()) { + Ok(iter) => Ok(iter), + Err(legacy_err) => Err(format!( + "Failed to load PDF with both v0.2.1+ ({}) and v0.2.0 ({}) loaders", + e, legacy_err + ) + .into()), + } + } else { + Err(e) + } + } + } + } + + /// Loads a file using the v0.2.1+ format for lazy iteration. + fn from_file_v2(path: &Path) -> Result> { let file = File::open(path)?; let buf_reader = BufReader::new(file); Self::new(buf_reader) } + /// Loads a file using the legacy v0.2.0 format and converts it to v0.2.1+ format for lazy iteration. + fn from_file_legacy(path: &Path) -> Result> { + let legacy_reader = neopdf_legacy::writer::GridArrayReader::from_file(path)?; + + let legacy_metadata = legacy_reader.metadata(); + let metadata = MetaData::from((**legacy_metadata).clone()); + + let mut new_grids = Vec::new(); + for i in 0..legacy_reader.len() { + let legacy_grid_with_meta = legacy_reader.load_grid(i)?; + let converted_grid = GridArrayReader::convert_legacy_grid(legacy_grid_with_meta.grid); + new_grids.push(converted_grid); + } + + let temp_file = NamedTempFile::new()?; + let temp_path = temp_file.path(); + + let grid_refs: Vec<&GridArray> = new_grids.iter().collect(); + GridArrayCollection::compress(&grid_refs, &metadata, temp_path)?; + let result = Self::from_file_v2(temp_path)?; + + Ok(result) + } + /// Returns a reference to the shared metadata. pub fn metadata(&self) -> &Arc { &self.metadata @@ -413,10 +573,7 @@ impl Iterator for LazyGridArrayIterator { } let result = (|| -> Result> { - // Read size let size: u64 = bincode::deserialize_from(&mut self.cursor)?; - - // Read grid data self.buffer.resize(size as usize, 0); self.cursor.read_exact(&mut self.buffer)?; @@ -446,11 +603,11 @@ mod tests { use ndarray::Array1; use tempfile::NamedTempFile; - use crate::metadata::{InterpolatorType, MetaDataV1, SetType}; + use crate::metadata::{InterpolatorType, MetaDataV2, SetType}; #[test] fn test_collection_with_metadata() { - let metadata_v1 = MetaDataV1 { + let metadata = MetaDataV2 { set_desc: "Test PDF".into(), set_index: 1, num_members: 2, @@ -482,8 +639,12 @@ mod tests { m_top: 0.0, alphas_type: String::new(), number_flavors: 0, + // V2 fields + xi_min: 0.0, + xi_max: 0.0, + delta_min: 0.0, + delta_max: 0.0, }; - let metadata = MetaData::new_v1(metadata_v1); let test_grid = test_grid(); let grids = vec![&test_grid, &test_grid]; diff --git a/neopdf/tests/pdf.rs b/neopdf/tests/pdf.rs index 6f71ea2b..35c0899a 100644 --- a/neopdf/tests/pdf.rs +++ b/neopdf/tests/pdf.rs @@ -270,7 +270,7 @@ pub fn test_multi_members_loader() { pub fn test_multi_members_lazy_loader() { let pdfs = PDF::load_pdfs_lazy("NNPDF40_nnlo_as_01180.neopdf.lz4"); - let _ = pdfs.map(|pdf| { + pdfs.for_each(|pdf| { let result = match pdf { Ok(t) => t, Err(err) => unreachable!("{err}"), diff --git a/neopdf_capi/docs/source/examples.rst b/neopdf_capi/docs/source/examples.rst index 92efa438..6c28dc91 100644 --- a/neopdf_capi/docs/source/examples.rst +++ b/neopdf_capi/docs/source/examples.rst @@ -327,7 +327,7 @@ below in the case the grid should explicitly depend on more parameters. .. code-block:: cpp - :linenos: + :linenos: #include #include @@ -506,7 +506,7 @@ a dependence on the transverse momentum :math:`k_T`. The following example makes the `TMDlib `_ library to provide the TMD distributions. .. code-block:: cpp - :linenos: + :linenos: #include "neopdf_capi.h" #include "tmdlib/TMDlib.h" diff --git a/neopdf_capi/src/include/NeoPDF.hpp b/neopdf_capi/src/include/NeoPDF.hpp index c3ab2699..f1361382 100644 --- a/neopdf_capi/src/include/NeoPDF.hpp +++ b/neopdf_capi/src/include/NeoPDF.hpp @@ -95,6 +95,44 @@ struct MetaData { } }; +/** @brief C++ representation of NeoPDFMetaDataV2. */ +struct MetaDataV2 : public MetaData { + double xi_min = 1.0; + double xi_max = 1.0; + double delta_min = 0.0; + double delta_max = 0.0; + + // Conversion to C struct + NeoPDFMetaDataV2 to_c_v2() const { + NeoPDFMetaDataV2 c_meta; + c_meta.set_desc = set_desc.c_str(); + c_meta.set_index = set_index; + c_meta.num_members = num_members; + c_meta.x_min = x_min; + c_meta.x_max = x_max; + c_meta.q_min = q_min; + c_meta.q_max = q_max; + c_meta.flavors = flavors.data(); + c_meta.num_flavors = flavors.size(); + c_meta.format = format.c_str(); + c_meta.alphas_q_values = alphas_q_values.data(); + c_meta.num_alphas_q = alphas_q_values.size(); + c_meta.alphas_vals = alphas_vals.data(); + c_meta.num_alphas_vals = alphas_vals.size(); + c_meta.polarised = polarised; + c_meta.set_type = set_type; + c_meta.interpolator_type = interpolator_type; + c_meta.error_type = error_type.c_str(); + c_meta.hadron_pid = hadron_pid; + c_meta.phys_params = phys_params.to_c(); + c_meta.xi_min = xi_min; + c_meta.xi_max = xi_max; + c_meta.delta_min = delta_min; + c_meta.delta_max = delta_max; + return c_meta; + } +}; + class NeoPDFs; // Forward declaration /** @brief Base PDF class that instantiates the PDF object. */ @@ -420,6 +458,47 @@ class GridWriter { } } + /** + * @brief Adds a subgrid to the current grid (v2 for 8D). + * + * @param nucleons Vector of nucleon numbers. + * @param alphas Vector of alpha_s values. + * @param xis Vector of xi values. + * @param deltas Vector of delta values. + * @param kts Vector of kt values. + * @param xs Vector of x values. + * @param q2s Vector of Q2 values. + * @param grid_data Vector of grid data. + */ + void add_subgrid_v2( + const std::vector& nucleons, + const std::vector& alphas, + const std::vector& xis, + const std::vector& deltas, + const std::vector& kts, + const std::vector& xs, + const std::vector& q2s, + const std::vector& grid_data + ) { + if (!current_grid) { + throw std::runtime_error("No grid started. Call new_grid() first."); + } + NeopdfResult result = neopdf_grid_add_subgridv2( + current_grid, + nucleons.data(), nucleons.size(), + alphas.data(), alphas.size(), + xis.data(), xis.size(), + deltas.data(), deltas.size(), + kts.data(), kts.size(), + xs.data(), xs.size(), + q2s.data(), q2s.size(), + grid_data.data(), grid_data.size() + ); + if (result != NeopdfResult::NEOPDF_RESULT_SUCCESS) { + throw std::runtime_error("Failed to add subgrid (v2)"); + } + } + /** * @brief Finalizes the current grid, sets its flavors, and adds it to the collection. * @@ -468,6 +547,27 @@ class GridWriter { throw std::runtime_error("Failed to compress grid data"); } } + + /** + * @brief Compresses the added grids and writes them to a file using V2 metadata. + * + * @param metadata The V2 metadata for the PDF set. + * @param output_path The path to the output file. + */ + void compress_v2(const MetaDataV2& metadata, const std::string& output_path) { + if (current_grid) { + neopdf_grid_free(current_grid); + current_grid = nullptr; + throw std::runtime_error("A grid was being built but was not committed before compress()."); + } + + NeoPDFMetaDataV2 c_meta = metadata.to_c_v2(); + NeopdfResult result = neopdf_grid_compress_v2(collection_raw, &c_meta, output_path.c_str()); + + if (result != NeopdfResult::NEOPDF_RESULT_SUCCESS) { + throw std::runtime_error("Failed to compress grid data (v2)"); + } + } }; } // namespace neopdf diff --git a/neopdf_capi/src/lib.rs b/neopdf_capi/src/lib.rs index 8768ed7d..f0a296b0 100644 --- a/neopdf_capi/src/lib.rs +++ b/neopdf_capi/src/lib.rs @@ -5,7 +5,7 @@ use std::os::raw::{c_char, c_double, c_int}; use std::slice; use neopdf::gridpdf::{ForcePositive, GridArray}; -use neopdf::metadata::{InterpolatorType, MetaData, MetaDataV1, SetType}; +use neopdf::metadata::{InterpolatorType, MetaData, SetType}; use neopdf::parser::SubgridData; use neopdf::pdf::PDF; use neopdf::writer::GridArrayCollection; @@ -478,6 +478,10 @@ pub enum NeopdfSubgridParams { Nucleons, /// The strong coupling constant (`alpha_s`) parameter. Alphas, + /// The xi parameter. + Xi, + /// The delta parameter. + Delta, /// The transverse momentum `kT` parameter. Kt, /// The momentum fraction (x) parameter. @@ -531,6 +535,11 @@ pub unsafe extern "C" fn neopdf_pdf_param_range( pdf_obj.param_ranges().alphas.min, pdf_obj.param_ranges().alphas.max, ], + NeopdfSubgridParams::Xi => &[pdf_obj.param_ranges().xi.min, pdf_obj.param_ranges().xi.max], + NeopdfSubgridParams::Delta => &[ + pdf_obj.param_ranges().delta.min, + pdf_obj.param_ranges().delta.max, + ], NeopdfSubgridParams::Kt => &[pdf_obj.param_ranges().kt.min, pdf_obj.param_ranges().kt.max], NeopdfSubgridParams::Momentum => { &[pdf_obj.param_ranges().x.min, pdf_obj.param_ranges().x.max] @@ -570,6 +579,8 @@ pub unsafe extern "C" fn neopdf_pdf_subgrids_shape_for_param( .map(|sub| match subgrid_param { NeopdfSubgridParams::Nucleons => sub.nucleons.len(), NeopdfSubgridParams::Alphas => sub.alphas.len(), + NeopdfSubgridParams::Xi => sub.xis.len(), + NeopdfSubgridParams::Delta => sub.deltas.len(), NeopdfSubgridParams::Kt => sub.kts.len(), NeopdfSubgridParams::Momentum => sub.xs.len(), NeopdfSubgridParams::Scale => sub.q2s.len(), @@ -606,6 +617,8 @@ pub unsafe extern "C" fn neopdf_pdf_subgrids_for_param( let subgrid_knots = match subgrid_param { NeopdfSubgridParams::Nucleons => &pdf_obj.subgrids()[subgrid_index].nucleons, NeopdfSubgridParams::Alphas => &pdf_obj.subgrids()[subgrid_index].alphas, + NeopdfSubgridParams::Xi => &pdf_obj.subgrids()[subgrid_index].xis, + NeopdfSubgridParams::Delta => &pdf_obj.subgrids()[subgrid_index].deltas, NeopdfSubgridParams::Kt => &pdf_obj.subgrids()[subgrid_index].kts, NeopdfSubgridParams::Momentum => &pdf_obj.subgrids()[subgrid_index].xs, NeopdfSubgridParams::Scale => &pdf_obj.subgrids()[subgrid_index].q2s, @@ -662,6 +675,8 @@ impl NeoPDFGrid { SubgridData { nucleons: slice::from_raw_parts(nucleons, num_nucleons).to_vec(), alphas: slice::from_raw_parts(alphas, num_alphas).to_vec(), + xis: vec![0.0], + deltas: vec![0.0], kts: slice::from_raw_parts(kts, num_kts).to_vec(), xs: slice::from_raw_parts(xs, num_xs).to_vec(), q2s: slice::from_raw_parts(q2s, num_q2s).to_vec(), @@ -682,6 +697,57 @@ impl NeoPDFGrid { NeopdfResult::Success } + + /// Adds a subgrid to the grid (v2 for 8D) + #[allow(clippy::too_many_arguments)] + unsafe fn add_subgrid_v2( + &mut self, + nucleons: *const c_double, + num_nucleons: usize, + alphas: *const c_double, + num_alphas: usize, + xis: *const c_double, + num_xis: usize, + deltas: *const c_double, + num_deltas: usize, + kts: *const c_double, + num_kts: usize, + xs: *const c_double, + num_xs: usize, + q2s: *const c_double, + num_q2s: usize, + grid_data: *const c_double, + grid_data_len: usize, + ) -> NeopdfResult { + // Check for null pointers + if nucleons.is_null() + || alphas.is_null() + || xis.is_null() + || deltas.is_null() + || kts.is_null() + || xs.is_null() + || q2s.is_null() + || grid_data.is_null() + { + return NeopdfResult::ErrorNullPointer; + } + + let subgrid = unsafe { + SubgridData { + nucleons: slice::from_raw_parts(nucleons, num_nucleons).to_vec(), + alphas: slice::from_raw_parts(alphas, num_alphas).to_vec(), + xis: slice::from_raw_parts(xis, num_xis).to_vec(), + deltas: slice::from_raw_parts(deltas, num_deltas).to_vec(), + kts: slice::from_raw_parts(kts, num_kts).to_vec(), + xs: slice::from_raw_parts(xs, num_xs).to_vec(), + q2s: slice::from_raw_parts(q2s, num_q2s).to_vec(), + grid_data: slice::from_raw_parts(grid_data, grid_data_len).to_vec(), + } + }; + self.subgrids.push(subgrid); + + NeopdfResult::Success + } } /// Creates a new, empty `NeoPDFGrid`. @@ -736,6 +802,58 @@ pub unsafe extern "C" fn neopdf_grid_add_subgrid( } } +/// Adds a subgrid to an existing `NeoPDFGrid` (v2 for 8D). +/// +/// This function takes ownership of the provided data arrays and resizes them as needed. +/// +/// # Safety +/// - `grid` must be a valid pointer to a `NeoPDFGrid` created by `neopdf_grid_new`. +/// - The data pointers must be valid for the specified lengths. +#[no_mangle] +pub unsafe extern "C" fn neopdf_grid_add_subgridv2( + grid: *mut NeoPDFGrid, + nucleons: *const c_double, + num_nucleons: usize, + alphas: *const c_double, + num_alphas: usize, + xis: *const c_double, + num_xis: usize, + deltas: *const c_double, + num_deltas: usize, + kts: *const c_double, + num_kts: usize, + xs: *const c_double, + num_xs: usize, + q2s: *const c_double, + num_q2s: usize, + grid_data: *const c_double, + grid_data_len: usize, +) -> NeopdfResult { + unsafe { + grid.as_mut() + .map_or(NeopdfResult::ErrorNullPointer, |grid| { + grid.add_subgrid_v2( + nucleons, + num_nucleons, + alphas, + num_alphas, + xis, + num_xis, + deltas, + num_deltas, + kts, + num_kts, + xs, + num_xs, + q2s, + num_q2s, + grid_data, + grid_data_len, + ) + }) + } +} + /// Sets the flavor IDs for a `NeoPDFGrid`. /// /// # Safety @@ -822,6 +940,35 @@ pub struct NeoPDFMetaData { phys_params: NeoPDFPhysicsParameters, } +/// Metadata for PDF grids (V2 with extended fields) +#[repr(C)] +pub struct NeoPDFMetaDataV2 { + set_desc: *const c_char, + set_index: u32, + num_members: u32, + x_min: c_double, + x_max: c_double, + q_min: c_double, + q_max: c_double, + flavors: *const c_int, + num_flavors: usize, + format: *const c_char, + alphas_q_values: *const c_double, + num_alphas_q: usize, + alphas_vals: *const c_double, + num_alphas_vals: usize, + polarised: bool, + set_type: SetType, + interpolator_type: InterpolatorType, + error_type: *const c_char, + hadron_pid: c_int, + phys_params: NeoPDFPhysicsParameters, + xi_min: c_double, + xi_max: c_double, + delta_min: c_double, + delta_max: c_double, +} + /// Safely converts C string to Rust string unsafe fn cstr_to_string(ptr: *const c_char) -> Option { if ptr.is_null() { @@ -857,7 +1004,68 @@ fn process_metadata(meta: *const NeoPDFMetaData) -> Option { let flavor_scheme = unsafe { cstr_to_string(meta.phys_params.flavor_scheme) }?; let alphas_type = unsafe { cstr_to_string(meta.phys_params.alphas_type) }?; - let metadata_v1 = MetaDataV1 { + // Create MetaData (now MetaDataV2) directly + let metadata = MetaData { + set_desc, + set_index: meta.set_index, + num_members: meta.num_members, + x_min: meta.x_min, + x_max: meta.x_max, + q_min: meta.q_min, + q_max: meta.q_max, + flavors, + format, + alphas_q_values, + alphas_vals, + polarised: meta.polarised, + set_type: meta.set_type.clone(), + interpolator_type: meta.interpolator_type.clone(), + error_type, + hadron_pid: meta.hadron_pid, + git_version: String::new(), // placeholder to be overwritten + code_version: String::new(), // placeholder to be overwritten + flavor_scheme, + order_qcd: meta.phys_params.order_qcd, + alphas_order_qcd: meta.phys_params.alphas_order_qcd, + m_w: meta.phys_params.m_w, + m_z: meta.phys_params.m_z, + m_up: meta.phys_params.m_up, + m_down: meta.phys_params.m_down, + m_strange: meta.phys_params.m_strange, + m_charm: meta.phys_params.m_charm, + m_bottom: meta.phys_params.m_bottom, + m_top: meta.phys_params.m_top, + alphas_type, + number_flavors: meta.phys_params.number_flavors, + // New V2 fields with defaults + xi_min: 1.0, + xi_max: 1.0, + delta_min: 0.0, + delta_max: 0.0, + }; + + Some(metadata) +} + +/// Processes metadata from C struct to Rust struct (V2) +fn process_metadata_v2(meta: *const NeoPDFMetaDataV2) -> Option { + if meta.is_null() { + return None; + } + + let meta = unsafe { &*meta }; + + let set_desc = unsafe { cstr_to_string(meta.set_desc) }?; + let format = unsafe { cstr_to_string(meta.format) }?; + let flavors = unsafe { carray_to_vec(meta.flavors, meta.num_flavors) }?; + let alphas_q_values = unsafe { carray_to_vec(meta.alphas_q_values, meta.num_alphas_q) }?; + let alphas_vals = unsafe { carray_to_vec(meta.alphas_vals, meta.num_alphas_vals) }?; + let error_type = unsafe { cstr_to_string(meta.error_type) }?; + let flavor_scheme = unsafe { cstr_to_string(meta.phys_params.flavor_scheme) }?; + let alphas_type = unsafe { cstr_to_string(meta.phys_params.alphas_type) }?; + + // Create MetaData directly from V2 struct + let metadata = MetaData { set_desc, set_index: meta.set_index, num_members: meta.num_members, @@ -889,8 +1097,12 @@ fn process_metadata(meta: *const NeoPDFMetaData) -> Option { m_top: meta.phys_params.m_top, alphas_type, number_flavors: meta.phys_params.number_flavors, + // New V2 fields + xi_min: meta.xi_min, + xi_max: meta.xi_max, + delta_min: meta.delta_min, + delta_max: meta.delta_max, }; - let metadata = MetaData::new_v1(metadata_v1); Some(metadata) } @@ -1116,6 +1328,54 @@ pub unsafe extern "C" fn neopdf_grid_compress( } } +/// Compresses a collection of `NeoPDFGrid` objects and writes them to a file (V2). +/// +/// This function iterates through the grids in the collection, converts them to `GridArray`s, +/// and then uses the `neopdf::writer::GridArrayCollection::compress` function to write them. +/// +/// # Safety +/// - `collection` must be a valid, non-null pointer to a `NeoPDFGridArrayCollection`. +/// - `metadata` must be a valid, non-null pointer to a `NeoPDFMetaDataV2` struct. +/// - `output_path` must be a valid, null-terminated C string representing the output file path. +#[no_mangle] +pub unsafe extern "C" fn neopdf_grid_compress_v2( + collection: *const NeoPDFGridArrayCollection, + metadata: *const NeoPDFMetaDataV2, + output_path: *const c_char, +) -> NeopdfResult { + if collection.is_null() || metadata.is_null() || output_path.is_null() { + return NeopdfResult::ErrorNullPointer; + } + + let collection = unsafe { &*collection }; + + let Some(meta) = process_metadata_v2(metadata) else { + return NeopdfResult::ErrorInvalidData; + }; + + let out_path = unsafe { CStr::from_ptr(output_path).to_str() }; + let Ok(out_path) = out_path else { + return NeopdfResult::ErrorInvalidData; + }; + + let mut grid_arrays = Vec::with_capacity(collection.len()); + + for i in 0..collection.len() { + let Some(grid) = collection.get(i) else { + return NeopdfResult::ErrorInvalidData; + }; + let grid_array = GridArray::new(grid.subgrids.clone(), grid.flavors.clone()); + grid_arrays.push(grid_array); + } + + let grid_refs: Vec<&GridArray> = grid_arrays.iter().collect(); + + match GridArrayCollection::compress(&grid_refs, &meta, out_path) { + Ok(()) => NeopdfResult::Success, + Err(_) => NeopdfResult::ErrorMemoryError, + } +} + // LHAPDF C-API drop-in compatibility layer. /// /// This global state stores the loaded PDF set and the currently selected diff --git a/neopdf_capi/tests/Makefile b/neopdf_capi/tests/Makefile index 46eb724f..11087df9 100644 --- a/neopdf_capi/tests/Makefile +++ b/neopdf_capi/tests/Makefile @@ -5,7 +5,7 @@ NEOPDF_DEPS != pkg-config --cflags --libs neopdf_capi LHAPDF_DEPS != pkg-config --cflags --libs lhapdf MATH_LIBS = -lm -PROGRAMS = check-capi check-oop check-writer check-writer-oop check-xapi check-xwriter check-lhapdf-compatibility +PROGRAMS = check-capi check-oop check-writer check-writer-8d check-writer-oop check-xapi check-xwriter check-lhapdf-compatibility all: $(PROGRAMS) @@ -18,6 +18,9 @@ check-capi: check-capi.cpp check-writer: check-writer.cpp $(CXX) $(CXXFLAGS) $< $(LHAPDF_DEPS) $(NEOPDF_DEPS) -o $@ +check-writer-8d: check-writer-8d.cpp + $(CXX) $(CXXFLAGS) $< $(LHAPDF_DEPS) $(NEOPDF_DEPS) -o $@ + check-oop: check-oop.cpp $(CXX) $(CXXFLAGS) $< $(LHAPDF_DEPS) $(NEOPDF_DEPS) -o $@ diff --git a/neopdf_capi/tests/check-writer-8d.cpp b/neopdf_capi/tests/check-writer-8d.cpp new file mode 100644 index 00000000..a65c35d9 --- /dev/null +++ b/neopdf_capi/tests/check-writer-8d.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const double TOLERANCE = 1e-10; // Increased tolerance for synthetic data + +template +std::vector geomspace(T start, T stop, int num, bool endpoint = false) { + std::vector result(num); + + if (num == 1) { + result[0] = start; + return result; + } + + T log_start = std::log(start); + T log_stop = std::log(stop); + T step = (log_stop - log_start) / (endpoint ? (num - 1) : num); + + for (int i = 0; i < num; ++i) { + result[i] = std::exp(log_start + i * step); + } + + return result; +} + +// Helper function to extract subgrid parameters +std::vector extract_subgrid_params( + NeoPDFWrapper* pdf, + NeopdfSubgridParams param_type, + std::size_t subgrid_idx, + std::size_t num_subgrids +) { + std::vector shape(num_subgrids); + neopdf_pdf_subgrids_shape_for_param( + pdf, + shape.data(), + num_subgrids, + param_type + ); + + std::vector values(shape[subgrid_idx]); + neopdf_pdf_subgrids_for_param( + pdf, + values.data(), + param_type, + num_subgrids, + shape.data(), + subgrid_idx + ); + + return values; +} + +int main() { + const char* pdfname = "NNPDF40_nnlo_as_01180"; + // Load all PDF members + NeoPDFMembers neo_pdfs = neopdf_pdf_load_all(pdfname); + if (neo_pdfs.size == 0) { + std::cerr << "Failed to load any PDF members!\n"; + return 1; + } + std::cout << "Loaded " << neo_pdfs.size << " PDF members\n"; + + // Extract the PID values of the PDF set + std::size_t num_pids = neopdf_pdf_num_pids(neo_pdfs.pdfs[0]); + std::vector pids(num_pids); + neopdf_pdf_pids(neo_pdfs.pdfs[0], pids.data(), num_pids); + + // Extract the number of subgrids + std::size_t num_subgrids = neopdf_pdf_num_subgrids(neo_pdfs.pdfs[0]); + + // Define synthetic xi and delta values + std::vector xis = {0.0}; + std::vector deltas = {0.0}; + + // Create a collection + NeoPDFGridArrayCollection* collection = neopdf_gridarray_collection_new(); + if (!collection) { + std::cerr << "Failed to create grid array collection!\n"; + neopdf_pdf_array_free(neo_pdfs); + return 1; + } + + // For each member, build a grid + for (size_t m = 0; m < neo_pdfs.size; ++m) { + NeoPDFWrapper* pdf = neo_pdfs.pdfs[m]; + NeoPDFGrid* grid = neopdf_grid_new(); + + if (!grid) { + std::cerr << "Failed to create grid for member: " << m << "!\n"; + continue; + } + + // Loop over the Subgrids + bool member_failed = false; + for (std::size_t subgrid_idx = 0; subgrid_idx < num_subgrids; ++subgrid_idx) { + // Extract base parameters from the original PDF (x, q2, nucleons, alphas, kts) + auto xs = extract_subgrid_params(pdf, NEOPDF_SUBGRID_PARAMS_MOMENTUM, subgrid_idx, num_subgrids); + auto q2s = extract_subgrid_params(pdf, NEOPDF_SUBGRID_PARAMS_SCALE, subgrid_idx, num_subgrids); + auto alphas = extract_subgrid_params(pdf, NEOPDF_SUBGRID_PARAMS_ALPHAS, subgrid_idx, num_subgrids); + auto nucleons = extract_subgrid_params(pdf, NEOPDF_SUBGRID_PARAMS_NUCLEONS, subgrid_idx, num_subgrids); + auto kts = extract_subgrid_params(pdf, NEOPDF_SUBGRID_PARAMS_KT, subgrid_idx, num_subgrids); + + // Compute grid_data: [q2s][xs][flavors], instead of [nucleons][alphas][xis][deltas][q2s][xs][flavors] + // NOTE: This assumes that there is no 'A', `alphas`, `xis`, and `deltas` dependence. + assert(alphas.size() == 1); + assert(kts.size() == 1); + assert(xis.size() == 1); + assert(deltas.size() == 1); + assert(nucleons.size() == 1); + std::vector grid_data; + for (size_t xi = 0; xi < xs.size(); ++xi) { + for (size_t qi = 0; qi < q2s.size(); ++qi) { + for (size_t f = 0; f < pids.size(); ++f) { + int pid = pids[f]; + double val = neopdf_pdf_xfxq2(pdf, pid, xs[xi], q2s[qi]); + grid_data.push_back(val); + } + } + } + + // Add subgrid using the v2 function + NeopdfResult add_subgrid_res = neopdf_grid_add_subgridv2( + grid, + nucleons.data(), nucleons.size(), + alphas.data(), alphas.size(), + xis.data(), xis.size(), + deltas.data(), deltas.size(), + kts.data(), kts.size(), + xs.data(), xs.size(), + q2s.data(), q2s.size(), + grid_data.data(), grid_data.size() + ); + if (add_subgrid_res != NEOPDF_RESULT_SUCCESS) { + std::cerr << "Failed to add subgrid (v2) for member: " << m << "!\n"; + neopdf_grid_free(grid); + member_failed = true; + break; + } + } + + if (member_failed) { + continue; + } + + // Set flavor PIDs + int add_flavors = neopdf_grid_set_flavors(grid, pids.data(), pids.size()); + if (add_flavors != 0) { + std::cerr << "Failed to set flavors for member: " << m << "!\n"; + neopdf_grid_free(grid); + continue; + } + + // Add grid to collection + int add_grid = neopdf_gridarray_collection_add_grid(collection, grid); + if (add_grid != 0) { + std::cerr << "Failed to add grid to collection for member: " << m << "!\n"; + neopdf_grid_free(grid); + continue; + } + std::cout << "Added grid for member " << m << "\n"; + } + + // Fill the running of alphas with some random values + double alphas_qs[] = {2.0}; + double alphas_vals[] = {0.118}; + + // Extract the ranges for the momentum x and scale Q2 + std::vector x_range(2); + std::vector q2_range(2); + neopdf_pdf_param_range(neo_pdfs.pdfs[0], NEOPDF_SUBGRID_PARAMS_MOMENTUM, x_range.data()); + neopdf_pdf_param_range(neo_pdfs.pdfs[0], NEOPDF_SUBGRID_PARAMS_SCALE, q2_range.data()); + + NeoPDFPhysicsParameters phys_params = { + .flavor_scheme = "variable", + .order_qcd = 2, + .alphas_order_qcd = 2, + .m_w = 80.352, + .m_z = 91.1876, + .m_up = 0.0, + .m_down = 0.0, + .m_strange = 0.0, + .m_charm = 1.51, + .m_bottom = 4.92, + .m_top = 172.5, + .alphas_type = "ipol", + .number_flavors = 4, + }; + + NeoPDFMetaDataV2 meta = { + .set_desc = "NNPDF40_nnlo_as_01180 8D collection", + .set_index = 0, + .num_members = (uint32_t)neo_pdfs.size, + .x_min = x_range[0], + .x_max = x_range[1], + .q_min = sqrt(q2_range[0]), + .q_max = sqrt(q2_range[1]), + .flavors = pids.data(), + .num_flavors = (size_t)pids.size(), + .format = "neopdf", + .alphas_q_values = alphas_qs, + .num_alphas_q = 1, + .alphas_vals = alphas_vals, + .num_alphas_vals = 1, + .polarised = false, + .set_type = NEOPDF_SET_TYPE_SPACE_LIKE, + .interpolator_type = NEOPDF_INTERPOLATOR_TYPE_LOG_BICUBIC, + .error_type = "replicas", + .hadron_pid = 2212, + .phys_params = phys_params, + .xi_min = xis[0], + .xi_max = xis.back(), + .delta_min = deltas[0], + .delta_max = deltas.back(), + }; + + // Check if `NEOPDF_DATA_PATH` is defined and store the Grid there. + const char* filename = "check-writer-8d.neopdf.lz4"; + const char* neopdf_path = std::getenv("NEOPDF_DATA_PATH"); + std::string output_path = neopdf_path + ? std::string(neopdf_path) + (std::string(neopdf_path).back() == '/' ? "" : "/") + filename + : filename; + + // Write the PDF Grid into disk + int result = neopdf_grid_compress_v2(collection, &meta, output_path.c_str()); + if (result != 0) { + std::cerr << "Compression failed with code " << result << "\n"; + } else { + std::cout << "Compression succeeded!\n"; + } + + // If `NEOPDF_DATA_PATH` is defined, reload the grid and check the results. + if (neopdf_path) { + int pid_test = 21; + double x_test = 1e-3; + double q2_test = 1e2; + + double ref = neopdf_pdf_xfxq2(neo_pdfs.pdfs[0], pid_test, x_test, q2_test); + + // For the newly written 8D PDF + NeoPDFWrapper* wpdf = neopdf_pdf_load(output_path.c_str(), 0); + std::vector params = {x_test, q2_test}; + double res = neopdf_pdf_xfxq2_nd(wpdf, pid_test, params.data(), params.size()); + + assert(std::abs(res - ref) < TOLERANCE); + + // Delete PDF object from memory + neopdf_pdf_free(wpdf); + } + + // Cleanup + neopdf_gridarray_collection_free(collection); + neopdf_pdf_array_free(neo_pdfs); + + return result == 0 ? 0 : 1; +} diff --git a/neopdf_capi/tests/check-writer-8d.output b/neopdf_capi/tests/check-writer-8d.output new file mode 100644 index 00000000..ac273bda --- /dev/null +++ b/neopdf_capi/tests/check-writer-8d.output @@ -0,0 +1,103 @@ +Loaded 101 PDF members +Added grid for member 0 +Added grid for member 1 +Added grid for member 2 +Added grid for member 3 +Added grid for member 4 +Added grid for member 5 +Added grid for member 6 +Added grid for member 7 +Added grid for member 8 +Added grid for member 9 +Added grid for member 10 +Added grid for member 11 +Added grid for member 12 +Added grid for member 13 +Added grid for member 14 +Added grid for member 15 +Added grid for member 16 +Added grid for member 17 +Added grid for member 18 +Added grid for member 19 +Added grid for member 20 +Added grid for member 21 +Added grid for member 22 +Added grid for member 23 +Added grid for member 24 +Added grid for member 25 +Added grid for member 26 +Added grid for member 27 +Added grid for member 28 +Added grid for member 29 +Added grid for member 30 +Added grid for member 31 +Added grid for member 32 +Added grid for member 33 +Added grid for member 34 +Added grid for member 35 +Added grid for member 36 +Added grid for member 37 +Added grid for member 38 +Added grid for member 39 +Added grid for member 40 +Added grid for member 41 +Added grid for member 42 +Added grid for member 43 +Added grid for member 44 +Added grid for member 45 +Added grid for member 46 +Added grid for member 47 +Added grid for member 48 +Added grid for member 49 +Added grid for member 50 +Added grid for member 51 +Added grid for member 52 +Added grid for member 53 +Added grid for member 54 +Added grid for member 55 +Added grid for member 56 +Added grid for member 57 +Added grid for member 58 +Added grid for member 59 +Added grid for member 60 +Added grid for member 61 +Added grid for member 62 +Added grid for member 63 +Added grid for member 64 +Added grid for member 65 +Added grid for member 66 +Added grid for member 67 +Added grid for member 68 +Added grid for member 69 +Added grid for member 70 +Added grid for member 71 +Added grid for member 72 +Added grid for member 73 +Added grid for member 74 +Added grid for member 75 +Added grid for member 76 +Added grid for member 77 +Added grid for member 78 +Added grid for member 79 +Added grid for member 80 +Added grid for member 81 +Added grid for member 82 +Added grid for member 83 +Added grid for member 84 +Added grid for member 85 +Added grid for member 86 +Added grid for member 87 +Added grid for member 88 +Added grid for member 89 +Added grid for member 90 +Added grid for member 91 +Added grid for member 92 +Added grid for member 93 +Added grid for member 94 +Added grid for member 95 +Added grid for member 96 +Added grid for member 97 +Added grid for member 98 +Added grid for member 99 +Added grid for member 100 +Compression succeeded! diff --git a/neopdf_cli/src/converter.rs b/neopdf_cli/src/converter.rs index 16274b43..a0eca83d 100644 --- a/neopdf_cli/src/converter.rs +++ b/neopdf_cli/src/converter.rs @@ -168,6 +168,7 @@ fn load_pdf_names( /// # Errors /// /// TODO +#[allow(clippy::implicit_clone)] #[allow(clippy::needless_pass_by_value)] pub fn run_cli(cli: Cli) -> Result<(), Box> { match &cli.command { diff --git a/neopdf_cli/src/read.rs b/neopdf_cli/src/read.rs index a01c79b7..120f67ae 100644 --- a/neopdf_cli/src/read.rs +++ b/neopdf_cli/src/read.rs @@ -30,6 +30,9 @@ pub enum ReadCommands { /// Print the git version of the code that generated the PDF. #[command(name = "git-version")] GitVersion(PdfNameArgs), + /// Print the code version of the code that generated the PDF. + #[command(name = "code-version")] + CodeVersion(PdfNameArgs), } /// Arguments for the metadata subcommand. @@ -89,6 +92,7 @@ pub struct SubgridArgs { /// # Panics /// /// This function panics when a PID not present in the Grid is requested. +#[allow(clippy::too_many_lines)] #[allow(clippy::needless_pass_by_value)] pub fn main(cli: ReadCli) { match &cli.command { @@ -136,14 +140,28 @@ pub fn main(cli: ReadCli) { } println!(); - let grid_slice = subgrid.grid.slice(s![ - args.nucleon_index, - args.alphas_index, - pid_idx, - args.kt_index, - .., - .. - ]); + let grid_slice = match &subgrid.grid { + neopdf::subgrid::GridData::Grid6D(grid) => grid.slice(s![ + args.nucleon_index, + args.alphas_index, + pid_idx, + args.kt_index, + .., + .. + ]), + neopdf::subgrid::GridData::Grid8D(grid) => { + grid.slice(s![ + args.nucleon_index, + args.alphas_index, + 0, // xi_index - default to first + 0, // delta_index - default to first + args.kt_index, + pid_idx, + .., + .. + ]) + } + }; let width = if let Some((Width(w), _)) = terminal_size() { w as usize @@ -184,5 +202,9 @@ pub fn main(cli: ReadCli) { let pdf = neopdf::pdf::PDF::load(&args.pdf_name, 0); println!("{}", pdf.metadata().git_version); } + ReadCommands::CodeVersion(args) => { + let pdf = neopdf::pdf::PDF::load(&args.pdf_name, 0); + println!("{}", pdf.metadata().code_version); + } } } diff --git a/neopdf_cli/src/tmd_converter.rs b/neopdf_cli/src/tmd_converter.rs index b03c9b5e..216d6df7 100644 --- a/neopdf_cli/src/tmd_converter.rs +++ b/neopdf_cli/src/tmd_converter.rs @@ -6,7 +6,7 @@ use std::f64::consts::PI; use std::fs; use neopdf::gridpdf::GridArray; -use neopdf::metadata::{InterpolatorType, MetaData, MetaDataV1, SetType}; +use neopdf::metadata::{InterpolatorType, MetaDataV2, SetType}; use neopdf::subgrid::SubGrid; use neopdf::writer::GridArrayCollection; use neopdf_tmdlib::Tmd; @@ -246,7 +246,7 @@ pub fn convert_tmd(input_path: &str, output_path: &str) -> Result<(), Box = member_grids.iter().collect(); - let meta = MetaData::new_v1(MetaDataV1 { + let meta = MetaDataV2 { set_desc: config.set_desc, set_index: config.set_index, num_members: n_members as u32, @@ -278,7 +278,11 @@ pub fn convert_tmd(input_path: &str, output_path: &str) -> Result<(), Box) -> PyResult<()> { Ok(()) } -/// Converts an LHAPDF set to the NeoPDF format and writes it to disk. +/// Converts an LHAPDF set to the `NeoPDF` format and writes it to disk. /// -/// Converts the specified LHAPDF set into the NeoPDF format and saves the result to the given +/// Converts the specified LHAPDF set into the `NeoPDF` format and saves the result to the given /// output path. /// /// # Parameters /// -/// - `pdf_name`: The name of the LHAPDF set (e.g., "NNPDF40_nnlo_as_01180"). -/// - `output_path`: The path to the output NeoPDF file. +/// - `pdf_name`: The name of the LHAPDF set (e.g., "`NNPDF40_nnlo_as_01180`"). +/// - `output_path`: The path to the output `NeoPDF` file. /// /// # Returns /// @@ -42,15 +42,15 @@ pub fn py_convert_lhapdf(pdf_name: &str, output_path: &str) -> PyResult<()> { .map_err(|e| PyRuntimeError::new_err(format!("Conversion failed: {e}"))) } -/// Combines a list of nuclear PDF sets into a single NeoPDF file with explicit A dependence. +/// Combines a list of nuclear PDF sets into a single `NeoPDF` file with explicit A dependence. /// -/// Combines multiple LHAPDF nuclear PDF sets into a single NeoPDF file, allowing for explicit +/// Combines multiple LHAPDF nuclear PDF sets into a single `NeoPDF` file, allowing for explicit /// nuclear dependence. /// /// # Parameters /// /// - `pdf_names`: List of PDF set names (each with a different A). -/// - `output_path`: Output NeoPDF file path. +/// - `output_path`: Output `NeoPDF` file path. /// /// # Returns /// diff --git a/neopdf_pyapi/src/gridpdf.rs b/neopdf_pyapi/src/gridpdf.rs index 841fbfdb..681d6d06 100644 --- a/neopdf_pyapi/src/gridpdf.rs +++ b/neopdf_pyapi/src/gridpdf.rs @@ -1,9 +1,9 @@ -use ndarray::Array1; -use numpy::{PyArrayMethods, PyReadonlyArray6}; +use ndarray::{Array1, Dimension}; +use numpy::{PyArrayMethods, PyReadonlyArrayDyn}; use pyo3::prelude::*; use neopdf::gridpdf::GridArray; -use neopdf::subgrid::{ParamRange, SubGrid}; +use neopdf::subgrid::{GridData, ParamRange, SubGrid}; /// Python wrapper for the `SubGrid` struct. #[pyclass(name = "SubGrid")] @@ -20,9 +20,11 @@ impl PySubGrid { /// - `xs`: The x-axis values. /// - `q2s`: The Q^2-axis values. /// - `kts`: The kT-axis values. + /// - `xsis`: The Skeweness `\xi`. + /// - `deltas`: The total momentum fraction `\Delta`. /// - `nucleons`: The nucleon number axis values. - /// - `alphas`: The alpha_s axis values. - /// - `grid`: The 6D grid data as a NumPy array. + /// - `alphas`: The `alpha_s` axis values. + /// - `grid`: The 6D grid data as a `NumPy` array. /// /// # Returns /// @@ -36,18 +38,23 @@ impl PySubGrid { /// /// Returns a `PyErr` if the grid cannot be constructed from the input data. #[new] + #[allow(clippy::too_many_arguments)] #[allow(clippy::needless_pass_by_value)] pub fn new( xs: Vec, q2s: Vec, kts: Vec, + xsis: Vec, + deltas: Vec, nucleons: Vec, alphas: Vec, - grid: PyReadonlyArray6, + grid: PyReadonlyArrayDyn, ) -> PyResult { let alphas_range = ParamRange::new(*alphas.first().unwrap(), *alphas.last().unwrap()); let x_range = ParamRange::new(*xs.first().unwrap(), *xs.last().unwrap()); let q2_range = ParamRange::new(*q2s.first().unwrap(), *q2s.last().unwrap()); + let xsi_range = ParamRange::new(*xsis.first().unwrap(), *xsis.last().unwrap()); + let delta_range = ParamRange::new(*deltas.first().unwrap(), *deltas.last().unwrap()); let kt_range = ParamRange::new(*kts.first().unwrap(), *kts.last().unwrap()); let nucleons_range = ParamRange::new(*nucleons.first().unwrap(), *nucleons.last().unwrap()); @@ -55,11 +62,15 @@ impl PySubGrid { xs: Array1::from(xs), q2s: Array1::from(q2s), kts: Array1::from(kts), - grid: grid.to_owned_array(), + xis: Array1::from(xsis), + deltas: Array1::from(deltas), + grid: GridData::Grid8D(grid.to_owned_array()), nucleons: Array1::from(nucleons), alphas: Array1::from(alphas), nucleons_range, alphas_range, + xi_range: xsi_range, + delta_range, kt_range, x_range, q2_range, @@ -68,7 +79,7 @@ impl PySubGrid { Ok(Self { subgrid }) } - /// Returns the minimum and maximum values of the alpha_s axis. + /// Returns the minimum and maximum values of the `alpha_s` axis. #[must_use] pub const fn alphas_range(&self) -> (f64, f64) { (self.subgrid.alphas_range.min, self.subgrid.alphas_range.max) @@ -86,6 +97,18 @@ impl PySubGrid { (self.subgrid.q2_range.min, self.subgrid.q2_range.max) } + /// Returns the minimum and maximum values of the skeweness `xi`. + #[must_use] + pub const fn xi_range(&self) -> (f64, f64) { + (self.subgrid.xi_range.min, self.subgrid.xi_range.max) + } + + /// Returns the minimum and maximum values of the total momentum fraction `delta`. + #[must_use] + pub const fn delta_range(&self) -> (f64, f64) { + (self.subgrid.delta_range.min, self.subgrid.delta_range.max) + } + /// Returns the minimum and maximum values of the Nucleon number `A`. #[must_use] pub const fn nucleons_range(&self) -> (f64, f64) { @@ -102,9 +125,19 @@ impl PySubGrid { } /// Returns the shape of the subgrid + /// + /// # Panics + /// + /// TODO #[must_use] - pub fn grid_shape(&self) -> (usize, usize, usize, usize, usize, usize) { - self.subgrid.grid.dim() + pub fn grid_shape(&self) -> Vec { + match &self.subgrid.grid { + GridData::Grid6D(grid) => { + let (d0, d1, d2, d3, d4, d5) = grid.dim(); + vec![d0, d1, d2, d3, d4, d5] + } + GridData::Grid8D(grid) => grid.dim().as_array_view().to_vec(), + } } } diff --git a/neopdf_pyapi/src/lib.rs b/neopdf_pyapi/src/lib.rs index cf817f77..ca714d4e 100644 --- a/neopdf_pyapi/src/lib.rs +++ b/neopdf_pyapi/src/lib.rs @@ -19,7 +19,7 @@ pub mod pdf; /// Python bindings for the `writer` module. pub mod writer; -/// PyO3 Python module that contains all exposed classes from Rust. +/// `PyO3` Python module that contains all exposed classes from Rust. #[pymodule] fn neopdf(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("version", env!("CARGO_PKG_VERSION"))?; diff --git a/neopdf_pyapi/src/manage.rs b/neopdf_pyapi/src/manage.rs index 8384d4db..f5fd557f 100644 --- a/neopdf_pyapi/src/manage.rs +++ b/neopdf_pyapi/src/manage.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub enum PyPdfSetFormat { /// LHAPDF format (standard PDF set format used by LHAPDF). Lhapdf, - /// NeoPDF format (native format for this library). + /// `NeoPDF` format (native format for this library). Neopdf, } @@ -29,7 +29,7 @@ pub struct PyManageData { #[pymethods] impl PyManageData { - /// Create a new ManageData instance. + /// Create a new `ManageData` instance. #[new] #[must_use] pub fn new(set_name: &str, format: PyPdfSetFormat) -> Self { diff --git a/neopdf_pyapi/src/metadata.rs b/neopdf_pyapi/src/metadata.rs index d05d59d5..8ca7f5d8 100644 --- a/neopdf_pyapi/src/metadata.rs +++ b/neopdf_pyapi/src/metadata.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; -use neopdf::metadata::{InterpolatorType, MetaData, MetaDataV1, SetType}; +use neopdf::metadata::{InterpolatorType, MetaData, SetType}; /// The type of the set. #[pyclass(eq, eq_int, name = "SetType")] @@ -46,6 +46,10 @@ pub enum PyInterpolatorType { NDLinear, /// Chebyshev logarithmic interpolation strategy. LogChebyshev, + /// Four-dimensional cubic logarithmic interpolation strategy. + LogFourCubic, + /// Five-dimensional cubic logarithmic interpolation strategy. + LogFiveCubic, } impl From<&InterpolatorType> for PyInterpolatorType { @@ -57,6 +61,8 @@ impl From<&InterpolatorType> for PyInterpolatorType { InterpolatorType::LogTricubic => Self::LogTricubic, InterpolatorType::InterpNDLinear => Self::NDLinear, InterpolatorType::LogChebyshev => Self::LogChebyshev, + InterpolatorType::LogFourCubic => Self::LogFourCubic, + InterpolatorType::LogFiveCubic => Self::LogFiveCubic, } } } @@ -70,6 +76,8 @@ impl From<&PyInterpolatorType> for InterpolatorType { PyInterpolatorType::LogTricubic => Self::LogTricubic, PyInterpolatorType::NDLinear => Self::InterpNDLinear, PyInterpolatorType::LogChebyshev => Self::LogChebyshev, + PyInterpolatorType::LogFourCubic => Self::LogFourCubic, + PyInterpolatorType::LogFiveCubic => Self::LogFiveCubic, } } } @@ -95,7 +103,7 @@ pub struct PyPhysicsParameters { #[pymethods] impl PyPhysicsParameters { - /// Constructor for PyPhysicsParameters. + /// Constructor for `PyPhysicsParameters`. #[new] #[must_use] #[allow(clippy::too_many_arguments)] @@ -151,7 +159,7 @@ impl PyPhysicsParameters { /// # Errors /// /// Raises an error if the values are not Python compatible. - pub fn to_dict(&self, py: Python) -> PyResult { + pub fn to_dict(&self, py: Python) -> PyResult> { let dict = pyo3::types::PyDict::new(py); dict.set_item("flavor_scheme", &self.flavor_scheme)?; dict.set_item("order_qcd", self.order_qcd)?; @@ -199,11 +207,11 @@ pub struct PyMetaData { #[pymethods] impl PyMetaData { - /// Constructor for PyMetaData. + /// Constructor for `PyMetaData`. #[new] #[must_use] - #[allow(clippy::needless_pass_by_value)] #[allow(clippy::too_many_arguments)] + #[allow(clippy::needless_pass_by_value)] #[pyo3(signature = ( set_desc, set_index, @@ -212,6 +220,10 @@ impl PyMetaData { x_max, q_min, q_max, + xsi_min, + xsi_max, + delta_min, + delta_max, flavors, format, alphas_q_values = vec![], @@ -231,6 +243,10 @@ impl PyMetaData { x_max: f64, q_min: f64, q_max: f64, + xsi_min: f64, + xsi_max: f64, + delta_min: f64, + delta_max: f64, flavors: Vec, format: String, alphas_q_values: Vec, @@ -242,45 +258,45 @@ impl PyMetaData { hadron_pid: i32, phys_params: PyPhysicsParameters, ) -> Self { - let git_version = String::new(); - let code_version = String::new(); - - let meta_v1 = MetaDataV1 { - set_desc, - set_index, - num_members, - x_min, - x_max, - q_min, - q_max, - flavors, - format, - alphas_q_values, - alphas_vals, - polarised, - set_type: SetType::from(&set_type), - interpolator_type: InterpolatorType::from(&interpolator_type), - error_type, - hadron_pid, - git_version, // placeholder to be overwritten - code_version, // placeholder to be overwritten - flavor_scheme: phys_params.flavor_scheme, - order_qcd: phys_params.order_qcd, - alphas_order_qcd: phys_params.alphas_order_qcd, - m_w: phys_params.m_w, - m_z: phys_params.m_z, - m_up: phys_params.m_up, - m_down: phys_params.m_down, - m_strange: phys_params.m_strange, - m_charm: phys_params.m_charm, - m_bottom: phys_params.m_bottom, - m_top: phys_params.m_top, - alphas_type: phys_params.alphas_type, - number_flavors: phys_params.number_flavors, - }; - Self { - meta: MetaData::new_v1(meta_v1), + meta: MetaData { + set_desc, + set_index, + num_members, + x_min, + x_max, + q_min, + q_max, + flavors, + format, + alphas_q_values, + alphas_vals, + polarised, + set_type: SetType::from(&set_type), + interpolator_type: InterpolatorType::from(&interpolator_type), + error_type, + hadron_pid, + git_version: String::new(), // placeholder to be overwritten + code_version: String::new(), // placeholder to be overwritten + flavor_scheme: phys_params.flavor_scheme, + order_qcd: phys_params.order_qcd, + alphas_order_qcd: phys_params.alphas_order_qcd, + m_w: phys_params.m_w, + m_z: phys_params.m_z, + m_up: phys_params.m_up, + m_down: phys_params.m_down, + m_strange: phys_params.m_strange, + m_charm: phys_params.m_charm, + m_bottom: phys_params.m_bottom, + m_top: phys_params.m_top, + alphas_type: phys_params.alphas_type, + number_flavors: phys_params.number_flavors, + // New V2 fields with defaults + xi_min: xsi_min, + xi_max: xsi_max, + delta_min, + delta_max, + }, } } @@ -289,7 +305,7 @@ impl PyMetaData { /// # Errors /// /// Raises an erro if the values are not Python compatible. - pub fn to_dict(&self, py: Python) -> PyResult { + pub fn to_dict(&self, py: Python) -> PyResult> { let dict = pyo3::types::PyDict::new(py); let set_type = match &self.meta.set_type { @@ -304,6 +320,8 @@ impl PyMetaData { InterpolatorType::LogTricubic => "LogTricubic", InterpolatorType::InterpNDLinear => "NDLinear", InterpolatorType::LogChebyshev => "LogChebyshev", + InterpolatorType::LogFourCubic => "LogFourCubic", + InterpolatorType::LogFiveCubic => "LogFiveCubic", }; dict.set_item("set_desc", &self.meta.set_desc)?; @@ -341,73 +359,97 @@ impl PyMetaData { /// The description of the set. #[must_use] - pub fn set_desc(&self) -> &String { + pub const fn set_desc(&self) -> &String { &self.meta.set_desc } /// The index of the grid. #[must_use] - pub fn set_index(&self) -> u32 { + pub const fn set_index(&self) -> u32 { self.meta.set_index } /// The number of sets in the grid. #[must_use] - pub fn number_sets(&self) -> u32 { + pub const fn number_sets(&self) -> u32 { self.meta.num_members } /// The minimum value of `x` in the grid. #[must_use] - pub fn x_min(&self) -> f64 { + pub const fn x_min(&self) -> f64 { self.meta.x_min } /// The maximum value of `x` in the grid. #[must_use] - pub fn x_max(&self) -> f64 { + pub const fn x_max(&self) -> f64 { self.meta.x_max } /// The minimum value of `q` in the grid. #[must_use] - pub fn q_min(&self) -> f64 { + pub const fn q_min(&self) -> f64 { self.meta.q_min } /// The maximum value of `q` in the grid. #[must_use] - pub fn q_max(&self) -> f64 { + pub const fn q_max(&self) -> f64 { self.meta.q_max } + /// The minimum value of `xi` in the grid. + #[must_use] + pub const fn xi_min(&self) -> f64 { + self.meta.xi_min + } + + /// The maximum value of `xi` in the grid. + #[must_use] + pub const fn xi_max(&self) -> f64 { + self.meta.xi_max + } + + /// The minimum value of `delta` in the grid. + #[must_use] + pub const fn delta_min(&self) -> f64 { + self.meta.delta_min + } + + /// The maximum value of `delta` in the grid. + #[must_use] + pub const fn delta_max(&self) -> f64 { + self.meta.delta_max + } + /// The particle IDs of the grid. #[must_use] - pub fn pids(&self) -> &Vec { + pub const fn pids(&self) -> &Vec { &self.meta.flavors } /// The format of the grid. #[must_use] - pub fn format(&self) -> &String { + pub const fn format(&self) -> &String { &self.meta.format } /// The values of `q` for the running of the strong coupling constant. #[must_use] - pub fn alphas_q(&self) -> &Vec { + pub const fn alphas_q(&self) -> &Vec { &self.meta.alphas_q_values } /// The values of the running of the strong coupling constant. #[must_use] - pub fn alphas_values(&self) -> &Vec { + pub const fn alphas_values(&self) -> &Vec { &self.meta.alphas_vals } /// Whether the grid is polarised. #[must_use] - pub fn is_polarised(&self) -> bool { + pub const fn is_polarised(&self) -> bool { self.meta.polarised } @@ -425,13 +467,13 @@ impl PyMetaData { /// The type of error. #[must_use] - pub fn error_type(&self) -> &String { + pub const fn error_type(&self) -> &String { &self.meta.error_type } /// The hadron PID. #[must_use] - pub fn hadron_pid(&self) -> i32 { + pub const fn hadron_pid(&self) -> i32 { self.meta.hadron_pid } } diff --git a/neopdf_pyapi/src/parser.rs b/neopdf_pyapi/src/parser.rs index 28b54e27..a7248798 100644 --- a/neopdf_pyapi/src/parser.rs +++ b/neopdf_pyapi/src/parser.rs @@ -12,7 +12,7 @@ pub struct PyLhapdfSet { #[pymethods] impl PyLhapdfSet { - /// Create a new LhapdfSet instance for a given PDF set name. + /// Create a new `LhapdfSet` instance for a given PDF set name. #[new] #[must_use] pub fn new(pdf_name: &str) -> Self { @@ -56,7 +56,7 @@ pub struct PyNeopdfSet { #[pymethods] impl PyNeopdfSet { - /// Create a new NeopdfSet instance for a given PDF set name. + /// Create a new `NeopdfSet` instance for a given PDF set name. #[new] #[must_use] pub fn new(pdf_name: &str) -> Self { diff --git a/neopdf_pyapi/src/pdf.rs b/neopdf_pyapi/src/pdf.rs index 7ad5b5c5..4dba4fc9 100644 --- a/neopdf_pyapi/src/pdf.rs +++ b/neopdf_pyapi/src/pdf.rs @@ -121,6 +121,7 @@ pub struct PyPDF { } #[pymethods] +#[allow(clippy::doc_markdown)] impl PyPDF { /// Creates a new `PDF` instance for a given PDF set and member. /// diff --git a/neopdf_pyapi/src/writer.rs b/neopdf_pyapi/src/writer.rs index b4416e5e..df633c8d 100644 --- a/neopdf_pyapi/src/writer.rs +++ b/neopdf_pyapi/src/writer.rs @@ -6,7 +6,7 @@ use pyo3::prelude::*; use super::gridpdf::PyGridArray; use super::metadata::PyMetaData; -/// Python interface for GridArrayCollection utilities. +/// Python interface for `GridArrayCollection` utilities. /// /// This module provides functions to compress, decompress, and extract metadata from /// collections of PDF grids. @@ -23,7 +23,7 @@ pub fn writer(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { Ok(()) } -/// Compresses and writes a collection of GridArrays and shared metadata to a file. +/// Compresses and writes a collection of `GridArrays` and shared metadata to a file. /// /// Compresses the provided grids and metadata and writes them to the specified file path. /// @@ -52,7 +52,7 @@ pub fn py_compress( .map_err(|e| PyRuntimeError::new_err(format!("Compress failed: {e}"))) } -/// Decompresses and loads all GridArrays and shared metadata from a file. +/// Decompresses and loads all `GridArrays` and shared metadata from a file. /// /// Loads and decompresses all grid arrays and their associated metadata from the specified file. /// diff --git a/neopdf_pyapi/tests/test_gridpdf.py b/neopdf_pyapi/tests/test_gridpdf.py index 1080b3e8..7f3089f2 100644 --- a/neopdf_pyapi/tests/test_gridpdf.py +++ b/neopdf_pyapi/tests/test_gridpdf.py @@ -8,38 +8,58 @@ def test_subgrid(self, xq2_points): xmin, xmax, q2min, q2max = (1e-5, 1.0, 1.65, 1.0e8) xs, q2s = xq2_points(xmin, xmax, q2min, q2max) kts = [0.5, 1.0] + xis = [0.0] # dummy values + deltas = [0.0] # dummy values nucleons = [1.0, 2.0] alphas = [0.118, 0.120] grid = np.random.rand( - len(nucleons), len(alphas), len(kts), len(xs), len(q2s), 1 + len(nucleons), + len(alphas), + len(xis), + len(deltas), + len(kts), + len(xs), + len(q2s), + 1, ) - subgrid = SubGrid(xs, q2s, kts, nucleons, alphas, grid) + subgrid = SubGrid(xs, q2s, kts, xis, deltas, nucleons, alphas, grid) assert subgrid.alphas_range() == (0.118, 0.120) assert subgrid.x_range() == (xs[0], xs[-1]) assert subgrid.q2_range() == (q2s[0], q2s[-1]) - assert subgrid.grid_shape() == ( + assert subgrid.grid_shape() == [ len(nucleons), len(alphas), + len(xis), + len(deltas), len(kts), len(xs), len(q2s), 1, - ) + ] def test_gridarray(self, xq2_points): xmin, xmax, q2min, q2max = (1e-5, 1.0, 1.65, 1.0e8) xs, q2s = xq2_points(xmin, xmax, q2min, q2max) kts = [0.5, 1.0] + xis = [0.0] # dummy values + deltas = [0.0] # dummy values nucleons = [1.0, 2.0] alphas = [0.118, 0.120] grid = np.random.rand( - len(nucleons), len(alphas), len(kts), len(xs), len(q2s), 1 + len(nucleons), + len(alphas), + len(xis), + len(deltas), + len(kts), + len(xs), + len(q2s), + 1, ) - subgrid1 = SubGrid(xs, q2s, kts, nucleons, alphas, grid) - subgrid2 = SubGrid(xs, q2s, kts, nucleons, alphas, grid) + subgrid1 = SubGrid(xs, q2s, kts, xis, deltas, nucleons, alphas, grid) + subgrid2 = SubGrid(xs, q2s, kts, xis, deltas, nucleons, alphas, grid) pids = [21, -2, -1, 1, 2] grid_array = GridArray(pids, [subgrid1, subgrid2]) diff --git a/neopdf_pyapi/tests/test_metadata.py b/neopdf_pyapi/tests/test_metadata.py index e27240f6..34b46fcd 100644 --- a/neopdf_pyapi/tests/test_metadata.py +++ b/neopdf_pyapi/tests/test_metadata.py @@ -40,6 +40,10 @@ def test_metadata_creation(self): x_max=1.0, q_min=1.65, q_max=1.0e4, + xsi_min=0.0, + xsi_max=0.0, + delta_min=0.0, + delta_max=0.0, flavors=[21, 1, 2, 3, 4, -1, -2, -3, -4], format="test_format", alphas_q_values=[1.0, 2.0], @@ -59,6 +63,10 @@ def test_metadata_creation(self): assert meta.x_max() == 1.0 assert meta.q_min() == 1.65 assert meta.q_max() == 1.0e4 + assert meta.xi_min() == 0.0 + assert meta.xi_max() == 0.0 + assert meta.delta_min() == 0.0 + assert meta.delta_max() == 0.0 assert meta.pids() == [21, 1, 2, 3, 4, -1, -2, -3, -4] assert meta.format() == "test_format" assert meta.alphas_q() == [1.0, 2.0] diff --git a/pixi.lock b/pixi.lock index 3d288202..d353de35 100644 --- a/pixi.lock +++ b/pixi.lock @@ -14,19 +14,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.44-h4852527_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.44-h4bf12b8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.44-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311hfdbb021_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py314ha160325_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.11.0-h4d9bdce_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.7.14-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cargo-c-0.10.14-hc3c1012_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.7.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311hf29c0ef_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-14.3.0-hb991d5c_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.10.1-py311h3778330_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.11.0-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.11.0-hfcd1e18_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-14.3.0-h76bdaa0_4.conda @@ -49,14 +49,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lhapdf-6.5.5-py311hd49b95e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lhapdf-6.5.5-py314h3a4f467_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-32_h59b9bed_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-32_he106b2a_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-h85bb3a7_104.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_4.conda @@ -66,8 +66,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-32_h7ac8fdf_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-hd08acf3_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda @@ -75,11 +75,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h85bb3a7_104.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/maturin-1.9.2-py39h3314b2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mergedeep-1.3.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda @@ -87,13 +86,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-material-9.6.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py311h2e04523_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.1-h7b32b05_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.4-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda @@ -103,17 +102,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-env-tag-1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rust-1.88.0-h1a8d7c4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-x86_64-unknown-linux-gnu-1.88.0-h2c6d0dc_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_8.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda @@ -122,17 +120,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py311h38be061_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py314hdafbbf9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py311h9ecbd09_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py314h31f8a6b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/1f/d4/6fe9c62bec56212b96607bdc26ee1072518213414d77c4ff878c17c7889c/mkdocs_roamlinks_plugin-0.3.2-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backrefs-5.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311hc356e98_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py314hb6723d8_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda @@ -141,7 +138,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1021.4-h67a6458_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1021.4-haa85c18_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.7.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311h137bacd_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py314h8ca4d5a_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_h3571c67_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h576c50e_3.conda @@ -154,7 +151,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-h52031e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-hc6f8467_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.10.1-py311hfbe4617_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.11.0-py314hb9c7d66_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-15.1.0-hcc3c99d_0.conda @@ -174,7 +171,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-954.16-hc3792c1_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-954.16-hf1c22e8_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lhapdf-6.5.5-py311hc3782e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lhapdf-6.5.5-py314haba150f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-32_h7f60823_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-32_hff6cab4_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_h3571c67_3.conda @@ -184,7 +181,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.1.0-h5f6db21_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-15.1.0-h5f6db21_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.1.0-hfa3c126_0.conda @@ -193,6 +190,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-32_h236ab99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-hc29ff6c_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.64.0-hc7306c3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda @@ -203,7 +201,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-he90a8e3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-h3fe3016_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/maturin-1.9.2-py39h58e7067_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mergedeep-1.3.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda @@ -213,13 +211,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py311h09fcace_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.1-hc426f3f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.4-py314hf08249b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.45-hf733adb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pkg-config-0.29.2-hf7e621a_1009.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda @@ -229,17 +227,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-env-tag-1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/rust-1.88.0-h34a2095_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-x86_64-apple-darwin-1.88.0-h38e4360_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-0.1.3-h88f4db0_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1300.6.5-h390ca13_0.conda @@ -249,18 +246,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py311h4d7f069_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py314h03d016b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.23.0-py311h4d7f069_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py314h12c88b1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/1f/d4/6fe9c62bec56212b96607bdc26ee1072518213414d77c4ff878c17c7889c/mkdocs_roamlinks_plugin-0.3.2-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backrefs-5.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311h155a34a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py314he8615de_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.5-h5505292_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-compiler-1.11.0-h61f9b84_0.conda @@ -269,7 +265,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1021.4-hd01ab73_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_osx-arm64-1021.4-haeb51d2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.7.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h3a79f62_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19-19.1.7-default_hf90f093_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19.1.7-default_h474c9e2_3.conda @@ -282,7 +278,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-19.1.7-hd2aecb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-19.1.7-h7969c41_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.10.1-py311h2fe624c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.11.0-py314hb7e19f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cxx-compiler-1.11.0-h88570a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gfortran-15.1.0-h3ef1dbf_0.conda @@ -302,7 +298,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-954.16-he86490a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-954.16-hc42d924_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lhapdf-6.5.5-py311h6dccdbe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lhapdf-6.5.5-py314h999937e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-32_h10e41b3_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-32_hb3479ef_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp19.1-19.1.7-default_hf90f093_3.conda @@ -312,17 +308,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.1.0-hfdf1602_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-arm64-15.1.0-hfdf1602_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.1.0-hb74de2c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgit2-1.9.1-h41192e5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.84.2-hbec27ea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgit2-1.9.1-h3653167_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.0-he69a767_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-hfe07756_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-32_hc9a63f6_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm19-19.1.7-hc4b4ae8_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.64.0-h6d7220d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_h60d53f8_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda @@ -333,7 +330,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19-19.1.7-h87a4c7e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19.1.7-hd2aecb6_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/maturin-1.9.2-py39he71e08c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mergedeep-1.3.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda @@ -343,13 +340,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py311h0856f98_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.1-h81ee809_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.4-py314h5b5928d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.45-ha881caa_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pkg-config-0.29.2-hde07d2e_1009.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda @@ -359,17 +356,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.0-h40d2674_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-env-tag-1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rust-1.88.0-h4ff7c5d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-aarch64-apple-darwin-1.88.0-hf6ec828_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-0.1.3-h44b9a77_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda @@ -379,12 +375,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py311h917b07b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py314hb84d1df_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.23.0-py311h917b07b_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py314h163e31d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/1f/d4/6fe9c62bec56212b96607bdc26ee1072518213414d77c4ff878c17c7889c/mkdocs_roamlinks_plugin-0.3.2-py3-none-any.whl win-64: @@ -392,18 +387,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backrefs-5.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/binutils_impl_win-64-2.44-h095e170_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311hda3d55a_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py314h13fbf68_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/c-compiler-1.11.0-h528c1b4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.7.14-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cargo-c-0.10.14-h4834f17_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.7.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311he736701_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh7428d3b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/conda-gcc-specs-15.1.0-h69eaf6f_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.10.1-py311h3f79411_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.11.0-py314h2359020_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cxx-compiler-1.11.0-h1c1089f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gcc-15.1.0-he7be042_4.conda @@ -423,7 +418,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-32_h641d27c_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-32_h5e41251_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.1.0-h1383e82_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_win-64-15.1.0-hec057c1_104.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgfortran5-15.1.0-h997fb6f_4.conda @@ -433,6 +428,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-h135ad9c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-32_h1aa476e_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libstdcxx-15.1.0-h904f734_4.conda @@ -441,7 +437,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/m2w64-sysroot_win-64-12.0.0.r4.gg4f2fc60ca-h7428d3b_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/maturin-1.9.2-py39h9f238f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mergedeep-1.3.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-crt-git-12.0.0.r4.gg4f2fc60ca-h7428d3b_9.conda @@ -453,12 +449,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-material-9.6.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.2-py311h80b3fa1_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.1-h725018a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.4-py314h06c3c77_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/paginate-0.5.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda @@ -467,16 +463,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-env-tag-1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/rust-1.88.0-hf8d6059_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-x86_64-pc-windows-msvc-1.88.0-h17fc481_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda @@ -491,12 +486,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_31.conda - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py311h1ea47a8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py314h86ab7b2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.23.0-py311he736701_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py314h4667ab5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - pypi: https://files.pythonhosted.org/packages/1f/d4/6fe9c62bec56212b96607bdc26ee1072518213414d77c4ff878c17c7889c/mkdocs_roamlinks_plugin-0.3.2-py3-none-any.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -600,73 +595,73 @@ packages: purls: [] size: 36046 timestamp: 1752032788780 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311hfdbb021_3.conda - sha256: 4fab04fcc599853efb2904ea3f935942108613c7515f7dd57e7f034650738c52 - md5: 8565f7297b28af62e5de2d968ca32e31 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py314ha160325_4.conda + sha256: c3580d093d1662fd4f5370dd07492d161c3d8e09c524c56d28ff57da78f2824b + md5: a5529cb388bf7c6c9ae3d9d1ae54be56 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 constrains: - - libbrotlicommon 1.1.0 hb9d3cd8_3 + - libbrotlicommon 1.1.0 hb03c661_4 license: MIT license_family: MIT purls: - pkg:pypi/brotli?source=hash-mapping - size: 350166 - timestamp: 1749230304421 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311hc356e98_3.conda - sha256: 63f3771e23a1f3f9866ece0252586b5b57eefba8d83a2871a72c82716944cc7b - md5: 7259b2f4870cab602f1512562e5cbb30 + size: 354296 + timestamp: 1756599490120 +- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py314hb6723d8_4.conda + sha256: abf4d71502aa7e72191d3b7e293705bdb2a0218ddff736d166c58d85909c9082 + md5: 7478ccd9121628f21a5db0a5f4bf2c49 depends: - __osx >=10.13 - - libcxx >=18 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - libcxx >=19 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 constrains: - - libbrotlicommon 1.1.0 h6e16a3a_3 + - libbrotlicommon 1.1.0 h1c43f85_4 license: MIT license_family: MIT purls: - pkg:pypi/brotli?source=hash-mapping - size: 367210 - timestamp: 1749230581348 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311h155a34a_3.conda - sha256: 7414997b02a5f07d0b089fb24f1e755347fd827fa5fd158681766fce9583dd9b - md5: ba41239b4753557a20cf2ac2cd4250c5 + size: 369206 + timestamp: 1756600353583 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py314he8615de_4.conda + sha256: a98896c84230c17250e1a4cd2f23bc0fda78ba5e36daba2592dffa9c8595b24e + md5: ba3469ba447f703495855c75827ae68f depends: - __osx >=11.0 - - libcxx >=18 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - libcxx >=19 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 constrains: - - libbrotlicommon 1.1.0 h5505292_3 + - libbrotlicommon 1.1.0 h6caf38d_4 license: MIT license_family: MIT purls: - pkg:pypi/brotli?source=hash-mapping - size: 338502 - timestamp: 1749230799184 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311hda3d55a_3.conda - sha256: a602b15fe1b3a6b40aab7d99099a410b69ccad9bb273779531cef00fc52d762e - md5: 2d99144abeb3b6b65608fdd7810dbcbd - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + size: 341292 + timestamp: 1756599838917 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py314h13fbf68_4.conda + sha256: b8ee84b8fe3418373fb3a79ca5976d427117c499a0311451acf598d64912df07 + md5: 4a0473238e95bddd97b32f68983fc0fe + depends: + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - - libbrotlicommon 1.1.0 h2466b09_3 + - libbrotlicommon 1.1.0 hfd05255_4 license: MIT license_family: MIT purls: - pkg:pypi/brotli?source=hash-mapping - size: 321757 - timestamp: 1749231264056 + size: 323005 + timestamp: 1756599884143 - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d md5: 62ee74e96c5ebb0af99386de58cf9553 @@ -946,69 +941,69 @@ packages: - pkg:pypi/certifi?source=hash-mapping size: 159755 timestamp: 1752493370797 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311hf29c0ef_0.conda - sha256: bc47aa39c8254e9e487b8bcd74cfa3b4a3de3648869eb1a0b89905986b668e35 - md5: 55553ecd5328336368db611f350b7039 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + sha256: c6339858a0aaf5d939e00d345c98b99e4558f285942b27232ac098ad17ac7f8e + md5: cf45f4278afd6f4e6d03eda0f435d527 depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.4,<4.0a0 - - libgcc >=13 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 license: MIT license_family: MIT purls: - pkg:pypi/cffi?source=hash-mapping - size: 302115 - timestamp: 1725560701719 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311h137bacd_0.conda - sha256: 012ee7b1ed4f9b0490d6e90c72decf148d7575173c7eaf851cd87fd434d2cacc - md5: a4b0f531064fa3dd5e3afbb782ea2cd5 + size: 300271 + timestamp: 1761203085220 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py314h8ca4d5a_1.conda + sha256: e2c58cc2451cc96db2a3c8ec34e18889878db1e95cc3e32c85e737e02a7916fb + md5: 71c2caaa13f50fe0ebad0f961aee8073 depends: - __osx >=10.13 - - libffi >=3.4,<4.0a0 + - libffi >=3.5.2,<3.6.0a0 - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 license: MIT license_family: MIT purls: - pkg:pypi/cffi?source=hash-mapping - size: 288762 - timestamp: 1725560945833 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h3a79f62_0.conda - sha256: 253605b305cc4548b8f97eb7c2e146697e0c7672b099c4862ec5ca7e8e995307 - md5: a42272c5dbb6ffbc1a5af70f24c7b448 + size: 293633 + timestamp: 1761203106369 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + sha256: 5b5ee5de01eb4e4fd2576add5ec9edfc654fbaf9293e7b7ad2f893a67780aa98 + md5: 10dd19e4c797b8f8bdb1ec1fbb6821d7 depends: - __osx >=11.0 - - libffi >=3.4,<4.0a0 + - libffi >=3.5.2,<3.6.0a0 - pycparser - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 license: MIT license_family: MIT purls: - pkg:pypi/cffi?source=hash-mapping - size: 288211 - timestamp: 1725560745212 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311he736701_0.conda - sha256: 9689fbd8a31fdf273f826601e90146006f6631619767a67955048c7ad7798a1d - md5: e1c69be23bd05471a6c623e91680ad59 + size: 292983 + timestamp: 1761203354051 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + sha256: 924f2f01fa7a62401145ef35ab6fc95f323b7418b2644a87fea0ea68048880ed + md5: c360170be1c9183654a240aadbedad94 depends: - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: MIT license_family: MIT purls: - pkg:pypi/cffi?source=hash-mapping - size: 297627 - timestamp: 1725561079708 + size: 294731 + timestamp: 1761203441365 - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda sha256: 535ae5dcda8022e31c6dc063eb344c80804c537a5a04afba43a845fa6fa130f5 md5: 40fe4284b8b5835a9073a645139f35af @@ -1290,56 +1285,56 @@ packages: purls: [] size: 56517 timestamp: 1753904326451 -- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.10.1-py311h3778330_0.conda - sha256: b29c8d2217d45bbde6e3096fcded2317d624de4daf4b32015483aea8fbfc0492 - md5: ad5c4453544b1bbbb9cc29e4ab9a97cd +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.11.0-py314h67df5f8_0.conda + sha256: d928f6d0567807c8a09786e18966445b011ec2eb85c7e18382c0b4870cf12f17 + md5: 6ff84b39468623308dee97e5dfbafca0 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 - tomli license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 388308 - timestamp: 1753652308151 -- conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.10.1-py311hfbe4617_0.conda - sha256: 8a43d954f0685a3e36a90c895a771cacd7774b1f2d477afbd1a8557bc721c66d - md5: 8a7f0fecd1a61504e54b0f5a396c2de8 + size: 402708 + timestamp: 1760545023449 +- conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.11.0-py314hb9c7d66_0.conda + sha256: e5d099e7daa1c71c53b420fbc5b211e941a3820ca7514b5235f7720c09da2448 + md5: a8ce02c59aa971f762a8983a01f5749d depends: - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 - tomli license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 385986 - timestamp: 1753652453774 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.10.1-py311h2fe624c_0.conda - sha256: 6a199ce363c3030905726e28ffe593b18e1da70e3d1b41fbfc74befa7c971db4 - md5: fa7911d8486c7d9983d1c60f0078c436 + size: 401937 + timestamp: 1760545293564 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.11.0-py314hb7e19f3_0.conda + sha256: c977b9e080860cf64d247aa07528f65d8d01c1ad51a23ac1c6ad7a3f5f2a34bc + md5: a9d2395b30c275eb59b35292ec104233 depends: - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 - tomli license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 385989 - timestamp: 1753652480016 -- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.10.1-py311h3f79411_0.conda - sha256: 62e2b5ab514072d5c4f86b7def9debfe629deedd81e73565cfbf10dfa62222a0 - md5: 60f4e897f4f16069855ff5d30c04b11c - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + size: 402944 + timestamp: 1760545272491 +- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.11.0-py314h2359020_0.conda + sha256: 9042f962d7c644c7e6e14afcd3549c97e05c41d4d51b50ff2159088584ede6de + md5: ca8c9e9f8aa2b715b88b595420769e58 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 - tomli - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -1348,8 +1343,8 @@ packages: license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 413579 - timestamp: 1753652687642 + size: 429894 + timestamp: 1760545168798 - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.11.0-hfcd1e18_0.conda sha256: 3fcc97ae3e89c150401a50a4de58794ffc67b1ed0e1851468fcc376980201e25 md5: 5da8c935dca9186673987f79cef0b2a5 @@ -1973,47 +1968,47 @@ packages: purls: [] size: 776789 timestamp: 1752032889203 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lhapdf-6.5.5-py311hd49b95e_0.conda - sha256: e08dccb394ed0ab6da50b15a35818d87adb3133a01c6f043b93368dfe57404d7 - md5: 8494b9aa28229722e46ee9d1b7ec4cc1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lhapdf-6.5.5-py314h3a4f467_1.conda + sha256: 572dfb2a12101c5ccfc726cf5c95fde5a1f17636ff17c076596431e3629ccebf + md5: addcbc713a59383c8c7433f7217e5095 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 license: GPL-3.0-only license_family: GPL purls: [] - size: 919155 - timestamp: 1737702964993 -- conda: https://conda.anaconda.org/conda-forge/osx-64/lhapdf-6.5.5-py311hc3782e1_0.conda - sha256: f5b1934a6d5403ce85b19b4ed29662d768491ef39be0681346846b883e980369 - md5: 991820ec74d2d89ace4fea10eee9ae2b + size: 918168 + timestamp: 1756661811989 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lhapdf-6.5.5-py314haba150f_1.conda + sha256: 11424e103f51e2dfa60c8e2fd38677be52310376cc381ce4b3f5c0a76c6984c6 + md5: 3b447f848127f5a3df93205cc932d854 depends: - __osx >=10.13 - - libcxx >=18 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - libcxx >=19 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 license: GPL-3.0-only license_family: GPL purls: [] - size: 820385 - timestamp: 1737703124758 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lhapdf-6.5.5-py311h6dccdbe_0.conda - sha256: 7b680f89ce17e81ce2e8e52f5f51dd7c7e86434d2cafa6e1fe98834b89342405 - md5: 517d2de2cc123ae1a8b7aeed0507228b + size: 804384 + timestamp: 1756662066473 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lhapdf-6.5.5-py314h999937e_1.conda + sha256: 52328898334b6a00c39943b120f034b2bb8a892f2ddb1abd6e6997021e3b0572 + md5: d595b1769d6840e77ae8697f9c50fc66 depends: - __osx >=11.0 - - libcxx >=18 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - libcxx >=19 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 license: GPL-3.0-only license_family: GPL purls: [] - size: 836481 - timestamp: 1737703209161 + size: 814845 + timestamp: 1756662103383 - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-32_h59b9bed_openblas.conda build_number: 32 sha256: 1540bf739feb446ff71163923e7f044e867d163c50b605c8b421c55ff39aa338 @@ -2371,49 +2366,49 @@ packages: purls: [] size: 141322 timestamp: 1752719767870 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab - md5: ede4673863426c0883c0063d853bbd85 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 57433 - timestamp: 1743434498161 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - sha256: 6394b1bc67c64a21a5cc73d1736d1d4193a64515152e861785c44d2cfc49edf3 - md5: 4ca9ea59839a9ca8df84170fab4ceb41 + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0 + md5: d214916b24c625bcc459b245d509f22e depends: - __osx >=10.13 license: MIT license_family: MIT purls: [] - size: 51216 - timestamp: 1743434595269 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - sha256: c6a530924a9b14e193ea9adfe92843de2a806d1b7dbfd341546ece9653129e60 - md5: c215a60c2935b517dcda8cad4705734d + size: 52573 + timestamp: 1760295626449 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f + md5: 411ff7cd5d1472bba0f55c0faf04453b depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 39839 - timestamp: 1743434670405 -- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda - sha256: d3b0b8812eab553d3464bbd68204f007f1ebadf96ce30eb0cbc5159f72e353f5 - md5: 85d8fa5e55ed8f93f874b3b23ed54ec6 + size: 40251 + timestamp: 1760295839166 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + sha256: ddff25aaa4f0aa535413f5d831b04073789522890a4d8626366e43ecde1534a3 + md5: ba4ad812d2afc22b9a34ce8327a0930f depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: MIT license_family: MIT purls: [] - size: 44978 - timestamp: 1743435053850 + size: 44866 + timestamp: 1760295760649 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_4.conda sha256: 144e35c1c2840f2dc202f6915fc41879c19eddbb8fa524e3ca4aa0d14018b26f md5: f406dcbb2e7bef90d793e50e79a2882b @@ -2603,22 +2598,22 @@ packages: purls: [] size: 886237 timestamp: 1749251348265 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgit2-1.9.1-h41192e5_0.conda - sha256: 05594297662f86188f9c2d94b051785b8dd76c63d8ff0e37cba5d3e0cdeeae3a - md5: d61f6e69a3d4eaeba9ae72d33bc2be81 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgit2-1.9.1-h3653167_1.conda + sha256: 25443863cb8258699fb54a56b710500e485119c8579934fa23e5b1702edd5a0c + md5: 60405477889cfb1ce078e02082ee3c6c depends: - - libcxx >=18 - __osx >=11.0 - - openssl >=3.5.0,<4.0a0 - - pcre2 >=10.45,<10.46.0a0 - - libiconv >=1.18,<2.0a0 + - libcxx >=19 + - openssl >=3.5.3,<4.0a0 - libssh2 >=1.11.1,<2.0a0 - libzlib >=1.3.1,<2.0a0 + - libiconv >=1.18,<2.0a0 + - pcre2 >=10.46,<10.47.0a0 license: GPL-2.0-only WITH GCC-exception-2.0 license_family: GPL purls: [] - size: 840704 - timestamp: 1749251385909 + size: 840795 + timestamp: 1758613963815 - conda: https://conda.anaconda.org/conda-forge/win-64/libgit2-1.9.1-hc9b8bfc_0.conda sha256: bfcc750d2b07ac176eaca7ff6fb0b6e76d5508999f498b7267e77296c374bd2b md5: 9287db16b7f545abd2c0a63b9ec2822c @@ -2636,22 +2631,22 @@ packages: purls: [] size: 1197428 timestamp: 1749251415886 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.84.2-hbec27ea_0.conda - sha256: 5fcc5e948706cc64e45e2454267f664ed5a1e84f15345aae04a41d852a879c0e - md5: 7bbb8961dca1b4b9f2b01b6e722111a7 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.0-he69a767_1.conda + sha256: 58b0ccce58b6503cd9448d332c46de9b0757bee6251eb14ac5dd95f7ad3e83fe + md5: 16edb7fa702df38c414e1638de3596de depends: - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - libiconv >=1.18,<2.0a0 - - libintl >=0.24.1,<1.0a0 + - libintl >=0.25.1,<1.0a0 - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.45,<10.46.0a0 + - pcre2 >=10.46,<10.47.0a0 constrains: - - glib 2.84.2 *_0 + - glib 2.86.0 *_1 license: LGPL-2.1-or-later purls: [] - size: 3666180 - timestamp: 1747837044507 + size: 3656888 + timestamp: 1761246684692 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_4.conda sha256: e0487a8fec78802ac04da0ac1139c3510992bc58a58cde66619dde3b363c2933 md5: 3baf8976c96134738bba224e9ef6b1e5 @@ -2862,6 +2857,49 @@ packages: purls: [] size: 104935 timestamp: 1749230611612 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee + md5: c7e925f37e3b40d893459e625f6a53f1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 91183 + timestamp: 1748393666725 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e + md5: 18b81186a6adb43f000ad19ed7b70381 + depends: + - __osx >=10.13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 77667 + timestamp: 1748393757154 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 + md5: 85ccccb47823dd9f7a99d2c7f530342f + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 71829 + timestamp: 1748393749336 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + sha256: fc529fc82c7caf51202cc5cec5bb1c2e8d90edbac6d0a4602c966366efe3c7bf + md5: 74860100b2029e2523cf480804c76b9b + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 88657 + timestamp: 1723861474602 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda sha256: b0f2b3695b13a989f75d8fd7f4778e1c7aabe3b36db83f0fe80b2cd812c0e975 md5: 19e57602824042dfd0446292ef90488b @@ -2911,17 +2949,6 @@ packages: purls: [] size: 566719 timestamp: 1729572385640 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 - md5: d864d34357c3b65a4b731f78c0801dc4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-only - license_family: GPL - purls: [] - size: 33731 - timestamp: 1750274110928 - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_1.conda sha256: 3f3fc30fe340bc7f8f46fea6a896da52663b4d95caed1f144e8ea114b4bb6b61 md5: 7e2ba4ca7e6ffebb7f7fc2da2744df61 @@ -3114,16 +3141,17 @@ packages: purls: [] size: 29317 timestamp: 1753903924491 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 - md5: 40b61aab5c7ba9ff276c41cfffe6b80b +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 33601 - timestamp: 1680112270483 + size: 37135 + timestamp: 1758626800002 - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda sha256: 373f2973b8a358528b22be5e8d84322c165b4c5577d24d94fd67ad1bb0a0f261 md5: 08bfa5da6e242025304b206d152479ef @@ -3136,15 +3164,6 @@ packages: purls: [] size: 35794 timestamp: 1737099561703 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c - md5: 5aa797f8787fe7a17d1b0821485b5adc - depends: - - libgcc-ng >=12 - license: LGPL-2.1-or-later - purls: [] - size: 100393 - timestamp: 1702724383534 - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.13.8-h93c44a6_0.conda sha256: 4b29663164d7beb9a9066ddcb8578fc67fe0e9b40f7553ea6255cd6619d24205 md5: e42a93a31cbc6826620144343d42f472 @@ -3351,70 +3370,21 @@ packages: - pkg:pypi/markdown?source=hash-mapping size: 80353 timestamp: 1750360406187 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - sha256: 0291d90706ac6d3eea73e66cd290ef6d805da3fad388d1d476b8536ec92ca9a8 - md5: 6565a715337ae279e351d0abd8ffe88a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 25354 - timestamp: 1733219879408 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - sha256: e9965b5d4c29b17b1512035b24a7c126ed7bdb6b39103b52cae099d5bb4194a9 - md5: 1d6596ca7c7b66215c5c0d58b3cb0dd3 +- conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + sha256: e0cbfea51a19b3055ca19428bd9233a25adca956c208abb9d00b21e7259c7e03 + md5: fab1be106a50e20f10fe5228fd1d1651 depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24688 - timestamp: 1733219887972 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - sha256: 4f738a7c80e34e5e5d558e946b06d08e7c40e3cc4bdf08140bf782c359845501 - md5: 249e2f6f5393bb6b36b3d3a3eebdcdf9 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24976 - timestamp: 1733219849253 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda - sha256: 6f756e13ccf1a521d3960bd3cadddf564e013e210eaeced411c5259f070da08e - md5: c1f2ddad665323278952a453912dc3bd - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - python >=3.10 constrains: - jinja2 >=3.0.0 + track_features: + - markupsafe_no_compile license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/markupsafe?source=hash-mapping - size: 28238 - timestamp: 1733220208800 + size: 15499 + timestamp: 1759055275624 - conda: https://conda.anaconda.org/conda-forge/linux-64/maturin-1.9.2-py39h3314b2b_0.conda noarch: python sha256: 0f0e38651b9524c7f5ff5ad50436704e69236a256e24537f3f5e8760641d8ade @@ -3714,69 +3684,69 @@ packages: purls: [] size: 797030 timestamp: 1738196177597 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py311h2e04523_0.conda - sha256: 16f2c3c3f912e55ee133cb5c2ddc719152e418335ffadb9130e2ee1e269b6ea3 - md5: 61e1b42eb1d9f0ebaf038522ce9ca2fe +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.4-py314h2b28147_0.conda + sha256: c440f429b2e217cb3afbda92eb65a8a768aaf1be90657a133cf02871caa89fc4 + md5: 1a829816158b0129acfe809f2971c14e depends: - python - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - libcblas >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping - size: 9415425 - timestamp: 1753401560940 -- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py311h09fcace_0.conda - sha256: d72790ba7a79548b7c9d8c509d0a8f6093e0fd7bbe97e9d1bd175ffd60f426a8 - md5: 37f9922b287fd0f76a743f8e38cbea8d + size: 8952104 + timestamp: 1761162099395 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.4-py314hf08249b_0.conda + sha256: 6b9c236e59a4494b290807c17f0f631d8836cb7c1a8c5ddda9c924cd8d13e9e7 + md5: 997a0a22d754b95696dfdb055e1075ba depends: - python - - __osx >=10.13 - libcxx >=19 - - libblas >=3.9.0,<4.0a0 + - __osx >=10.13 - liblapack >=3.9.0,<4.0a0 - - python_abi 3.11.* *_cp311 - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping - size: 8551212 - timestamp: 1753401547206 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py311h0856f98_0.conda - sha256: ffe161b3ee67c55ad994fb7dfab2411f99c9baa801ef9538de756cab53bbe92d - md5: d399436ee3e7a06af9941cdf624d99c9 + size: 8098251 + timestamp: 1761161570315 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.4-py314h5b5928d_0.conda + sha256: 9e28281e67a94e4efb25617247cfcc171b30277a3407cd75c8f64a18275eed60 + md5: b61ad142f0d5978e98a4bb67cd5a4e22 depends: - python - - python 3.11.* *_cpython + - python 3.14.* *_cp314 - libcxx >=19 - __osx >=11.0 + - liblapack >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - libblas >=3.9.0,<4.0a0 - - python_abi 3.11.* *_cp311 - - liblapack >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7273975 - timestamp: 1753401541542 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.2-py311h80b3fa1_0.conda - sha256: d4a3832f3c79f0142b613bcf1670b90d1646c636f46ccc1d69d0d3b60af8d44c - md5: ecbc2648b2d6f542a45176ee67c63764 + - pkg:pypi/numpy?source=compressed-mapping + size: 6818770 + timestamp: 1761161593428 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.4-py314h06c3c77_0.conda + sha256: 4aa2ad078817c1bf8e97d4fa534550efa4ff55c83a27582d6007f87323a8fb62 + md5: 7c802e1e0b259eca63442c17f7e01132 depends: - python - vc >=14.3,<15 @@ -3785,55 +3755,55 @@ packages: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=compressed-mapping - size: 8017357 - timestamp: 1753401560734 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.1-h7b32b05_0.conda - sha256: 942347492164190559e995930adcdf84e2fea05307ec8012c02a505f5be87462 - md5: c87df2ab1448ba69169652ab9547082d + size: 7526912 + timestamp: 1761161584209 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda + sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 + md5: 14edad12b59ccbfa3910d42c72adc2a0 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - - libgcc >=13 + - libgcc >=14 license: Apache-2.0 license_family: Apache purls: [] - size: 3131002 - timestamp: 1751390382076 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.1-hc426f3f_0.conda - sha256: d5dc7da2ef7502a14f88443675c4894db336592ac7b9ae0517e1339ebb94f38a - md5: f1ac2dbc36ce2017bd8f471960b1261d + size: 3119624 + timestamp: 1759324353651 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a + md5: 075eaad78f96bbf5835952afbe44466e depends: - __osx >=10.13 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2744123 - timestamp: 1751391059798 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.1-h81ee809_0.conda - sha256: f94fde0f096fa79794c8aa0a2665630bbf9026cc6438e8253f6555fc7281e5a8 - md5: a8ac77e7c7e58d43fa34d60bd4361062 + size: 2747108 + timestamp: 1759326402264 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 + md5: 71118318f37f717eefe55841adb172fd depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 3071649 - timestamp: 1751390309393 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.1-h725018a_0.conda - sha256: 2b2eb73b0661ff1aed55576a3d38614852b5d857c2fa9205ac115820c523306c - md5: d124fc2fd7070177b5e2450627f8fc1a + size: 3067808 + timestamp: 1759324763146 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994 + md5: f28ffa510fe055ab518cbd9d6ddfea23 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -3842,8 +3812,8 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 9327033 - timestamp: 1751392489008 + size: 9218823 + timestamp: 1759326176247 - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 md5: 58335b26c38bf4a20f399384c33cbcf9 @@ -3903,9 +3873,9 @@ packages: purls: [] size: 1086588 timestamp: 1745955211398 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.45-ha881caa_0.conda - sha256: e9ecb706b58b5a2047c077b3a1470e8554f3aad02e9c3c00cfa35d537420fea3 - md5: a52385b93558d8e6bbaeec5d61a21cd7 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda + sha256: 5bf2eeaa57aab6e8e95bea6bd6bb2a739f52eb10572d8ed259d25864d3528240 + md5: 0e6e82c3cc3835f4692022e9b9cd5df8 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -3913,21 +3883,19 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 837826 - timestamp: 1745955207242 -- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - sha256: ec9ed3cef137679f3e3a68e286c6efd52144684e1be0b05004d9699882dadcdd - md5: dfce4b2af4bfe90cdcaf56ca0b28ddf5 + size: 835080 + timestamp: 1756743041908 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda + sha256: 20fe420bb29c7e655988fd0b654888e6d7755c1d380f82ca2f1bd2493b95d650 + md5: e7ab34d5a93e0819b62563c78635d937 depends: - - python >=3.9,<3.13.0a0 - - setuptools - - wheel + - python >=3.13.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/pip?source=compressed-mapping - size: 1177168 - timestamp: 1753924973872 + - pkg:pypi/pip?source=hash-mapping + size: 1179951 + timestamp: 1753925011027 - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda sha256: c9601efb1af5391317e04eca77c6fe4d716bf1ca1ad8da2a05d15cb7c28d7d4e md5: 1bee70681f504ea424fb07cdb090c001 @@ -4080,99 +4048,109 @@ packages: - pkg:pypi/pytest-cov?source=hash-mapping size: 28216 timestamp: 1749778064293 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda - sha256: 9979a7d4621049388892489267139f1aa629b10c26601ba5dce96afc2b1551d4 - md5: 8c399445b6dc73eab839659e6c7b5ad1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda + build_number: 102 + sha256: 76d750045b94fded676323bfd01975a26a474023635735773d0e4d80aaa72518 + md5: 0a19d2cc6eb15881889b0c6fa7d6a78d depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.50.0,<4.0a0 - - libuuid >=2.38.1,<3.0a0 - - libxcrypt >=4.4.36 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - constrains: - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 purls: [] - size: 30629559 - timestamp: 1749050021812 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda - sha256: d8e15db837c10242658979bc475298059bd6615524f2f71365ab8e54fbfea43c - md5: 6e28c31688c6f1fdea3dc3d48d33e1c0 + size: 36681389 + timestamp: 1761176838143 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda + build_number: 102 + sha256: 2470866eee70e75d6be667aa537424b63f97c397a0a90f05f2bab347b9ed5a51 + md5: 7917d1205eed3e72366a3397dca8a2af depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - constrains: - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 purls: [] - size: 15423460 - timestamp: 1749049420299 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda - sha256: 2c966293ef9e97e66b55747c7a97bc95ba0311ac1cf0d04be4a51aafac60dcb1 - md5: 95facc4683b7b3b9cf8ae0ed10f30dce + size: 14427639 + timestamp: 1761177864469 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.0-h40d2674_102_cp314.conda + build_number: 102 + sha256: 3ca1da026fe5df8a479d60e1d3ed02d9bc50fcbafd5f125d86abe70d21a34cc7 + md5: a9ff09231c555da7e30777747318321b depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - constrains: - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 purls: [] - size: 14573820 - timestamp: 1749048947732 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda - sha256: 723dbca1384f30bd2070f77dd83eefd0e8d7e4dda96ac3332fbf8fe5573a8abb - md5: bedbb6f7bb654839719cd528f9b298ad + size: 13590581 + timestamp: 1761177195716 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda + build_number: 102 + sha256: 2b8c8fcafcc30690b4c5991ee28eb80c962e50e06ce7da03b2b302e2d39d6a81 + md5: 3e1ce2fb0f277cebcae01a3c418eb5e2 depends: - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 - tk >=8.6.13,<8.7.0a0 - tzdata - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - python_abi 3.11.* *_cp311 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 purls: [] - size: 18242669 - timestamp: 1749048351218 + size: 16706286 + timestamp: 1761175439068 + python_site_packages_path: Lib/site-packages - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 md5: 5b8d21249ff20967101ffa321cab24e8 @@ -4186,17 +4164,17 @@ packages: - pkg:pypi/python-dateutil?source=hash-mapping size: 233310 timestamp: 1751104122689 -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 - sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 - md5: 8fcb6b0e2161850556231336dae58358 + sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 + md5: 0539938c55b6b1a59b560e843ad864a4 constrains: - - python 3.11.* *_cpython + - python 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD purls: [] - size: 7003 - timestamp: 1752805919375 + size: 6989 + timestamp: 1752805904792 - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda sha256: 8d2a8bf110cc1fc3df6904091dead158ba3e614d8402a83e51ed3a8aa93cdeb0 md5: bc8e3267d44011051f2eb14d22fb0960 @@ -4208,66 +4186,20 @@ packages: - pkg:pypi/pytz?source=hash-mapping size: 189015 timestamp: 1742920947249 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda - sha256: d107ad62ed5c62764fba9400f2c423d89adf917d687c7f2e56c3bfed605fb5b3 - md5: 014417753f948da1f70d132b2de573be - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 213136 - timestamp: 1737454846598 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda - sha256: 4855c51eedcde05f3d9666a0766050c7cbdff29b150d63c1adc4071637ba61d7 - md5: f49b0da3b1e172263f4f1e2f261a490d - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 197287 - timestamp: 1737454852180 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda - sha256: 2af6006c9f692742181f4aa2e0656eb112981ccb0b420b899d3dd42c881bd72f - md5: 250b2ee8777221153fd2de9c279a7efa +- conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + sha256: 828af2fd7bb66afc9ab1c564c2046be391aaf66c0215f05afaf6d7a9a270fe2a + md5: b12f41c0d7fb5ab81709fcc86579688f depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 196951 - timestamp: 1737454935552 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda - sha256: 6095e1d58c666f6a06c55338df09485eac34c76e43d92121d5786794e195aa4d - md5: e474ba674d780f0fa3b979ae9e81ba91 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - yaml >=0.2.5,<0.3.0a0 + - python >=3.10.* + - yaml + track_features: + - pyyaml_no_compile license: MIT license_family: MIT purls: - pkg:pypi/pyyaml?source=hash-mapping - size: 187430 - timestamp: 1737454904007 + size: 45223 + timestamp: 1758891992558 - conda: https://conda.anaconda.org/conda-forge/noarch/pyyaml-env-tag-1.1-pyhd8ed1ab_0.conda sha256: 69ab63bd45587406ae911811fc4d4c1bf972d643fa57a009de7c01ac978c4edd md5: e8e53c4150a1bba3b160eacf9d53a51b @@ -4421,17 +4353,6 @@ packages: purls: [] size: 37041309 timestamp: 1751057508252 -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 - md5: 4de79c071274a53dcaf2a8c749d1499e - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/setuptools?source=hash-mapping - size: 748788 - timestamp: 1748804951958 - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-0.1.3-h88f4db0_0.tar.bz2 sha256: 46fdeadf8f8d725819c4306838cdfd1099cd8fe3e17bd78862a5dfdcd6de61cf md5: fbfb84b9de9a6939cb165c02c69b1865 @@ -4686,72 +4607,61 @@ packages: purls: [] size: 238764 timestamp: 1745560912727 -- conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py311h38be061_0.conda - sha256: 3b22d78a338b6b237566175dbd5f37a79cbeb13ed271a36ddc6ec8c812afb3bd - md5: 7904988363c292e8ca9d3161f5fcd16a +- conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py314hdafbbf9_1.conda + sha256: 0fee43f08e1f7d407588e8bffb0916c63d1ab907b1a18003666ed3467674f1dc + md5: 099125fcb130e9fe134c6b610f33b0e1 depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 - pyyaml >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/watchdog?source=hash-mapping - size: 144958 - timestamp: 1730493000568 -- conda: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py311h4d7f069_0.conda - sha256: 7fd445533ad206a22b30f66798e408113e70abb1ad0a6ba4fc179fddf3c23a06 - md5: 97ad1750d3e143044fbece36644fc900 + size: 151503 + timestamp: 1756135453073 +- conda: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py314h03d016b_1.conda + sha256: d449cd438bcbd5dc98dae6053d1cf3d1dedbe918b2f495f519eee073786db700 + md5: 6b0e1a579b0c407480b006a834b00e0c depends: - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 - pyyaml >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/watchdog?source=hash-mapping - size: 152566 - timestamp: 1730493137565 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py311h917b07b_0.conda - sha256: 8058fec3cafb5d8fec494d7494ee65ad774ff5e777d05b80c675a40b54703758 - md5: 48b8db03ce8aa36248c5039d10aed423 + size: 159539 + timestamp: 1756135474975 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py314hb84d1df_1.conda + sha256: ffe72a3248f537c3a8da00150057d43b138df0baad4e228dd89a0bb2d399bb26 + md5: d1bbcc127fdc918a64267dc2bf9b5106 depends: - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 - pyyaml >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/watchdog?source=hash-mapping - size: 153434 - timestamp: 1730493114888 -- conda: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py311h1ea47a8_0.conda - sha256: 1613a3e7c9357a7c35184b6441577f3d69047008b5b0651ed86c31bdfb6d77df - md5: e351573c323c28d25de8af994bd04cec - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + size: 160938 + timestamp: 1756135543209 +- conda: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py314h86ab7b2_1.conda + sha256: 9de00692237b746e273c8855002a5c0285924f9f9a183563871e37216cfbc7a2 + md5: 2159a850d04a7b77f95a8d494eec5096 + depends: + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 - pyyaml >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/watchdog?source=hash-mapping - size: 170407 - timestamp: 1730493371386 -- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce - md5: 75cb7132eb58d97896e173ef12ac9986 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/wheel?source=hash-mapping - size: 62931 - timestamp: 1733130309598 + size: 176905 + timestamp: 1756135615605 - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f md5: 46e441ba871f524e2b067929da3051c2 @@ -4842,66 +4752,77 @@ packages: purls: [] size: 77606 timestamp: 1727963209370 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py311h9ecbd09_2.conda - sha256: 76d28240cc9fa0c3cb2cde750ecaf98716ce397afaf1ce90f8d18f5f43a122f1 - md5: ca02de88df1cc3cfc8f24766ff50cb3c +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py314h31f8a6b_0.conda + sha256: ec4e66b4e042ea9554b9db92b509358f75390f7dcbafb8eead940a2880486a63 + md5: 68bd13651618354987763f746ee1fadc depends: - - __glibc >=2.17,<3.0.a0 + - python - cffi >=1.11 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.5.8.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 731883 - timestamp: 1745869796301 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.23.0-py311h4d7f069_2.conda - sha256: 72ab78bbde3396ffb2b81a2513f48a27c128ddc4e06a8d3dbcfa790479deab40 - md5: 2712198232a6fcc673f9eef62fce85d5 + size: 127864 + timestamp: 1757930108791 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py314h12c88b1_0.conda + sha256: e66dbbac3e2b999336620d6d2f6ea21f234989e5672c1ba708b22f3483e91a5b + md5: 60b35d160b3095b1c754ee6567c43f6d depends: - - __osx >=10.13 + - python - cffi >=1.11 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.5.8.0a0 + - __osx >=10.13 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 691672 - timestamp: 1745869990327 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.23.0-py311h917b07b_2.conda - sha256: 7c7f7e24ff49dc6ecb804373bedca663d3c24d57cac55524be8c83da90313928 - md5: 9fd87c9aae7db68b4a3427886b5f3eea + size: 123692 + timestamp: 1757930114277 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py314h163e31d_0.conda + sha256: 5b707d7b80d9b410fce776a439273213745ffc3fa4553ec31f264bbaf63a6ec6 + md5: c824d8cd887ce1d7af8963ca4087a764 depends: - - __osx >=11.0 + - python - cffi >=1.11 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.5.8.0a0 + - __osx >=11.0 + - python 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 532851 - timestamp: 1745869893672 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.23.0-py311he736701_2.conda - sha256: aaae40057eac5b5996db4e6b3d8eb00d38455e67571e796135d29702a19736bd - md5: 8355ec073f73581e29adf77c49096aed + size: 125883 + timestamp: 1757930173407 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py314h4667ab5_0.conda + sha256: 285890e987cb14b2ac27f74a4ae844eb80e73654ab3321f085e13538c6ac7d23 + md5: 765406da1e9f31231a56ea4d2e191d7c depends: + - python - cffi >=1.11 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.5.8.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 445673 - timestamp: 1745870127079 + size: 285971 + timestamp: 1757930137171 - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 @@ -4937,3 +4858,16 @@ packages: purls: [] size: 399979 timestamp: 1742433432699 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + sha256: bc64864377d809b904e877a98d0584f43836c9f2ef27d3d2a1421fa6eae7ca04 + md5: 21f56217d6125fb30c3c3f10c786d751 + depends: + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 354697 + timestamp: 1742433568506 diff --git a/pixi.toml b/pixi.toml index 131f7d87..9a543215 100644 --- a/pixi.toml +++ b/pixi.toml @@ -6,7 +6,7 @@ channels = ["conda-forge"] platforms = ["linux-64", "osx-64", "win-64", "osx-arm64"] [dependencies] -python = ">=3.10,<3.12" +python = "3.14.*" pip = "*" rust = "*"