use cubecl::prelude::*;
use std::f32::consts::{LN_2, SQRT_2};
const fn factorial(n: u32) -> f64 {
let mut result = 1.0;
let mut i = 2u32;
while i <= n {
result *= i as f64;
i += 1;
}
result
}
const fn powi(base: f64, exponent: u32) -> f64 {
let mut result = 1.0;
let mut i = 0u32;
while i < exponent {
result *= base;
i += 1;
}
result
}
const fn taylor_half_pi(power: u32) -> f32 {
let sign = if (power / 2).is_multiple_of(2) {
1.0
} else {
-1.0
};
(sign * powi(std::f64::consts::PI / 2.0, power) / factorial(power)) as f32
}
const SIN_1: f32 = taylor_half_pi(1);
const SIN_3: f32 = taylor_half_pi(3);
const SIN_5: f32 = taylor_half_pi(5);
const SIN_7: f32 = taylor_half_pi(7);
const SIN_9: f32 = taylor_half_pi(9);
const COS_2: f32 = taylor_half_pi(2);
const COS_4: f32 = taylor_half_pi(4);
const COS_6: f32 = taylor_half_pi(6);
const COS_8: f32 = taylor_half_pi(8);
const fn atanh_term(power: u32) -> f32 {
(2.0 / power as f64) as f32
}
const ATANH_1: f32 = atanh_term(1);
const ATANH_3: f32 = atanh_term(3);
const ATANH_5: f32 = atanh_term(5);
const ATANH_7: f32 = atanh_term(7);
#[cube]
pub fn cos_sin_turns<N: Size>(turns: Vector<f32, N>) -> (Vector<f32, N>, Vector<f32, N>) {
let quarters = turns * Vector::new(4.0f32);
let quadrant = Vector::<i32, N>::cast_from(quarters + Vector::new(0.5f32));
let offset = quarters - Vector::<f32, N>::cast_from(quadrant);
let square = offset * offset;
let sine = fma(
fma(
fma(
fma(Vector::new(SIN_9), square, Vector::new(SIN_7)),
square,
Vector::new(SIN_5),
),
square,
Vector::new(SIN_3),
),
square,
Vector::new(SIN_1),
) * offset;
let cosine = fma(
fma(
fma(
fma(Vector::new(COS_8), square, Vector::new(COS_6)),
square,
Vector::new(COS_4),
),
square,
Vector::new(COS_2),
),
square,
Vector::new(1.0f32),
);
let quadrant = quadrant & Vector::new(3i32);
let swapped = (quadrant & Vector::new(1i32)).equal(&Vector::new(1i32));
let cosine_magnitude = select_many(swapped, sine, cosine);
let sine_magnitude = select_many(swapped, cosine, sine);
let cosine_negative =
((quadrant + Vector::new(1i32)) & Vector::new(2i32)).equal(&Vector::new(2i32));
let sine_negative = (quadrant & Vector::new(2i32)).equal(&Vector::new(2i32));
(
select_many(cosine_negative, -cosine_magnitude, cosine_magnitude),
select_many(sine_negative, -sine_magnitude, sine_magnitude),
)
}
#[cube]
pub fn ln<N: Size>(x: Vector<f32, N>) -> Vector<f32, N> {
let bits = Vector::<u32, N>::reinterpret(x);
let exponent = Vector::<i32, N>::cast_from(bits >> Vector::new(23u32)) - Vector::new(127i32);
let mantissa = Vector::<f32, N>::reinterpret(
(bits & Vector::new(0x007f_ffffu32)) | Vector::new(0x3f80_0000u32),
);
let halved = mantissa.greater_than(&Vector::new(SQRT_2));
let mantissa = select_many(halved, mantissa * Vector::new(0.5f32), mantissa);
let exponent = select_many(halved, exponent + Vector::new(1i32), exponent);
let ratio = (mantissa - Vector::new(1.0f32)) / (mantissa + Vector::new(1.0f32));
let square = ratio * ratio;
let series = fma(
fma(
fma(Vector::new(ATANH_7), square, Vector::new(ATANH_5)),
square,
Vector::new(ATANH_3),
),
square,
Vector::new(ATANH_1),
);
fma(
Vector::<f32, N>::cast_from(exponent),
Vector::new(LN_2),
ratio * series,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derived_constants_match_f64_formula() {
let pi_half = std::f64::consts::PI / 2.0;
assert_eq!(SIN_1, (pi_half.powi(1) / 1.0) as f32);
assert_eq!(SIN_3, (-pi_half.powi(3) / 6.0) as f32);
assert_eq!(SIN_5, (pi_half.powi(5) / 120.0) as f32);
assert_eq!(SIN_7, (-pi_half.powi(7) / 5040.0) as f32);
assert_eq!(SIN_9, (pi_half.powi(9) / 362880.0) as f32);
assert_eq!(COS_2, (-pi_half.powi(2) / 2.0) as f32);
assert_eq!(COS_4, (pi_half.powi(4) / 24.0) as f32);
assert_eq!(COS_6, (-pi_half.powi(6) / 720.0) as f32);
assert_eq!(COS_8, (pi_half.powi(8) / 40320.0) as f32);
assert_eq!(ATANH_1, (2.0 / 1.0) as f32);
assert_eq!(ATANH_3, (2.0 / 3.0) as f32);
assert_eq!(ATANH_5, (2.0 / 5.0) as f32);
assert_eq!(ATANH_7, (2.0 / 7.0) as f32);
}
}