use pulp::Simd;
const EXP_C: [f64; 12] = [
f64::from_bits(0x3ff0000000000000),
f64::from_bits(0x3ff0000000000000),
f64::from_bits(0x3fe0000000000010),
f64::from_bits(0x3fc55555555554a2),
f64::from_bits(0x3fa555555554f370),
f64::from_bits(0x3f81111111130dd6),
f64::from_bits(0x3f56c16c1878111c),
f64::from_bits(0x3f2a01a0110572b2),
f64::from_bits(0x3efa01992d0fe736),
f64::from_bits(0x3ec71df4520aaeeb),
f64::from_bits(0x3e928b311c7eb84f),
f64::from_bits(0x3e5ad661c903688b),
];
const LOG1P_H: [f64; 10] = [
f64::from_bits(0x3fe5555555555555),
f64::from_bits(0x3fd999999999a455),
f64::from_bits(0x3fd24924923cd3a0),
f64::from_bits(0x3fcc71c727660721),
f64::from_bits(0x3fc745cefc3caf8b),
f64::from_bits(0x3fc3b18cab0fef6e),
f64::from_bits(0x3fc10ab0536ce75b),
f64::from_bits(0x3fbebaa07b021d58),
f64::from_bits(0x3fb67ff2751e342c),
f64::from_bits(0x3fc4b8585fced69a),
];
const EXP_DEG: usize = EXP_C.len() - 1; const LOG1P_DEG: usize = LOG1P_H.len() - 1;
const LN2HI: f64 = f64::from_bits(0x3fe62e42fee00000); const LN2LO: f64 = f64::from_bits(0x3dea39ef35793c76); const LOG2E: f64 = f64::from_bits(0x3ff71547652b82fe); const RND_MAGIC: f64 = 1.5 * (1u64 << 52) as f64;
const BIAS_MAGIC: f64 = (1u64 << 52) as f64 + 1023.0;
const MANT_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
const SHIFT52: u64 = 1u64 << 52;
const EXP_ARG_FLOOR: f64 = -700.0;
const EXP_ARG_CEIL: f64 = 700.0;
pub(crate) const FUSED_DEFAULT: bool = cfg!(not(target_arch = "wasm32"));
#[inline(always)]
fn fmadd<S: Simd, const FUSED: bool>(simd: S, a: S::f64s, b: S::f64s, c: S::f64s) -> S::f64s {
if FUSED {
simd.mul_add_f64s(a, b, c)
} else {
simd.add_f64s(simd.mul_f64s(a, b), c)
}
}
#[inline(always)]
fn fmadd_scalar<const FUSED: bool>(a: f64, b: f64, c: f64) -> f64 {
if FUSED {
a.mul_add(b, c)
} else {
a * b + c
}
}
#[inline(always)]
fn simd_exp_reduced<S: Simd, const FUSED: bool>(simd: S, x: S::f64s) -> S::f64s {
let t = fmadd::<S, FUSED>(simd, x, simd.splat_f64s(LOG2E), simd.splat_f64s(RND_MAGIC));
let kf = simd.sub_f64s(t, simd.splat_f64s(RND_MAGIC));
let neg_kf = simd.neg_f64s(kf);
let hi = fmadd::<S, FUSED>(simd, neg_kf, simd.splat_f64s(LN2HI), x); let r = fmadd::<S, FUSED>(simd, neg_kf, simd.splat_f64s(LN2LO), hi); let mut acc = simd.splat_f64s(EXP_C[EXP_DEG]);
let mut j = EXP_DEG;
while j > 0 {
j -= 1;
acc = fmadd::<S, FUSED>(simd, acc, r, simd.splat_f64s(EXP_C[j]));
}
let e = simd.add_f64s(kf, simd.splat_f64s(BIAS_MAGIC));
let m = simd.and_u64s(simd.transmute_u64s_f64s(e), simd.splat_u64s(MANT_MASK));
let pow2 = simd.transmute_f64s_u64s(simd.mul_u64s(m, simd.splat_u64s(SHIFT52)));
simd.mul_f64s(acc, pow2)
}
#[inline(always)]
fn simd_log1p_unit<S: Simd, const FUSED: bool>(simd: S, z: S::f64s) -> S::f64s {
let f = z;
let hfsq = simd.mul_f64s(simd.splat_f64s(0.5), simd.mul_f64s(f, f));
let s = simd.div_f64s(f, simd.add_f64s(simd.splat_f64s(2.0), f));
let w = simd.mul_f64s(s, s);
let mut acc = simd.splat_f64s(LOG1P_H[LOG1P_DEG]);
let mut j = LOG1P_DEG;
while j > 0 {
j -= 1;
acc = fmadd::<S, FUSED>(simd, acc, w, simd.splat_f64s(LOG1P_H[j]));
}
let rr = simd.mul_f64s(w, acc);
let inner = simd.mul_f64s(s, simd.add_f64s(hfsq, rr));
simd.sub_f64s(f, simd.sub_f64s(hfsq, inner))
}
#[inline(always)]
fn simd_z_mask<S: Simd, const FUSED: bool>(simd: S, eta: S::f64s) -> (S::f64s, S::m64s) {
let neg_abs = simd.max_f64s(
simd.neg_f64s(simd.abs_f64s(eta)),
simd.splat_f64s(EXP_ARG_FLOOR),
);
let z = simd_exp_reduced::<S, FUSED>(simd, neg_abs);
let mask = simd.greater_than_or_equal_f64s(eta, simd.splat_f64s(0.0));
(z, mask)
}
#[inline(always)]
fn simd_fused<S: Simd, const FUSED: bool>(simd: S, eta: S::f64s) -> (S::f64s, S::f64s, S::f64s) {
let one = simd.splat_f64s(1.0);
let (z, mask) = simd_z_mask::<S, FUSED>(simd, eta);
let l = simd_log1p_unit::<S, FUSED>(simd, z);
let opz = simd.add_f64s(one, z);
let p = simd.select_f64s(mask, simd.div_f64s(one, opz), simd.div_f64s(z, opz));
let lp = simd.select_f64s(mask, simd.add_f64s(eta, l), l);
let w = simd.max_f64s(
simd.mul_f64s(p, simd.sub_f64s(one, p)),
simd.splat_f64s(crate::glm::WEIGHT_CLAMP),
);
(p, w, lp)
}
#[inline]
fn scalar_exp_reduced<const FUSED: bool>(x: f64) -> f64 {
let kf = fmadd_scalar::<FUSED>(x, LOG2E, RND_MAGIC) - RND_MAGIC;
let hi = fmadd_scalar::<FUSED>(-kf, LN2HI, x);
let r = fmadd_scalar::<FUSED>(-kf, LN2LO, hi);
let mut acc = EXP_C[EXP_DEG];
let mut j = EXP_DEG;
while j > 0 {
j -= 1;
acc = fmadd_scalar::<FUSED>(acc, r, EXP_C[j]);
}
let m = (kf + BIAS_MAGIC).to_bits() & MANT_MASK;
acc * f64::from_bits(m.wrapping_mul(SHIFT52))
}
#[inline]
fn scalar_log1p_unit<const FUSED: bool>(z: f64) -> f64 {
let f = z;
let hfsq = 0.5 * (f * f);
let s = f / (2.0 + f);
let w = s * s;
let mut acc = LOG1P_H[LOG1P_DEG];
let mut j = LOG1P_DEG;
while j > 0 {
j -= 1;
acc = fmadd_scalar::<FUSED>(acc, w, LOG1P_H[j]);
}
let rr = w * acc;
f - (hfsq - s * (hfsq + rr))
}
#[inline]
fn scalar_z<const FUSED: bool>(eta: f64) -> f64 {
scalar_exp_reduced::<FUSED>((-eta.abs()).max(EXP_ARG_FLOOR))
}
#[inline]
fn scalar_fused<const FUSED: bool>(eta: f64) -> (f64, f64, f64) {
let z = scalar_z::<FUSED>(eta);
let l = scalar_log1p_unit::<FUSED>(z);
let (p, lp) = if eta >= 0.0 {
(1.0 / (1.0 + z), eta + l)
} else {
(z / (1.0 + z), l)
};
let w = (p * (1.0 - p)).max(crate::glm::WEIGHT_CLAMP);
(p, w, lp)
}
struct PwLog1pexpOp<'a, const FUSED: bool> {
eta: &'a [f64],
p: &'a mut [f64],
w: &'a mut [f64],
}
impl<const FUSED: bool> pulp::WithSimd for PwLog1pexpOp<'_, FUSED> {
type Output = f64;
#[inline(always)]
fn with_simd<S: Simd>(self, simd: S) -> f64 {
let (eh, et) = S::as_simd_f64s(self.eta);
let (ph, pt) = S::as_mut_simd_f64s(self.p);
let (wh, wt) = S::as_mut_simd_f64s(self.w);
let mut dsum = simd.splat_f64s(0.0);
for i in 0..eh.len() {
let (p, w, lp) = simd_fused::<S, FUSED>(simd, eh[i]);
ph[i] = p;
wh[i] = w;
dsum = simd.add_f64s(dsum, lp);
}
let mut acc = simd.reduce_sum_f64s(dsum);
for i in 0..et.len() {
let (p, w, lp) = scalar_fused::<FUSED>(et[i]);
pt[i] = p;
wt[i] = w;
acc += lp;
}
acc
}
}
pub(crate) fn pw_and_log1pexp_sum(eta: &[f64], p: &mut [f64], w: &mut [f64]) -> f64 {
debug_assert_eq!(eta.len(), p.len());
debug_assert_eq!(eta.len(), w.len());
pulp::Arch::new().dispatch(PwLog1pexpOp::<{ FUSED_DEFAULT }> { eta, p, w })
}
struct SigmoidInplaceOp<'a, const FUSED: bool> {
buf: &'a mut [f64],
}
impl<const FUSED: bool> pulp::WithSimd for SigmoidInplaceOp<'_, FUSED> {
type Output = ();
#[inline(always)]
fn with_simd<S: Simd>(self, simd: S) {
let one = simd.splat_f64s(1.0);
let (head, tail) = S::as_mut_simd_f64s(self.buf);
for x in head.iter_mut() {
let (z, mask) = simd_z_mask::<S, FUSED>(simd, *x);
let opz = simd.add_f64s(one, z);
*x = simd.select_f64s(mask, simd.div_f64s(one, opz), simd.div_f64s(z, opz));
}
for x in tail.iter_mut() {
let z = scalar_z::<FUSED>(*x);
*x = if *x >= 0.0 {
1.0 / (1.0 + z)
} else {
z / (1.0 + z)
};
}
}
}
#[inline]
pub fn sigmoid_fill(buf: &mut [f64]) {
pulp::Arch::new().dispatch(SigmoidInplaceOp::<{ FUSED_DEFAULT }> { buf });
}
#[inline]
pub fn exp_nonpos(x: f64) -> f64 {
scalar_exp_reduced::<{ FUSED_DEFAULT }>(x.max(EXP_ARG_FLOOR))
}
pub(crate) fn exp_clamped(x: f64) -> f64 {
scalar_exp_reduced::<{ FUSED_DEFAULT }>(x.clamp(EXP_ARG_FLOOR, EXP_ARG_CEIL))
}
struct ExpInplaceOp<'a, const FUSED: bool> {
buf: &'a mut [f64],
}
impl<const FUSED: bool> pulp::WithSimd for ExpInplaceOp<'_, FUSED> {
type Output = ();
#[inline(always)]
fn with_simd<S: Simd>(self, simd: S) {
let lo = simd.splat_f64s(EXP_ARG_FLOOR);
let hi = simd.splat_f64s(EXP_ARG_CEIL);
let (head, tail) = S::as_mut_simd_f64s(self.buf);
for x in head.iter_mut() {
*x = simd_exp_reduced::<S, FUSED>(simd, simd.min_f64s(simd.max_f64s(*x, lo), hi));
}
for x in tail.iter_mut() {
*x = exp_clamped(*x);
}
}
}
#[inline]
pub fn exp_fill(buf: &mut [f64]) {
pulp::Arch::new().dispatch(ExpInplaceOp::<{ FUSED_DEFAULT }> { buf });
}
const LN_U_FLOOR: f64 = 4.8828125e-4; const LN_U_CEIL: f64 = f64::from_bits(0x3FEF_FFFF_FFFF_FFFF); const ONE_BITS: u64 = 0x3FF0_0000_0000_0000;
#[inline]
fn scalar_ln_unit<const FUSED: bool>(u: f64) -> f64 {
let u = u.clamp(LN_U_FLOOR, LN_U_CEIL);
let mut kf = 0.0f64;
let mut th = std::f64::consts::SQRT_2 * 0.5;
for _ in 0..11 {
if u < th {
kf -= 1.0;
}
th *= 0.5;
}
let m = f64::from_bits((u.to_bits() & MANT_MASK) | ONE_BITS);
let m = if m < std::f64::consts::SQRT_2 {
m
} else {
0.5 * m
};
let l = scalar_log1p_unit::<FUSED>(m - 1.0);
fmadd_scalar::<FUSED>(kf, LN2HI, fmadd_scalar::<FUSED>(kf, LN2LO, l))
}
struct LnInplaceOp<'a, const FUSED: bool> {
buf: &'a mut [f64],
}
impl<const FUSED: bool> pulp::WithSimd for LnInplaceOp<'_, FUSED> {
type Output = ();
#[inline(always)]
fn with_simd<S: Simd>(self, simd: S) {
let one = simd.splat_f64s(1.0);
let (head, tail) = S::as_mut_simd_f64s(self.buf);
for v in head.iter_mut() {
let u = simd.min_f64s(
simd.max_f64s(*v, simd.splat_f64s(LN_U_FLOOR)),
simd.splat_f64s(LN_U_CEIL),
);
let mut kf = simd.splat_f64s(0.0);
let mut th = std::f64::consts::SQRT_2 * 0.5;
for _ in 0..11 {
let mask = simd.less_than_f64s(u, simd.splat_f64s(th));
kf = simd.select_f64s(mask, simd.sub_f64s(kf, one), kf);
th *= 0.5;
}
let m = simd.transmute_f64s_u64s(simd.or_u64s(
simd.and_u64s(simd.transmute_u64s_f64s(u), simd.splat_u64s(MANT_MASK)),
simd.splat_u64s(ONE_BITS),
));
let lt = simd.less_than_f64s(m, simd.splat_f64s(std::f64::consts::SQRT_2));
let m = simd.select_f64s(lt, m, simd.mul_f64s(simd.splat_f64s(0.5), m));
let l = simd_log1p_unit::<S, FUSED>(simd, simd.sub_f64s(m, one));
let inner = fmadd::<S, FUSED>(simd, kf, simd.splat_f64s(LN2LO), l);
*v = fmadd::<S, FUSED>(simd, kf, simd.splat_f64s(LN2HI), inner);
}
for v in tail.iter_mut() {
*v = scalar_ln_unit::<FUSED>(*v);
}
}
}
#[inline]
pub fn ln_owned(u: f64) -> f64 {
scalar_ln_unit::<{ FUSED_DEFAULT }>(u)
}
#[inline]
pub fn ln_fill(buf: &mut [f64]) {
pulp::Arch::new().dispatch(LnInplaceOp::<{ FUSED_DEFAULT }> { buf });
}
const ERF_A1: f64 = 0.254829592;
const ERF_A2: f64 = -0.284496736;
const ERF_A3: f64 = 1.421413741;
const ERF_A4: f64 = -1.453152027;
const ERF_A5: f64 = 1.061405429;
const ERF_P: f64 = 0.3275911;
struct PhiInplaceOp<'a, const FUSED: bool> {
buf: &'a mut [f64],
}
impl<const FUSED: bool> pulp::WithSimd for PhiInplaceOp<'_, FUSED> {
type Output = ();
#[inline(always)]
fn with_simd<S: Simd>(self, simd: S) {
let one = simd.splat_f64s(1.0);
let half = simd.splat_f64s(0.5);
let c = simd.splat_f64s(std::f64::consts::FRAC_1_SQRT_2);
let (head, tail) = S::as_mut_simd_f64s(self.buf);
for v in head.iter_mut() {
let x = simd.mul_f64s(simd.neg_f64s(*v), c); let neg = simd.less_than_f64s(x, simd.splat_f64s(0.0));
let ax = simd.abs_f64s(x);
let t = simd.div_f64s(
one,
simd.add_f64s(one, simd.mul_f64s(simd.splat_f64s(ERF_P), ax)),
);
let mut poly = simd.add_f64s(
simd.mul_f64s(simd.splat_f64s(ERF_A5), t),
simd.splat_f64s(ERF_A4),
);
poly = simd.add_f64s(simd.mul_f64s(poly, t), simd.splat_f64s(ERF_A3));
poly = simd.add_f64s(simd.mul_f64s(poly, t), simd.splat_f64s(ERF_A2));
poly = simd.add_f64s(simd.mul_f64s(poly, t), simd.splat_f64s(ERF_A1));
poly = simd.mul_f64s(poly, t);
let e = simd_exp_reduced::<S, FUSED>(
simd,
simd.max_f64s(
simd.mul_f64s(simd.neg_f64s(ax), ax),
simd.splat_f64s(EXP_ARG_FLOOR),
),
);
let y = simd.sub_f64s(one, simd.mul_f64s(poly, e));
let erf = simd.select_f64s(neg, simd.neg_f64s(y), y);
*v = simd.mul_f64s(half, simd.sub_f64s(one, erf));
}
for v in tail.iter_mut() {
*v = scalar_phi(*v);
}
}
}
#[inline]
fn scalar_phi(z: f64) -> f64 {
let x = -z * std::f64::consts::FRAC_1_SQRT_2;
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let ax = x.abs();
let t = 1.0 / (1.0 + ERF_P * ax);
let poly = (((((ERF_A5 * t + ERF_A4) * t) + ERF_A3) * t + ERF_A2) * t + ERF_A1) * t;
let y = 1.0 - poly * exp_nonpos(-ax * ax);
0.5 * (1.0 - sign * y)
}
#[inline]
pub fn phi_fill(buf: &mut [f64]) {
pulp::Arch::new().dispatch(PhiInplaceOp::<{ FUSED_DEFAULT }> { buf });
}
#[cfg(test)]
mod tests {
use super::*;
fn ulp(a: f64, b: f64) -> i128 {
let o = |x: f64| {
let b = x.to_bits() as i64;
(if b < 0 { i64::MIN.wrapping_sub(b) } else { b }) as i128
};
(o(a) - o(b)).abs()
}
fn libm_fused(eta: f64) -> (f64, f64) {
if eta >= 0.0 {
let z = (-eta).exp();
(1.0 / (1.0 + z), eta + z.ln_1p())
} else {
let z = eta.exp();
(z / (1.0 + z), z.ln_1p())
}
}
#[test]
fn simd_kernel_within_1ulp_of_libm() {
let n = 20_003usize; let eta: Vec<f64> = (0..n).map(|k| -40.0 + 80.0 * k as f64 / n as f64).collect();
let (mut pmax, mut lpmax) = (0i128, 0i128);
for &e in &eta {
let (p, w, lp) = scalar_fused::<{ FUSED_DEFAULT }>(e);
let (libp, liblp) = libm_fused(e);
pmax = pmax.max(ulp(p, libp));
lpmax = lpmax.max(ulp(lp, liblp));
assert!(w >= crate::glm::WEIGHT_CLAMP && w.is_finite());
}
assert!(pmax <= 2, "sigmoid p drifted {pmax} ULP from libm");
assert!(lpmax <= 2, "log1pexp drifted {lpmax} ULP from libm");
let mut p = vec![0.0; n];
let mut w = vec![0.0; n];
let lp_sum = pw_and_log1pexp_sum(&eta, &mut p, &mut w);
let mut p_simd_max = 0i128;
let mut ref_sum = 0.0;
for i in 0..n {
let (libp, liblp) = libm_fused(eta[i]);
p_simd_max = p_simd_max.max(ulp(p[i], libp));
ref_sum += liblp;
}
assert!(
p_simd_max <= 2,
"SIMD-path p drifted {p_simd_max} ULP from libm"
);
assert!(
(lp_sum - ref_sum).abs() <= 1e-9 * ref_sum.abs().max(1.0),
"Σlog1pexp drift {lp_sum} vs {ref_sum}"
);
}
#[test]
fn unfused_kernel_within_3ulp_of_libm() {
let n = 20_003usize;
let eta: Vec<f64> = (0..n).map(|k| -40.0 + 80.0 * k as f64 / n as f64).collect();
let (mut pmax, mut lpmax) = (0i128, 0i128);
for &e in &eta {
let (p, w, lp) = scalar_fused::<false>(e);
let (libp, liblp) = libm_fused(e);
pmax = pmax.max(ulp(p, libp));
lpmax = lpmax.max(ulp(lp, liblp));
assert!(w >= crate::glm::WEIGHT_CLAMP && w.is_finite());
}
assert!(pmax <= 3, "unfused sigmoid p drifted {pmax} ULP from libm");
assert!(lpmax <= 3, "unfused log1pexp drifted {lpmax} ULP from libm");
let mut p = vec![0.0; n];
let mut w = vec![0.0; n];
let lp_sum = pulp::Arch::new().dispatch(PwLog1pexpOp::<false> {
eta: &eta,
p: &mut p,
w: &mut w,
});
let mut ref_sum = 0.0;
let mut p_simd_max = 0i128;
for i in 0..n {
let (libp, liblp) = libm_fused(eta[i]);
p_simd_max = p_simd_max.max(ulp(p[i], libp));
ref_sum += liblp;
}
assert!(
p_simd_max <= 3,
"unfused SIMD p drifted {p_simd_max} ULP from libm"
);
assert!((lp_sum - ref_sum).abs() <= 1e-9 * ref_sum.abs().max(1.0));
}
#[test]
fn sigmoid_fill_within_2ulp_of_libm() {
let n = 20_003usize;
let eta: Vec<f64> = (0..n).map(|k| -40.0 + 80.0 * k as f64 / n as f64).collect();
let mut buf = eta.clone();
sigmoid_fill(&mut buf);
let mut pmax = 0i128;
for i in 0..n {
let (libp, _) = libm_fused(eta[i]);
pmax = pmax.max(ulp(buf[i], libp));
assert!(buf[i].is_finite() && (0.0..=1.0).contains(&buf[i]));
}
assert!(pmax <= 2, "sigmoid_fill drifted {pmax} ULP from libm");
}
#[test]
fn exp_fill_within_1ulp_of_libm_full_domain() {
let n = 20_003usize;
let xs: Vec<f64> = (0..n)
.map(|k| -700.0 + 1400.0 * k as f64 / n as f64)
.collect();
let mut emax = 0i128;
for &x in &xs {
emax = emax.max(ulp(exp_clamped(x), x.exp()));
}
assert!(emax <= 1, "exp_clamped drifted {emax} ULP from libm");
let mut buf = xs.clone();
exp_fill(&mut buf);
let mut smax = 0i128;
for i in 0..n {
smax = smax.max(ulp(buf[i], xs[i].exp()));
}
assert!(smax <= 1, "exp_fill drifted {smax} ULP from libm");
let mut edge = vec![-1.0e9, 1.0e9];
exp_fill(&mut edge);
assert!(edge[0] > 0.0 && edge[1].is_finite());
}
#[test]
fn ln_fill_within_2ulp_of_libm() {
let n = 20_003usize;
let lo = 9.5e-4f64;
let us: Vec<f64> = (0..n)
.map(|k| lo + (1.0 - lo) * k as f64 / n as f64)
.collect();
let mut smax = 0i128;
for &u in &us {
smax = smax.max(ulp(ln_owned(u), u.ln()));
}
assert!(smax <= 2, "ln_owned drifted {smax} ULP from libm");
let mut buf = us.clone();
ln_fill(&mut buf);
let mut vmax = 0i128;
for i in 0..n {
vmax = vmax.max(ulp(buf[i], us[i].ln()));
}
assert!(vmax <= 2, "ln_fill drifted {vmax} ULP from libm");
assert!(ln_owned(1.0e-9) < -6.96);
assert!(ln_owned(0.0) < -6.96);
assert!(ln_owned(1.0) < 0.0 && ln_owned(1.0) > -3.0e-16);
}
#[test]
fn phi_fill_bit_identical_to_scalar_phi() {
let n = 20_003usize;
let z: Vec<f64> = (0..n).map(|k| -9.0 + 18.0 * k as f64 / n as f64).collect();
let mut buf = z.clone();
phi_fill(&mut buf);
for i in 0..n {
assert_eq!(
buf[i].to_bits(),
scalar_phi(z[i]).to_bits(),
"phi_fill diverged from scalar phi at z={}",
z[i]
);
}
}
#[test]
fn weight_clamped_and_finite() {
let eta: Vec<f64> = vec![-50.0, -10.0, -1e-9, 0.0, 1e-9, 10.0, 50.0, 1e3];
let mut p = vec![0.0; eta.len()];
let mut w = vec![0.0; eta.len()];
pw_and_log1pexp_sum(&eta, &mut p, &mut w);
for i in 0..eta.len() {
assert!(p[i].is_finite() && (0.0..=1.0).contains(&p[i]));
assert!(w[i] >= crate::glm::WEIGHT_CLAMP && w[i].is_finite());
}
}
}