use crate::dd::DD;
use crate::dual::dd_param;
use crate::dv::DV;
use crate::od::{alg, mutate, quick_two_sum, vecsum, Limb, OD};
use crate::qd::QD;
use crate::qv::QV;
use crate::simd::{self, Kernel, Native, Simd};
use crate::v8::{Divisor, M8, V8};
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Clone, Copy, Debug)]
pub struct OV<S: Simd> {
pub c: [V8<S>; 8],
}
impl<S: Simd> Limb for V8<S> {
type M = M8<S>;
#[inline(always)]
fn zero() -> Self {
V8::zero()
}
#[inline(always)]
fn splat(x: f64) -> Self {
V8::splat(x)
}
#[inline(always)]
fn add(self, o: Self) -> Self {
V8::add(self, o)
}
#[inline(always)]
fn sub(self, o: Self) -> Self {
V8::sub(self, o)
}
#[inline(always)]
fn mul(self, o: Self) -> Self {
V8::mul(self, o)
}
#[inline(always)]
fn div(self, o: Self) -> Self {
V8::div(self, o)
}
#[inline(always)]
fn fma(self, b: Self, c: Self) -> Self {
V8::fma(self, b, c)
}
#[inline(always)]
fn fms(self, b: Self, c: Self) -> Self {
if mutate::on(mutate::OV_FMA_PLAIN) && S::NAME == Native::NAME {
V8::sub(V8::mul(self, b), c)
} else {
V8::fms(self, b, c)
}
}
#[inline(always)]
fn neg(self) -> Self {
V8::neg(self)
}
#[inline(always)]
fn sqrt(self) -> Self {
V8::sqrt(self)
}
#[inline(always)]
fn pow2_floor(self) -> Self {
V8::pow2_floor(self)
}
#[inline(always)]
fn is_zero(self) -> M8<S> {
self.eq(V8::zero())
}
#[inline(always)]
fn sel(m: M8<S>, t: Self, f: Self) -> Self {
m.sel(t, f)
}
#[inline(always)]
fn renorm<const N: usize>(c: [Self; N]) -> [Self; 8] {
S::outline(
#[inline(always)]
|| renorm_lanes(c),
)
}
#[inline(always)]
fn outline<T>(f: impl FnOnce() -> T) -> T {
S::outline(f)
}
}
#[inline(always)]
fn renorm_lanes<S: Simd, const N: usize>(c: [V8<S>; N]) -> [V8<S>; 8] {
let z = V8::<S>::zero();
let one = V8::splat(1.0);
let seven = V8::splat(7.0);
let inf = c[0].abs().eq(V8::splat(f64::INFINITY));
let orig = c;
let c = vecsum(c);
let mut out = [z; 8];
let mut k = z;
let mut acc = c[0];
for i in 1..N {
let (t, e) = quick_two_sum(acc, c[i]);
let room = seven.gt(k);
let emit = if mutate::on(mutate::OV_RENORM_MASK) {
room
} else {
room.and_ne(e, z)
};
for m in 0..i.min(7) {
out[m] = emit.and_eq(k, V8::splat(m as f64)).sel(t, out[m]);
}
acc = emit.sel(e, t);
k = k.add_where(emit, one);
}
for m in 0..N.min(8) {
out[m] = k.eq(V8::splat(m as f64)).sel(acc, out[m]);
}
if mutate::on(mutate::RENORM_DROP) {
out[7] = z;
}
std::array::from_fn(|m| inf.sel(if m < N { orig[m] } else { z }, out[m]))
}
impl<S: Simd> OV<S> {
#[inline(always)]
pub fn zero() -> Self {
OV { c: [V8::zero(); 8] }
}
#[inline]
pub fn new(c: [V8<S>; 8]) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: <V8<S> as Limb>::renorm(c),
},
)
}
#[inline(always)]
pub fn splat(x: OD) -> Self {
OV { c: x.x.map(V8::splat) }
}
#[inline(always)]
pub fn splat_f(x: f64) -> Self {
OV::splat(OD::from_f64(x))
}
#[inline(always)]
pub fn splat_dd(x: DD) -> Self {
OV::splat(OD::from_dd(x))
}
#[inline(always)]
pub fn splat_qd(x: QD) -> Self {
OV::splat(OD::from_qd(x))
}
#[inline(always)]
pub fn from_v8(x: V8<S>) -> Self {
let mut c = [V8::zero(); 8];
c[0] = x;
OV { c }
}
#[inline(always)]
pub fn from_dv(x: DV<S>) -> Self {
let mut c = [V8::zero(); 8];
c[0] = x.hi;
c[1] = x.lo;
OV { c }
}
#[inline(always)]
pub fn from_qv(x: QV<S>) -> Self {
let mut c = [V8::zero(); 8];
c[..4].copy_from_slice(&x.c);
OV { c }
}
#[inline(always)]
pub fn to_dv(self) -> DV<S> {
let (hi, lo) = alg::to_dd(&self.c);
DV { hi, lo }
}
#[inline(always)]
pub fn to_qv(self) -> QV<S> {
let [a, b, c, d] = alg::to_qd_parts(&self.c);
QV {
c: crate::qv::renorm4(a, b, c, d),
}
}
#[inline(always)]
pub fn to_v8(self) -> V8<S> {
let (h, t) = alg::to_f64_parts(&self.c);
t.eq(V8::zero()).sel(h, h.add(t))
}
#[inline(always)]
pub fn load(c: [&[f64]; 8]) -> Self {
OV { c: c.map(V8::load) }
}
#[inline(always)]
pub fn store(self, c: [&mut [f64]; 8]) {
for (v, s) in self.c.iter().zip(c) {
v.store(s);
}
}
pub fn from_lanes(x: &[OD; 8]) -> Self {
OV {
c: std::array::from_fn(|k| V8::from_array(std::array::from_fn(|j| x[j].x[k]))),
}
}
#[inline(always)]
pub fn lane(self, j: usize) -> OD {
OD {
x: self.c.map(|v| v.to_array()[j]),
}
}
pub fn lanes(self) -> [OD; 8] {
let a = self.c.map(|v| v.to_array());
std::array::from_fn(|j| OD {
x: std::array::from_fn(|k| a[k][j]),
})
}
#[inline(always)]
pub fn bcast(self, j: usize) -> Self {
OV {
c: self.c.map(|v| v.bcast(j)),
}
}
#[inline(always)]
pub fn abs_hi(self) -> V8<S> {
self.c[0].abs()
}
#[inline]
pub fn abs(self) -> Self {
S::vectorize(
#[inline(always)]
|| {
let neg = V8::zero().gt(self.c[0]);
let n = -self;
OV {
c: std::array::from_fn(|k| neg.sel(n.c[k], self.c[k])),
}
},
)
}
#[inline(always)]
pub fn mul_pwr2(self, b: f64) -> Self {
debug_assert!(crate::dd::is_pwr2(b), "mul_pwr2: {b} is not a power of two");
OV {
c: alg::mul_pwr2(&self.c, V8::splat(b)),
}
}
#[inline]
pub fn add_v(self, b: V8<S>) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::add_f(&self.c, b),
},
)
}
#[inline]
pub fn add_f(self, b: f64) -> Self {
self.add_v(V8::splat(b))
}
#[inline]
pub fn mul_v(self, b: V8<S>) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::mul_f(&self.c, b),
},
)
}
#[inline]
pub fn mul_f(self, b: f64) -> Self {
self.mul_v(V8::splat(b))
}
#[inline]
pub fn div_by(self, b: Self, r: Divisor<S>) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::div(&self.c, &b.c, |x: V8<S>| x.div_by(r)),
},
)
}
#[inline]
pub fn recip(self) -> Self {
OV::splat_f(1.0) / self
}
#[inline]
pub fn sqrt(self) -> Self {
S::vectorize(
#[inline(always)]
|| {
let w = |c: &[V8<S>; 8]| OV { c: *c };
OV {
c: alg::sqrt(
&self.c,
|a, b| (w(a) * w(b)).c,
|a, b| (w(a) + w(b)).c,
|a, b| w(a).add_v(b).c,
),
}
},
)
}
#[inline]
pub fn lt(self, o: Self) -> M8<S> {
let mut r = self.c[7].gt(self.c[7]); let mut eq = self.c[0].eq(self.c[0]).or(self.c[0].ne(self.c[0])); for k in 0..8 {
r = r.or(eq.and(o.c[k].gt(self.c[k])));
eq = eq.and_eq(self.c[k], o.c[k]);
}
r
}
#[inline]
pub fn gt(self, o: Self) -> M8<S> {
o.lt(self)
}
#[inline]
pub fn eq(self, o: Self) -> M8<S> {
let mut eq = self.c[0].eq(o.c[0]);
for k in 1..8 {
eq = eq.and_eq(self.c[k], o.c[k]);
}
eq
}
#[inline]
pub fn le(self, o: Self) -> M8<S> {
self.lt(o).or(self.eq(o))
}
#[inline]
pub fn ge(self, o: Self) -> M8<S> {
o.le(self)
}
}
impl<S: Simd> Neg for OV<S> {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
OV { c: alg::neg(&self.c) }
}
}
impl<S: Simd> Add for OV<S> {
type Output = Self;
#[inline]
fn add(self, b: Self) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::add(&self.c, &b.c),
},
)
}
}
impl<S: Simd> Sub for OV<S> {
type Output = Self;
#[inline]
fn sub(self, b: Self) -> Self {
self + (-b)
}
}
impl<S: Simd> Mul for OV<S> {
type Output = Self;
#[inline]
fn mul(self, b: Self) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::mul(&self.c, &b.c),
},
)
}
}
impl<S: Simd> Div for OV<S> {
type Output = Self;
#[inline]
fn div(self, b: Self) -> Self {
S::vectorize(
#[inline(always)]
|| OV {
c: alg::div(&self.c, &b.c, |x: V8<S>| x.div(b.c[0])),
},
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ODotAcc<S: Simd> {
pub s: [V8<S>; 9],
}
impl<S: Simd> ODotAcc<S> {
#[inline(always)]
pub fn zero() -> Self {
ODotAcc { s: [V8::zero(); 9] }
}
#[inline]
pub fn add_prod(self, a: OV<S>, b: OV<S>) -> Self {
S::vectorize(
#[inline(always)]
|| {
let t = alg::expand(&a.c, &b.c);
let mut s = self.s;
alg::acc_levels(&mut s, &t);
ODotAcc { s }
},
)
}
#[inline]
pub fn add_prod_mut(&mut self, a: &OV<S>, b: &OV<S>) {
S::vectorize(
#[inline(always)]
|| {
let t = alg::expand(&a.c, &b.c);
alg::acc_levels(&mut self.s, &t);
},
)
}
#[inline]
pub fn add(self, a: OV<S>) -> Self {
S::vectorize(
#[inline(always)]
|| {
let mut s = self.s;
alg::acc_levels(&mut s, &a.c);
ODotAcc { s }
},
)
}
#[inline]
pub fn finish(self) -> OV<S> {
S::vectorize(
#[inline(always)]
|| {
let mut r = OV::from_v8(self.s[0]);
for l in 1..9 {
r = OV {
c: alg::add_f(&r.c, self.s[l]),
};
}
r
},
)
}
}
impl<S: Simd> crate::dual::Lane for OV<S> {
type S = S;
type Acc = ODotAcc<S>;
dd_param!();
const TANGENT: bool = false;
const VAL_OD: bool = true;
#[inline(always)]
fn zero() -> Self {
OV::zero()
}
#[inline(always)]
fn cst(c: DV<S>) -> Self {
OV::from_dv(c)
}
#[inline(always)]
fn mulc(self, c: DV<S>) -> Self {
self * OV::from_dv(c)
}
#[inline(always)]
fn mulf(self, s: f64) -> Self {
self.mul_f(s)
}
#[inline(always)]
fn mul_pwr2(self, s: f64) -> Self {
OV::mul_pwr2(self, s)
}
#[inline(always)]
fn addc(self, c: DV<S>) -> Self {
self + OV::from_dv(c)
}
#[inline(always)]
fn bcast(self, j: usize) -> Self {
OV::bcast(self, j)
}
#[inline(always)]
fn val(self) -> DV<S> {
self.to_dv()
}
#[inline(always)]
fn tan(self) -> DV<S> {
DV::zero()
}
#[inline(always)]
fn from_vt(v: DV<S>, _t: DV<S>) -> Self {
OV::from_dv(v)
}
#[inline(always)]
fn acc0() -> ODotAcc<S> {
ODotAcc::zero()
}
#[inline(always)]
fn acc(a: ODotAcc<S>, x: Self, y: Self) -> ODotAcc<S> {
a.add_prod(x, y)
}
#[inline(always)]
fn acc_mut(a: &mut ODotAcc<S>, x: &Self, y: &Self) {
a.add_prod_mut(x, y);
}
#[inline(always)]
fn accc(a: ODotAcc<S>, x: Self, c: DV<S>) -> ODotAcc<S> {
a.add_prod(x, OV::from_dv(c))
}
#[inline(always)]
fn acc1(a: ODotAcc<S>, x: Self) -> ODotAcc<S> {
a.add(x)
}
#[inline(always)]
fn fin(a: ODotAcc<S>) -> Self {
a.finish()
}
#[inline(always)]
fn coef_qd(self, k: usize) -> QV<S> {
if k == 0 {
self.to_qv()
} else {
QV::zero()
}
}
#[inline(always)]
fn coef_od(self, k: usize) -> OV<S> {
if k == 0 {
self
} else {
OV::zero()
}
}
}
struct AxpyParts<'a> {
a: f64,
x: [&'a [f64]; 8],
y: [&'a [f64]; 8],
out: [&'a mut [f64]; 8],
}
impl Kernel for AxpyParts<'_> {
type Output = ();
#[inline(always)]
fn run<S: Simd>(self) {
let n = self.y[0].len();
let full = n / 8 * 8;
let (a, x, y) = (self.a, self.x, self.y);
let mut out = self.out;
for i in (0..full).step_by(8) {
let xv = OV::<S>::load(x.map(|p| &p[i..i + 8]));
let yv = OV::<S>::load(y.map(|p| &p[i..i + 8]));
let s = yv + xv.mul_f(a);
for (q, o) in out.iter_mut().enumerate() {
s.c[q].store(&mut o[i..i + 8]);
}
}
for i in full..n {
let xi = OD { x: x.map(|p| p[i]) };
let yi = OD { x: y.map(|p| p[i]) };
let s = yi + xi.mul_f(a);
for (q, o) in out.iter_mut().enumerate() {
o[i] = s.x[q];
}
}
}
}
pub fn axpy_parts(a: f64, x: [&[f64]; 8], y: [&[f64]; 8]) -> [Vec<f64>; 8] {
let n = y[0].len();
assert!(
x.iter().chain(y.iter()).all(|p| p.len() == n),
"axpy_parts: the sixteen arrays must have one length"
);
let mut out: [Vec<f64>; 8] = std::array::from_fn(|_| vec![0.0; n]);
let [o0, o1, o2, o3, o4, o5, o6, o7] = &mut out;
simd::dispatch(AxpyParts {
a,
x,
y,
out: [o0, o1, o2, o3, o4, o5, o6, o7].map(|o| &mut o[..]),
});
out
}