use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use crate::scalar::{LocalQp, Rational, Scalar};
pub(crate) const ADELE_PREC_NOMINAL: u128 = 16;
pub(crate) fn adele_prec(p: u128) -> u128 {
let mut k = ADELE_PREC_NOMINAL;
while k > 1
&& p.checked_pow(
k.try_into()
.expect("adele precision exponent fits the platform exponent type"),
)
.is_none_or(|pk| pk > i128::MAX as u128)
{
k -= 1;
}
k
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AdelePlace {
Real,
Prime(u128),
}
fn primes_dividing(n: i128) -> BTreeSet<u128> {
let mut ps = BTreeSet::new();
let mut m = n.abs();
let mut d = 2i128;
while d * d <= m {
if m % d == 0 {
ps.insert(d as u128);
while m % d == 0 {
m /= d;
}
}
d += 1;
}
if m > 1 {
ps.insert(m as u128);
}
ps
}
fn p_pow_rational(p: u128, e: i128) -> Rational {
let mut acc = 1i128;
for _ in 0..e.unsigned_abs() {
acc = acc.checked_mul(p as i128).expect("p-power exceeds i128");
}
if e >= 0 {
Rational::from_int(acc)
} else {
Rational::new(1, acc)
}
}
#[derive(Clone, PartialEq)]
pub struct Adele {
principal: Rational,
real: Rational,
finite: BTreeMap<u128, LocalQp>,
}
impl Adele {
pub fn from_rational(q: &Rational) -> Adele {
Adele {
principal: q.clone(),
real: q.clone(),
finite: BTreeMap::new(),
}
}
pub fn with_correction(mut self, p: u128, dev: LocalQp) -> Adele {
assert_eq!(
dev.prime(),
p,
"Adele correction key p={p} must match LocalQp prime {}",
dev.prime()
);
assert_eq!(
dev.precision(),
adele_prec(p),
"Adele correction at p={p} must use precision {}",
adele_prec(p)
);
if dev.is_zero() {
self.finite.remove(&p);
} else {
self.finite.insert(p, dev);
}
self
}
pub fn with_archimedean(mut self, real: Rational) -> Adele {
self.real = real;
self
}
pub fn principal(&self) -> &Rational {
&self.principal
}
pub fn archimedean(&self) -> &Rational {
&self.real
}
fn diag_at(&self, p: u128) -> LocalQp {
LocalQp::from_rational(p, adele_prec(p), &self.principal)
}
pub fn local_at(&self, p: u128) -> LocalQp {
let d = self.diag_at(p);
match self.finite.get(&p) {
Some(dev) => d.add(dev),
None => d,
}
}
pub fn is_principal(&self) -> bool {
self.finite.is_empty() && self.real == self.principal
}
fn active_primes(&self) -> BTreeSet<u128> {
let mut ps = primes_dividing(self.principal.numer());
ps.extend(primes_dividing(self.principal.denom()));
ps.extend(self.finite.keys().copied());
ps
}
pub fn is_idele(&self) -> bool {
if self.real.numer() == 0 || self.principal.numer() == 0 {
return false;
}
self.active_primes()
.into_iter()
.all(|p| !self.local_at(p).is_zero())
}
pub fn is_integral(&self) -> bool {
self.active_primes().into_iter().all(|p| {
let x = self.local_at(p);
x.is_zero() || x.valuation().map(|v| v >= 0).unwrap_or(true)
})
}
pub fn absolute_value_at(&self, place: AdelePlace) -> Rational {
match place {
AdelePlace::Real => {
if self.real.sign() == std::cmp::Ordering::Less {
self.real.neg()
} else {
self.real.clone()
}
}
AdelePlace::Prime(p) => match self.local_at(p).valuation() {
None => Rational::zero(), Some(v) => p_pow_rational(p, -v), },
}
}
pub fn idele_norm(&self) -> Rational {
let mut prod = self.absolute_value_at(AdelePlace::Real);
for p in self.active_primes() {
prod = prod.mul(&self.absolute_value_at(AdelePlace::Prime(p)));
}
prod
}
pub fn satisfies_product_formula(&self) -> bool {
self.is_idele() && self.idele_norm() == Rational::one()
}
}
impl Scalar for Adele {
fn zero() -> Self {
Adele::from_rational(&Rational::zero())
}
fn one() -> Self {
Adele::from_rational(&Rational::one())
}
fn add(&self, rhs: &Self) -> Self {
let principal = self.principal.add(&rhs.principal);
let real = self.real.add(&rhs.real);
let mut finite = BTreeMap::new();
let keys: BTreeSet<u128> = self
.finite
.keys()
.chain(rhs.finite.keys())
.copied()
.collect();
for p in keys {
let da = self
.finite
.get(&p)
.copied()
.unwrap_or_else(|| LocalQp::zero(p, adele_prec(p)));
let db = rhs
.finite
.get(&p)
.copied()
.unwrap_or_else(|| LocalQp::zero(p, adele_prec(p)));
let dev = da.add(&db);
if !dev.is_zero() {
finite.insert(p, dev);
}
}
Adele {
principal,
real,
finite,
}
}
fn neg(&self) -> Self {
Adele {
principal: self.principal.neg(),
real: self.real.neg(),
finite: self.finite.iter().map(|(&p, d)| (p, d.neg())).collect(),
}
}
fn mul(&self, rhs: &Self) -> Self {
let principal = self.principal.mul(&rhs.principal);
let real = self.real.mul(&rhs.real);
let mut finite = BTreeMap::new();
let keys: BTreeSet<u128> = self
.finite
.keys()
.chain(rhs.finite.keys())
.copied()
.collect();
for p in keys {
let prod = self.local_at(p).mul(&rhs.local_at(p));
let diag = LocalQp::from_rational(p, adele_prec(p), &principal);
let dev = prod.add(&diag.neg());
if !dev.is_zero() {
finite.insert(p, dev);
}
}
Adele {
principal,
real,
finite,
}
}
fn characteristic() -> u128 {
0
}
fn inv(&self) -> Option<Self> {
if !self.is_idele() {
return None;
}
let principal = self.principal.inv()?;
let real = self.real.inv()?;
let mut finite = BTreeMap::new();
for &p in self.finite.keys() {
let lx = self.local_at(p);
let lx_inv = lx.inv().expect("idele ⇒ nonzero at every finite place");
let diag_inv = LocalQp::from_rational(p, adele_prec(p), &principal);
let dev = lx_inv.add(&diag_inv.neg());
if !dev.is_zero() {
finite.insert(p, dev);
}
}
Some(Adele {
principal,
real,
finite,
})
}
}
impl fmt::Display for Adele {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Adele(principal={}", self.principal)?;
if self.real != self.principal {
write!(f, ", real={}", self.real)?;
}
for (p, dev) in &self.finite {
write!(f, ", Q_{p}={dev}")?;
}
write!(f, ")")
}
}
impl fmt::Debug for Adele {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn q(n: i128, d: i128) -> Rational {
Rational::new(n, d)
}
#[test]
fn diagonal_embedding_is_a_ring_homomorphism() {
for a in -6i128..=6 {
for b in -6i128..=6 {
for d in 1i128..=4 {
let (ra, rb) = (q(a, d), q(b, d));
let (ea, eb) = (Adele::from_rational(&ra), Adele::from_rational(&rb));
assert_eq!(ea.add(&eb), Adele::from_rational(&ra.add(&rb)));
assert_eq!(ea.mul(&eb), Adele::from_rational(&ra.mul(&rb)));
}
}
}
assert_eq!(Adele::zero(), Adele::from_rational(&Rational::zero()));
assert_eq!(Adele::one(), Adele::from_rational(&Rational::one()));
}
#[test]
fn inv_is_total_on_principal_ideles() {
for n in -10i128..=10 {
for d in 1i128..=6 {
if n == 0 {
continue;
}
let x = Adele::from_rational(&q(n, d));
assert!(x.is_idele());
let xi = x.inv().expect("nonzero rational is an idele");
assert_eq!(xi, Adele::from_rational(&q(d, n)));
assert_eq!(x.mul(&xi), Adele::one());
}
}
assert_eq!(Adele::zero().inv(), None);
}
#[test]
fn product_formula_on_principal_ideles() {
for n in -12i128..=12 {
for d in 1i128..=8 {
if n == 0 {
continue;
}
let x = Adele::from_rational(&q(n, d));
assert_eq!(x.idele_norm(), Rational::one(), "‖{n}/{d}‖ ≠ 1");
assert!(x.satisfies_product_formula());
}
}
}
#[test]
fn absolute_values_factor_the_rational() {
let x = Adele::from_rational(&q(12, 5));
assert_eq!(x.absolute_value_at(AdelePlace::Real), q(12, 5));
assert_eq!(x.absolute_value_at(AdelePlace::Prime(2)), q(1, 4));
assert_eq!(x.absolute_value_at(AdelePlace::Prime(3)), q(1, 3));
assert_eq!(
x.absolute_value_at(AdelePlace::Prime(5)),
Rational::from_int(5)
);
assert_eq!(x.absolute_value_at(AdelePlace::Prime(7)), Rational::one());
assert_eq!(x.idele_norm(), Rational::one());
}
#[test]
fn a_nonprincipal_idele_and_its_inverse() {
let dev = LocalQp::from_int(7, adele_prec(7), 1); let x = Adele::from_rational(&q(2, 3)).with_correction(7, dev);
assert!(!x.is_principal());
assert!(x.is_idele());
let xi = x.inv().expect("idele inverts");
assert_eq!(x.mul(&xi), Adele::one());
assert_eq!(
x.local_at(7).mul(&xi.local_at(7)),
LocalQp::one(7, adele_prec(7))
);
}
#[test]
fn a_correction_to_zero_is_not_an_idele() {
let diag5 = LocalQp::from_rational(5, adele_prec(5), &q(2, 1));
let x = Adele::from_rational(&q(2, 1)).with_correction(5, diag5.neg());
assert!(x.local_at(5).is_zero());
assert!(!x.is_idele());
assert_eq!(x.inv(), None);
}
#[test]
#[should_panic(expected = "must match LocalQp prime")]
fn correction_prime_must_match_key() {
let dev = LocalQp::zero(5, adele_prec(5));
let _ = Adele::one().with_correction(7, dev);
}
#[test]
#[should_panic(expected = "must use precision")]
fn correction_precision_must_match_adele_policy() {
let dev = LocalQp::zero(5, adele_prec(5) + 1);
let _ = Adele::one().with_correction(5, dev);
}
#[test]
fn additive_group_facts_hold_exactly() {
let xs: Vec<Adele> = (-4i128..=4)
.flat_map(|n| (1i128..=3).map(move |d| Adele::from_rational(&q(n, d))))
.collect();
let zero = Adele::zero();
for a in &xs {
assert_eq!(a.add(&zero), *a);
assert_eq!(a.add(&a.neg()), zero);
for b in &xs {
assert_eq!(a.add(b), b.add(a)); }
}
}
}