use crate::lane::DMat;
use multidouble::dd::DD;
pub const PI: DD = DD {
hi: std::f64::consts::PI,
lo: 1.2246467991473532e-16,
};
const PI_2: DD = DD {
hi: std::f64::consts::FRAC_PI_2,
lo: 6.123233995736766e-17,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Parity {
Odd,
Even,
Full,
}
fn sin_cos_series(r: DD) -> (DD, DD) {
let r2 = r * r;
let (mut s, mut c) = (r, DD::ONE);
let (mut ts, mut tc) = (r, DD::ONE);
for k in 1..30 {
let kf = k as f64;
ts = -(ts * r2) / DD::from_f64((2.0 * kf) * (2.0 * kf + 1.0));
tc = -(tc * r2) / DD::from_f64((2.0 * kf - 1.0) * (2.0 * kf));
s = s + ts;
c = c + tc;
if tc.hi.abs() < 2e-35 && ts.hi.abs() < 2e-35 * r.hi.abs().max(1e-300) {
break;
}
}
(s, c)
}
fn quadrant(n: i64, (s, c): (DD, DD)) -> (DD, DD) {
match n.rem_euclid(4) {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
pub fn sin_cos(x: DD) -> (DD, DD) {
let n = (x.hi / std::f64::consts::FRAC_PI_2).round();
let r = x - PI_2.mul_f(n);
quadrant(n as i64, sin_cos_series(r))
}
pub fn sin_cos_pi(p: i64, q: i64) -> (DD, DD) {
assert!(q > 0, "sin_cos_pi: q = {q}");
let n = (4 * p + q).div_euclid(2 * q);
let num = 2 * p - n * q;
let r = PI.mul_f(num as f64) / DD::from_f64((2 * q) as f64);
quadrant(n, sin_cos_series(r))
}
fn period_points(parity: Parity, m: usize) -> i64 {
match parity {
Parity::Full => m as i64,
_ => 2 * m as i64,
}
}
pub fn theta(parity: Parity, m: usize, j: usize) -> DD {
PI.mul_f(2.0 * j as f64) / DD::from_f64(period_points(parity, m) as f64)
}
pub fn sin_cos_grid(parity: Parity, m: usize, j: usize, k: i64) -> (DD, DD) {
sin_cos_pi(2 * k * j as i64, period_points(parity, m))
}
fn d_full(d: i64, n: i64) -> DD {
let (s, c) = sin_cos_pi(d, n);
let v = (c / s).mul_f(0.5);
if d.rem_euclid(2) == 0 {
v
} else {
-v
}
}
pub fn dmat<const B: usize>(parity: Parity) -> DMat<B> {
let m = 8 * B;
let n = period_points(parity, m);
let mut hi = vec![0.0; m * m];
let mut lo = vec![0.0; m * m];
for i in 0..m {
for j in 0..m {
let mut v = DD::ZERO;
let images: &[(usize, f64)] = match parity {
Parity::Full => &[(j, 1.0)],
Parity::Odd => &[(j, 1.0), (j + m, -1.0)],
Parity::Even => &[(j, 1.0), (j + m, 1.0)],
};
for &(jj, sgn) in images {
if jj != i {
v = v + d_full(i as i64 - jj as i64, n).mul_f(sgn);
}
}
hi[i * m + j] = v.hi;
lo[i * m + j] = v.lo;
}
}
DMat::<B>::from_rows(&hi, &lo)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lane::DV;
use crate::prec::nan_max;
use multidouble::field::Fl;
#[test]
fn sin_cos_agree() {
for p in -30..30 {
let x = PI.mul_f(p as f64) / DD::from_f64(7.0);
let (s, c) = sin_cos(x);
let (s2, c2) = sin_cos_pi(p, 7);
assert!((s - s2).hi.abs() < 1e-30 && (c - c2).hi.abs() < 1e-30, "p {p}");
assert!((s.hi - x.hi.sin()).abs() < 1e-15 && (c.hi - x.hi.cos()).abs() < 1e-15);
assert!((s * s + c * c - DD::ONE).hi.abs() < 1e-31);
}
}
#[test]
fn dmat_differentiates_harmonics() {
const B: usize = 2;
let m = 8 * B;
for (parity, ks) in [
(Parity::Odd, vec![1i64, 3, 5, 15]),
(Parity::Even, vec![0i64, 2, 8, 14]),
(Parity::Full, vec![0i64, 1, 4, 7]),
] {
let d = dmat::<B>(parity);
for &k in &ks {
let samples = |f: &dyn Fn(usize) -> DD| {
let v: Vec<DD> = (0..m).map(f).collect();
let (h, l) = multidouble::dd::split(&v);
Fl::<DV, B>::load(&h, &l)
};
let s = samples(&|j| sin_cos_grid(parity, m, j, k).0);
let c = samples(&|j| sin_cos_grid(parity, m, j, k).1);
let ds = d.apply(&s);
let dc = d.apply(&c);
for j in 0..m {
let kf = k as f64;
let e1 = (ds.get(j) - c.get(j).mul_f(kf)).hi.abs();
let e2 = (dc.get(j) + s.get(j).mul_f(kf)).hi.abs();
assert!(e1 < 1e-29 && e2 < 1e-29, "{parity:?} k {k} j {j}: {e1:e} {e2:e}");
}
}
}
}
#[test]
fn dmat_control_wrong_parity_fails() {
const B: usize = 1;
let m = 8 * B;
let d = dmat::<B>(Parity::Odd);
let v: Vec<DD> = (0..m).map(|j| sin_cos_grid(Parity::Odd, m, j, 2).1).collect();
let (h, l) = multidouble::dd::split(&v);
let c = Fl::<DV, B>::load(&h, &l);
let s: Vec<DD> = (0..m).map(|j| sin_cos_grid(Parity::Odd, m, j, 2).0).collect();
let dc = d.apply(&c);
let err = (0..m)
.map(|j| (dc.get(j) + s[j].mul_f(2.0)).hi.abs())
.fold(0.0, nan_max);
assert!(err > 1e-3, "{err:e}");
}
}