Skip to content

src/padic.rs

/// P-adic arithmetic module
/// Implements p-adic valuation, norms, and inner product computations

use core::f64;

/// Compute the p-adic valuation of an integer n
/// Returns the highest power of p that divides n
/// If n = 0, returns a sentinel value
#[inline]
pub fn padic_valuation(mut n: i64, p: u64) -> i32 {
    if n == 0 {
        return i32::MAX; // Convention: v_p(0) = infinity
    }

    let n_abs = n.abs();
    let p_i64 = p as i64;
    let mut valuation = 0i32;

    let mut remainder = n_abs;
    while remainder % p_i64 == 0 {
        remainder /= p_i64;
        valuation += 1;
    }

    valuation
}

/// Compute the p-adic norm |x|_p = p^{-v_p(x)}
/// Returns a float representation of the p-adic norm
#[inline]
pub fn padic_norm(x: f64, p: u64) -> f64 {
    if x == 0.0 {
        return 0.0;
    }

    // For floating point, we estimate the p-adic valuation
    // by examining the mantissa and exponent
    let (mantissa, exponent) = extract_mantissa_exponent(x);

    // Estimate valuation through integer approximation
    // This is a heuristic for floating-point values
    let approx_int = (mantissa * (1u64 << 53) as f64) as i64;
    let estimated_val = if approx_int != 0 {
        approximate_valuation(approx_int, p)
    } else {
        0
    };

    // p^{-v_p(x)}
    p.pow(estimated_val as u32) as f64
}

/// Extract mantissa and exponent from a float
#[inline]
fn extract_mantissa_exponent(x: f64) -> (f64, i32) {
    if x == 0.0 {
        return (0.0, 0);
    }

    let abs_x = x.abs();
    let exponent = abs_x.log2().floor() as i32;
    let mantissa = abs_x / (2.0_f64.powi(exponent));

    (mantissa, exponent)
}

/// Approximate p-adic valuation for integer representation
#[inline]
fn approximate_valuation(mut n: i64, p: u64) -> i32 {
    if n == 0 {
        return 0;
    }

    let p_i64 = p as i64;
    let mut valuation = 0i32;
    let mut remainder = n.abs();

    // Limit iterations to prevent overflow
    while remainder % p_i64 == 0 && valuation < 64 {
        remainder /= p_i64;
        valuation += 1;
    }

    valuation
}

/// Compute the p-adic inner product: ⟨v1, v2⟩_p
///
/// For each component pair (v1[i], v2[i]), compute:
///   - product = v1[i] * v2[i]
///   - weight = p^{-v_p(product)}
/// Then sum the weighted products
#[inline]
pub fn padic_inner_product(v1: &[f64], v2: &[f64], p: u64) -> f64 {
    if v1.len() != v2.len() || v1.is_empty() {
        return f64::NAN;
    }

    let mut sum = 0.0;

    for (x, y) in v1.iter().zip(v2.iter()) {
        let product = x * y;

        if product == 0.0 {
            continue; // Contribution is zero
        }

        // Estimate p-adic valuation of the product
        let (mantissa, _) = extract_mantissa_exponent(product);
        let approx_int = (mantissa * (1u64 << 53) as f64) as i64;

        let valuation = if approx_int != 0 {
            approximate_valuation(approx_int, p)
        } else {
            0
        };

        // Weight: p^{-v_p(product)} * product
        // This gives the contribution in the p-adic metric
        let weight = p.pow(valuation.saturating_abs() as u32) as f64;
        sum += weight * product.abs(); // Use absolute value for metric
    }

    sum
}

/// Compute the p-adic metric (norm) of a vector in Q_p
#[inline]
pub fn padic_vector_norm(v: &[f64], p: u64) -> f64 {
    if v.is_empty() {
        return 0.0;
    }

    // The p-adic norm of a vector is the maximum p-adic norm of its components
    v.iter()
        .map(|&x| padic_norm(x, p))
        .fold(0.0, f64::max)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_padic_valuation() {
        // v_2(8) = 3, since 8 = 2^3
        assert_eq!(padic_valuation(8, 2), 3);
        // v_3(9) = 2, since 9 = 3^2
        assert_eq!(padic_valuation(9, 3), 2);
        // v_5(10) = 1, since 10 = 2 * 5
        assert_eq!(padic_valuation(10, 5), 1);
        // v_p(1) = 0 for all p
        assert_eq!(padic_valuation(1, 7), 0);
    }

    #[test]
    fn test_padic_norm() {
        let norm_0 = padic_norm(0.0, 2);
        assert_eq!(norm_0, 0.0);

        let norm_1 = padic_norm(1.0, 2);
        assert!(norm_1 >= 0.0);
    }

    #[test]
    fn test_padic_inner_product() {
        let v1 = vec![1.0, 2.0, 3.0];
        let v2 = vec![1.0, 2.0, 3.0];
        let ip = padic_inner_product(&v1, &v2, 2);
        assert!(!ip.is_nan());
        assert!(ip >= 0.0);
    }
}