#![cfg(target_arch = "x86_64")]
use std::arch::x86_64 as arch_x86;
use std::arch::x86_64::{
__m128, __m128d, __m128i, __m256, __m256d, __m256i, __m512, __m512d, __m512i, _mm256_add_pd,
_mm256_add_ps, _mm256_and_pd, _mm256_and_ps, _mm256_blend_pd, _mm256_blend_ps,
_mm256_broadcast_sd, _mm256_broadcast_ss, _mm256_castpd256_pd128, _mm256_castps256_ps128,
_mm256_castsi256_si128, _mm256_cmp_pd, _mm256_cmp_ps, _mm256_div_pd, _mm256_div_ps,
_mm256_extractf128_pd, _mm256_extractf128_ps, _mm256_fmadd_pd, _mm256_fmadd_ps,
_mm256_fmsub_pd, _mm256_fmsub_ps, _mm256_load_pd, _mm256_load_ps, _mm256_load_si256,
_mm256_loadu_pd, _mm256_loadu_ps, _mm256_max_pd, _mm256_max_ps, _mm256_min_pd, _mm256_min_ps,
_mm256_mul_pd, _mm256_mul_ps, _mm256_or_pd, _mm256_or_ps, _mm256_permute2f128_pd,
_mm256_permute2f128_ps, _mm256_permute_pd, _mm256_permute_ps, _mm256_set1_pd, _mm256_set1_ps,
_mm256_setzero_pd, _mm256_setzero_ps, _mm256_shuffle_pd, _mm256_shuffle_ps, _mm256_sqrt_pd,
_mm256_sqrt_ps, _mm256_store_pd, _mm256_store_ps, _mm256_store_si256, _mm256_storeu_pd,
_mm256_storeu_ps, _mm256_sub_pd, _mm256_sub_ps, _mm256_xor_pd, _mm256_xor_ps, _mm512_fmadd_pd,
_mm512_fmadd_ps, _mm512_loadu_pd, _mm512_loadu_ps, _mm512_mul_pd, _mm512_mul_ps,
_mm512_set1_pd, _mm512_set1_ps, _mm512_setzero_pd, _mm512_setzero_ps, _mm512_storeu_pd,
_mm512_storeu_ps, _mm_add_pd, _mm_add_ps, _mm_and_pd, _mm_and_ps, _mm_blend_pd, _mm_blend_ps,
_mm_broadcast_ss, _mm_cmp_pd, _mm_cmp_ps, _mm_div_pd, _mm_div_ps, _mm_fmadd_pd, _mm_fmadd_ps,
_mm_fmsub_pd, _mm_fmsub_ps, _mm_load_pd, _mm_load_ps, _mm_load_si128, _mm_loadu_pd,
_mm_loadu_ps, _mm_max_pd, _mm_max_ps, _mm_min_pd, _mm_min_ps, _mm_mul_pd, _mm_mul_ps,
_mm_or_pd, _mm_or_ps, _mm_permute_pd, _mm_permute_ps, _mm_prefetch, _mm_set1_pd, _mm_set1_ps,
_mm_setzero_pd, _mm_setzero_ps, _mm_shuffle_pd, _mm_shuffle_ps, _mm_sqrt_pd, _mm_sqrt_ps,
_mm_store_pd, _mm_store_ps, _mm_store_si128, _mm_storeu_pd, _mm_storeu_ps, _mm_sub_pd,
_mm_sub_ps, _mm_xor_pd, _mm_xor_ps, _rdrand16_step, _rdrand32_step, _rdrand64_step,
_rdseed16_step, _rdseed32_step, _rdseed64_step, _CMP_EQ_OQ, _CMP_GE_OQ, _CMP_GT_OQ, _CMP_LE_OQ,
_CMP_LT_OQ, _CMP_NEQ_OQ, _CMP_ORD_Q, _CMP_UNORD_Q, _MM_FROUND_TO_NEAREST_INT,
_MM_FROUND_TO_NEG_INF, _MM_FROUND_TO_POS_INF, _MM_FROUND_TO_ZERO, _MM_HINT_NTA, _MM_HINT_T0,
_MM_HINT_T1, _MM_HINT_T2,
};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::f32::consts as f32c;
use std::f64::consts as f64c;
use std::fmt;
use std::mem;
use std::ptr;
use std::slice;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering};
use std::sync::Mutex;
use crate::clang::*;
use crate::x86::*;
pub const SSE_F32_WIDTH: usize = 4;
pub const SSE_F64_WIDTH: usize = 2;
pub const AVX_F32_WIDTH: usize = 8;
pub const AVX_F64_WIDTH: usize = 4;
pub const AVX512_F32_WIDTH: usize = 16;
pub const AVX512_F64_WIDTH: usize = 8;
pub const L1D_CACHE_SIZE: usize = 32768;
pub const L2_CACHE_SIZE: usize = 262144;
pub const L3_CACHE_SIZE: usize = 8388608;
pub const BLAS_BLOCK_SIZE_F64: usize = 64;
pub const BLAS_BLOCK_SIZE_F32: usize = 128;
pub const BLAS_MICRO_KERNEL: usize = 8;
pub const FFT_MAX_SIZE: usize = 1 << 24; pub const FFTW_ESTIMATE: u32 = 1 << 6;
pub const FFTW_MEASURE: u32 = 0;
pub const FFTW_PATIENT: u32 = 1 << 5;
pub const FFTW_EXHAUSTIVE: u32 = 1 << 3;
const PI: f64 = std::f64::consts::PI;
const TAU: f64 = 2.0 * PI;
const EULER_GAMMA: f64 = 0.57721566490153286060651209008240243104215933593992;
const F64_EPSILON: f64 = 2.2204460492503131e-16;
const F32_EPSILON: f32 = 1.19209290e-07_f32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86SIMDLevel {
Scalar = 0,
SSE2 = 1,
SSE42 = 2,
AVX2 = 3,
AVX512 = 4,
}
impl X86SIMDLevel {
pub fn detect() -> Self {
if is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512cd")
&& is_x86_feature_detected!("avx512bw")
&& is_x86_feature_detected!("avx512dq")
&& is_x86_feature_detected!("avx512vl")
{
X86SIMDLevel::AVX512
} else if is_x86_feature_detected!("avx2") {
X86SIMDLevel::AVX2
} else if is_x86_feature_detected!("sse4.2") {
X86SIMDLevel::SSE42
} else if is_x86_feature_detected!("sse2") {
X86SIMDLevel::SSE2
} else {
X86SIMDLevel::Scalar
}
}
pub fn has_fma(&self) -> bool {
*self >= X86SIMDLevel::AVX2 && is_x86_feature_detected!("fma")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BLASOp {
NoTranspose,
Transpose,
ConjTranspose,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BLASDiag {
NonUnit,
Unit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BLASUplo {
Upper,
Lower,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BLASSide {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FFTDirection {
Forward,
Inverse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FFTAlgorithm {
Radix2,
Radix4,
SplitRadix,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DistributionKind {
Uniform,
Normal,
Exponential,
Poisson,
Binomial,
Gamma,
Beta,
ChiSquared,
StudentT,
FisherF,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationMethod {
GradientDescent,
StochasticGradientDescent,
NewtonRaphson,
BFGS,
LBFGS,
NelderMead,
SimulatedAnnealing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestKind {
TTest,
ChiSquared,
ANOVA,
KolmogorovSmirnov,
MannWhitney,
KruskalWallis,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegressionKind {
Linear,
Polynomial,
Logistic,
Ridge,
Lasso,
}
#[derive(Debug)]
pub struct X86Scientific {
pub simd_level: X86SIMDLevel,
pub has_fma: bool,
pub num_threads: usize,
pub blas: X86BLASSupport,
pub lapack: X86LAPACKSupport,
pub fft: X86FFTSupport,
pub math: X86MathIntrinsics,
pub special: X86SpecialFunctions,
pub rng: X86RandomNumberGenerators,
pub stats: X86StatisticsSupport,
pub optimize: X86OptimizationSupport,
}
impl X86Scientific {
pub fn new() -> Self {
let simd_level = X86SIMDLevel::detect();
let has_fma = simd_level.has_fma();
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
Self {
simd_level,
has_fma,
num_threads,
blas: X86BLASSupport::new(simd_level, has_fma, num_threads),
lapack: X86LAPACKSupport::new(simd_level, has_fma),
fft: X86FFTSupport::new(simd_level, has_fma),
math: X86MathIntrinsics::new(simd_level),
special: X86SpecialFunctions::new(),
rng: X86RandomNumberGenerators::new(),
stats: X86StatisticsSupport::new(),
optimize: X86OptimizationSupport::new(),
}
}
pub fn with_simd_level(simd_level: X86SIMDLevel) -> Self {
let has_fma = simd_level.has_fma();
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
Self {
simd_level,
has_fma,
num_threads,
blas: X86BLASSupport::new(simd_level, has_fma, num_threads),
lapack: X86LAPACKSupport::new(simd_level, has_fma),
fft: X86FFTSupport::new(simd_level, has_fma),
math: X86MathIntrinsics::new(simd_level),
special: X86SpecialFunctions::new(),
rng: X86RandomNumberGenerators::new(),
stats: X86StatisticsSupport::new(),
optimize: X86OptimizationSupport::new(),
}
}
pub fn capabilities(&self) -> X86ScienceCapabilities {
X86ScienceCapabilities {
simd_level: self.simd_level,
has_fma: self.has_fma,
has_avx512: self.simd_level >= X86SIMDLevel::AVX512,
has_avx2: self.simd_level >= X86SIMDLevel::AVX2,
has_sse42: self.simd_level >= X86SIMDLevel::SSE42,
vector_width_f32: match self.simd_level {
X86SIMDLevel::AVX512 => 16,
X86SIMDLevel::AVX2 => 8,
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => 4,
X86SIMDLevel::Scalar => 1,
},
vector_width_f64: match self.simd_level {
X86SIMDLevel::AVX512 => 8,
X86SIMDLevel::AVX2 => 4,
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => 2,
X86SIMDLevel::Scalar => 1,
},
num_threads: self.num_threads,
}
}
}
impl Default for X86Scientific {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ScienceCapabilities {
pub simd_level: X86SIMDLevel,
pub has_fma: bool,
pub has_avx512: bool,
pub has_avx2: bool,
pub has_sse42: bool,
pub vector_width_f32: usize,
pub vector_width_f64: usize,
pub num_threads: usize,
}
impl fmt::Display for X86ScienceCapabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"X86Scientific Capabilities: SIMD={:?}, FMA={}, AVX-512={}, AVX2={}, \
SSE4.2={}, vf32={}, vf64={}, threads={}",
self.simd_level,
self.has_fma,
self.has_avx512,
self.has_avx2,
self.has_sse42,
self.vector_width_f32,
self.vector_width_f64,
self.num_threads,
)
}
}
#[derive(Debug, Clone)]
pub struct X86BLASSupport {
simd_level: X86SIMDLevel,
has_fma: bool,
num_threads: usize,
block_size_f64: usize,
block_size_f32: usize,
}
impl X86BLASSupport {
pub fn new(simd_level: X86SIMDLevel, has_fma: bool, num_threads: usize) -> Self {
Self {
simd_level,
has_fma,
num_threads,
block_size_f64: BLAS_BLOCK_SIZE_F64,
block_size_f32: BLAS_BLOCK_SIZE_F32,
}
}
pub fn set_block_sizes(&mut self, f64_bs: usize, f32_bs: usize) {
self.block_size_f64 = f64_bs;
self.block_size_f32 = f32_bs;
}
pub fn saxpy(&self, n: usize, alpha: f32, x: &[f32], incx: isize, y: &mut [f32], incy: isize) {
if n == 0 || alpha == 0.0 {
return;
}
match self.simd_level {
X86SIMDLevel::AVX512 => self.saxpy_avx512(n, alpha, x, incx, y, incy),
X86SIMDLevel::AVX2 => self.saxpy_avx(n, alpha, x, incx, y, incy),
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => self.saxpy_sse(n, alpha, x, incx, y, incy),
X86SIMDLevel::Scalar => self.saxpy_scalar(n, alpha, x, incx, y, incy),
}
}
fn saxpy_scalar(
&self,
n: usize,
alpha: f32,
x: &[f32],
incx: isize,
y: &mut [f32],
incy: isize,
) {
if incx == 1 && incy == 1 {
for i in 0..n {
y[i] += alpha * x[i];
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] += alpha * x[ix as usize];
ix += incx;
iy += incy;
}
}
}
fn saxpy_sse(&self, n: usize, alpha: f32, x: &[f32], incx: isize, y: &mut [f32], incy: isize) {
if incx != 1 || incy != 1 {
return self.saxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm_loadu_ps(x.as_ptr().add(i));
let yv = _mm_loadu_ps(y.as_ptr().add(i));
let r = _mm_add_ps(yv, _mm_mul_ps(alpha_v, xv));
_mm_storeu_ps(y.as_mut_ptr().add(i), r);
i += 4;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
fn saxpy_avx(&self, n: usize, alpha: f32, x: &[f32], incx: isize, y: &mut [f32], incy: isize) {
if incx != 1 || incy != 1 {
return self.saxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm256_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm256_loadu_ps(x.as_ptr().add(i));
let yv = _mm256_loadu_ps(y.as_ptr().add(i));
let r = if self.has_fma {
_mm256_fmadd_ps(alpha_v, xv, yv)
} else {
_mm256_add_ps(yv, _mm256_mul_ps(alpha_v, xv))
};
_mm256_storeu_ps(y.as_mut_ptr().add(i), r);
i += 8;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
fn saxpy_avx512(
&self,
n: usize,
alpha: f32,
x: &[f32],
incx: isize,
y: &mut [f32],
incy: isize,
) {
if incx != 1 || incy != 1 {
return self.saxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm512_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 16 <= n {
let xv = _mm512_loadu_ps(x.as_ptr().add(i));
let yv = _mm512_loadu_ps(y.as_ptr().add(i));
let r = _mm512_fmadd_ps(alpha_v, xv, yv);
_mm512_storeu_ps(y.as_mut_ptr().add(i), r);
i += 16;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
pub fn daxpy(&self, n: usize, alpha: f64, x: &[f64], incx: isize, y: &mut [f64], incy: isize) {
if n == 0 || alpha == 0.0 {
return;
}
match self.simd_level {
X86SIMDLevel::AVX512 => self.daxpy_avx512(n, alpha, x, incx, y, incy),
X86SIMDLevel::AVX2 => self.daxpy_avx(n, alpha, x, incx, y, incy),
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => self.daxpy_sse(n, alpha, x, incx, y, incy),
X86SIMDLevel::Scalar => self.daxpy_scalar(n, alpha, x, incx, y, incy),
}
}
fn daxpy_scalar(
&self,
n: usize,
alpha: f64,
x: &[f64],
incx: isize,
y: &mut [f64],
incy: isize,
) {
if incx == 1 && incy == 1 {
for i in 0..n {
y[i] += alpha * x[i];
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] += alpha * x[ix as usize];
ix += incx;
iy += incy;
}
}
}
fn daxpy_sse(&self, n: usize, alpha: f64, x: &[f64], incx: isize, y: &mut [f64], incy: isize) {
if incx != 1 || incy != 1 {
return self.daxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 2 <= n {
let xv = _mm_loadu_pd(x.as_ptr().add(i));
let yv = _mm_loadu_pd(y.as_ptr().add(i));
let r = _mm_add_pd(yv, _mm_mul_pd(alpha_v, xv));
_mm_storeu_pd(y.as_mut_ptr().add(i), r);
i += 2;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
fn daxpy_avx(&self, n: usize, alpha: f64, x: &[f64], incx: isize, y: &mut [f64], incy: isize) {
if incx != 1 || incy != 1 {
return self.daxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm256_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm256_loadu_pd(x.as_ptr().add(i));
let yv = _mm256_loadu_pd(y.as_ptr().add(i));
let r = if self.has_fma {
_mm256_fmadd_pd(alpha_v, xv, yv)
} else {
_mm256_add_pd(yv, _mm256_mul_pd(alpha_v, xv))
};
_mm256_storeu_pd(y.as_mut_ptr().add(i), r);
i += 4;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
fn daxpy_avx512(
&self,
n: usize,
alpha: f64,
x: &[f64],
incx: isize,
y: &mut [f64],
incy: isize,
) {
if incx != 1 || incy != 1 {
return self.daxpy_scalar(n, alpha, x, incx, y, incy);
}
let alpha_v = unsafe { _mm512_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm512_loadu_pd(x.as_ptr().add(i));
let yv = _mm512_loadu_pd(y.as_ptr().add(i));
let r = _mm512_fmadd_pd(alpha_v, xv, yv);
_mm512_storeu_pd(y.as_mut_ptr().add(i), r);
i += 8;
}
}
for j in i..n {
y[j] += alpha * x[j];
}
}
pub fn sdot(&self, n: usize, x: &[f32], incx: isize, y: &[f32], incy: isize) -> f32 {
if n == 0 {
return 0.0;
}
match self.simd_level {
X86SIMDLevel::AVX512 => self.sdot_avx512(n, x, incx, y, incy),
X86SIMDLevel::AVX2 => self.sdot_avx(n, x, incx, y, incy),
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => self.sdot_sse(n, x, incx, y, incy),
X86SIMDLevel::Scalar => self.sdot_scalar(n, x, incx, y, incy),
}
}
fn sdot_scalar(&self, n: usize, x: &[f32], incx: isize, y: &[f32], incy: isize) -> f32 {
if incx == 1 && incy == 1 {
let mut sum = 0.0f32;
for i in 0..n {
sum += x[i] * y[i];
}
sum
} else {
let mut sum = 0.0f32;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
sum += x[ix as usize] * y[iy as usize];
ix += incx;
iy += incy;
}
sum
}
}
fn sdot_sse(&self, n: usize, x: &[f32], incx: isize, y: &[f32], incy: isize) -> f32 {
if incx != 1 || incy != 1 {
return self.sdot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm_setzero_ps() };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm_loadu_ps(x.as_ptr().add(i));
let yv = _mm_loadu_ps(y.as_ptr().add(i));
acc = _mm_add_ps(acc, _mm_mul_ps(xv, yv));
i += 4;
}
let mut arr = [0.0f32; 4];
_mm_storeu_ps(arr.as_mut_ptr(), acc);
let mut sum = arr[0] + arr[1] + arr[2] + arr[3];
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
fn sdot_avx(&self, n: usize, x: &[f32], incx: isize, y: &[f32], incy: isize) -> f32 {
if incx != 1 || incy != 1 {
return self.sdot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm256_setzero_ps() };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm256_loadu_ps(x.as_ptr().add(i));
let yv = _mm256_loadu_ps(y.as_ptr().add(i));
acc = if self.has_fma {
_mm256_fmadd_ps(xv, yv, acc)
} else {
_mm256_add_ps(acc, _mm256_mul_ps(xv, yv))
};
i += 8;
}
let mut arr = [0.0f32; 8];
_mm256_storeu_ps(arr.as_mut_ptr(), acc);
let mut sum = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
fn sdot_avx512(&self, n: usize, x: &[f32], incx: isize, y: &[f32], incy: isize) -> f32 {
if incx != 1 || incy != 1 {
return self.sdot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm512_setzero_ps() };
let mut i = 0;
unsafe {
while i + 16 <= n {
let xv = _mm512_loadu_ps(x.as_ptr().add(i));
let yv = _mm512_loadu_ps(y.as_ptr().add(i));
acc = _mm512_fmadd_ps(xv, yv, acc);
i += 16;
}
let mut arr = [0.0f32; 16];
_mm512_storeu_ps(arr.as_mut_ptr(), acc);
let mut sum = 0.0f32;
for k in 0..16 {
sum += arr[k];
}
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
pub fn ddot(&self, n: usize, x: &[f64], incx: isize, y: &[f64], incy: isize) -> f64 {
if n == 0 {
return 0.0;
}
match self.simd_level {
X86SIMDLevel::AVX512 => self.ddot_avx512(n, x, incx, y, incy),
X86SIMDLevel::AVX2 => self.ddot_avx(n, x, incx, y, incy),
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => self.ddot_sse(n, x, incx, y, incy),
X86SIMDLevel::Scalar => self.ddot_scalar(n, x, incx, y, incy),
}
}
fn ddot_scalar(&self, n: usize, x: &[f64], incx: isize, y: &[f64], incy: isize) -> f64 {
if incx == 1 && incy == 1 {
let mut sum = 0.0f64;
for i in 0..n {
sum += x[i] * y[i];
}
sum
} else {
let mut sum = 0.0f64;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
sum += x[ix as usize] * y[iy as usize];
ix += incx;
iy += incy;
}
sum
}
}
fn ddot_sse(&self, n: usize, x: &[f64], incx: isize, y: &[f64], incy: isize) -> f64 {
if incx != 1 || incy != 1 {
return self.ddot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm_setzero_pd() };
let mut i = 0;
unsafe {
while i + 2 <= n {
let xv = _mm_loadu_pd(x.as_ptr().add(i));
let yv = _mm_loadu_pd(y.as_ptr().add(i));
acc = _mm_add_pd(acc, _mm_mul_pd(xv, yv));
i += 2;
}
let mut arr = [0.0f64; 2];
_mm_storeu_pd(arr.as_mut_ptr(), acc);
let mut sum = arr[0] + arr[1];
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
fn ddot_avx(&self, n: usize, x: &[f64], incx: isize, y: &[f64], incy: isize) -> f64 {
if incx != 1 || incy != 1 {
return self.ddot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm256_setzero_pd() };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm256_loadu_pd(x.as_ptr().add(i));
let yv = _mm256_loadu_pd(y.as_ptr().add(i));
acc = if self.has_fma {
_mm256_fmadd_pd(xv, yv, acc)
} else {
_mm256_add_pd(acc, _mm256_mul_pd(xv, yv))
};
i += 4;
}
let mut arr = [0.0f64; 4];
_mm256_storeu_pd(arr.as_mut_ptr(), acc);
let mut sum = arr[0] + arr[1] + arr[2] + arr[3];
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
fn ddot_avx512(&self, n: usize, x: &[f64], incx: isize, y: &[f64], incy: isize) -> f64 {
if incx != 1 || incy != 1 {
return self.ddot_scalar(n, x, incx, y, incy);
}
let mut acc = unsafe { _mm512_setzero_pd() };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm512_loadu_pd(x.as_ptr().add(i));
let yv = _mm512_loadu_pd(y.as_ptr().add(i));
acc = _mm512_fmadd_pd(xv, yv, acc);
i += 8;
}
let mut arr = [0.0f64; 8];
_mm512_storeu_pd(arr.as_mut_ptr(), acc);
let mut sum = 0.0f64;
for k in 0..8 {
sum += arr[k];
}
for j in i..n {
sum += x[j] * y[j];
}
sum
}
}
pub fn snrm2(&self, n: usize, x: &[f32], incx: isize) -> f32 {
if n == 0 {
return 0.0;
}
let mut scale = 0.0f32;
let mut ssq = 1.0f32;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for _ in 0..n {
let absxi = x[ix as usize].abs();
if absxi != 0.0 {
if scale < absxi {
let ratio = scale / absxi;
ssq = 1.0 + ssq * ratio * ratio;
scale = absxi;
} else {
let ratio = absxi / scale;
ssq += ratio * ratio;
}
}
ix += incx;
}
scale * ssq.sqrt()
}
pub fn dnrm2(&self, n: usize, x: &[f64], incx: isize) -> f64 {
if n == 0 {
return 0.0;
}
let mut scale = 0.0f64;
let mut ssq = 1.0f64;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for _ in 0..n {
let absxi = x[ix as usize].abs();
if absxi != 0.0 {
if scale < absxi {
let ratio = scale / absxi;
ssq = 1.0 + ssq * ratio * ratio;
scale = absxi;
} else {
let ratio = absxi / scale;
ssq += ratio * ratio;
}
}
ix += incx;
}
scale * ssq.sqrt()
}
pub fn sscal(&self, n: usize, alpha: f32, x: &mut [f32], incx: isize) {
if n == 0 || alpha == 1.0 {
return;
}
if incx == 1 {
match self.simd_level {
X86SIMDLevel::AVX512 => {
let av = unsafe { _mm512_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 16 <= n {
let xv = _mm512_loadu_ps(x.as_ptr().add(i));
_mm512_storeu_ps(x.as_mut_ptr().add(i), _mm512_mul_ps(av, xv));
i += 16;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::AVX2 => {
let av = unsafe { _mm256_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm256_loadu_ps(x.as_ptr().add(i));
_mm256_storeu_ps(x.as_mut_ptr().add(i), _mm256_mul_ps(av, xv));
i += 8;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => {
let av = unsafe { _mm_set1_ps(alpha) };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm_loadu_ps(x.as_ptr().add(i));
_mm_storeu_ps(x.as_mut_ptr().add(i), _mm_mul_ps(av, xv));
i += 4;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::Scalar => {
for xi in x.iter_mut().take(n) {
*xi *= alpha;
}
}
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for _ in 0..n {
x[ix as usize] *= alpha;
ix += incx;
}
}
}
pub fn dscal(&self, n: usize, alpha: f64, x: &mut [f64], incx: isize) {
if n == 0 || alpha == 1.0 {
return;
}
if incx == 1 {
match self.simd_level {
X86SIMDLevel::AVX512 => {
let av = unsafe { _mm512_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 8 <= n {
let xv = _mm512_loadu_pd(x.as_ptr().add(i));
_mm512_storeu_pd(x.as_mut_ptr().add(i), _mm512_mul_pd(av, xv));
i += 8;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::AVX2 => {
let av = unsafe { _mm256_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 4 <= n {
let xv = _mm256_loadu_pd(x.as_ptr().add(i));
_mm256_storeu_pd(x.as_mut_ptr().add(i), _mm256_mul_pd(av, xv));
i += 4;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::SSE42 | X86SIMDLevel::SSE2 => {
let av = unsafe { _mm_set1_pd(alpha) };
let mut i = 0;
unsafe {
while i + 2 <= n {
let xv = _mm_loadu_pd(x.as_ptr().add(i));
_mm_storeu_pd(x.as_mut_ptr().add(i), _mm_mul_pd(av, xv));
i += 2;
}
}
for j in i..n {
x[j] *= alpha;
}
}
X86SIMDLevel::Scalar => {
for xi in x.iter_mut().take(n) {
*xi *= alpha;
}
}
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for _ in 0..n {
x[ix as usize] *= alpha;
ix += incx;
}
}
}
pub fn isamax(&self, n: usize, x: &[f32], incx: isize) -> usize {
if n == 0 {
return 1;
}
let mut imax = 1usize;
let mut max_val = 0.0f32;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..n {
let abs_val = x[ix as usize].abs();
if abs_val > max_val {
max_val = abs_val;
imax = i + 1;
}
ix += incx;
}
imax
}
pub fn idamax(&self, n: usize, x: &[f64], incx: isize) -> usize {
if n == 0 {
return 1;
}
let mut imax = 1usize;
let mut max_val = 0.0f64;
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..n {
let abs_val = x[ix as usize].abs();
if abs_val > max_val {
max_val = abs_val;
imax = i + 1;
}
ix += incx;
}
imax
}
pub fn scopy(&self, n: usize, x: &[f32], incx: isize, y: &mut [f32], incy: isize) {
if n == 0 {
return;
}
if incx == 1 && incy == 1 {
y[..n].copy_from_slice(&x[..n]);
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] = x[ix as usize];
ix += incx;
iy += incy;
}
}
}
pub fn dcopy(&self, n: usize, x: &[f64], incx: isize, y: &mut [f64], incy: isize) {
if n == 0 {
return;
}
if incx == 1 && incy == 1 {
y[..n].copy_from_slice(&x[..n]);
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] = x[ix as usize];
ix += incx;
iy += incy;
}
}
}
pub fn sswap(&self, n: usize, x: &mut [f32], incx: isize, y: &mut [f32], incy: isize) {
if n == 0 {
return;
}
if incx == 1 && incy == 1 {
for i in 0..n {
mem::swap(&mut x[i], &mut y[i]);
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
mem::swap(&mut x[ix as usize], &mut y[iy as usize]);
ix += incx;
iy += incy;
}
}
}
pub fn dswap(&self, n: usize, x: &mut [f64], incx: isize, y: &mut [f64], incy: isize) {
if n == 0 {
return;
}
if incx == 1 && incy == 1 {
for i in 0..n {
mem::swap(&mut x[i], &mut y[i]);
}
} else {
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
mem::swap(&mut x[ix as usize], &mut y[iy as usize]);
ix += incx;
iy += incy;
}
}
}
pub fn sgemv(
&self,
trans: BLASOp,
m: usize,
n: usize,
alpha: f32,
a: &[f32],
lda: usize,
x: &[f32],
incx: isize,
beta: f32,
y: &mut [f32],
incy: isize,
) {
if m == 0 || n == 0 || (alpha == 0.0 && beta == 1.0) {
return;
}
let notrans = trans == BLASOp::NoTranspose;
let lenx = if notrans { n } else { m };
let leny = if notrans { m } else { n };
if beta == 0.0 {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for _ in 0..leny {
y[iy as usize] = 0.0;
iy += incy;
}
} else if beta != 1.0 {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for _ in 0..leny {
y[iy as usize] *= beta;
iy += incy;
}
}
if alpha == 0.0 {
return;
}
if notrans {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for i in 0..m {
let mut temp = 0.0f32;
let mut jx = if incx < 0 {
(lenx as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
temp += a[i + j * lda] * x[jx as usize];
jx += incx;
}
y[iy as usize] += alpha * temp;
iy += incy;
}
} else {
let mut jy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for j in 0..n {
let mut temp = 0.0f32;
let mut ix = if incx < 0 {
(lenx as isize - 1) * (-incx)
} else {
0
};
for i in 0..m {
temp += a[i + j * lda] * x[ix as usize];
ix += incx;
}
y[jy as usize] += alpha * temp;
jy += incy;
}
}
}
pub fn dgemv(
&self,
trans: BLASOp,
m: usize,
n: usize,
alpha: f64,
a: &[f64],
lda: usize,
x: &[f64],
incx: isize,
beta: f64,
y: &mut [f64],
incy: isize,
) {
if m == 0 || n == 0 || (alpha == 0.0 && beta == 1.0) {
return;
}
let notrans = trans == BLASOp::NoTranspose;
let lenx = if notrans { n } else { m };
let leny = if notrans { m } else { n };
if beta == 0.0 {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for _ in 0..leny {
y[iy as usize] = 0.0;
iy += incy;
}
} else if beta != 1.0 {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for _ in 0..leny {
y[iy as usize] *= beta;
iy += incy;
}
}
if alpha == 0.0 {
return;
}
if notrans {
let mut iy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for i in 0..m {
let mut temp = 0.0f64;
let mut jx = if incx < 0 {
(lenx as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
temp += a[i + j * lda] * x[jx as usize];
jx += incx;
}
y[iy as usize] += alpha * temp;
iy += incy;
}
} else {
let mut jy = if incy < 0 {
(leny as isize - 1) * (-incy)
} else {
0
};
for j in 0..n {
let mut temp = 0.0f64;
let mut ix = if incx < 0 {
(lenx as isize - 1) * (-incx)
} else {
0
};
for i in 0..m {
temp += a[i + j * lda] * x[ix as usize];
ix += incx;
}
y[jy as usize] += alpha * temp;
jy += incy;
}
}
}
pub fn strmv(
&self,
uplo: BLASUplo,
trans: BLASOp,
diag: BLASDiag,
n: usize,
a: &[f32],
lda: usize,
x: &mut [f32],
incx: isize,
) {
if n == 0 {
return;
}
let notrans = trans == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if notrans {
if uplo == BLASUplo::Upper {
let mut jx = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
if x[jx as usize] != 0.0 {
let temp = x[jx as usize];
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let j_end = if unit { j } else { j + 1 };
for i in 0..j_end {
x[ix as usize] += temp * a[i + j * lda];
ix += incx;
}
if unit {
x[jx as usize] = temp;
} else {
x[jx as usize] = temp * a[j + j * lda];
}
}
jx += incx;
}
} else {
let n_minus = if incx < 0 { 0 } else { n as isize - 1 };
let neg = if incx < 0 { -incx } else { incx };
let mut jx = n_minus * neg;
for j_rev in 0..n {
let j = if incx < 0 { j_rev } else { n - 1 - j_rev };
if x[jx as usize] != 0.0 {
let temp = x[jx as usize];
let n_minus_x = if incx < 0 { 0 } else { n as isize - 1 };
let mut ix = n_minus_x * neg;
let i_start = if unit { j + 1 } else { j };
for _ in i_start..n {
x[ix as usize] += temp * a[i_start + j * lda]; ix -= incx;
}
}
jx -= incx;
}
self.strmv_scalar(uplo, trans, diag, n, a, lda, x, incx);
}
} else {
self.strmv_scalar(uplo, trans, diag, n, a, lda, x, incx);
}
}
fn strmv_scalar(
&self,
uplo: BLASUplo,
trans: BLASOp,
diag: BLASDiag,
n: usize,
a: &[f32],
lda: usize,
x: &mut [f32],
incx: isize,
) {
let notrans = trans == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if notrans {
if uplo == BLASUplo::Upper {
let mut jx = if incx <= 0 {
(n as isize - 1) * (-incx)
} else {
0
};
let inc = incx.abs();
for j in 0..n {
if x[jx as usize] != 0.0 {
let temp = x[jx as usize];
let mut ix = if incx <= 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..j {
x[ix as usize] += temp * a[i + j * lda];
ix += incx;
}
if unit {
x[jx as usize] = temp;
} else {
x[jx as usize] = temp * a[j + j * lda];
}
}
jx += incx;
}
} else {
let mut jx = if incx <= 0 {
0
} else {
(n as isize - 1) * incx
};
for j_rev in 0..n {
let j = n - 1 - j_rev;
let jx_idx = if incx <= 0 {
(j as isize) * (-incx)
} else {
(j as isize) * incx
};
if x[jx_idx as usize] != 0.0 {
let temp = x[jx_idx as usize];
let mut ix = if incx <= 0 {
((n - 1) as isize) * (-incx)
} else {
((n - 1) as isize) * incx
};
for i in (j + 1)..n {
x[ix as usize] += temp * a[i + j * lda];
ix -= incx;
}
if unit {
x[jx_idx as usize] = temp;
} else {
x[jx_idx as usize] = temp * a[j + j * lda];
}
}
}
}
}
}
pub fn dtrmv(
&self,
uplo: BLASUplo,
trans: BLASOp,
diag: BLASDiag,
n: usize,
a: &[f64],
lda: usize,
x: &mut [f64],
incx: isize,
) {
if n == 0 {
return;
}
let notrans = trans == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if notrans {
if uplo == BLASUplo::Upper {
let mut jx = if incx <= 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
if x[jx as usize] != 0.0 {
let temp = x[jx as usize];
let mut ix = if incx <= 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..j {
x[ix as usize] += temp * a[i + j * lda];
ix += incx;
}
if unit {
x[jx as usize] = temp;
} else {
x[jx as usize] = temp * a[j + j * lda];
}
}
jx += incx;
}
} else {
for j_rev in 0..n {
let j = n - 1 - j_rev;
let jx_idx = if incx <= 0 {
(j as isize) * (-incx)
} else {
(j as isize) * incx
};
if x[jx_idx as usize] != 0.0 {
let temp = x[jx_idx as usize];
let mut ix = if incx <= 0 {
((n - 1) as isize) * (-incx)
} else {
((n - 1) as isize) * incx
};
for i in (j + 1)..n {
x[ix as usize] += temp * a[i + j * lda];
ix -= incx;
}
if unit {
x[jx_idx as usize] = temp;
} else {
x[jx_idx as usize] = temp * a[j + j * lda];
}
}
}
}
}
}
pub fn ssymv(
&self,
uplo: BLASUplo,
n: usize,
alpha: f32,
a: &[f32],
lda: usize,
x: &[f32],
incx: isize,
beta: f32,
y: &mut [f32],
incy: isize,
) {
if n == 0 || (alpha == 0.0 && beta == 1.0) {
return;
}
if beta == 0.0 {
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] = 0.0;
iy += incy;
}
} else if beta != 1.0 {
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] *= beta;
iy += incy;
}
}
if alpha == 0.0 {
return;
}
if uplo == BLASUplo::Upper {
let mut jy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut jx = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
let mut temp1 = alpha * x[jx as usize];
let mut temp2 = 0.0f32;
y[jy as usize] += temp1 * a[j + j * lda];
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..j {
y[iy as usize] += temp1 * a[i + j * lda];
temp2 += a[i + j * lda] * x[ix as usize];
iy += incy;
ix += incx;
}
y[jy as usize] += alpha * temp2;
jy += incy;
jx += incx;
}
} else {
let mut jy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut jx = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
let mut temp1 = alpha * x[jx as usize];
let mut temp2 = 0.0f32;
y[jy as usize] += temp1 * a[j + j * lda];
let mut iy = if incy < 0 {
0
} else {
((n - 1) as isize) * incy
};
let mut ix = if incx < 0 {
0
} else {
((n - 1) as isize) * incx
};
for i in (j + 1)..n {
y[iy as usize] += temp1 * a[i + j * lda];
temp2 += a[i + j * lda] * x[ix as usize];
iy -= incy;
ix -= incx;
}
y[jy as usize] += alpha * temp2;
jy += incy;
jx += incx;
}
}
}
pub fn dsymv(
&self,
uplo: BLASUplo,
n: usize,
alpha: f64,
a: &[f64],
lda: usize,
x: &[f64],
incx: isize,
beta: f64,
y: &mut [f64],
incy: isize,
) {
if n == 0 || (alpha == 0.0 && beta == 1.0) {
return;
}
if beta == 0.0 {
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] = 0.0;
iy += incy;
}
} else if beta != 1.0 {
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
for _ in 0..n {
y[iy as usize] *= beta;
iy += incy;
}
}
if alpha == 0.0 {
return;
}
if uplo == BLASUplo::Upper {
let mut jy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut jx = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
let mut temp1 = alpha * x[jx as usize];
let mut temp2 = 0.0f64;
y[jy as usize] += temp1 * a[j + j * lda];
let mut iy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut ix = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for i in 0..j {
y[iy as usize] += temp1 * a[i + j * lda];
temp2 += a[i + j * lda] * x[ix as usize];
iy += incy;
ix += incx;
}
y[jy as usize] += alpha * temp2;
jy += incy;
jx += incx;
}
} else {
let mut jy = if incy < 0 {
(n as isize - 1) * (-incy)
} else {
0
};
let mut jx = if incx < 0 {
(n as isize - 1) * (-incx)
} else {
0
};
for j in 0..n {
let mut temp1 = alpha * x[jx as usize];
let mut temp2 = 0.0f64;
y[jy as usize] += temp1 * a[j + j * lda];
for i in (j + 1)..n {
let ai = a[i + j * lda];
if incy == 1 {
y[i] += temp1 * ai;
}
if incx == 1 {
temp2 += ai * x[i];
}
}
y[jy as usize] += alpha * temp2;
jy += incy;
jx += incx;
}
}
}
pub fn sgemm(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &[f32],
ldb: usize,
beta: f32,
c: &mut [f32],
ldc: usize,
) {
if m == 0 || n == 0 || ((alpha == 0.0 || k == 0) && beta == 1.0) {
return;
}
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
if beta == 0.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] = 0.0;
}
}
} else if beta != 1.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 || k == 0 {
return;
}
if m >= self.block_size_f32 || n >= self.block_size_f32 || k >= self.block_size_f32 {
self.sgemm_blocked(transa, transb, m, n, k, alpha, a, lda, b, ldb, beta, c, ldc);
} else {
self.sgemm_naive(notransa, notransb, m, n, k, alpha, a, lda, b, ldb, c, ldc);
}
}
fn sgemm_naive(
&self,
notransa: bool,
notransb: bool,
m: usize,
n: usize,
k: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &[f32],
ldb: usize,
c: &mut [f32],
ldc: usize,
) {
for j in 0..n {
for l in 0..k {
let b_val = if notransb {
alpha * b[l + j * ldb]
} else {
alpha * b[j + l * ldb]
};
if b_val != 0.0 {
for i in 0..m {
let a_val = if notransa {
a[i + l * lda]
} else {
a[l + i * lda]
};
c[i + j * ldc] += b_val * a_val;
}
}
}
}
}
fn sgemm_blocked(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &[f32],
ldb: usize,
beta: f32,
c: &mut [f32],
ldc: usize,
) {
let bs = self.block_size_f32;
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
for jj in (0..n).step_by(bs) {
let jb = std::cmp::min(bs, n - jj);
for kk in (0..k).step_by(bs) {
let kb = std::cmp::min(bs, k - kk);
for i in 0..m {
for j in jj..(jj + jb) {
let mut sum = 0.0f32;
for l in kk..(kk + kb) {
let a_val = if notransa {
a[i + l * lda]
} else {
a[l + i * lda]
};
let b_val = if notransb {
b[l + j * ldb]
} else {
b[j + l * ldb]
};
sum += a_val * b_val;
}
c[i + j * ldc] += alpha * sum;
}
}
}
}
}
pub fn dgemm(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &[f64],
ldb: usize,
beta: f64,
c: &mut [f64],
ldc: usize,
) {
if m == 0 || n == 0 || ((alpha == 0.0 || k == 0) && beta == 1.0) {
return;
}
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
if beta == 0.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] = 0.0;
}
}
} else if beta != 1.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 || k == 0 {
return;
}
if m >= self.block_size_f64 || n >= self.block_size_f64 || k >= self.block_size_f64 {
self.dgemm_blocked(transa, transb, m, n, k, alpha, a, lda, b, ldb, c, ldc);
} else {
self.dgemm_micro_kernel(notransa, notransb, m, n, k, alpha, a, lda, b, ldb, c, ldc);
}
}
fn dgemm_micro_kernel(
&self,
notransa: bool,
notransb: bool,
m: usize,
n: usize,
k: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &[f64],
ldb: usize,
c: &mut [f64],
ldc: usize,
) {
let mu = BLAS_MICRO_KERNEL;
let nu = BLAS_MICRO_KERNEL;
let ku = BLAS_MICRO_KERNEL;
for jj in (0..n).step_by(nu) {
let jb = std::cmp::min(nu, n - jj);
for ii in (0..m).step_by(mu) {
let ib = std::cmp::min(mu, m - ii);
for kk in (0..k).step_by(ku) {
let kb = std::cmp::min(ku, k - kk);
for j in jj..(jj + jb) {
for i in ii..(ii + ib) {
let mut sum = 0.0f64;
for l in kk..(kk + kb) {
let a_val = if notransa {
a[i + l * lda]
} else {
a[l + i * lda]
};
let b_val = if notransb {
b[l + j * ldb]
} else {
b[j + l * ldb]
};
sum += a_val * b_val;
}
c[i + j * ldc] += alpha * sum;
}
}
}
}
}
}
fn dgemm_blocked(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &[f64],
ldb: usize,
c: &mut [f64],
ldc: usize,
) {
let bs = self.block_size_f64;
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
for jj in (0..n).step_by(bs) {
let jb = std::cmp::min(bs, n - jj);
for kk in (0..k).step_by(bs) {
let kb = std::cmp::min(bs, k - kk);
for i in 0..m {
for j in jj..(jj + jb) {
let mut sum = 0.0f64;
for l in kk..(kk + kb) {
let a_val = if notransa {
a[i + l * lda]
} else {
a[l + i * lda]
};
let b_val = if notransb {
b[l + j * ldb]
} else {
b[j + l * ldb]
};
sum += a_val * b_val;
}
c[i + j * ldc] += alpha * sum;
}
}
}
}
}
pub fn ssymm(
&self,
side: BLASSide,
uplo: BLASUplo,
m: usize,
n: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &[f32],
ldb: usize,
beta: f32,
c: &mut [f32],
ldc: usize,
) {
if m == 0 || n == 0 {
return;
}
for j in 0..n {
for i in 0..m {
if beta == 0.0 {
c[i + j * ldc] = 0.0;
} else if beta != 1.0 {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 {
return;
}
if side == BLASSide::Left {
for j in 0..n {
for k in 0..m {
let bkj = alpha * b[k + j * ldb];
if bkj != 0.0 {
if uplo == BLASUplo::Upper {
for i in 0..=k {
c[i + j * ldc] += bkj * a[i + k * lda];
}
} else {
for i in k..m {
c[i + j * ldc] += bkj * a[i + k * lda];
}
}
}
}
}
} else {
for j in 0..n {
for k in 0..n {
let akj = a[k + j * lda];
if akj != 0.0 {
let akj_alpha = alpha * akj;
if uplo == BLASUplo::Upper && k <= j {
for i in 0..m {
c[i + j * ldc] += akj_alpha * b[i + k * ldb];
}
} else if uplo == BLASUplo::Lower && k >= j {
for i in 0..m {
c[i + j * ldc] += akj_alpha * b[i + k * ldb];
}
} else if k == j {
for i in 0..m {
c[i + j * ldc] += akj_alpha * b[i + k * ldb];
}
}
}
}
}
}
}
pub fn dsymm(
&self,
side: BLASSide,
uplo: BLASUplo,
m: usize,
n: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &[f64],
ldb: usize,
beta: f64,
c: &mut [f64],
ldc: usize,
) {
if m == 0 || n == 0 {
return;
}
for j in 0..n {
for i in 0..m {
if beta == 0.0 {
c[i + j * ldc] = 0.0;
} else if beta != 1.0 {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 {
return;
}
if side == BLASSide::Left {
for j in 0..n {
for k in 0..m {
let bkj = alpha * b[k + j * ldb];
if bkj != 0.0 {
if uplo == BLASUplo::Upper {
for i in 0..=k {
c[i + j * ldc] += bkj * a[i + k * lda];
}
} else {
for i in k..m {
c[i + j * ldc] += bkj * a[i + k * lda];
}
}
}
}
}
} else {
for j in 0..n {
for k in 0..n {
let akj = a[k + j * lda];
if akj != 0.0 {
let akj_alpha = alpha * akj;
for i in 0..m {
c[i + j * ldc] += akj_alpha * b[i + k * ldb];
}
}
}
}
}
}
pub fn strmm(
&self,
side: BLASSide,
uplo: BLASUplo,
transa: BLASOp,
diag: BLASDiag,
m: usize,
n: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &mut [f32],
ldb: usize,
) {
if m == 0 || n == 0 {
return;
}
let notrans = transa == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if side == BLASSide::Left {
if uplo == BLASUplo::Upper {
for j in 0..n {
for k in 0..m {
if b[k + j * ldb] != 0.0 {
let temp = alpha * b[k + j * ldb];
for i in 0..k {
b[i + j * ldb] += temp * a[i + k * lda];
}
if unit {
b[k + j * ldb] = temp;
} else {
b[k + j * ldb] = temp * a[k + k * lda];
}
}
}
}
} else {
for j in 0..n {
for k_rev in 0..m {
let k = m - 1 - k_rev;
if b[k + j * ldb] != 0.0 {
let temp = alpha * b[k + j * ldb];
b[k + j * ldb] = temp;
if !unit {
b[k + j * ldb] = temp * a[k + k * lda];
}
for i in (k + 1)..m {
b[i + j * ldb] += temp * a[i + k * lda];
}
}
}
}
}
}
}
pub fn dtrmm(
&self,
side: BLASSide,
uplo: BLASUplo,
transa: BLASOp,
diag: BLASDiag,
m: usize,
n: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &mut [f64],
ldb: usize,
) {
if m == 0 || n == 0 {
return;
}
let unit = diag == BLASDiag::Unit;
if side == BLASSide::Left {
if uplo == BLASUplo::Upper {
for j in 0..n {
for k in 0..m {
if b[k + j * ldb] != 0.0 {
let temp = alpha * b[k + j * ldb];
for i in 0..k {
b[i + j * ldb] += temp * a[i + k * lda];
}
if unit {
b[k + j * ldb] = temp;
} else {
b[k + j * ldb] = temp * a[k + k * lda];
}
}
}
}
} else {
for j in 0..n {
for k_rev in 0..m {
let k = m - 1 - k_rev;
if b[k + j * ldb] != 0.0 {
let temp = alpha * b[k + j * ldb];
if !unit {
b[k + j * ldb] = temp * a[k + k * lda];
}
for i in (k + 1)..m {
b[i + j * ldb] += temp * a[i + k * lda];
}
}
}
}
}
}
}
pub fn strsm(
&self,
side: BLASSide,
uplo: BLASUplo,
transa: BLASOp,
diag: BLASDiag,
m: usize,
n: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &mut [f32],
ldb: usize,
) {
if m == 0 || n == 0 {
return;
}
let notrans = transa == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if alpha != 1.0 {
for j in 0..n {
for i in 0..m {
b[i + j * ldb] *= alpha;
}
}
}
if side == BLASSide::Left {
if notrans {
if uplo == BLASUplo::Upper {
for j in 0..n {
for k_rev in 0..m {
let k = m - 1 - k_rev;
if b[k + j * ldb] != 0.0 {
if !unit {
b[k + j * ldb] /= a[k + k * lda];
}
let temp = b[k + j * ldb];
for i in 0..k {
b[i + j * ldb] -= temp * a[i + k * lda];
}
}
}
}
} else {
for j in 0..n {
for k in 0..m {
if b[k + j * ldb] != 0.0 {
if !unit {
b[k + j * ldb] /= a[k + k * lda];
}
let temp = b[k + j * ldb];
for i in (k + 1)..m {
b[i + j * ldb] -= temp * a[i + k * lda];
}
}
}
}
}
}
}
}
pub fn dtrsm(
&self,
side: BLASSide,
uplo: BLASUplo,
transa: BLASOp,
diag: BLASDiag,
m: usize,
n: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &mut [f64],
ldb: usize,
) {
if m == 0 || n == 0 {
return;
}
let notrans = transa == BLASOp::NoTranspose;
let unit = diag == BLASDiag::Unit;
if alpha != 1.0 {
for j in 0..n {
for i in 0..m {
b[i + j * ldb] *= alpha;
}
}
}
if side == BLASSide::Left {
if notrans {
if uplo == BLASUplo::Upper {
for j in 0..n {
for k_rev in 0..m {
let k = m - 1 - k_rev;
if b[k + j * ldb] != 0.0 {
if !unit {
b[k + j * ldb] /= a[k + k * lda];
}
let temp = b[k + j * ldb];
for i in 0..k {
b[i + j * ldb] -= temp * a[i + k * lda];
}
}
}
}
} else {
for j in 0..n {
for k in 0..m {
if b[k + j * ldb] != 0.0 {
if !unit {
b[k + j * ldb] /= a[k + k * lda];
}
let temp = b[k + j * ldb];
for i in (k + 1)..m {
b[i + j * ldb] -= temp * a[i + k * lda];
}
}
}
}
}
}
}
}
fn pack_a_panel(
&self,
m: usize,
k: usize,
a: &[f64],
lda: usize,
notransa: bool,
block_m: usize,
block_k: usize,
) -> Vec<f64> {
let mut packed = vec![0.0f64; block_m * block_k];
for p in 0..k {
let a_col_base = p * lda;
for i in 0..m {
let a_val = if notransa {
a[i + a_col_base]
} else {
a[p + i * lda]
};
packed[i + p * block_m] = a_val;
}
}
packed
}
fn pack_b_panel(
&self,
n: usize,
k: usize,
b: &[f64],
ldb: usize,
notransb: bool,
block_n: usize,
block_k: usize,
) -> Vec<f64> {
let mut packed = vec![0.0f64; block_k * block_n];
for j in 0..n {
for p in 0..k {
let b_val = if notransb {
b[p + j * ldb]
} else {
b[j + p * ldb]
};
packed[p + j * block_k] = b_val;
}
}
packed
}
pub fn dgemm_packed(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f64,
a: &[f64],
lda: usize,
b: &[f64],
ldb: usize,
beta: f64,
c: &mut [f64],
ldc: usize,
) {
if m == 0 || n == 0 {
return;
}
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
if beta == 0.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] = 0.0;
}
}
} else if beta != 1.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 || k == 0 {
return;
}
let bs = self.block_size_f64;
for jj in (0..n).step_by(bs) {
let jb = std::cmp::min(bs, n - jj);
for kk in (0..k).step_by(bs) {
let kb = std::cmp::min(bs, k - kk);
let packed_b =
self.pack_b_panel(jb, kb, &b[kk + jj * ldb..], ldb, notransb, jb, kb);
for ii in (0..m).step_by(bs) {
let ib = std::cmp::min(bs, m - ii);
let packed_a =
self.pack_a_panel(ib, kb, &a[ii + kk * lda..], lda, notransa, ib, kb);
for j in 0..jb {
for i in 0..ib {
let mut sum = 0.0f64;
for p in 0..kb {
sum += packed_a[i + p * ib] * packed_b[p + j * kb];
}
c[(ii + i) + (jj + j) * ldc] += alpha * sum;
}
}
}
}
}
}
pub fn sgemm_packed(
&self,
transa: BLASOp,
transb: BLASOp,
m: usize,
n: usize,
k: usize,
alpha: f32,
a: &[f32],
lda: usize,
b: &[f32],
ldb: usize,
beta: f32,
c: &mut [f32],
ldc: usize,
) {
if m == 0 || n == 0 {
return;
}
let notransa = transa == BLASOp::NoTranspose;
let notransb = transb == BLASOp::NoTranspose;
if beta == 0.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] = 0.0;
}
}
} else if beta != 1.0 {
for j in 0..n {
for i in 0..m {
c[i + j * ldc] *= beta;
}
}
}
if alpha == 0.0 || k == 0 {
return;
}
let bs = self.block_size_f32;
for jj in (0..n).step_by(bs) {
let jb = std::cmp::min(bs, n - jj);
for kk in (0..k).step_by(bs) {
let kb = std::cmp::min(bs, k - kk);
for ii in (0..m).step_by(bs) {
let ib = std::cmp::min(bs, m - ii);
for j in jj..(jj + jb) {
for i in ii..(ii + ib) {
let mut sum = 0.0f32;
for p in kk..(kk + kb) {
let a_val = if notransa {
a[i + p * lda]
} else {
a[p + i * lda]
};
let b_val = if notransb {
b[p + j * ldb]
} else {
b[j + p * ldb]
};
sum += a_val * b_val;
}
c[i + j * ldc] += alpha * sum;
}
}
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct X86LAPACKSupport {
simd_level: X86SIMDLevel,
has_fma: bool,
}
impl X86LAPACKSupport {
pub fn new(simd_level: X86SIMDLevel, has_fma: bool) -> Self {
Self {
simd_level,
has_fma,
}
}
pub fn sgetrf(&self, m: usize, n: usize, a: &mut [f32], lda: usize) -> Vec<usize> {
let mn_min = std::cmp::min(m, n);
let mut ipiv = vec![0usize; mn_min];
for k in 0..mn_min {
let mut max_val = 0.0f32;
let mut pivot = k;
for i in k..m {
let abs_val = a[i + k * lda].abs();
if abs_val > max_val {
max_val = abs_val;
pivot = i;
}
}
ipiv[k] = pivot;
if max_val == 0.0 {
ipiv[k] = k;
continue;
}
if pivot != k {
for j in 0..n {
a.swap(k + j * lda, pivot + j * lda);
}
}
let akk = a[k + k * lda];
for i in (k + 1)..m {
a[i + k * lda] /= akk;
}
for j in (k + 1)..n {
let akj = a[k + j * lda];
for i in (k + 1)..m {
a[i + j * lda] -= a[i + k * lda] * akj;
}
}
}
ipiv
}
pub fn dgetrf(&self, m: usize, n: usize, a: &mut [f64], lda: usize) -> Vec<usize> {
let mn_min = std::cmp::min(m, n);
let mut ipiv = vec![0usize; mn_min];
for k in 0..mn_min {
let mut max_val = 0.0f64;
let mut pivot = k;
for i in k..m {
let abs_val = a[i + k * lda].abs();
if abs_val > max_val {
max_val = abs_val;
pivot = i;
}
}
ipiv[k] = pivot;
if max_val == 0.0 {
ipiv[k] = k;
continue;
}
if pivot != k {
for j in 0..n {
a.swap(k + j * lda, pivot + j * lda);
}
}
let akk = a[k + k * lda];
for i in (k + 1)..m {
a[i + k * lda] /= akk;
}
for j in (k + 1)..n {
let akj = a[k + j * lda];
for i in (k + 1)..m {
a[i + j * lda] -= a[i + k * lda] * akj;
}
}
}
ipiv
}
pub fn spotrf(&self, uplo: BLASUplo, n: usize, a: &mut [f32], lda: usize) -> bool {
if uplo == BLASUplo::Upper {
for j in 0..n {
for i in 0..j {
let mut sum = a[i + j * lda];
for k in 0..i {
sum -= a[k + i * lda] * a[k + j * lda];
}
a[i + j * lda] = sum / a[i + i * lda];
}
let mut sum = a[j + j * lda];
for k in 0..j {
let akj = a[k + j * lda];
sum -= akj * akj;
}
if sum <= 0.0 {
return false; }
a[j + j * lda] = sum.sqrt();
}
} else {
for j in 0..n {
for i in j..n {
let mut sum = a[i + j * lda];
for k in 0..j {
sum -= a[i + k * lda] * a[j + k * lda];
}
a[i + j * lda] = sum;
}
if a[j + j * lda] <= 0.0 {
return false;
}
a[j + j * lda] = a[j + j * lda].sqrt();
let inv_diag = 1.0 / a[j + j * lda];
for i in (j + 1)..n {
a[i + j * lda] *= inv_diag;
}
}
}
true
}
pub fn dpotrf(&self, uplo: BLASUplo, n: usize, a: &mut [f64], lda: usize) -> bool {
if uplo == BLASUplo::Upper {
for j in 0..n {
for i in 0..j {
let mut sum = a[i + j * lda];
for k in 0..i {
sum -= a[k + i * lda] * a[k + j * lda];
}
a[i + j * lda] = sum / a[i + i * lda];
}
let mut sum = a[j + j * lda];
for k in 0..j {
let akj = a[k + j * lda];
sum -= akj * akj;
}
if sum <= 0.0 {
return false;
}
a[j + j * lda] = sum.sqrt();
}
} else {
for j in 0..n {
for i in j..n {
let mut sum = a[i + j * lda];
for k in 0..j {
sum -= a[i + k * lda] * a[j + k * lda];
}
a[i + j * lda] = sum;
}
if a[j + j * lda] <= 0.0 {
return false;
}
a[j + j * lda] = a[j + j * lda].sqrt();
let inv_diag = 1.0 / a[j + j * lda];
for i in (j + 1)..n {
a[i + j * lda] *= inv_diag;
}
}
}
true
}
pub fn sgeqrf(&self, m: usize, n: usize, a: &mut [f32], lda: usize, tau: &mut [f32]) {
let k = std::cmp::min(m, n);
for j in 0..k {
let mut xnorm = 0.0f32;
for i in j..m {
xnorm += a[i + j * lda] * a[i + j * lda];
}
xnorm = xnorm.sqrt();
let ajj = a[j + j * lda];
let alpha = if ajj > 0.0 { -xnorm } else { xnorm };
let mut v1 = ajj - alpha;
a[j + j * lda] = v1;
let mut vtv = v1 * v1;
for i in (j + 1)..m {
vtv += a[i + j * lda] * a[i + j * lda];
}
tau[j] = if vtv != 0.0 { 2.0 * v1 * v1 / vtv } else { 0.0 };
a[j + j * lda] = alpha;
if j + 1 < n {
let ajj = a[j + j * lda];
if tau[j] != 0.0 {
let tauj = tau[j] / (ajj * (ajj - v1)); for col in (j + 1)..n {
let mut dot = ajj * a[j + col * lda];
for i in (j + 1)..m {
dot += a[i + j * lda] * a[i + col * lda];
}
let scale = tauj * dot; a[j + col * lda] -= scale * ajj;
for i in (j + 1)..m {
a[i + col * lda] -= scale * a[i + j * lda];
}
}
}
}
}
}
pub fn dgeqrf(&self, m: usize, n: usize, a: &mut [f64], lda: usize, tau: &mut [f64]) {
let k = std::cmp::min(m, n);
for j in 0..k {
let mut xnorm = 0.0f64;
for i in j..m {
xnorm += a[i + j * lda] * a[i + j * lda];
}
xnorm = xnorm.sqrt();
let ajj = a[j + j * lda];
let alpha = if ajj > 0.0 { -xnorm } else { xnorm };
let v1 = ajj - alpha;
let mut vtv = v1 * v1;
for i in (j + 1)..m {
vtv += a[i + j * lda] * a[i + j * lda];
}
tau[j] = if vtv != 0.0 { 2.0 * v1 * v1 / vtv } else { 0.0 };
a[j + j * lda] = alpha;
if j + 1 < n && tau[j] != 0.0 {
for col in (j + 1)..n {
let mut dot = a[j + col * lda];
for i in (j + 1)..m {
dot += a[i + j * lda] * a[i + col * lda];
}
let scale = tau[j] * dot;
a[j + col * lda] -= scale;
for i in (j + 1)..m {
a[i + col * lda] -= scale * a[i + j * lda];
}
}
}
}
}
pub fn sgesvd(
&self,
m: usize,
n: usize,
a: &[f32],
lda: usize,
) -> (Vec<Vec<f32>>, Vec<f32>, Vec<Vec<f32>>) {
let mn_min = std::cmp::min(m, n);
let mut u = vec![vec![0.0f32; m]; m];
let mut s = vec![0.0f32; mn_min];
let mut vt = vec![vec![0.0f32; n]; n];
let mut work = vec![0.0f32; m * n];
for j in 0..n {
for i in 0..m {
work[i + j * m] = a[i + j * lda];
}
}
let mut d = vec![0.0f32; mn_min];
let mut e = vec![0.0f32; mn_min];
for k in 0..mn_min {
let mut col_norm = 0.0f32;
for i in k..m {
col_norm += work[i + k * m] * work[i + k * m];
}
col_norm = col_norm.sqrt();
if col_norm > 0.0 {
let akk = work[k + k * m];
let alpha = if akk > 0.0 { -col_norm } else { col_norm };
let v1 = akk - alpha;
work[k + k * m] = v1;
let mut vtv = v1 * v1;
for i in (k + 1)..m {
vtv += work[i + k * m] * work[i + k * m];
}
let tau = if vtv > 0.0 { 2.0 / vtv } else { 0.0 };
work[k + k * m] = alpha;
d[k] = alpha;
if k + 1 < n {
for j in (k + 1)..n {
let mut dot = work[k + j * m];
for i in (k + 1)..m {
dot += work[i + k * m] * work[i + j * m];
}
let scale = tau * dot;
work[k + j * m] -= scale;
for i in (k + 1)..m {
work[i + j * m] -= scale * work[i + k * m];
}
}
}
} else {
d[k] = work[k + k * m];
}
if k + 1 < n {
let mut row_norm = 0.0f32;
for j in (k + 1)..n {
row_norm += work[k + j * m] * work[k + j * m];
}
row_norm = row_norm.sqrt();
e[k] = row_norm;
if row_norm > 0.0 {
let akk1 = work[k + (k + 1) * m];
let alpha = if akk1 > 0.0 { -row_norm } else { row_norm };
let v1 = akk1 - alpha;
work[k + (k + 1) * m] = v1;
let mut vtv = v1 * v1;
for j in (k + 2)..n {
vtv += work[k + j * m] * work[k + j * m];
}
let tau = if vtv > 0.0 { 2.0 / vtv } else { 0.0 };
work[k + (k + 1) * m] = alpha;
e[k] = alpha;
if k + 1 < m {
for i in (k + 1)..m {
let mut dot = work[i + (k + 1) * m];
for j in (k + 2)..n {
dot += work[k + j * m] * work[i + j * m];
}
let scale = tau * dot;
work[i + (k + 1) * m] -= scale;
for j in (k + 2)..n {
work[i + j * m] -= scale * work[k + j * m];
}
}
}
}
}
}
for sweep in 0..30 {
let mut converged = true;
for k in 0..(mn_min - 1) {
let ek = e[k].abs();
if ek < F32_EPSILON * (d[k].abs() + d[k + 1].abs()) {
e[k] = 0.0;
} else {
converged = false;
let f = d[k];
let g = e[k];
let h = d[k + 1];
let r = (f * f + g * g).sqrt();
let c = f / r;
let s_val = -g / r;
d[k] = r;
d[k + 1] = c * h - s_val * e[k + 1]; e[k] = s_val * d[k + 1] + c * e[k]; }
}
if converged {
break;
}
}
for i in 0..mn_min {
s[i] = d[i].abs();
}
for i in 0..m {
u[i][i] = 1.0;
}
for i in 0..n {
vt[i][i] = 1.0;
}
let mut indices: Vec<usize> = (0..mn_min).collect();
indices.sort_by(|&a, &b| s[b].partial_cmp(&s[a]).unwrap_or(Ordering::Equal));
let mut s_sorted = vec![0.0f32; mn_min];
for (i, &idx) in indices.iter().enumerate() {
s_sorted[i] = s[idx];
}
s = s_sorted;
(u, s, vt)
}
pub fn dgesvd(
&self,
m: usize,
n: usize,
a: &[f64],
lda: usize,
) -> (Vec<Vec<f64>>, Vec<f64>, Vec<Vec<f64>>) {
let mn_min = std::cmp::min(m, n);
let mut u = vec![vec![0.0f64; m]; m];
let mut s = vec![0.0f64; mn_min];
let mut vt = vec![vec![0.0f64; n]; n];
let mut work = vec![0.0f64; m * n];
for j in 0..n {
for i in 0..m {
work[i + j * m] = a[i + j * lda];
}
}
let mut d = vec![0.0f64; mn_min];
let mut e = vec![0.0f64; mn_min];
for k in 0..mn_min {
let mut col_norm = 0.0f64;
for i in k..m {
col_norm += work[i + k * m] * work[i + k * m];
}
col_norm = col_norm.sqrt();
if col_norm > 0.0 {
let akk = work[k + k * m];
let alpha = if akk > 0.0 { -col_norm } else { col_norm };
work[k + k * m] = alpha;
d[k] = alpha;
if k + 1 < n {
let v1 = akk - alpha;
let mut vtv = v1 * v1;
for i in (k + 1)..m {
vtv += work[i + k * m] * work[i + k * m];
}
let tau = if vtv > 0.0 { 2.0 / vtv } else { 0.0 };
for j in (k + 1)..n {
let mut dot = v1 * work[k + j * m];
for i in (k + 1)..m {
dot += work[i + k * m] * work[i + j * m];
}
let scale = tau * dot;
work[k + j * m] -= scale * v1;
for i in (k + 1)..m {
work[i + j * m] -= scale * work[i + k * m];
}
}
}
} else {
d[k] = work[k + k * m];
}
if k + 1 < n {
let mut row_norm = 0.0f64;
for j in (k + 1)..n {
row_norm += work[k + j * m] * work[k + j * m];
}
row_norm = row_norm.sqrt();
e[k] = row_norm;
}
}
for _sweep in 0..30 {
let mut converged = true;
for k in 0..(mn_min - 1) {
if e[k].abs() < F64_EPSILON * (d[k].abs() + d[k + 1].abs()) {
e[k] = 0.0;
} else {
converged = false;
let f = d[k];
let g = e[k];
let r = (f * f + g * g).sqrt();
let c = f / r;
let s_val = -g / r;
d[k] = r;
d[k + 1] = c * d[k + 1];
e[k] = s_val * d[k + 1] + c * e[k];
}
}
if converged {
break;
}
}
for i in 0..mn_min {
s[i] = d[i].abs();
}
for i in 0..m {
u[i][i] = 1.0;
}
for i in 0..n {
vt[i][i] = 1.0;
}
let mut indices: Vec<usize> = (0..mn_min).collect();
indices.sort_by(|&a, &b| s[b].partial_cmp(&s[a]).unwrap_or(Ordering::Equal));
let s_sorted: Vec<f64> = indices.iter().map(|&i| s[i]).collect();
s = s_sorted;
(u, s, vt)
}
pub fn ssyev(
&self,
_jobz: char,
_uplo: BLASUplo,
n: usize,
a: &[f32],
lda: usize,
) -> (Vec<f32>, Vec<Vec<f32>>) {
let mut eigenvalues = vec![0.0f32; n];
let mut eigenvectors = vec![vec![0.0f32; n]; n];
let mut work = vec![0.0f32; n * n];
for j in 0..n {
for i in 0..n {
work[i + j * n] = a[i + j * lda];
}
eigenvectors[j][j] = 1.0;
}
let mut d = vec![0.0f32; n];
let mut e = vec![0.0f32; n];
for k in 0..(n - 1) {
d[k] = work[k + k * n];
let mut norm_x = 0.0f32;
for i in (k + 1)..n {
norm_x += work[i + k * n] * work[i + k * n];
}
e[k] = norm_x.sqrt();
if e[k] > 0.0 {
let mut sum = 0.0f32;
for i in (k + 1)..n {
sum += work[i + k * n] * work[i + k * n];
}
let tau = if sum > 0.0 { 2.0 / sum } else { 0.0 };
for j in (k + 1)..n {
let mut dot = 0.0f32;
for i in (k + 1)..n {
dot += work[i + k * n] * work[i + j * n];
}
let scale = tau * dot;
for i in (k + 1)..n {
work[i + j * n] -= scale * work[i + k * n];
}
}
}
}
d[n - 1] = work[(n - 1) + (n - 1) * n];
for _iter in 0..30 {
let mut converged = true;
for k in 0..(n - 1) {
if e[k].abs() > F32_EPSILON * (d[k].abs() + d[k + 1].abs()) {
converged = false;
let f = d[k];
let g = e[k];
let r = (f * f + g * g).sqrt();
let c = f / r;
let s_val = -g / r;
d[k] = r;
d[k + 1] = c * d[k + 1];
e[k] = s_val * d[k + 1] + c * e[k];
}
}
if converged {
break;
}
}
eigenvalues = d;
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by(|&a, &b| {
eigenvalues[a]
.partial_cmp(&eigenvalues[b])
.unwrap_or(Ordering::Equal)
});
let sorted_ev: Vec<f32> = indices.iter().map(|&i| eigenvalues[i]).collect();
(sorted_ev, eigenvectors)
}
pub fn dsyev(
&self,
_jobz: char,
_uplo: BLASUplo,
n: usize,
a: &[f64],
lda: usize,
) -> (Vec<f64>, Vec<Vec<f64>>) {
let mut eigenvalues = vec![0.0f64; n];
let mut eigenvectors = vec![vec![0.0f64; n]; n];
let mut work = vec![0.0f64; n * n];
for j in 0..n {
for i in 0..n {
work[i + j * n] = a[i + j * lda];
}
eigenvectors[j][j] = 1.0;
}
let mut d = vec![0.0f64; n];
let mut e = vec![0.0f64; n];
for k in 0..(n - 1) {
d[k] = work[k + k * n];
let mut norm_x = 0.0f64;
for i in (k + 1)..n {
norm_x += work[i + k * n] * work[i + k * n];
}
e[k] = norm_x.sqrt();
}
d[n - 1] = work[(n - 1) + (n - 1) * n];
for _iter in 0..30 {
let mut converged = true;
for k in 0..(n - 1) {
if e[k].abs() > F64_EPSILON * (d[k].abs() + d[k + 1].abs()) {
converged = false;
let f = d[k];
let g = e[k];
let r = (f * f + g * g).sqrt();
let c = f / r;
let s_val = -g / r;
d[k] = r;
d[k + 1] = c * d[k + 1];
e[k] = s_val * d[k + 1] + c * e[k];
}
}
if converged {
break;
}
}
eigenvalues = d;
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by(|&a, &b| {
eigenvalues[a]
.partial_cmp(&eigenvalues[b])
.unwrap_or(Ordering::Equal)
});
let sorted_ev: Vec<f64> = indices.iter().map(|&i| eigenvalues[i]).collect();
(sorted_ev, eigenvectors)
}
pub fn sgesv(
&self,
n: usize,
nrhs: usize,
a: &mut [f32],
lda: usize,
b: &mut [f32],
ldb: usize,
) -> bool {
let ipiv = self.sgetrf(n, n, a, lda);
for k in 0..n {
if a[k + k * lda].abs() < F32_EPSILON {
return false;
}
}
for j in 0..nrhs {
for i in 0..n {
if ipiv[i] != i {
b.swap(i + j * ldb, ipiv[i] + j * ldb);
}
}
for i in 0..n {
for k in 0..i {
b[i + j * ldb] -= a[i + k * lda] * b[k + j * ldb];
}
}
}
for j in 0..nrhs {
for i_rev in 0..n {
let i = n - 1 - i_rev;
for k in (i + 1)..n {
b[i + j * ldb] -= a[i + k * lda] * b[k + j * ldb];
}
let akk = a[i + i * lda];
if akk.abs() < F32_EPSILON {
return false;
}
b[i + j * ldb] /= akk;
}
}
true
}
pub fn dgesv(
&self,
n: usize,
nrhs: usize,
a: &mut [f64],
lda: usize,
b: &mut [f64],
ldb: usize,
) -> bool {
let ipiv = self.dgetrf(n, n, a, lda);
for k in 0..n {
if a[k + k * lda].abs() < F64_EPSILON {
return false;
}
}
for j in 0..nrhs {
for i in 0..n {
if ipiv[i] != i {
b.swap(i + j * ldb, ipiv[i] + j * ldb);
}
}
for i in 0..n {
for k in 0..i {
b[i + j * ldb] -= a[i + k * lda] * b[k + j * ldb];
}
}
}
for j in 0..nrhs {
for i_rev in 0..n {
let i = n - 1 - i_rev;
for k in (i + 1)..n {
b[i + j * ldb] -= a[i + k * lda] * b[k + j * ldb];
}
let akk = a[i + i * lda];
if akk.abs() < F64_EPSILON {
return false;
}
b[i + j * ldb] /= akk;
}
}
true
}
}
#[derive(Debug)]
pub struct X86FFTSupport {
simd_level: X86SIMDLevel,
has_fma: bool,
twiddle_cache: Mutex<BTreeMap<usize, Vec<FFTComplex>>>,
bitrev_cache: Mutex<BTreeMap<usize, Vec<usize>>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FFTComplex {
pub re: f64,
pub im: f64,
}
impl FFTComplex {
pub fn new(re: f64, im: f64) -> Self {
FFTComplex { re, im }
}
pub fn from_polar(r: f64, theta: f64) -> Self {
FFTComplex {
re: r * theta.cos(),
im: r * theta.sin(),
}
}
pub fn add(&self, other: &FFTComplex) -> FFTComplex {
FFTComplex {
re: self.re + other.re,
im: self.im + other.im,
}
}
pub fn sub(&self, other: &FFTComplex) -> FFTComplex {
FFTComplex {
re: self.re - other.re,
im: self.im - other.im,
}
}
pub fn mul(&self, other: &FFTComplex) -> FFTComplex {
FFTComplex {
re: self.re * other.re - self.im * other.im,
im: self.re * other.im + self.im * other.re,
}
}
pub fn scale(&self, s: f64) -> FFTComplex {
FFTComplex {
re: self.re * s,
im: self.im * s,
}
}
pub fn conj(&self) -> FFTComplex {
FFTComplex {
re: self.re,
im: -self.im,
}
}
}
impl X86FFTSupport {
pub fn new(simd_level: X86SIMDLevel, has_fma: bool) -> Self {
Self {
simd_level,
has_fma,
twiddle_cache: Mutex::new(BTreeMap::new()),
bitrev_cache: Mutex::new(BTreeMap::new()),
}
}
fn is_power_of_two(n: usize) -> bool {
n > 0 && (n & (n - 1)) == 0
}
fn bit_reverse_table(&self, n: usize) -> Vec<usize> {
{
let cache = self.bitrev_cache.lock().unwrap();
if let Some(table) = cache.get(&n) {
return table.clone();
}
}
let bits = n.trailing_zeros() as usize;
let mut table = vec![0usize; n];
for i in 0..n {
let mut rev = 0usize;
let mut val = i;
for _ in 0..bits {
rev = (rev << 1) | (val & 1);
val >>= 1;
}
table[i] = rev;
}
{
let mut cache = self.bitrev_cache.lock().unwrap();
cache.insert(n, table.clone());
}
table
}
fn compute_twiddles(&self, n: usize) -> Vec<FFTComplex> {
{
let cache = self.twiddle_cache.lock().unwrap();
if let Some(tw) = cache.get(&n) {
return tw.clone();
}
}
let mut twiddles = Vec::with_capacity(n / 2);
for k in 0..(n / 2) {
let theta = -TAU * (k as f64) / (n as f64);
twiddles.push(FFTComplex::from_polar(1.0, theta));
}
{
let mut cache = self.twiddle_cache.lock().unwrap();
cache.insert(n, twiddles.clone());
}
twiddles
}
pub fn rfft(&self, data: &[f32]) -> Vec<FFTComplex> {
let n = data.len();
if !Self::is_power_of_two(n) || n > FFT_MAX_SIZE {
return data
.iter()
.map(|&x| FFTComplex::new(x as f64, 0.0))
.collect();
}
let n_out = n / 2 + 1;
let mut complex_data: Vec<FFTComplex> = data
.iter()
.map(|&x| FFTComplex::new(x as f64, 0.0))
.collect();
self.fft_radix4(&mut complex_data, FFTDirection::Forward);
let mut result = Vec::with_capacity(n_out);
for i in 0..n_out {
result.push(complex_data[i]);
}
result
}
pub fn irfft(&self, freq: &[FFTComplex], out_len: usize) -> Vec<f32> {
if !Self::is_power_of_two(out_len) || out_len > FFT_MAX_SIZE {
return vec![0.0; out_len];
}
let n = out_len;
let mut temp = vec![FFTComplex::new(0.0, 0.0); n];
for i in 0..freq.len() {
temp[i] = freq[i];
}
for i in freq.len()..n {
let mirror = n - i;
if mirror < freq.len() {
temp[i] = freq[mirror].conj();
}
}
self.fft_radix4(&mut temp, FFTDirection::Inverse);
temp.iter().map(|c| (c.re / n as f64) as f32).collect()
}
pub fn rfft_f64(&self, data: &[f64]) -> Vec<FFTComplex> {
let n = data.len();
if !Self::is_power_of_two(n) || n > FFT_MAX_SIZE {
return data.iter().map(|&x| FFTComplex::new(x, 0.0)).collect();
}
let n_out = n / 2 + 1;
let mut complex_data: Vec<FFTComplex> =
data.iter().map(|&x| FFTComplex::new(x, 0.0)).collect();
self.fft_radix4(&mut complex_data, FFTDirection::Forward);
complex_data.truncate(n_out);
complex_data
}
pub fn irfft_f64(&self, freq: &[FFTComplex], out_len: usize) -> Vec<f64> {
if !Self::is_power_of_two(out_len) || out_len > FFT_MAX_SIZE {
return vec![0.0; out_len];
}
let n = out_len;
let mut temp = vec![FFTComplex::new(0.0, 0.0); n];
for i in 0..freq.len() {
temp[i] = freq[i];
}
for i in freq.len()..n {
let mirror = n - i;
if mirror < freq.len() {
temp[i] = freq[mirror].conj();
}
}
self.fft_radix4(&mut temp, FFTDirection::Inverse);
temp.iter().map(|c| c.re / n as f64).collect()
}
pub fn cfft(&self, data: &mut [FFTComplex]) {
let n = data.len();
if !Self::is_power_of_two(n) || n > FFT_MAX_SIZE {
return;
}
self.fft_radix4(data, FFTDirection::Forward);
}
pub fn icfft(&self, data: &mut [FFTComplex]) {
let n = data.len();
if !Self::is_power_of_two(n) || n > FFT_MAX_SIZE {
return;
}
self.fft_radix4(data, FFTDirection::Inverse);
let inv_n = 1.0 / n as f64;
for x in data.iter_mut() {
*x = x.scale(inv_n);
}
}
pub fn fft_radix2(&self, data: &mut [FFTComplex], direction: FFTDirection) {
let n = data.len();
if n <= 1 {
return;
}
let bitrev = self.bit_reverse_table(n);
for i in 0..n {
let j = bitrev[i];
if i < j {
data.swap(i, j);
}
}
let twiddles = self.compute_twiddles(n);
let sign = match direction {
FFTDirection::Forward => -1.0,
FFTDirection::Inverse => 1.0,
};
let mut len = 2;
while len <= n {
let half_len = len / 2;
let step = n / len;
for i in (0..n).step_by(len) {
for j in 0..half_len {
let tw = twiddles[j * step];
let tw_im = tw.im * sign;
let u = data[i + j];
let v = data[i + j + half_len].mul(&FFTComplex::new(tw.re, tw_im));
data[i + j] = u.add(&v);
data[i + j + half_len] = u.sub(&v);
}
}
len <<= 1;
}
}
pub fn fft_radix4(&self, data: &mut [FFTComplex], direction: FFTDirection) {
let n = data.len();
if n <= 1 {
return;
}
if n & (n - 1) != 0 || (n.trailing_zeros() % 2 != 0) {
if n.trailing_zeros() % 2 == 0 && Self::is_power_of_two(n) {
} else {
self.fft_radix2(data, direction);
return;
}
}
let bitrev = self.bit_reverse_table(n);
for i in 0..n {
let j = bitrev[i];
if i < j {
data.swap(i, j);
}
}
let twiddles = self.compute_twiddles(n);
let sign = match direction {
FFTDirection::Forward => -1.0,
FFTDirection::Inverse => 1.0,
};
let mut len = 4;
while len <= n {
let quarter = len / 4;
let step = n / len;
for i in (0..n).step_by(len) {
for j in 0..quarter {
let tw1 = twiddles[j * step];
let tw2 = twiddles[2 * j * step];
let tw3 = twiddles[3 * j * step];
let tw1_im = tw1.im * sign;
let tw2_im = tw2.im * sign;
let tw3_im = tw3.im * sign;
let idx0 = i + j;
let idx1 = i + j + quarter;
let idx2 = i + j + 2 * quarter;
let idx3 = i + j + 3 * quarter;
let u0 = data[idx0];
let u1 = data[idx1].mul(&FFTComplex::new(tw1.re, tw1_im));
let u2 = data[idx2].mul(&FFTComplex::new(tw2.re, tw2_im));
let u3 = data[idx3].mul(&FFTComplex::new(tw3.re, tw3_im));
let t0 = u0.add(&u2);
let t1 = u0.sub(&u2);
let t2 = u1.add(&u3);
let t3 = u1.sub(&u3);
let t3_rot = FFTComplex::new(t3.im * sign, -t3.re * sign);
data[idx0] = t0.add(&t2);
data[idx1] = t1.add(&t3_rot);
data[idx2] = t0.sub(&t2);
data[idx3] = t1.sub(&t3_rot);
}
}
len <<= 2;
}
}
pub fn fft_split_radix(&self, data: &mut [FFTComplex], direction: FFTDirection) {
self.fft_radix4(data, direction);
}
pub fn fft_auto(&self, data: &mut [FFTComplex], direction: FFTDirection) {
let n = data.len();
if n <= 16 {
self.fft_radix2(data, direction);
} else if n.trailing_zeros() % 2 == 0 {
self.fft_radix4(data, direction);
} else {
self.fft_split_radix(data, direction);
}
}
pub fn fft_convolve(&self, f: &[f64], g: &[f64]) -> Vec<f64> {
let conv_len = f.len() + g.len() - 1;
let fft_len = conv_len.next_power_of_two();
let mut fa: Vec<FFTComplex> = f
.iter()
.map(|&x| FFTComplex::new(x, 0.0))
.chain(std::iter::repeat(FFTComplex::new(0.0, 0.0)).take(fft_len - f.len()))
.collect();
let mut ga: Vec<FFTComplex> = g
.iter()
.map(|&x| FFTComplex::new(x, 0.0))
.chain(std::iter::repeat(FFTComplex::new(0.0, 0.0)).take(fft_len - g.len()))
.collect();
self.cfft(&mut fa);
self.cfft(&mut ga);
for i in 0..fft_len {
fa[i] = fa[i].mul(&ga[i]);
}
self.icfft(&mut fa);
fa.truncate(conv_len);
fa.iter().map(|c| c.re).collect()
}
pub fn dct_ii(&self, x: &[f64]) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let m = 2 * n;
let mut y = vec![FFTComplex::new(0.0, 0.0); m];
for k in 0..n {
y[k] = FFTComplex::new(x[k], 0.0);
y[m - 1 - k] = FFTComplex::new(x[k], 0.0);
}
self.fft_auto(&mut y, FFTDirection::Forward);
let scale = 2.0 / (m as f64).sqrt();
let mut result = Vec::with_capacity(n);
for k in 0..n {
let theta = -PI * k as f64 / (2.0 * n as f64);
let twiddle = FFTComplex::new(theta.cos(), theta.sin());
let prod = y[k].mul(&twiddle);
result.push(scale * prod.re);
}
if n > 0 {
result[0] /= 2.0f64.sqrt();
}
result
}
pub fn dct_iii(&self, x: &[f64]) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let m = 2 * n;
let mut y = vec![FFTComplex::new(0.0, 0.0); m];
let inv_scale = (m as f64).sqrt() / 2.0;
for k in 0..n {
let theta = PI * k as f64 / (2.0 * n as f64);
let twiddle = FFTComplex::new(theta.cos(), theta.sin());
let mut val = FFTComplex::new(x[k] * inv_scale, 0.0);
if k == 0 {
val = val.scale(2.0f64.sqrt());
}
let prod = val.mul(&twiddle);
y[k] = prod;
y[m - 1 - k] = prod;
}
self.fft_auto(&mut y, FFTDirection::Inverse);
y.iter().take(n).map(|c| c.re / n as f64).collect()
}
pub fn dst_ii(&self, x: &[f64]) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let m = 2 * n + 2;
let mut y = vec![FFTComplex::new(0.0, 0.0); m];
for k in 0..n {
let val = x[k];
y[k + 1] = FFTComplex::new(val, 0.0);
y[m - 1 - k] = FFTComplex::new(-val, 0.0);
}
self.fft_auto(&mut y, FFTDirection::Forward);
let scale = 1.0 / (2.0 * (n + 1) as f64).sqrt();
let mut result = Vec::with_capacity(n);
for k in 0..n {
let theta = -PI * (k + 1) as f64 / (2.0 * (n + 1) as f64);
let twiddle = FFTComplex::new(theta.cos(), theta.sin());
let prod = y[k + 1].mul(&twiddle);
result.push(-2.0 * scale * prod.im);
}
result
}
pub fn dst_iii(&self, x: &[f64]) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let m = 2 * n + 2;
let mut y = vec![FFTComplex::new(0.0, 0.0); m];
for k in 0..n {
let theta = PI * (k + 1) as f64 / (2.0 * (n + 1) as f64);
let twiddle = FFTComplex::new(theta.cos(), theta.sin());
let val = FFTComplex::new(0.0, x[k]).mul(&twiddle);
y[k + 1] = val;
y[m - 1 - k] = FFTComplex::new(-val.re, -val.im);
}
self.fft_auto(&mut y, FFTDirection::Inverse);
let scale = (2.0 * (n + 1) as f64).sqrt() / (2.0 * (n + 1) as f64);
y.iter().skip(1).take(n).map(|c| scale * c.im).collect()
}
pub fn fft_correlate(&self, f: &[f64], g: &[f64]) -> Vec<f64> {
let n = f.len().max(g.len());
let fft_len = (2 * n - 1).next_power_of_two();
let mut fa: Vec<FFTComplex> = f
.iter()
.map(|&x| FFTComplex::new(x, 0.0))
.chain(std::iter::repeat(FFTComplex::new(0.0, 0.0)).take(fft_len - f.len()))
.collect();
let mut ga: Vec<FFTComplex> = g
.iter()
.map(|&x| FFTComplex::new(x, 0.0))
.chain(std::iter::repeat(FFTComplex::new(0.0, 0.0)).take(fft_len - g.len()))
.collect();
self.cfft(&mut fa);
self.cfft(&mut ga);
for i in 0..fft_len {
fa[i] = fa[i].conj().mul(&ga[i]);
}
self.icfft(&mut fa);
fa.truncate(2 * n - 1);
fa.iter().map(|c| c.re).collect()
}
pub fn psd_periodogram(&self, x: &[f64]) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let mut data: Vec<FFTComplex> = x.iter().map(|&v| FFTComplex::new(v, 0.0)).collect();
self.cfft(&mut data);
let scale = 1.0 / n as f64;
data.iter()
.take(n / 2 + 1)
.map(|c| scale * (c.re * c.re + c.im * c.im))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct X86MathIntrinsics {
simd_level: X86SIMDLevel,
}
impl X86MathIntrinsics {
pub fn new(simd_level: X86SIMDLevel) -> Self {
Self { simd_level }
}
pub fn _mm_sin_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.sin_f32(xi);
}
result
}
pub fn _mm_sin_pd(&self, x: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.sin_f64(xi);
}
result
}
pub fn _mm_cos_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.cos_f32(xi);
}
result
}
pub fn _mm_cos_pd(&self, x: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.cos_f64(xi);
}
result
}
pub fn _mm_exp_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.exp_f32(xi);
}
result
}
pub fn _mm_exp_pd(&self, x: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.exp_f64(xi);
}
result
}
pub fn _mm_log_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.log_f32(xi);
}
result
}
pub fn _mm_log_pd(&self, x: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.log_f64(xi);
}
result
}
pub fn _mm_sqrt_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = xi.sqrt();
}
result
}
pub fn _mm_sqrt_pd(&self, x: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for (i, &xi) in x.iter().enumerate() {
result[i] = xi.sqrt();
}
result
}
fn sin_f32(&self, x: f32) -> f32 {
let mut x = x;
let pi = f32c::PI;
let two_pi = 2.0 * pi;
x = x - (x / two_pi).round() * two_pi;
let x2 = x * x;
let x3 = x2 * x;
let x5 = x3 * x2;
let x7 = x5 * x2;
x - x3 / 6.0 + x5 / 120.0 - x7 / 5040.0
}
fn sin_f64(&self, x: f64) -> f64 {
let mut x = x;
x = x - (x / TAU).round() * TAU;
let x2 = x * x;
let x3 = x2 * x;
let x5 = x3 * x2;
let x7 = x5 * x2;
let x9 = x7 * x2;
let x11 = x9 * x2;
x - x3 / 6.0 + x5 / 120.0 - x7 / 5040.0 + x9 / 362880.0 - x11 / 39916800.0
}
fn cos_f32(&self, x: f32) -> f32 {
self.sin_f32(f32c::FRAC_PI_2 - x)
}
fn cos_f64(&self, x: f64) -> f64 {
self.sin_f64(std::f64::consts::FRAC_PI_2 - x)
}
fn exp_f32(&self, x: f32) -> f32 {
if x < -87.0 {
return 0.0;
}
if x > 88.0 {
return f32::INFINITY;
}
let inv_ln2 = 1.4426950408889634f32; let k = (x * inv_ln2).round() as i32;
let r = x - k as f32 * std::f32::consts::LN_2;
let r2 = r * r;
let r3 = r2 * r;
let r4 = r3 * r;
let r5 = r4 * r;
let poly = 1.0 + r + r2 / 2.0 + r3 / 6.0 + r4 / 24.0 + r5 / 120.0;
if k >= 0 {
poly * (1u32 << k) as f32
} else {
poly / (1u32 << (-k)) as f32
}
}
fn exp_f64(&self, x: f64) -> f64 {
if x < -745.0 {
return 0.0;
}
if x > 709.0 {
return f64::INFINITY;
}
let inv_ln2 = 1.4426950408889634f64;
let k = (x * inv_ln2).round() as i64;
let r = x - k as f64 * std::f64::consts::LN_2;
let r2 = r * r;
let r3 = r2 * r;
let r4 = r3 * r;
let r5 = r4 * r;
let r6 = r5 * r;
let poly = 1.0 + r + r2 / 2.0 + r3 / 6.0 + r4 / 24.0 + r5 / 120.0 + r6 / 720.0;
if k >= 0 {
poly * (1u64 << k) as f64
} else {
poly / (1u64 << (-k)) as f64
}
}
fn log_f32(&self, x: f32) -> f32 {
if x <= 0.0 {
return f32::NAN;
}
let bits = x.to_bits();
let e = ((bits >> 23) & 0xFF) as i32 - 127;
let m = f32::from_bits((bits & 0x7FFFFF) | 0x3F800000);
let m_minus_1 = m - 1.0;
let m_plus_1 = m + 1.0;
let y = m_minus_1 / m_plus_1;
let y2 = y * y;
let y3 = y2 * y;
let y5 = y3 * y2;
let y7 = y5 * y2;
let log_m = 2.0 * (y + y3 / 3.0 + y5 / 5.0 + y7 / 7.0);
log_m + e as f32 * std::f32::consts::LN_2
}
fn log_f64(&self, x: f64) -> f64 {
if x <= 0.0 {
return f64::NAN;
}
let bits = x.to_bits();
let e = ((bits >> 52) & 0x7FF) as i64 - 1023;
let m = f64::from_bits((bits & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000);
let m_minus_1 = m - 1.0;
let m_plus_1 = m + 1.0;
let y = m_minus_1 / m_plus_1;
let y2 = y * y;
let y3 = y2 * y;
let y5 = y3 * y2;
let y7 = y5 * y2;
let y9 = y7 * y2;
let log_m = 2.0 * (y + y3 / 3.0 + y5 / 5.0 + y7 / 7.0 + y9 / 9.0);
log_m + e as f64 * std::f64::consts::LN_2
}
pub fn fmaf(&self, a: f32, b: f32, c: f32) -> f32 {
if self.simd_level >= X86SIMDLevel::AVX2 && is_x86_feature_detected!("fma") {
a.mul_add(b, c)
} else {
a * b + c
}
}
pub fn fma(&self, a: f64, b: f64, c: f64) -> f64 {
if self.simd_level >= X86SIMDLevel::AVX2 && is_x86_feature_detected!("fma") {
a.mul_add(b, c)
} else {
a * b + c
}
}
pub fn fmal(&self, a: f64, b: f64, c: f64) -> f64 {
self.fma(a, b, c)
}
pub fn _mm_fmadd_ps(&self, a: [f32; 4], b: [f32; 4], c: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for i in 0..4 {
result[i] = self.fmaf(a[i], b[i], c[i]);
}
result
}
pub fn _mm_fmadd_pd(&self, a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for i in 0..2 {
result[i] = self.fma(a[i], b[i], c[i]);
}
result
}
pub fn _mm_fmsub_ps(&self, a: [f32; 4], b: [f32; 4], c: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for i in 0..4 {
result[i] = self.fmaf(a[i], b[i], -c[i]);
}
result
}
pub fn _mm_fmsub_pd(&self, a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> [f64; 2] {
let mut result = [0.0f64; 2];
for i in 0..2 {
result[i] = self.fma(a[i], b[i], -c[i]);
}
result
}
pub fn _mm_tan_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
result[i] = self.sin_f32(xi) / self.cos_f32(xi);
}
result
}
pub fn _mm_atan_ps(&self, x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for (i, &xi) in x.iter().enumerate() {
let x2 = xi * xi;
let x3 = x2 * xi;
let x5 = x3 * x2;
let x7 = x5 * x2;
result[i] = xi - x3 / 3.0 + x5 / 5.0 - x7 / 7.0;
}
result
}
pub fn _mm_atan2_ps(&self, y: [f32; 4], x: [f32; 4]) -> [f32; 4] {
let mut result = [0.0f32; 4];
for i in 0..4 {
result[i] = y[i].atan2(x[i]);
}
result
}
}
#[derive(Debug, Clone)]
pub struct X86SpecialFunctions;
impl X86SpecialFunctions {
pub fn new() -> Self {
Self
}
pub fn j0(&self, x: f64) -> f64 {
let ax = x.abs();
if ax < 8.0 {
let y = x * x;
let ans1 = 57568490574.0
+ y * (-13362590354.0
+ y * (651619640.7
+ y * (-11214424.18 + y * (77392.33017 + y * (-184.9052456)))));
let ans2 = 57568490411.0
+ y * (1029532985.0
+ y * (9494680.718 + y * (59272.64853 + y * (267.8532712 + y * 1.0))));
ans1 / ans2
} else {
let z = 8.0 / ax;
let y = z * z;
let xx = ax - 0.785398164;
let ans1 = 1.0
+ y * (-0.1098628627e-2
+ y * (0.2734510407e-4 + y * (-0.2073370639e-5 + y * 0.2093887211e-6)));
let ans2 = -0.1562499995e-1
+ y * (0.1430488765e-3
+ y * (-0.6911147651e-5 + y * (0.7621095161e-6 - y * 0.934935152e-7)));
(0.7978845608028654 / ax.sqrt()) * (ans1 * xx.cos() - z * ans2 * xx.sin())
}
}
pub fn j1(&self, x: f64) -> f64 {
let ax = x.abs();
if ax < 8.0 {
let y = x * x;
let ans1 = x
* (72362614232.0
+ y * (-7895059235.0
+ y * (242396853.1
+ y * (-2972611.439 + y * (15704.48260 + y * (-30.16036606))))));
let ans2 = 144725228442.0
+ y * (2300535178.0
+ y * (18583304.74 + y * (99447.43394 + y * (376.9991397 + y * 1.0))));
if x < 0.0 {
-ans1 / ans2
} else {
ans1 / ans2
}
} else {
let z = 8.0 / ax;
let y = z * z;
let xx = ax - 2.356194491;
let ans1 = 1.0
+ y * (0.183105e-2
+ y * (-0.3516396496e-4 + y * (0.2457520174e-5 + y * (-0.240337019e-6))));
let ans2 = 0.04687499995
+ y * (-0.2002690873e-3
+ y * (0.8449199096e-5 + y * (-0.88228987e-6 + y * 0.105787412e-6)));
let result = (0.7978845608028654 / ax.sqrt()) * (ans1 * xx.cos() - z * ans2 * xx.sin());
if x < 0.0 {
-result
} else {
result
}
}
}
pub fn jn(&self, n: i32, x: f64) -> f64 {
if n == 0 {
return self.j0(x);
}
if n == 1 {
return self.j1(x);
}
if n < 0 {
let n_abs = (-n) as u32;
if n_abs % 2 == 0 {
return self.jn(-n, x);
} else {
return -self.jn(-n, x);
}
}
let ax = x.abs();
if ax == 0.0 {
return 0.0;
}
if ax > n as f64 {
let mut tox = 2.0 / ax;
let mut bjm = self.j0(ax);
let mut bj = self.j1(ax);
let mut ans = 0.0;
for j in 1..n {
let bjp = j as f64 * tox * bj - bjm;
bjm = bj;
bj = bjp;
ans = bj;
}
ans
} else {
let tox = 2.0 / ax;
let m = 2 * ((n as f64 + (40.0 * n as f64).sqrt()) as i32);
let mut bjp = 0.0;
let mut bj = 1.0;
let mut ans = 0.0;
let mut bjm;
for j in (1..=m).rev() {
bjm = j as f64 * tox * bj - bjp;
bjp = bj;
bj = bjm;
if j == n {
ans = bjp;
}
}
ans * self.j0(ax) / bj
}
}
pub fn y0(&self, x: f64) -> f64 {
if x < 0.0 {
return f64::NAN;
}
if x < 8.0 {
let y = x * x;
let ans1 = -2957821389.0
+ y * (7062834065.0
+ y * (-512359803.6
+ y * (10879881.29 + y * (-86327.92757 + y * 228.4622733))));
let ans2 = 40076544269.0
+ y * (745249964.8
+ y * (7189466.438 + y * (47447.26470 + y * (226.1030244 + y * 1.0))));
(ans1 / ans2) + 0.636619772 * self.j0(x) * x.ln()
} else {
let z = 8.0 / x;
let y = z * z;
let xx = x - 0.785398164;
let ans1 = 1.0
+ y * (-0.1098628627e-2
+ y * (0.2734510407e-4 + y * (-0.2073370639e-5 + y * 0.2093887211e-6)));
let ans2 = -0.1562499995e-1
+ y * (0.1430488765e-3
+ y * (-0.6911147651e-5 + y * (0.7621095161e-6 + y * (-0.934945152e-7))));
(0.7978845608028654 / x.sqrt()) * (ans1 * xx.sin() + z * ans2 * xx.cos())
}
}
pub fn y1(&self, x: f64) -> f64 {
if x < 0.0 {
return f64::NAN;
}
if x < 8.0 {
let y = x * x;
let ans1 = x
* (-0.4900604943e13
+ y * (0.1275274390e13
+ y * (-0.5153438139e11
+ y * (0.7349264551e9 + y * (-0.4237922726e7 + y * 0.8511937935e4)))));
let ans2 = 0.2499580570e14
+ y * (0.4244419664e12
+ y * (0.3733650367e10
+ y * (0.2245904002e8 + y * (0.1020426050e6 + y * (0.3549632885e3 + y)))));
(ans1 / ans2) + 0.636619772 * (self.j1(x) * x.ln() - 1.0 / x)
} else {
let z = 8.0 / x;
let y = z * z;
let xx = x - 2.356194491;
let ans1 = 1.0
+ y * (0.183105e-2
+ y * (-0.3516396496e-4 + y * (0.2457520174e-5 + y * (-0.240337019e-6))));
let ans2 = 0.04687499995
+ y * (-0.2002690873e-3
+ y * (0.8449199096e-5 + y * (-0.88228987e-6 + y * 0.105787412e-6)));
(0.7978845608028654 / x.sqrt()) * (ans1 * xx.sin() + z * ans2 * xx.cos())
}
}
pub fn yn(&self, n: i32, x: f64) -> f64 {
if n == 0 {
return self.y0(x);
}
if n == 1 {
return self.y1(x);
}
let mut bym = self.y0(x);
let mut by = self.y1(x);
let mut ans = by;
for j in 1..n {
let byp = j as f64 * 2.0 / x * by - bym;
bym = by;
by = byp;
ans = by;
}
ans
}
pub fn tgamma(&self, x: f64) -> f64 {
if x < 0.5 {
PI / ((PI * x).sin() * self.tgamma(1.0 - x))
} else {
let x = x - 1.0;
let p: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
let mut z = p[0];
for (i, coeff) in p.iter().enumerate().skip(1) {
z += coeff / (x + i as f64);
}
let t = x + 7.5;
(2.0 * PI).sqrt() * t.powf(x + 0.5) * (-t).exp() * z
}
}
pub fn lgamma(&self, x: f64) -> f64 {
if x <= 0.0 {
return f64::NAN;
}
let t = self.tgamma(x);
t.abs().ln()
}
pub fn digamma(&self, x: f64) -> f64 {
if x <= 0.0 {
return f64::NAN;
}
if x < 10.0 {
self.digamma(x + 1.0) - 1.0 / x
} else {
let inv_x = 1.0 / x;
let inv_x2 = inv_x * inv_x;
let inv_x4 = inv_x2 * inv_x2;
let inv_x6 = inv_x4 * inv_x2;
x.ln() - 0.5 * inv_x - inv_x2 / 12.0 + inv_x4 / 120.0 - inv_x6 / 252.0
}
}
pub fn erf(&self, x: f64) -> f64 {
if x < 0.0 {
return -self.erf(-x);
}
if x > 6.0 {
return 1.0;
}
let t = 1.0 / (1.0 + 0.3275911 * x);
let y = t
* (0.254829592
+ t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
1.0 - y * (-x * x).exp()
}
pub fn erfc(&self, x: f64) -> f64 {
1.0 - self.erf(x)
}
pub fn erfcx(&self, x: f64) -> f64 {
if x < 0.0 {
return 2.0 * (x * x).exp() - self.erfcx(-x);
}
if x > 25.0 {
return 1.0 / (PI.sqrt() * x);
}
(x * x).exp() * self.erfc(x)
}
pub fn expint(&self, x: f64) -> f64 {
if x <= 0.0 {
return f64::NAN;
}
if x < 1.0 {
let mut sum = -EULER_GAMMA - x.ln();
let mut term = -x;
let mut fact = 1.0;
for n in 1..=50 {
fact *= n as f64;
term *= -x / (n as f64);
let delta = term / (n as f64 * fact);
sum -= delta;
if delta.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
} else {
let mut a = 0.0;
let mut b = 1.0;
let mut c = 1.0;
let mut d = 0.0;
for n in 0..=40 {
let an = if n == 0 { 1.0 } else { -(n as f64 * n as f64) };
let bn = x + 1.0 + n as f64 * 2.0;
d = bn + an * d;
if d == 0.0 {
d = F64_EPSILON;
}
c = bn + an / c;
if c == 0.0 {
c = F64_EPSILON;
}
d = 1.0 / d;
let delta = c * d;
a = b;
b *= delta;
if (delta - 1.0).abs() < F64_EPSILON {
break;
}
}
(-x).exp() / (x + b / a)
}
}
pub fn logint(&self, x: f64) -> f64 {
if x <= 1.0 {
return f64::NAN;
}
if x < 2.0 {
let lnx = x.ln();
let mut sum = EULER_GAMMA + (lnx.ln().abs()).ln();
let mut term = lnx;
let mut fact = 1.0;
for n in 1..=20 {
fact *= n as f64;
term *= lnx;
sum += term / (n as f64 * fact);
}
sum
} else {
let lnx = x.ln();
-self.expint(-lnx)
}
}
pub fn zeta(&self, s: f64) -> f64 {
if s <= 1.0 {
return f64::NAN;
}
if s > 50.0 {
return 1.0 + 2.0_f64.powf(-s);
}
let n = 20;
let mut sum = 0.0;
for k in 1..=n {
sum += (k as f64).powf(-s);
}
let nf = n as f64;
sum + nf.powf(1.0 - s) / (s - 1.0) + 0.5 * nf.powf(-s) + s * nf.powf(-s - 1.0) / 12.0
- s * (s + 1.0) * (s + 2.0) * nf.powf(-s - 3.0) / 720.0
}
pub fn polylog(&self, s: f64, z: f64) -> f64 {
if z.abs() > 1.0 {
return f64::NAN;
}
if z == 1.0 {
return self.zeta(s);
}
let mut sum = 0.0;
let mut term = z;
for k in 1..=1000 {
let delta = term / (k as f64).powf(s);
sum += delta;
term *= z;
if delta.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
}
pub fn legendre_p(&self, n: u32, x: f64) -> f64 {
if n == 0 {
return 1.0;
}
if n == 1 {
return x;
}
let mut p0 = 1.0;
let mut p1 = x;
for k in 1..n {
let pk = ((2 * k + 1) as f64 * x * p1 - k as f64 * p0) / ((k + 1) as f64);
p0 = p1;
p1 = pk;
}
p1
}
pub fn legendre_associated(&self, l: u32, m: i32, x: f64) -> f64 {
let abs_m = m.unsigned_abs() as u32;
if abs_m > l {
return 0.0;
}
let mut pmm = 1.0;
if abs_m > 0 {
let somx2 = (1.0 - x * x).sqrt();
let mut fact = 1.0;
for _ in 1..=abs_m {
pmm *= -fact * somx2;
fact += 2.0;
}
}
if l == abs_m {
let result = pmm;
return if m < 0 {
let sign = if m % 2 == 0 { 1.0 } else { -1.0 };
sign * self.factorial(l - abs_m) as f64 / self.factorial(l + abs_m) as f64 * result
} else {
result
};
}
let mut pmmp1 = x * (2 * abs_m + 1) as f64 * pmm;
if l == abs_m + 1 {
let result = pmmp1;
return if m < 0 {
let sign = if m % 2 == 0 { 1.0 } else { -1.0 };
sign * self.factorial(l - abs_m) as f64 / self.factorial(l + abs_m) as f64 * result
} else {
result
};
}
let mut pll = 0.0;
for ll in (abs_m + 2)..=l {
pll = (x * (2 * ll - 1) as f64 * pmmp1 - (ll + abs_m - 1) as f64 * pmm)
/ (ll - abs_m) as f64;
pmm = pmmp1;
pmmp1 = pll;
}
if m < 0 {
let sign = if m % 2 == 0 { 1.0 } else { -1.0 };
sign * self.factorial(l - abs_m) as f64 / self.factorial(l + abs_m) as f64 * pll
} else {
pll
}
}
pub fn spherical_harmonic(&self, l: u32, m: i32, theta: f64, phi: f64) -> FFTComplex {
let abs_m = m.unsigned_abs() as u32;
let normalization = ((2 * l + 1) as f64 * self.factorial(l - abs_m) as f64
/ (4.0 * PI * self.factorial(l + abs_m) as f64))
.sqrt();
let plm = self.legendre_associated(l, m, theta.cos());
let val = normalization * plm;
let mf = m as f64 * phi;
FFTComplex::new(val * mf.cos(), val * mf.sin())
}
fn factorial(&self, n: u32) -> u64 {
let mut result = 1u64;
for i in 2..=n {
result *= i as u64;
}
result
}
pub fn beta(&self, x: f64, y: f64) -> f64 {
self.tgamma(x) * self.tgamma(y) / self.tgamma(x + y)
}
pub fn gammainc_lower(&self, a: f64, x: f64) -> f64 {
if x < 0.0 || a <= 0.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let mut sum = 1.0 / a;
let mut term = 1.0 / a;
for n in 1..=200 {
term *= x / (a + n as f64);
sum += term;
if term.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum * x.powf(a) * (-x).exp()
}
pub fn gammainc_upper(&self, a: f64, x: f64) -> f64 {
self.tgamma(a) - self.gammainc_lower(a, x)
}
pub fn airy_ai(&self, x: f64) -> f64 {
if x > 2.0 {
let z = 2.0 / 3.0 * x.powf(1.5);
(0.5 / PI.sqrt()) * x.powf(-0.25) * (-z).exp()
} else if x < -2.0 {
let z = 2.0 / 3.0 * (-x).powf(1.5);
(1.0 / PI.sqrt()) * (-x).powf(-0.25) * (z - PI / 4.0).sin()
} else {
let c1 = 0.3550280538878172;
let c2 = 0.2588194037928068;
let x2 = x * x;
let x3 = x2 * x;
c1 - c2 * x + 0.0 * x2 - (-x3 / 6.0) * c1
}
}
pub fn airy_bi(&self, x: f64) -> f64 {
if x > 2.0 {
let z = 2.0 / 3.0 * x.powf(1.5);
(1.0 / PI.sqrt()) * x.powf(-0.25) * z.exp()
} else if x < -2.0 {
let z = 2.0 / 3.0 * (-x).powf(1.5);
(1.0 / PI.sqrt()) * (-x).powf(-0.25) * (z - PI / 4.0).cos()
} else {
let c1 = 0.3550280538878172; let c2 = 0.6149266274460007; c2 + x * (0.4482883573538264)
}
}
pub fn elliptic_k(&self, k: f64) -> f64 {
if k >= 1.0 {
return f64::INFINITY;
}
if k <= -1.0 {
return f64::NAN;
}
if k.abs() < 1e-10 {
return std::f64::consts::FRAC_PI_2;
}
let mut a = 1.0f64;
let mut b = (1.0 - k * k).sqrt();
let mut c = k.abs();
for _ in 0..30 {
let a_new = (a + b) * 0.5;
let b_new = (a * b).sqrt();
let c_new = (a - b) * 0.5;
if (a_new - a).abs() < F64_EPSILON * a_new.abs() {
return std::f64::consts::FRAC_PI_2 / a_new;
}
a = a_new;
b = b_new;
c = c_new;
}
std::f64::consts::FRAC_PI_2 / a
}
pub fn elliptic_e(&self, k: f64) -> f64 {
if k > 1.0 || k < -1.0 {
return f64::NAN;
}
if (k.abs() - 1.0).abs() < F64_EPSILON {
return 1.0;
}
if k.abs() < 1e-10 {
return std::f64::consts::FRAC_PI_2;
}
let mut a = 1.0f64;
let mut b = (1.0 - k * k).sqrt();
let mut c = k.abs();
let mut sum = 0.0f64;
let mut pow2 = 1.0f64;
for _ in 0..30 {
let a_new = (a + b) * 0.5;
let b_new = (a * b).sqrt();
let c_new = (a - b) * 0.5;
sum += pow2 * c_new * c_new;
pow2 *= 2.0;
if (a_new - a).abs() < F64_EPSILON * a_new.abs() {
let k_val = std::f64::consts::FRAC_PI_2 / a_new;
return k_val * (1.0 - sum);
}
a = a_new;
b = b_new;
c = c_new;
}
let k_val = std::f64::consts::FRAC_PI_2 / a;
k_val * (1.0 - sum)
}
pub fn elliptic_f(&self, phi: f64, k: f64) -> f64 {
if k > 1.0 || k < -1.0 {
return f64::NAN;
}
let sin_phi = phi.sin();
let cos_phi = phi.cos();
if k.abs() < F64_EPSILON {
return phi;
}
if (k.abs() - 1.0).abs() < F64_EPSILON && sin_phi.abs() < 1.0 {
return ((phi / 2.0 + std::f64::consts::FRAC_PI_4).tan()).ln();
}
let mut k_i = k;
let mut factor = 1.0;
for _ in 0..20 {
let k_next = k_i / (1.0 + (1.0 - k_i * k_i).sqrt());
if k_next.abs() < F64_EPSILON {
break;
}
factor *= (1.0 + k_next) / (1.0 - k_next);
k_i = k_next;
}
factor * phi.atan() }
pub fn hypergeometric_2f1(&self, a: f64, b: f64, c: f64, z: f64) -> f64 {
if z.abs() >= 1.0 {
return f64::NAN;
}
if c <= 0.0 || c == (c as i64) as f64 && c <= 0.0 {
return f64::INFINITY;
}
let mut sum = 1.0;
let mut term = 1.0;
for n in 1..=500 {
let nf = n as f64;
term *= (a + nf - 1.0) * (b + nf - 1.0) * z / ((c + nf - 1.0) * nf);
sum += term;
if term.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
}
pub fn hypergeometric_1f1(&self, a: f64, b: f64, z: f64) -> f64 {
if b <= 0.0 || (b == (b as i64) as f64 && b <= 0.0) {
return f64::INFINITY;
}
let mut sum = 1.0;
let mut term = 1.0;
for n in 1..=500 {
let nf = n as f64;
term *= (a + nf - 1.0) * z / ((b + nf - 1.0) * nf);
sum += term;
if term.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
}
pub fn dawson(&self, x: f64) -> f64 {
if x.abs() > 10.0 {
let x2 = 2.0 * x * x;
return 0.5 / x * (1.0 + 0.5 / x2 + 0.75 / (x2 * x2));
}
let n = 32;
let h = 0.5;
let mut sum = 0.0;
let xh = x / h;
for i in 0..=n {
let t = (i as f64 - n as f64 / 2.0) * h;
let exp_term = (-(t - x) * (t - x)).exp();
let weight = if i == 0 || i == n { 0.5 } else { 1.0 };
sum += weight * exp_term;
}
sum * h / PI.sqrt()
}
pub fn fresnel_c(&self, x: f64) -> f64 {
if x.abs() < 1.5 {
let x2 = x * x;
let x2_half_pi = std::f64::consts::FRAC_PI_2 * x2;
let mut sum = x;
let mut term = x;
let mut fact = 1.0;
for n in 1..=40 {
let n4 = 4 * n;
fact *= (n4 - 3) as f64 * (n4 - 2) as f64;
term *= -x2_half_pi * x2_half_pi;
let delta = term / fact;
sum += delta;
if delta.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
} else {
let t = std::f64::consts::FRAC_PI_2 * x * x;
let a = 0.5 + self.fresnel_aux_f(t) * t.sin() - self.fresnel_aux_g(t) * t.cos();
if x < 0.0 {
-a
} else {
a
}
}
}
pub fn fresnel_s(&self, x: f64) -> f64 {
if x.abs() < 1.5 {
let x2 = x * x;
let x2_half_pi = std::f64::consts::FRAC_PI_2 * x2;
let x3 = x2 * x;
let mut sum = x2_half_pi * x / 3.0;
let mut term = x3 * std::f64::consts::FRAC_PI_2 / 3.0;
let mut fact = 1.0;
for n in 1..=40 {
let n4 = 4 * n;
fact *= (n4 - 1) as f64 * n4 as f64;
term *= -x2_half_pi * x2_half_pi;
let delta = term / fact;
sum += delta;
if delta.abs() < F64_EPSILON * sum.abs() {
break;
}
}
sum
} else {
let t = std::f64::consts::FRAC_PI_2 * x * x;
let a = 0.5 - self.fresnel_aux_f(t) * t.cos() - self.fresnel_aux_g(t) * t.sin();
if x < 0.0 {
-a
} else {
a
}
}
}
fn fresnel_aux_f(&self, t: f64) -> f64 {
if t.abs() < F64_EPSILON {
return 0.0;
}
(1.0 + 0.926 * t) / (2.0 + 1.792 * t + 3.104 * t * t)
}
fn fresnel_aux_g(&self, t: f64) -> f64 {
if t.abs() < F64_EPSILON {
return 0.0;
}
1.0 / (2.0 + 4.142 * t + 3.492 * t * t + 6.670 * t * t * t)
}
pub fn struve_h0(&self, x: f64) -> f64 {
let mut sum = 0.0;
let mut term = x;
for n in 0..=100 {
if n > 0 {
let denom = ((2 * n + 1) as f64) * ((2 * n + 1) as f64);
term *= -x * x / denom;
}
let gamma_arg = n as f64 + 1.5;
sum += term / self.tgamma(gamma_arg);
if term.abs() < F64_EPSILON * sum.abs() * 0.001 {
break;
}
}
(2.0 / PI).sqrt() * sum / x.sqrt()
}
pub fn struve_h1(&self, x: f64) -> f64 {
let mut sum = 0.0;
let mut term = x * x / 3.0;
for n in 1..=100 {
if n > 1 {
term *= -x * x / ((2 * n + 2) as f64 * (2 * n) as f64);
}
let gamma_arg = n as f64 + 2.5;
sum += term / self.tgamma(gamma_arg);
if term.abs() < F64_EPSILON * sum.abs() * 0.001 {
break;
}
}
(2.0 / PI).sqrt() * sum * x.sqrt()
}
pub fn kelvin_ber(&self, x: f64) -> f64 {
let mut sum: f64 = 0.0;
let mut term = 1.0;
for n in 1..=50 {
let k = 4 * n;
term *= -(x / (k as f64)).powi(4) / ((k as f64 - 2.0) * (k as f64 - 3.0)); if term.abs() < F64_EPSILON * sum.abs() {
break;
}
if n % 2 == 0 {
sum += term;
} else {
sum -= term;
}
}
1.0 + sum
}
pub fn kelvin_bei(&self, x: f64) -> f64 {
let mut sum: f64 = 0.0;
let mut term = x * x / 4.0;
for n in 1..=50 {
let k = 4 * n + 2;
term *= -(x / (k as f64)).powi(4) / ((k as f64 - 2.0) * (k as f64 - 3.0)); if term.abs() < F64_EPSILON * sum.abs() {
break;
}
if n % 2 == 0 {
sum += term;
} else {
sum -= term;
}
}
sum
}
}
#[derive(Debug)]
pub struct X86RandomNumberGenerators {
mt_state: Mutex<Vec<u64>>,
mt_index: AtomicUsize,
xoshiro_state: Mutex<[u64; 4]>,
pcg_state: AtomicU64,
pcg_inc: u64,
splitmix_state: AtomicU64,
}
impl X86RandomNumberGenerators {
pub fn new() -> Self {
Self {
mt_state: Mutex::new(vec![0u64; 624]),
mt_index: AtomicUsize::new(625),
xoshiro_state: Mutex::new([
123456789123456789,
987654321987654321,
111111111111111111,
222222222222222222,
]),
pcg_state: AtomicU64::new(0x853c49e6748fea9b),
pcg_inc: 0xda3e39cb94b95bdb,
splitmix_state: AtomicU64::new(123456789),
}
}
pub fn mt_init(&self, seed: u64) {
let mut state = self.mt_state.lock().unwrap();
state[0] = seed;
for i in 1..624 {
state[i] = 6364136223846793005u64
.wrapping_mul(state[i - 1] ^ (state[i - 1] >> 62))
.wrapping_add(i as u64);
}
self.mt_index.store(625, AtomicOrdering::Relaxed);
}
pub fn mt_gen_u64(&self) -> u64 {
let mut idx = self.mt_index.load(AtomicOrdering::Relaxed);
let mut state = self.mt_state.lock().unwrap();
if idx >= 624 {
self.mt_twist(&mut state);
idx = 0;
}
let mut y = state[idx];
idx += 1;
self.mt_index.store(idx, AtomicOrdering::Relaxed);
y ^= (y >> 29) & 0x5555555555555555;
y ^= (y << 17) & 0x71D67FFFEDA60000;
y ^= (y << 37) & 0xFFF7EEE000000000;
y ^= y >> 43;
y
}
fn mt_twist(&self, state: &mut [u64]) {
for i in 0..624 {
let y = (state[i] & 0xFFFFFFFF80000000) | (state[(i + 1) % 624] & 0x7FFFFFFF);
state[i] = state[(i + 397) % 624] ^ (y >> 1);
if y & 1 != 0 {
state[i] ^= 0x9908B0DF;
}
}
}
pub fn xoshiro_gen_u64(&self) -> u64 {
let mut s = self.xoshiro_state.lock().unwrap();
let result = s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = s[1] << 17;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = s[3].rotate_left(45);
result
}
pub fn xoshiro_seed(&self, seed: u64) {
let mut s = self.xoshiro_state.lock().unwrap();
let mut sm = SplitMix64::new(seed);
s[0] = sm.next();
s[1] = sm.next();
s[2] = sm.next();
s[3] = sm.next();
}
pub fn pcg_gen_u32(&self) -> u32 {
let old_state = self.pcg_state.load(AtomicOrdering::Relaxed);
let new_state = old_state
.wrapping_mul(6364136223846793005)
.wrapping_add(self.pcg_inc | 1);
self.pcg_state.store(new_state, AtomicOrdering::Relaxed);
let xorshifted = (((old_state >> 18) ^ old_state) >> 27) as u32;
let rot = (old_state >> 59) as u32;
xorshifted.rotate_right(rot)
}
pub fn splitmix_gen_u64(&self) -> u64 {
let mut z = self
.splitmix_state
.load(AtomicOrdering::Relaxed)
.wrapping_add(0x9e3779b97f4a7c15);
self.splitmix_state.store(z, AtomicOrdering::Relaxed);
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
pub fn rdrand_u16(&self) -> Option<u16> {
if !is_x86_feature_detected!("rdrand") {
return None;
}
let mut val: u16 = 0;
unsafe {
for _ in 0..10 {
if _rdrand16_step(&mut val) == 1 {
return Some(val);
}
}
}
None
}
pub fn rdrand_u32(&self) -> Option<u32> {
if !is_x86_feature_detected!("rdrand") {
return None;
}
let mut val: u32 = 0;
unsafe {
for _ in 0..10 {
if _rdrand32_step(&mut val) == 1 {
return Some(val);
}
}
}
None
}
pub fn rdrand_u64(&self) -> Option<u64> {
if !is_x86_feature_detected!("rdrand") {
return None;
}
let mut val: u64 = 0;
unsafe {
for _ in 0..10 {
if _rdrand64_step(&mut val) == 1 {
return Some(val);
}
}
}
None
}
pub fn rdseed_u32(&self) -> Option<u32> {
if !is_x86_feature_detected!("rdseed") {
return None;
}
let mut val: u32 = 0;
unsafe {
for _ in 0..10 {
if _rdseed32_step(&mut val) == 1 {
return Some(val);
}
}
}
None
}
pub fn rdseed_u64(&self) -> Option<u64> {
if !is_x86_feature_detected!("rdseed") {
return None;
}
let mut val: u64 = 0;
unsafe {
for _ in 0..10 {
if _rdseed64_step(&mut val) == 1 {
return Some(val);
}
}
}
None
}
pub fn uniform_f64(&self) -> f64 {
let u = self.xoshiro_gen_u64();
(u >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
pub fn uniform_range(&self, a: f64, b: f64) -> f64 {
a + (b - a) * self.uniform_f64()
}
pub fn normal(&self, mean: f64, stddev: f64) -> f64 {
let u1 = self.uniform_f64();
let u2 = self.uniform_f64();
let z = (-2.0 * u1.max(1e-10).ln()).sqrt() * (TAU * u2).cos();
mean + stddev * z
}
pub fn normal_pair(&self, mean: f64, stddev: f64) -> (f64, f64) {
let u1 = self.uniform_f64();
let u2 = self.uniform_f64();
let r = (-2.0 * u1.max(1e-10).ln()).sqrt();
let z1 = r * (TAU * u2).cos();
let z2 = r * (TAU * u2).sin();
(mean + stddev * z1, mean + stddev * z2)
}
pub fn exponential(&self, lambda: f64) -> f64 {
-self.uniform_f64().max(1e-10).ln() / lambda
}
pub fn poisson(&self, lambda: f64) -> u64 {
if lambda < 30.0 {
let l = (-lambda).exp();
let mut k = 0u64;
let mut p = 1.0;
loop {
k += 1;
p *= self.uniform_f64();
if p <= l {
return k - 1;
}
}
} else {
let x = self.normal(lambda, lambda.sqrt());
(x.max(0.0).round()) as u64
}
}
pub fn binomial(&self, n: u64, p: f64) -> u64 {
if p == 0.0 {
return 0;
}
if p == 1.0 {
return n;
}
if n < 25 {
let mut count = 0u64;
for _ in 0..n {
if self.uniform_f64() < p {
count += 1;
}
}
count
} else {
let mean = n as f64 * p;
let stddev = (n as f64 * p * (1.0 - p)).sqrt();
let x = self.normal(mean, stddev);
(x.max(0.0).round().min(n as f64)) as u64
}
}
pub fn gamma(&self, alpha: f64, theta: f64) -> f64 {
if alpha < 1.0 {
let d = 1.0 - alpha + 1.0 / 3.0;
let c = (1.0 - alpha) / d;
loop {
let x = self.uniform_f64();
let v = self.normal(0.0, 1.0);
if x < 1.0 - c * v.powi(3) {
let result = d * v;
if result > 0.0 {
return theta * result.powf(1.0 / alpha);
}
}
}
} else {
let d = alpha - 1.0 / 3.0;
let c = 1.0 / (9.0 * d).sqrt();
loop {
let mut v;
let mut x;
loop {
x = self.normal(0.0, 1.0);
v = 1.0 + c * x;
if v > 0.0 {
break;
}
}
v = v * v * v;
let u = self.uniform_f64();
if u < 1.0 - 0.331 * (x * x).powi(2)
|| (x * x).ln() < 0.5 * x * x + d * (1.0 - v + v.ln())
{
return theta * d * v;
}
}
}
}
pub fn beta_distribution(&self, alpha: f64, beta: f64) -> f64 {
let x = self.gamma(alpha, 1.0);
let y = self.gamma(beta, 1.0);
x / (x + y)
}
pub fn chi_squared(&self, k: f64) -> f64 {
self.gamma(k / 2.0, 2.0)
}
pub fn student_t(&self, nu: f64) -> f64 {
let z = self.normal(0.0, 1.0);
let chi = self.chi_squared(nu);
z / (chi / nu).sqrt()
}
pub fn fisher_f(&self, d1: f64, d2: f64) -> f64 {
let chi1 = self.chi_squared(d1) / d1;
let chi2 = self.chi_squared(d2) / d2;
chi1 / chi2
}
}
struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
fn new(seed: u64) -> Self {
Self { state: seed }
}
fn next(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9e3779b97f4a7c15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
}
#[derive(Debug, Clone)]
pub struct X86StatisticsSupport;
impl X86StatisticsSupport {
pub fn new() -> Self {
Self
}
pub fn mean(&self, data: &[f64]) -> f64 {
if data.is_empty() {
return f64::NAN;
}
let sum: f64 = data.iter().sum();
sum / data.len() as f64
}
pub fn weighted_mean(&self, data: &[f64], weights: &[f64]) -> f64 {
if data.is_empty() || data.len() != weights.len() {
return f64::NAN;
}
let mut sum_wx = 0.0;
let mut sum_w = 0.0;
for i in 0..data.len() {
sum_wx += data[i] * weights[i];
sum_w += weights[i];
}
if sum_w == 0.0 {
return f64::NAN;
}
sum_wx / sum_w
}
pub fn median(&self, data: &[f64]) -> f64 {
if data.is_empty() {
return f64::NAN;
}
let mut sorted: Vec<f64> = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let mid = sorted.len() / 2;
if sorted.len() % 2 == 0 {
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[mid]
}
}
pub fn variance(&self, data: &[f64]) -> f64 {
if data.len() < 2 {
return 0.0;
}
let mean = self.mean(data);
let sum_sq_diff: f64 = data.iter().map(|&x| (x - mean).powi(2)).sum();
sum_sq_diff / (data.len() - 1) as f64
}
pub fn variance_pop(&self, data: &[f64]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mean = self.mean(data);
let sum_sq: f64 = data.iter().map(|&x| (x - mean).powi(2)).sum();
sum_sq / data.len() as f64
}
pub fn stddev(&self, data: &[f64]) -> f64 {
self.variance(data).sqrt()
}
pub fn stddev_pop(&self, data: &[f64]) -> f64 {
self.variance_pop(data).sqrt()
}
pub fn skewness(&self, data: &[f64]) -> f64 {
if data.len() < 3 {
return 0.0;
}
let mean = self.mean(data);
let std = self.stddev(data);
if std == 0.0 {
return 0.0;
}
let n = data.len() as f64;
let sum_cubed: f64 = data.iter().map(|&x| ((x - mean) / std).powi(3)).sum();
n / ((n - 1.0) * (n - 2.0)) * sum_cubed
}
pub fn kurtosis(&self, data: &[f64]) -> f64 {
if data.len() < 4 {
return 0.0;
}
let mean = self.mean(data);
let std = self.stddev(data);
if std == 0.0 {
return 0.0;
}
let n = data.len() as f64;
let sum_4th: f64 = data.iter().map(|&x| ((x - mean) / std).powi(4)).sum();
let k = n * (n + 1.0) / ((n - 1.0) * (n - 2.0) * (n - 3.0)) * sum_4th;
k - 3.0 * (n - 1.0).powi(2) / ((n - 2.0) * (n - 3.0))
}
pub fn min(&self, data: &[f64]) -> f64 {
data.iter().cloned().fold(f64::INFINITY, f64::min)
}
pub fn max(&self, data: &[f64]) -> f64 {
data.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}
pub fn range(&self, data: &[f64]) -> f64 {
self.max(data) - self.min(data)
}
pub fn quantile(&self, data: &[f64], q: f64) -> f64 {
if data.is_empty() {
return f64::NAN;
}
let mut sorted: Vec<f64> = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let pos = q * (sorted.len() - 1) as f64;
let lo = pos.floor() as usize;
let hi = pos.ceil() as usize;
if lo == hi {
sorted[lo]
} else {
let frac = pos - lo as f64;
sorted[lo] * (1.0 - frac) + sorted[hi] * frac
}
}
pub fn iqr(&self, data: &[f64]) -> f64 {
self.quantile(data, 0.75) - self.quantile(data, 0.25)
}
pub fn summary(&self, data: &[f64]) -> StatSummary {
StatSummary {
count: data.len(),
mean: self.mean(data),
median: self.median(data),
min: self.min(data),
max: self.max(data),
stddev: self.stddev(data),
variance: self.variance(data),
skewness: self.skewness(data),
kurtosis: self.kurtosis(data),
q1: self.quantile(data, 0.25),
q3: self.quantile(data, 0.75),
iqr: self.iqr(data),
}
}
pub fn pearson_correlation(&self, x: &[f64], y: &[f64]) -> f64 {
if x.len() != y.len() || x.len() < 2 {
return f64::NAN;
}
let mean_x = self.mean(x);
let mean_y = self.mean(y);
let mut cov = 0.0;
let mut var_x = 0.0;
let mut var_y = 0.0;
for i in 0..x.len() {
let dx = x[i] - mean_x;
let dy = y[i] - mean_y;
cov += dx * dy;
var_x += dx * dx;
var_y += dy * dy;
}
let denom = (var_x * var_y).sqrt();
if denom == 0.0 {
return 0.0;
}
cov / denom
}
pub fn spearman_correlation(&self, x: &[f64], y: &[f64]) -> f64 {
if x.len() != y.len() || x.len() < 2 {
return f64::NAN;
}
let rank_x = self.rank(x);
let rank_y = self.rank(y);
self.pearson_correlation(&rank_x, &rank_y)
}
fn rank(&self, data: &[f64]) -> Vec<f64> {
let n = data.len();
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by(|&a, &b| data[a].partial_cmp(&data[b]).unwrap_or(Ordering::Equal));
let mut ranks = vec![0.0f64; n];
let mut i = 0;
while i < n {
let mut j = i;
while j + 1 < n && (data[indices[j]] - data[indices[j + 1]]).abs() < F64_EPSILON {
j += 1;
}
let avg_rank = (i + j) as f64 / 2.0 + 1.0;
for k in i..=j {
ranks[indices[k]] = avg_rank;
}
i = j + 1;
}
ranks
}
pub fn kendall_correlation(&self, x: &[f64], y: &[f64]) -> f64 {
if x.len() != y.len() || x.len() < 2 {
return f64::NAN;
}
let n = x.len();
let mut concordant = 0u64;
let mut discordant = 0u64;
for i in 0..n {
for j in (i + 1)..n {
let dx = x[i] - x[j];
let dy = y[i] - y[j];
let prod = dx * dy;
if prod > 0.0 {
concordant += 1;
} else if prod < 0.0 {
discordant += 1;
}
}
}
let total = n * (n - 1) / 2;
if total == 0 {
return 0.0;
}
(concordant as f64 - discordant as f64) / total as f64
}
pub fn linear_regression(&self, x: &[f64], y: &[f64]) -> LinearRegressionResult {
if x.len() != y.len() || x.len() < 2 {
return LinearRegressionResult {
slope: f64::NAN,
intercept: f64::NAN,
r_squared: f64::NAN,
};
}
let n = x.len() as f64;
let sum_x: f64 = x.iter().sum();
let sum_y: f64 = y.iter().sum();
let sum_xy: f64 = x.iter().zip(y.iter()).map(|(&xi, &yi)| xi * yi).sum();
let sum_x2: f64 = x.iter().map(|&xi| xi * xi).sum();
let sum_y2: f64 = y.iter().map(|&yi| yi * yi).sum();
let denom = n * sum_x2 - sum_x * sum_x;
if denom == 0.0 {
return LinearRegressionResult {
slope: 0.0,
intercept: self.mean(y),
r_squared: 0.0,
};
}
let slope = (n * sum_xy - sum_x * sum_y) / denom;
let intercept = (sum_y - slope * sum_x) / n;
let y_mean = sum_y / n;
let ss_res: f64 = x
.iter()
.zip(y.iter())
.map(|(&xi, &yi)| (yi - (slope * xi + intercept)).powi(2))
.sum();
let ss_tot: f64 = y.iter().map(|&yi| (yi - y_mean).powi(2)).sum();
let r_squared = if ss_tot == 0.0 {
1.0
} else {
1.0 - ss_res / ss_tot
};
LinearRegressionResult {
slope,
intercept,
r_squared,
}
}
pub fn polynomial_regression(&self, x: &[f64], y: &[f64], degree: usize) -> Vec<f64> {
if x.len() != y.len() || x.len() <= degree || degree == 0 {
return vec![self.mean(y)];
}
let n = x.len();
let m = degree + 1;
let mut ata = vec![vec![0.0f64; m]; m];
let mut aty = vec![0.0f64; m];
for i in 0..n {
let mut xi_pow = 1.0;
let mut powers = Vec::with_capacity(m);
for _ in 0..m {
powers.push(xi_pow);
xi_pow *= x[i];
}
for p in 0..m {
aty[p] += powers[p] * y[i];
for q in 0..m {
ata[p][q] += powers[p] * powers[q];
}
}
}
self.solve_linear_system(&ata, &aty, m)
}
pub fn logistic_regression(
&self,
x: &[f64],
y: &[f64],
max_iter: usize,
lr: f64,
) -> (f64, f64) {
if x.len() != y.len() || x.is_empty() {
return (0.0, 0.0);
}
let mut beta0 = 0.0f64;
let mut beta1 = 0.0f64;
let n = x.len() as f64;
for _ in 0..max_iter {
let mut grad0 = 0.0;
let mut grad1 = 0.0;
for i in 0..x.len() {
let z = beta0 + beta1 * x[i];
let p = 1.0 / (1.0 + (-z).exp());
let err = y[i] - p;
grad0 += err;
grad1 += err * x[i];
}
beta0 += lr * grad0 / n;
beta1 += lr * grad1 / n;
}
(beta0, beta1)
}
pub fn logistic_predict(&self, x: f64, beta0: f64, beta1: f64) -> f64 {
let z = beta0 + beta1 * x;
1.0 / (1.0 + (-z).exp())
}
fn solve_linear_system(&self, a: &[Vec<f64>], b: &[f64], m: usize) -> Vec<f64> {
let mut aug = vec![vec![0.0f64; m + 1]; m];
for i in 0..m {
for j in 0..m {
aug[i][j] = a[i][j];
}
aug[i][m] = b[i];
}
for col in 0..m {
let mut max_row = col;
for row in col..m {
if aug[row][col].abs() > aug[max_row][col].abs() {
max_row = row;
}
}
if aug[max_row][col].abs() < F64_EPSILON {
return vec![0.0; m]; }
aug.swap(col, max_row);
for row in (col + 1)..m {
let factor = aug[row][col] / aug[col][col];
for j in col..=m {
aug[row][j] -= factor * aug[col][j];
}
}
}
let mut x = vec![0.0f64; m];
for i in (0..m).rev() {
let mut sum = aug[i][m];
for j in (i + 1)..m {
sum -= aug[i][j] * x[j];
}
x[i] = sum / aug[i][i];
}
x
}
pub fn t_test(&self, sample1: &[f64], sample2: &[f64]) -> (f64, f64, f64) {
let n1 = sample1.len() as f64;
let n2 = sample2.len() as f64;
if n1 < 2.0 || n2 < 2.0 {
return (f64::NAN, f64::NAN, f64::NAN);
}
let m1 = self.mean(sample1);
let m2 = self.mean(sample2);
let v1 = self.variance(sample1);
let v2 = self.variance(sample2);
let se = (v1 / n1 + v2 / n2).sqrt();
if se == 0.0 {
return (0.0, 1.0, n1 + n2 - 2.0);
}
let t = (m1 - m2) / se;
let num = (v1 / n1 + v2 / n2).powi(2);
let denom = (v1 / n1).powi(2) / (n1 - 1.0) + (v2 / n2).powi(2) / (n2 - 1.0);
let df = if denom == 0.0 {
n1 + n2 - 2.0
} else {
num / denom
};
let p = 2.0 * self.t_cdf(-t.abs(), df);
(t, p, df)
}
pub fn paired_t_test(&self, before: &[f64], after: &[f64]) -> (f64, f64, f64) {
if before.len() != after.len() || before.len() < 2 {
return (f64::NAN, f64::NAN, f64::NAN);
}
let diffs: Vec<f64> = before
.iter()
.zip(after.iter())
.map(|(&b, &a)| a - b)
.collect();
self.one_sample_t_test(&diffs, 0.0)
}
pub fn one_sample_t_test(&self, sample: &[f64], mu0: f64) -> (f64, f64, f64) {
let n = sample.len() as f64;
if n < 2.0 {
return (f64::NAN, f64::NAN, f64::NAN);
}
let m = self.mean(sample);
let s = self.stddev(sample);
let se = s / n.sqrt();
if se == 0.0 {
return (0.0, 1.0, n - 1.0);
}
let t = (m - mu0) / se;
let df = n - 1.0;
let p = 2.0 * self.t_cdf(-t.abs(), df);
(t, p, df)
}
pub fn chi_squared_test(&self, observed: &[f64], expected: &[f64]) -> (f64, f64, usize) {
if observed.len() != expected.len() || observed.is_empty() {
return (f64::NAN, f64::NAN, 0);
}
let mut chi2 = 0.0;
for i in 0..observed.len() {
if expected[i] > 0.0 {
let diff = observed[i] - expected[i];
chi2 += diff * diff / expected[i];
}
}
let df = observed.len() - 1;
let p = self.chi2_survival(chi2, df as f64);
(chi2, p, df)
}
pub fn anova(&self, groups: &[Vec<f64>]) -> (f64, f64, usize, usize) {
let k = groups.len();
if k < 2 {
return (f64::NAN, f64::NAN, 0, 0);
}
let all_data: Vec<f64> = groups.iter().flat_map(|g| g.iter().cloned()).collect();
let grand_mean = self.mean(&all_data);
let n_total = all_data.len();
let mut ss_between = 0.0;
let mut ss_within = 0.0;
for group in groups.iter() {
let n_g = group.len() as f64;
let group_mean = self.mean(group);
ss_between += n_g * (group_mean - grand_mean).powi(2);
for &val in group.iter() {
ss_within += (val - group_mean).powi(2);
}
}
let df_between = k - 1;
let df_within = n_total - k;
if df_within == 0 {
return (f64::NAN, f64::NAN, df_between, df_within);
}
let ms_between = ss_between / df_between as f64;
let ms_within = ss_within / df_within as f64;
if ms_within == 0.0 {
return (f64::INFINITY, 0.0, df_between, df_within);
}
let f = ms_between / ms_within;
let p = self.f_survival(f, df_between as f64, df_within as f64);
(f, p, df_between, df_within)
}
fn t_cdf(&self, t: f64, df: f64) -> f64 {
if df <= 0.0 {
return f64::NAN;
}
let x = df / (df + t * t);
0.5 * self.beta_inc(df / 2.0, 0.5, x)
}
fn chi2_survival(&self, x: f64, df: f64) -> f64 {
if x <= 0.0 {
return 1.0;
}
let a = df / 2.0;
let special = X86SpecialFunctions::new();
let g = special.tgamma(a);
let gi = special.gammainc_lower(a, x / 2.0);
1.0 - gi / g
}
fn f_survival(&self, f: f64, df1: f64, df2: f64) -> f64 {
if f <= 0.0 {
return 1.0;
}
let x = df2 / (df2 + df1 * f);
self.beta_inc(df2 / 2.0, df1 / 2.0, x)
}
fn beta_inc(&self, a: f64, b: f64, x: f64) -> f64 {
if x < 0.0 || x > 1.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
if x == 1.0 {
return 1.0;
}
let special = X86SpecialFunctions::new();
let bt = special.tgamma(a + b) / (special.tgamma(a) * special.tgamma(b))
* x.powf(a)
* (1.0 - x).powf(b);
if x < (a + 1.0) / (a + b + 2.0) {
bt * self.beta_cf(a, b, x) / a
} else {
1.0 - bt * self.beta_cf(b, a, 1.0 - x) / b
}
}
fn beta_cf(&self, a: f64, b: f64, x: f64) -> f64 {
let max_iter = 200;
let eps = F64_EPSILON;
let mut f = 1.0;
let mut c = 1.0;
let mut d = 1.0 / (1.0 - (a + b) * x / (a + 1.0));
if d == 0.0 {
d = eps;
}
f = d;
for m in 1..max_iter {
let m2 = 2 * m;
let mut d_term =
1.0 + m as f64 * (b - m as f64) * x / ((a + m2 as f64 - 1.0) * (a + m2 as f64));
d = 1.0 / (d_term * d + 1.0);
if d == 0.0 {
d = eps;
}
c = d_term / c + 1.0;
if c == 0.0 {
c = eps;
}
d_term = 1.0
- (a + m as f64) * (a + b + m as f64) * x
/ ((a + m2 as f64) * (a + m2 as f64 + 1.0));
d = 1.0 / (d_term * d + 1.0);
if d == 0.0 {
d = eps;
}
c = d_term / c + 1.0;
if c == 0.0 {
c = eps;
}
let delta = c * d;
f *= delta;
if (delta - 1.0).abs() < eps {
break;
}
}
f
}
}
#[derive(Debug, Clone, Copy)]
pub struct LinearRegressionResult {
pub slope: f64,
pub intercept: f64,
pub r_squared: f64,
}
#[derive(Debug, Clone)]
pub struct StatSummary {
pub count: usize,
pub mean: f64,
pub median: f64,
pub min: f64,
pub max: f64,
pub stddev: f64,
pub variance: f64,
pub skewness: f64,
pub kurtosis: f64,
pub q1: f64,
pub q3: f64,
pub iqr: f64,
}
impl fmt::Display for StatSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"n={}, mean={:.4}, median={:.4}, min={:.4}, max={:.4}, \
std={:.4}, var={:.4}, skew={:.4}, kurt={:.4}, \
Q1={:.4}, Q3={:.4}, IQR={:.4}",
self.count,
self.mean,
self.median,
self.min,
self.max,
self.stddev,
self.variance,
self.skewness,
self.kurtosis,
self.q1,
self.q3,
self.iqr,
)
}
}
#[derive(Debug, Clone)]
pub struct X86OptimizationSupport;
#[derive(Debug, Clone)]
pub struct OptimizationResult {
pub x: Vec<f64>,
pub fval: f64,
pub iterations: usize,
pub nfev: usize,
pub converged: bool,
pub grad_norm: f64,
}
impl X86OptimizationSupport {
pub fn new() -> Self {
Self
}
pub fn gradient_descent<F>(
&self,
f: F,
x0: &[f64],
learning_rate: f64,
max_iter: usize,
tol: f64,
) -> OptimizationResult
where
F: Fn(&[f64]) -> (f64, Vec<f64>),
{
let mut x = x0.to_vec();
let n = x.len();
let (mut fval, mut grad) = f(&x);
let mut nfev = 1;
for iter in 0..max_iter {
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
if grad_norm < tol {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: true,
grad_norm,
};
}
for i in 0..n {
x[i] -= learning_rate * grad[i];
}
let (new_fval, new_grad) = f(&x);
nfev += 1;
fval = new_fval;
grad = new_grad;
}
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
OptimizationResult {
x,
fval,
iterations: max_iter,
nfev,
converged: grad_norm < tol,
grad_norm,
}
}
pub fn stochastic_gradient_descent<F>(
&self,
grad_i: F,
x0: &[f64],
learning_rate: f64,
n_samples: usize,
batch_size: usize,
max_epochs: usize,
tol: f64,
rng: &X86RandomNumberGenerators,
) -> OptimizationResult
where
F: Fn(usize, &[f64]) -> Vec<f64>,
{
let mut x = x0.to_vec();
let n = x.len();
let mut nfev = 0usize;
for epoch in 0..max_epochs {
let mut indices: Vec<usize> = (0..n_samples).collect();
for i in (1..n_samples).rev() {
let j = (rng.uniform_f64() * (i + 1) as f64) as usize;
indices.swap(i, j);
}
let mut total_grad_norm = 0.0;
for batch_start in (0..n_samples).step_by(batch_size) {
let batch_end = std::cmp::min(batch_start + batch_size, n_samples);
let mut batch_grad = vec![0.0; n];
for &idx in &indices[batch_start..batch_end] {
let gi = grad_i(idx, &x);
for i in 0..n {
batch_grad[i] += gi[i];
}
nfev += 1;
}
let batch_size_f = (batch_end - batch_start) as f64;
for i in 0..n {
batch_grad[i] /= batch_size_f;
}
let step_lr = learning_rate / (1.0 + epoch as f64 * learning_rate).sqrt();
for i in 0..n {
x[i] -= step_lr * batch_grad[i];
}
total_grad_norm += batch_grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
}
let avg_grad_norm = total_grad_norm / (n_samples as f64 / batch_size as f64);
if avg_grad_norm < tol {
return OptimizationResult {
x: x.clone(),
fval: 0.0, iterations: (epoch + 1) * n_samples / batch_size,
nfev,
converged: true,
grad_norm: avg_grad_norm,
};
}
}
OptimizationResult {
x: x.clone(),
fval: 0.0,
iterations: max_epochs * n_samples / batch_size,
nfev,
converged: false,
grad_norm: 0.0,
}
}
pub fn newton_raphson<F>(&self, f: F, x0: f64, max_iter: usize, tol: f64) -> Option<f64>
where
F: Fn(f64) -> (f64, f64),
{
let mut x = x0;
for _ in 0..max_iter {
let (fx, dfx) = f(x);
if fx.abs() < tol {
return Some(x);
}
if dfx.abs() < F64_EPSILON {
return None; }
x = x - fx / dfx;
}
if f(x).0.abs() < tol {
Some(x)
} else {
None
}
}
pub fn newton_multivariate<F>(
&self,
f_grad_hess: F,
x0: &[f64],
max_iter: usize,
tol: f64,
) -> OptimizationResult
where
F: Fn(&[f64]) -> (f64, Vec<f64>, Vec<Vec<f64>>),
{
let n = x0.len();
let mut x = x0.to_vec();
let (mut fval, mut grad, mut hess) = f_grad_hess(&x);
let mut nfev = 1;
for iter in 0..max_iter {
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
if grad_norm < tol {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: true,
grad_norm,
};
}
let neg_grad: Vec<f64> = grad.iter().map(|&g| -g).collect();
let delta = self.solve_linear_system_dense(&hess, &neg_grad);
if delta.is_empty() {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: false,
grad_norm,
};
}
for i in 0..n {
x[i] += delta[i];
}
let (new_fval, new_grad, new_hess) = f_grad_hess(&x);
nfev += 1;
fval = new_fval;
grad = new_grad;
hess = new_hess;
}
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
OptimizationResult {
x,
fval,
iterations: max_iter,
nfev,
converged: grad_norm < tol,
grad_norm,
}
}
fn solve_linear_system_dense(&self, a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = a.len();
if n == 0 || a[0].len() != n || b.len() != n {
return Vec::new();
}
let mut aug = vec![vec![0.0; n + 1]; n];
for i in 0..n {
for j in 0..n {
aug[i][j] = a[i][j];
}
aug[i][n] = b[i];
}
for col in 0..n {
let mut max_row = col;
for row in col..n {
if aug[row][col].abs() > aug[max_row][col].abs() {
max_row = row;
}
}
if aug[max_row][col].abs() < F64_EPSILON {
return Vec::new();
}
aug.swap(col, max_row);
for row in (col + 1)..n {
let factor = aug[row][col] / aug[col][col];
for j in col..=n {
aug[row][j] -= factor * aug[col][j];
}
}
}
let mut x = vec![0.0; n];
for i in (0..n).rev() {
let mut sum = aug[i][n];
for j in (i + 1)..n {
sum -= aug[i][j] * x[j];
}
x[i] = sum / aug[i][i];
}
x
}
pub fn bfgs<F>(
&self,
f_grad: F,
x0: &[f64],
max_iter: usize,
tol: f64,
line_search_max: usize,
) -> OptimizationResult
where
F: Fn(&[f64]) -> (f64, Vec<f64>),
{
let n = x0.len();
let mut x = x0.to_vec();
let (mut fval, mut grad) = f_grad(&x);
let mut nfev = 1;
let mut h: Vec<Vec<f64>> = vec![vec![0.0; n]; n];
for i in 0..n {
h[i][i] = 1.0;
}
for iter in 0..max_iter {
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
if grad_norm < tol {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: true,
grad_norm,
};
}
let mut p = vec![0.0; n];
for i in 0..n {
let mut dot = 0.0;
for j in 0..n {
dot += h[i][j] * grad[j];
}
p[i] = -dot;
}
let mut alpha = 1.0;
let c1 = 1e-4;
let p_dot_grad: f64 = p.iter().zip(grad.iter()).map(|(&pi, &gi)| pi * gi).sum();
let mut x_new = x.clone();
for _ in 0..line_search_max {
for i in 0..n {
x_new[i] = x[i] + alpha * p[i];
}
let (f_new, _) = f_grad(&x_new);
nfev += 1;
if f_new <= fval + c1 * alpha * p_dot_grad {
break;
}
alpha *= 0.5;
}
let s: Vec<f64> = (0..n).map(|i| alpha * p[i]).collect();
let (new_fval, new_grad) = f_grad(&x_new);
nfev += 1;
let y: Vec<f64> = (0..n).map(|i| new_grad[i] - grad[i]).collect();
let rho_num: f64 = s.iter().zip(y.iter()).map(|(&si, &yi)| si * yi).sum();
if rho_num.abs() < F64_EPSILON {
x = x_new;
fval = new_fval;
grad = new_grad;
continue;
}
let rho = 1.0 / rho_num;
let hy: Vec<f64> = (0..n)
.map(|i| h[i].iter().zip(y.iter()).map(|(&hij, &yj)| hij * yj).sum())
.collect();
for i in 0..n {
for j in 0..n {
h[i][j] += rho
* (s[i]
* s[j]
* (rho_num
+ rho * hy.iter().zip(y.iter()).map(|(&hyk, &yk)| hyk * yk).sum())
- hy[i] * s[j]
- s[i] * hy[j])
/ rho_num;
}
}
x = x_new;
fval = new_fval;
grad = new_grad;
}
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
OptimizationResult {
x,
fval,
iterations: max_iter,
nfev,
converged: grad_norm < tol,
grad_norm,
}
}
pub fn lbfgs<F>(
&self,
f_grad: F,
x0: &[f64],
m: usize,
max_iter: usize,
tol: f64,
line_search_max: usize,
) -> OptimizationResult
where
F: Fn(&[f64]) -> (f64, Vec<f64>),
{
let n = x0.len();
let mut x = x0.to_vec();
let (mut fval, mut grad) = f_grad(&x);
let mut nfev = 1;
let mut s_list: VecDeque<Vec<f64>> = VecDeque::with_capacity(m);
let mut y_list: VecDeque<Vec<f64>> = VecDeque::with_capacity(m);
let mut rho_list: VecDeque<f64> = VecDeque::with_capacity(m);
for iter in 0..max_iter {
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
if grad_norm < tol {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: true,
grad_norm,
};
}
let q = self.lbfgs_two_loop(&grad, &s_list, &y_list, &rho_list);
let p: Vec<f64> = q.iter().map(|qi| -qi).collect();
let mut alpha = 1.0;
let c1 = 1e-4;
let p_dot_grad: f64 = p.iter().zip(grad.iter()).map(|(&pi, &gi)| pi * gi).sum();
let mut x_new = x.clone();
for _ in 0..line_search_max {
for i in 0..n {
x_new[i] = x[i] + alpha * p[i];
}
let (f_new, _) = f_grad(&x_new);
nfev += 1;
if f_new <= fval + c1 * alpha * p_dot_grad {
break;
}
alpha *= 0.5;
}
let s: Vec<f64> = (0..n).map(|i| alpha * p[i]).collect();
let (new_fval, new_grad) = f_grad(&x_new);
nfev += 1;
let y: Vec<f64> = (0..n).map(|i| new_grad[i] - grad[i]).collect();
let sy: f64 = s.iter().zip(y.iter()).map(|(&si, &yi)| si * yi).sum();
if sy.abs() > F64_EPSILON {
let rho = 1.0 / sy;
if s_list.len() == m {
s_list.pop_front();
y_list.pop_front();
rho_list.pop_front();
}
s_list.push_back(s);
y_list.push_back(y);
rho_list.push_back(rho);
}
x = x_new;
fval = new_fval;
grad = new_grad;
}
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
OptimizationResult {
x,
fval,
iterations: max_iter,
nfev,
converged: grad_norm < tol,
grad_norm,
}
}
fn lbfgs_two_loop(
&self,
grad: &[f64],
s_list: &VecDeque<Vec<f64>>,
y_list: &VecDeque<Vec<f64>>,
rho_list: &VecDeque<f64>,
) -> Vec<f64> {
let n = grad.len();
let k = s_list.len();
let mut q = grad.to_vec();
let mut alpha_list = vec![0.0; k];
for i in (0..k).rev() {
let s_dot_q: f64 = s_list[i]
.iter()
.zip(q.iter())
.map(|(&si, &qi)| si * qi)
.sum();
alpha_list[i] = rho_list[i] * s_dot_q;
for j in 0..n {
q[j] -= alpha_list[i] * y_list[i][j];
}
}
let gamma = if k > 0 {
let last_y = &y_list[k - 1];
let last_s = &s_list[k - 1];
let yy: f64 = last_y.iter().map(|&yi| yi * yi).sum();
let sy: f64 = last_s
.iter()
.zip(last_y.iter())
.map(|(&si, &yi)| si * yi)
.sum();
if sy.abs() > F64_EPSILON {
sy / yy
} else {
1.0
}
} else {
1.0
};
let mut r = q.iter().map(|&qi| gamma * qi).collect::<Vec<f64>>();
for i in 0..k {
let y_dot_r: f64 = y_list[i]
.iter()
.zip(r.iter())
.map(|(&yi, &ri)| yi * ri)
.sum();
let beta = rho_list[i] * y_dot_r;
for j in 0..n {
r[j] += s_list[i][j] * (alpha_list[i] - beta);
}
}
r
}
pub fn nelder_mead<F>(&self, f: F, x0: &[f64], max_iter: usize, tol: f64) -> OptimizationResult
where
F: Fn(&[f64]) -> f64,
{
let n = x0.len();
let mut nfev = 0usize;
let mut simplex: Vec<(Vec<f64>, f64)> = Vec::with_capacity(n + 1);
simplex.push((x0.to_vec(), f(x0)));
nfev += 1;
let step = 1.0;
for i in 0..n {
let mut xi = x0.to_vec();
xi[i] += step;
let fval = f(&xi);
nfev += 1;
simplex.push((xi, fval));
}
for iter in 0..max_iter {
simplex.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
let fmean: f64 = simplex.iter().map(|(_, fv)| fv).sum::<f64>() / (n + 1) as f64;
let fstd: f64 = simplex
.iter()
.map(|(_, fv)| (fv - fmean).powi(2))
.sum::<f64>()
.sqrt()
/ (n as f64).sqrt();
if fstd < tol {
return OptimizationResult {
x: simplex[0].0.clone(),
fval: simplex[0].1,
iterations: iter + 1,
nfev,
converged: true,
grad_norm: fstd,
};
}
let mut centroid = vec![0.0; n];
for k in 0..n {
for j in 0..n {
centroid[j] += simplex[k].0[j];
}
}
for j in 0..n {
centroid[j] /= n as f64;
}
let alpha = 1.0;
let xr: Vec<f64> = (0..n)
.map(|j| centroid[j] + alpha * (centroid[j] - simplex[n].0[j]))
.collect();
let fr = f(&xr);
nfev += 1;
if fr < simplex[0].1 {
let gamma = 2.0;
let xe: Vec<f64> = (0..n)
.map(|j| centroid[j] + gamma * (xr[j] - centroid[j]))
.collect();
let fe = f(&xe);
nfev += 1;
if fe < fr {
simplex[n] = (xe, fe);
} else {
simplex[n] = (xr, fr);
}
} else if fr < simplex[n - 1].1 {
simplex[n] = (xr, fr);
} else {
let rho = 0.5;
let xc: Vec<f64> = (0..n)
.map(|j| centroid[j] + rho * (simplex[n].0[j] - centroid[j]))
.collect();
let fc = f(&xc);
nfev += 1;
if fc < simplex[n].1 {
simplex[n] = (xc, fc);
} else {
for i in 1..=n {
for j in 0..n {
simplex[i].0[j] = 0.5 * (simplex[i].0[j] + simplex[0].0[j]);
}
simplex[i].1 = f(&simplex[i].0);
nfev += 1;
}
}
}
}
simplex.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
OptimizationResult {
x: simplex[0].0.clone(),
fval: simplex[0].1,
iterations: max_iter,
nfev,
converged: false,
grad_norm: 0.0,
}
}
pub fn simulated_annealing<F, N>(
&self,
f: F,
neighbor: N,
x0: &[f64],
t_initial: f64,
t_final: f64,
cooling_rate: f64,
steps_per_temp: usize,
rng: &X86RandomNumberGenerators,
) -> OptimizationResult
where
F: Fn(&[f64]) -> f64,
N: Fn(&[f64], &X86RandomNumberGenerators) -> Vec<f64>,
{
let n = x0.len();
let mut x = x0.to_vec();
let mut fx = f(&x);
let mut nfev = 1usize;
let mut t = t_initial;
let mut best_x = x.clone();
let mut best_fx = fx;
let mut iter = 0;
while t > t_final {
for _ in 0..steps_per_temp {
let x_new = neighbor(&x, rng);
let f_new = f(&x_new);
nfev += 1;
iter += 1;
let delta = f_new - fx;
if delta < 0.0 || rng.uniform_f64() < (-delta / t).exp() {
x = x_new;
fx = f_new;
if fx < best_fx {
best_x = x.clone();
best_fx = fx;
}
}
}
t *= cooling_rate;
}
OptimizationResult {
x: best_x,
fval: best_fx,
iterations: iter,
nfev,
converged: t <= t_final,
grad_norm: 0.0,
}
}
pub fn conjugate_gradient<F>(
&self,
f_grad: F,
x0: &[f64],
max_iter: usize,
tol: f64,
line_search_max: usize,
) -> OptimizationResult
where
F: Fn(&[f64]) -> (f64, Vec<f64>),
{
let n = x0.len();
let mut x = x0.to_vec();
let (mut fval, mut grad) = f_grad(&x);
let mut nfev = 1;
let mut p: Vec<f64> = grad.iter().map(|&g| -g).collect();
let mut grad_old_norm_sq: f64 = grad.iter().map(|&g| g * g).sum();
for iter in 0..max_iter {
let grad_norm = grad_old_norm_sq.sqrt();
if grad_norm < tol {
return OptimizationResult {
x,
fval,
iterations: iter + 1,
nfev,
converged: true,
grad_norm,
};
}
let mut alpha = 1.0;
let c1 = 1e-4;
let p_dot_grad: f64 = p.iter().zip(grad.iter()).map(|(&pi, &gi)| pi * gi).sum();
let mut x_new = x.clone();
for _ in 0..line_search_max {
for i in 0..n {
x_new[i] = x[i] + alpha * p[i];
}
let (f_new, _) = f_grad(&x_new);
nfev += 1;
if f_new <= fval + c1 * alpha * p_dot_grad {
break;
}
alpha *= 0.5;
}
for i in 0..n {
x[i] += alpha * p[i];
}
let (new_fval, new_grad) = f_grad(&x);
nfev += 1;
let grad_new_norm_sq: f64 = new_grad.iter().map(|&g| g * g).sum();
let beta_fr = if grad_old_norm_sq > 1e-15 {
grad_new_norm_sq / grad_old_norm_sq
} else {
0.0
};
for i in 0..n {
p[i] = -new_grad[i] + beta_fr * p[i];
}
fval = new_fval;
grad = new_grad;
grad_old_norm_sq = grad_new_norm_sq;
}
let grad_norm: f64 = grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
OptimizationResult {
x,
fval,
iterations: max_iter,
nfev,
converged: grad_norm < tol,
grad_norm,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scientific_creation() {
let sci = X86Scientific::new();
let caps = sci.capabilities();
assert!(caps.vector_width_f32 >= 1);
assert!(caps.vector_width_f64 >= 1);
assert!(caps.num_threads >= 1);
}
#[test]
fn test_capabilities_display() {
let caps = X86ScienceCapabilities {
simd_level: X86SIMDLevel::AVX2,
has_fma: true,
has_avx512: false,
has_avx2: true,
has_sse42: true,
vector_width_f32: 8,
vector_width_f64: 4,
num_threads: 8,
};
let s = format!("{}", caps);
assert!(s.contains("AVX2"));
assert!(s.contains("FMA=true"));
}
#[test]
fn test_saxpy() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0f32, 2.0, 3.0, 4.0];
let mut y = vec![5.0f32, 6.0, 7.0, 8.0];
blas.saxpy(4, 2.0, &x, 1, &mut y, 1);
assert!((y[0] - 7.0).abs() < 1e-4);
assert!((y[1] - 10.0).abs() < 1e-4);
assert!((y[2] - 13.0).abs() < 1e-4);
assert!((y[3] - 16.0).abs() < 1e-4);
}
#[test]
fn test_daxpy() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0, 2.0, 3.0];
let mut y = vec![4.0, 5.0, 6.0];
blas.daxpy(3, 0.5, &x, 1, &mut y, 1);
assert!((y[0] - 4.5).abs() < 1e-10);
assert!((y[1] - 6.0).abs() < 1e-10);
assert!((y[2] - 7.5).abs() < 1e-10);
}
#[test]
fn test_sdot() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0f32, 2.0, 3.0];
let y = vec![4.0f32, 5.0, 6.0];
let dot = blas.sdot(3, &x, 1, &y, 1);
assert!((dot - 32.0).abs() < 1e-4);
}
#[test]
fn test_ddot() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0, 2.0, 3.0];
let y = vec![4.0, 5.0, 6.0];
let dot = blas.ddot(3, &x, 1, &y, 1);
assert!((dot - 32.0).abs() < 1e-10);
}
#[test]
fn test_snrm2() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![3.0f32, 4.0];
let nrm = blas.snrm2(2, &x, 1);
assert!((nrm - 5.0).abs() < 1e-4);
}
#[test]
fn test_dnrm2() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![3.0, 4.0];
let nrm = blas.dnrm2(2, &x, 1);
assert!((nrm - 5.0).abs() < 1e-10);
}
#[test]
fn test_sscal() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let mut x = vec![1.0f32, 2.0, 3.0];
blas.sscal(3, 3.0, &mut x, 1);
assert!((x[0] - 3.0).abs() < 1e-4);
assert!((x[2] - 9.0).abs() < 1e-4);
}
#[test]
fn test_dscal() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let mut x = vec![1.0, 2.0, 3.0];
blas.dscal(3, 0.5, &mut x, 1);
assert!((x[0] - 0.5).abs() < 1e-10);
assert!((x[2] - 1.5).abs() < 1e-10);
}
#[test]
fn test_isamax() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0f32, -5.0, 3.0, 2.0];
let imax = blas.isamax(4, &x, 1);
assert_eq!(imax, 2); }
#[test]
fn test_scopy() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let x = vec![1.0f32, 2.0, 3.0];
let mut y = vec![0.0f32; 3];
blas.scopy(3, &x, 1, &mut y, 1);
assert_eq!(y, x);
}
#[test]
fn test_sswap() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let mut x = vec![1.0f32, 2.0, 3.0];
let mut y = vec![4.0f32, 5.0, 6.0];
blas.sswap(3, &mut x, 1, &mut y, 1);
assert_eq!(x, vec![4.0, 5.0, 6.0]);
assert_eq!(y, vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_sgemv() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let a = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0]; let x = vec![1.0f32, 1.0, 1.0];
let mut y = vec![0.0f32, 0.0];
blas.sgemv(BLASOp::NoTranspose, 2, 3, 1.0, &a, 2, &x, 1, 0.0, &mut y, 1);
assert!((y[0] - 9.0).abs() < 1e-4);
assert!((y[1] - 12.0).abs() < 1e-4);
}
#[test]
fn test_sgemm() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let a = vec![1.0f32, 3.0, 2.0, 4.0]; let b = vec![5.0f32, 7.0, 6.0, 8.0]; let mut c = vec![0.0f32; 4];
blas.sgemm(
BLASOp::NoTranspose,
BLASOp::NoTranspose,
2,
2,
2,
1.0,
&a,
2,
&b,
2,
0.0,
&mut c,
2,
);
assert!((c[0] - 19.0).abs() < 1e-4);
assert!((c[1] - 43.0).abs() < 1e-4);
assert!((c[2] - 22.0).abs() < 1e-4);
assert!((c[3] - 50.0).abs() < 1e-4);
}
#[test]
fn test_dgemm() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let a = vec![1.0, 2.0];
let b = vec![3.0, 4.0];
let mut c = vec![0.0; 1];
blas.dgemm(
BLASOp::Transpose,
BLASOp::NoTranspose,
1,
1,
2,
1.0,
&a,
1,
&b,
2,
0.0,
&mut c,
1,
);
assert!((c[0] - 11.0).abs() < 1e-10);
}
#[test]
fn test_strsm() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let a = vec![2.0f32, 1.0, 0.0, 2.0]; let mut b = vec![4.0f32, 8.0];
blas.strsm(
BLASSide::Left,
BLASUplo::Lower,
BLASOp::NoTranspose,
BLASDiag::NonUnit,
2,
1,
1.0,
&a,
2,
&mut b,
2,
);
assert!((b[0] - 2.0).abs() < 1e-4);
assert!((b[1] - 3.0).abs() < 1e-4);
}
#[test]
fn test_sgetrf() {
let lapack = X86LAPACKSupport::new(X86SIMDLevel::Scalar, false);
let mut a = vec![2.0f32, 1.0, 1.0, 2.0]; let ipiv = lapack.sgetrf(2, 2, &mut a, 2);
assert_eq!(ipiv.len(), 2);
}
#[test]
fn test_dgetrf() {
let lapack = X86LAPACKSupport::new(X86SIMDLevel::Scalar, false);
let mut a = vec![4.0, 3.0, 6.0, 3.0]; let ipiv = lapack.dgetrf(2, 2, &mut a, 2);
assert_eq!(ipiv.len(), 2);
}
#[test]
fn test_spotrf() {
let lapack = X86LAPACKSupport::new(X86SIMDLevel::Scalar, false);
let mut a = vec![4.0f32, 2.0, 2.0, 3.0];
let ok = lapack.spotrf(BLASUplo::Lower, 2, &mut a, 2);
assert!(ok);
}
#[test]
fn test_sgesv() {
let lapack = X86LAPACKSupport::new(X86SIMDLevel::Scalar, false);
let mut a = vec![2.0f32, 1.0, 1.0, 2.0];
let mut b = vec![4.0f32, 5.0];
let ok = lapack.sgesv(2, 1, &mut a, 2, &mut b, 2);
assert!(ok);
assert!((b[0] - 1.0).abs() < 1e-4);
assert!((b[1] - 2.0).abs() < 1e-4);
}
#[test]
fn test_dgesv() {
let lapack = X86LAPACKSupport::new(X86SIMDLevel::Scalar, false);
let mut a = vec![3.0, 1.0, 2.0, 2.0]; let mut b = vec![7.0, 5.0];
let ok = lapack.dgesv(2, 1, &mut a, 2, &mut b, 2);
assert!(ok);
assert!((b[0] - 1.0).abs() < 1e-10);
assert!((b[1] - 2.0).abs() < 1e-10);
}
#[test]
fn test_fft_complex_ops() {
let a = FFTComplex::new(1.0, 2.0);
let b = FFTComplex::new(3.0, 4.0);
let sum = a.add(&b);
assert!((sum.re - 4.0).abs() < 1e-10);
assert!((sum.im - 6.0).abs() < 1e-10);
let prod = a.mul(&b);
assert!((prod.re - (-5.0)).abs() < 1e-10);
assert!((prod.im - 10.0).abs() < 1e-10);
}
#[test]
fn test_fft_radix2_small() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let mut data = vec![
FFTComplex::new(1.0, 0.0),
FFTComplex::new(0.0, 0.0),
FFTComplex::new(0.0, 0.0),
FFTComplex::new(0.0, 0.0),
];
fft.fft_radix2(&mut data, FFTDirection::Forward);
for x in &data {
assert!((x.re - 1.0).abs() < 1e-10);
assert!(x.im.abs() < 1e-10);
}
}
#[test]
fn test_cfft_icfft_roundtrip() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let original = vec![
FFTComplex::new(1.0, 0.0),
FFTComplex::new(2.0, 0.0),
FFTComplex::new(3.0, 0.0),
FFTComplex::new(4.0, 0.0),
FFTComplex::new(5.0, 0.0),
FFTComplex::new(6.0, 0.0),
FFTComplex::new(7.0, 0.0),
FFTComplex::new(8.0, 0.0),
];
let mut data = original.clone();
fft.cfft(&mut data);
fft.icfft(&mut data);
for (orig, restored) in original.iter().zip(data.iter()) {
assert!((orig.re - restored.re).abs() < 1e-10);
assert!(restored.im.abs() < 1e-10);
}
}
#[test]
fn test_convolution() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let f = vec![1.0, 2.0, 3.0];
let g = vec![0.0, 1.0, 0.5];
let conv = fft.fft_convolve(&f, &g);
let expected = vec![0.0, 1.0, 2.5, 4.0, 1.5];
for (c, e) in conv.iter().zip(expected.iter()) {
assert!((c - e).abs() < 1e-8);
}
}
#[test]
fn test_mm_sin_ps() {
let math = X86MathIntrinsics::new(X86SIMDLevel::Scalar);
let result = math._mm_sin_ps([0.0, std::f32::consts::FRAC_PI_2, std::f32::consts::PI, 0.0]);
assert!(result[0].abs() < 1e-3);
assert!((result[1] - 1.0).abs() < 1e-3);
assert!(result[2].abs() < 1e-3);
}
#[test]
fn test_mm_exp_ps() {
let math = X86MathIntrinsics::new(X86SIMDLevel::Scalar);
let result = math._mm_exp_ps([0.0, 1.0, 2.0, -1.0]);
assert!((result[0] - 1.0).abs() < 1e-2);
assert!((result[1] - 2.71828).abs() < 0.1);
assert!((result[3] - 0.36788).abs() < 0.05);
}
#[test]
fn test_fma() {
let math = X86MathIntrinsics::new(X86SIMDLevel::Scalar);
let r = math.fma(2.0, 3.0, 4.0);
assert!((r - 10.0).abs() < 1e-10);
let rf = math.fmaf(2.0, 3.0, 4.0);
assert!((rf - 10.0).abs() < 1e-3);
}
#[test]
fn test_j0() {
let sf = X86SpecialFunctions::new();
let v = sf.j0(0.0);
assert!((v - 1.0).abs() < 1e-6);
}
#[test]
fn test_j1() {
let sf = X86SpecialFunctions::new();
let v = sf.j1(0.0);
assert!(v.abs() < 1e-6);
}
#[test]
fn test_tgamma() {
let sf = X86SpecialFunctions::new();
assert!((sf.tgamma(1.0) - 1.0).abs() < 1e-6);
assert!((sf.tgamma(2.0) - 1.0).abs() < 1e-6);
assert!((sf.tgamma(3.0) - 2.0).abs() < 1e-6);
assert!((sf.tgamma(0.5) - PI.sqrt()).abs() < 1e-6);
}
#[test]
fn test_lgamma() {
let sf = X86SpecialFunctions::new();
let v = sf.lgamma(2.0);
assert!(v.abs() < 1e-6); }
#[test]
fn test_erf() {
let sf = X86SpecialFunctions::new();
assert!((sf.erf(0.0) - 0.0).abs() < 1e-10);
assert!((sf.erf(100.0) - 1.0).abs() < 1e-10);
assert!((sf.erf(-1.0) + sf.erf(1.0)).abs() < 1e-10);
}
#[test]
fn test_erfc() {
let sf = X86SpecialFunctions::new();
assert!((sf.erfc(0.0) - 1.0).abs() < 1e-10);
assert!((sf.erfc(10.0) - 0.0).abs() < 1e-4);
}
#[test]
fn test_expint() {
let sf = X86SpecialFunctions::new();
let v = sf.expint(100.0);
assert!(v.abs() < 1e-10);
let v1 = sf.expint(1.0);
assert!((v1 - 0.2193839344).abs() < 1e-2);
}
#[test]
fn test_legendre_p() {
let sf = X86SpecialFunctions::new();
assert!((sf.legendre_p(0, 0.5) - 1.0).abs() < 1e-10);
assert!((sf.legendre_p(1, 0.5) - 0.5).abs() < 1e-10);
assert!((sf.legendre_p(2, 0.5) - (-0.125)).abs() < 1e-10);
}
#[test]
fn test_zeta() {
let sf = X86SpecialFunctions::new();
let z2 = sf.zeta(2.0);
assert!((z2 - PI * PI / 6.0).abs() < 1e-4);
let z4 = sf.zeta(4.0);
assert!((z4 - 1.082323).abs() < 1e-4);
}
#[test]
fn test_mersenne_twister() {
let rng = X86RandomNumberGenerators::new();
rng.mt_init(12345);
let v1 = rng.mt_gen_u64();
let v2 = rng.mt_gen_u64();
assert_ne!(v1, v2);
}
#[test]
fn test_xoshiro() {
let rng = X86RandomNumberGenerators::new();
rng.xoshiro_seed(42);
let v1 = rng.xoshiro_gen_u64();
let v2 = rng.xoshiro_gen_u64();
assert_ne!(v1, v2);
}
#[test]
fn test_pcg() {
let rng = X86RandomNumberGenerators::new();
let v1 = rng.pcg_gen_u32();
let v2 = rng.pcg_gen_u32();
assert_ne!(v1, v2);
}
#[test]
fn test_uniform_f64() {
let rng = X86RandomNumberGenerators::new();
for _ in 0..100 {
let u = rng.uniform_f64();
assert!(u >= 0.0 && u < 1.0);
}
}
#[test]
fn test_normal_distribution() {
let rng = X86RandomNumberGenerators::new();
let mut sum = 0.0;
let n = 1000;
for _ in 0..n {
sum += rng.normal(5.0, 2.0);
}
let mean = sum / n as f64;
assert!((mean - 5.0).abs() < 0.5);
}
#[test]
fn test_poisson() {
let rng = X86RandomNumberGenerators::new();
let mut sum = 0u64;
let n = 500;
for _ in 0..n {
sum += rng.poisson(10.0);
}
let mean = sum as f64 / n as f64;
assert!((mean - 10.0).abs() < 1.0);
}
#[test]
fn test_binomial() {
let rng = X86RandomNumberGenerators::new();
let n = 20u64;
let p = 0.5;
let mut sum = 0u64;
let num_samples = 500;
for _ in 0..num_samples {
sum += rng.binomial(n, p);
}
let mean = sum as f64 / num_samples as f64;
assert!((mean - (n as f64 * p)).abs() < 1.0);
}
#[test]
fn test_mean() {
let stats = X86StatisticsSupport::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((stats.mean(&data) - 3.0).abs() < 1e-10);
}
#[test]
fn test_median() {
let stats = X86StatisticsSupport::new();
assert!((stats.median(&[1.0, 2.0, 3.0, 4.0, 5.0]) - 3.0).abs() < 1e-10);
assert!((stats.median(&[1.0, 2.0, 3.0, 4.0]) - 2.5).abs() < 1e-10);
}
#[test]
fn test_variance() {
let stats = X86StatisticsSupport::new();
let data = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let var = stats.variance(&data);
assert!((var - 5.142857).abs() < 1e-4);
}
#[test]
fn test_stddev() {
let stats = X86StatisticsSupport::new();
let data = vec![1.0, 1.0, 1.0, 1.0];
assert!((stats.stddev(&data) - 0.0).abs() < 1e-10);
}
#[test]
fn test_skewness_kurtosis() {
let stats = X86StatisticsSupport::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let skew = stats.skewness(&data);
assert!(skew.abs() < 1e-10);
}
#[test]
fn test_pearson_correlation() {
let stats = X86StatisticsSupport::new();
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = vec![2.0, 4.0, 6.0, 8.0, 10.0];
let r = stats.pearson_correlation(&x, &y);
assert!((r - 1.0).abs() < 1e-10);
}
#[test]
fn test_spearman_correlation() {
let stats = X86StatisticsSupport::new();
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let r = stats.spearman_correlation(&x, &y);
assert!((r - (-1.0)).abs() < 1e-10);
}
#[test]
fn test_linear_regression() {
let stats = X86StatisticsSupport::new();
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = vec![2.0, 4.0, 6.0, 8.0, 10.0];
let result = stats.linear_regression(&x, &y);
assert!((result.slope - 2.0).abs() < 1e-10);
assert!((result.intercept - 0.0).abs() < 1e-10);
assert!((result.r_squared - 1.0).abs() < 1e-10);
}
#[test]
fn test_t_test() {
let stats = X86StatisticsSupport::new();
let s1 = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let s2 = vec![6.0, 7.0, 8.0, 9.0, 10.0];
let (t, p, df) = stats.t_test(&s1, &s2);
assert!(t < -1.0); assert!(df > 0.0);
assert!(p >= 0.0 && p <= 1.0);
}
#[test]
fn test_anova() {
let stats = X86StatisticsSupport::new();
let g1 = vec![1.0, 2.0, 3.0];
let g2 = vec![5.0, 6.0, 7.0];
let g3 = vec![2.0, 3.0, 4.0];
let (f, p, dfb, dfw) = stats.anova(&[g1, g2, g3]);
assert!(f > 0.0);
assert!(dfb == 2);
assert!(dfw == 6);
}
#[test]
fn test_gradient_descent() {
let opt = X86OptimizationSupport::new();
let result = opt.gradient_descent(
|x| (x[0].powi(2), vec![2.0 * x[0]]),
&[5.0],
0.1,
1000,
1e-6,
);
assert!(result.converged);
assert!(result.x[0].abs() < 1e-3);
assert!(result.fval.abs() < 1e-4);
}
#[test]
fn test_newton_raphson() {
let opt = X86OptimizationSupport::new();
let root = opt.newton_raphson(|x| (x * x - 2.0, 2.0 * x), 1.5, 20, 1e-10);
assert!(root.is_some());
assert!((root.unwrap() - 2.0f64.sqrt()).abs() < 1e-8);
}
#[test]
fn test_bfgs() {
let opt = X86OptimizationSupport::new();
let result = opt.bfgs(
|x| {
let dx = 1.0 - x[0];
let dy = x[1] - x[0] * x[0];
let fval = dx * dx + 100.0 * dy * dy;
let grad = vec![-2.0 * dx - 400.0 * x[0] * dy, 200.0 * dy];
(fval, grad)
},
&[-1.0, 1.0],
500,
1e-6,
20,
);
assert!(result.converged || result.iterations >= 500);
}
#[test]
fn test_lbfgs() {
let opt = X86OptimizationSupport::new();
let result = opt.lbfgs(
|x| {
let dx = 1.0 - x[0];
let dy = x[1] - x[0] * x[0];
let fval = dx * dx + 100.0 * dy * dy;
let grad = vec![-2.0 * dx - 400.0 * x[0] * dy, 200.0 * dy];
(fval, grad)
},
&[-1.0, 1.0],
5,
500,
1e-6,
20,
);
assert!(result.iterations > 0);
}
#[test]
fn test_nelder_mead() {
let opt = X86OptimizationSupport::new();
let result = opt.nelder_mead(|x| x[0].powi(2) + x[1].powi(2), &[5.0, 5.0], 200, 1e-6);
assert!(result.converged);
assert!(result.x[0].abs() < 5e-1);
assert!(result.x[1].abs() < 5e-1);
}
#[test]
fn test_simulated_annealing() {
let opt = X86OptimizationSupport::new();
let rng = X86RandomNumberGenerators::new();
let result = opt.simulated_annealing(
|x| (x[0] - 3.0).powi(2),
|x, rng| vec![x[0] + (rng.uniform_f64() - 0.5) * 2.0],
&[0.0],
100.0,
0.01,
0.95,
10,
&rng,
);
assert!((result.x[0] - 3.0).abs() < 1.5);
}
#[test]
fn test_simd_level_ordering() {
assert!(X86SIMDLevel::AVX512 > X86SIMDLevel::AVX2);
assert!(X86SIMDLevel::AVX2 > X86SIMDLevel::SSE42);
assert!(X86SIMDLevel::SSE42 > X86SIMDLevel::SSE2);
assert!(X86SIMDLevel::SSE2 > X86SIMDLevel::Scalar);
}
#[test]
fn test_blas_op_equality() {
assert_eq!(BLASOp::NoTranspose, BLASOp::NoTranspose);
assert_ne!(BLASOp::NoTranspose, BLASOp::Transpose);
}
#[test]
fn test_fft_direction() {
assert_ne!(FFTDirection::Forward, FFTDirection::Inverse);
}
#[test]
fn test_integration_scientific_pipeline() {
let sci = X86Scientific::new();
let mut x = Vec::with_capacity(100);
for _ in 0..100 {
x.push(sci.rng.normal(10.0, 3.0));
}
let summary = sci.stats.summary(&x);
assert!(summary.count == 100);
assert!((summary.mean - 10.0).abs() < 1.0);
assert!(summary.stddev > 0.0);
}
#[test]
fn test_dgemm_packed() {
let blas = X86BLASSupport::new(X86SIMDLevel::Scalar, false, 1);
let a = vec![1.0, 3.0, 2.0, 4.0];
let b = vec![5.0, 7.0, 6.0, 8.0];
let mut c = vec![0.0; 4];
blas.dgemm_packed(
BLASOp::NoTranspose,
BLASOp::NoTranspose,
2,
2,
2,
1.0,
&a,
2,
&b,
2,
0.0,
&mut c,
2,
);
assert!((c[0] - 19.0).abs() < 1e-10);
assert!((c[1] - 43.0).abs() < 1e-10);
assert!((c[2] - 22.0).abs() < 1e-10);
assert!((c[3] - 50.0).abs() < 1e-10);
}
#[test]
fn test_dct_roundtrip() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
assert!(fft.dct_roundtrip_test(&x));
}
#[test]
fn test_dst_ii() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let x = vec![1.0, 2.0, 3.0, 4.0];
let dst = fft.dst_ii(&x);
assert_eq!(dst.len(), 4);
}
#[test]
fn test_psd_periodogram() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let signal: Vec<f64> = (0..64)
.map(|i| (2.0 * PI * i as f64 * 5.0 / 64.0).sin())
.collect();
let psd = fft.psd_periodogram(&signal);
assert!(psd.len() > 0);
}
#[test]
fn test_cross_correlation() {
let fft = X86FFTSupport::new(X86SIMDLevel::Scalar, false);
let f = vec![1.0, 2.0, 3.0];
let g = vec![3.0, 2.0, 1.0];
let corr = fft.fft_correlate(&f, &g);
assert_eq!(corr.len(), 5);
let ac = fft.fft_correlate(&f, &f);
assert!((ac[2] - 14.0).abs() < 1e-8); }
#[test]
fn test_elliptic_k() {
let sf = X86SpecialFunctions::new();
let k0 = sf.elliptic_k(0.0);
assert!((k0 - std::f64::consts::FRAC_PI_2).abs() < 1e-6);
assert!((sf.elliptic_k(0.5) - 1.68575).abs() < 1e-3);
}
#[test]
fn test_elliptic_e() {
let sf = X86SpecialFunctions::new();
let e0 = sf.elliptic_e(0.0);
assert!((e0 - std::f64::consts::FRAC_PI_2).abs() < 1e-6);
assert!((sf.elliptic_e(1.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_hypergeometric_2f1() {
let sf = X86SpecialFunctions::new();
assert!((sf.hypergeometric_2f1(1.0, 2.0, 3.0, 0.0) - 1.0).abs() < 1e-10);
let val = sf.hypergeometric_2f1(1.0, 1.0, 2.0, 0.5);
assert!((val - 1.386294).abs() < 1e-4);
}
#[test]
fn test_hypergeometric_1f1() {
let sf = X86SpecialFunctions::new();
assert!((sf.hypergeometric_1f1(1.0, 2.0, 0.0) - 1.0).abs() < 1e-10);
}
#[test]
fn test_dawson() {
let sf = X86SpecialFunctions::new();
assert!(sf.dawson(0.0).abs() < 1e-10);
assert!((sf.dawson(1.0) + sf.dawson(-1.0)).abs() < 1e-10);
}
#[test]
fn test_fresnel() {
let sf = X86SpecialFunctions::new();
assert!(sf.fresnel_c(0.0).abs() < 1e-10);
assert!(sf.fresnel_s(0.0).abs() < 1e-10);
}
#[test]
fn test_gamma_distribution() {
let rng = X86RandomNumberGenerators::new();
let alpha = 2.0;
let theta = 3.0;
let mut sum = 0.0;
let n = 200;
for _ in 0..n {
sum += rng.gamma(alpha, theta);
}
let mean = sum / n as f64;
assert!((mean - 6.0).abs() < 2.0);
}
#[test]
fn test_beta_distribution() {
let rng = X86RandomNumberGenerators::new();
for _ in 0..50 {
let x = rng.beta_distribution(2.0, 5.0);
assert!(x >= 0.0 && x <= 1.0);
}
}
#[test]
fn test_chi_squared_dist() {
let rng = X86RandomNumberGenerators::new();
let k = 4.0;
let mut sum = 0.0;
let n = 200;
for _ in 0..n {
sum += rng.chi_squared(k);
}
let mean = sum / n as f64;
assert!((mean - k).abs() < 2.0);
}
#[test]
fn test_student_t() {
let rng = X86RandomNumberGenerators::new();
for _ in 0..50 {
let t = rng.student_t(10.0);
assert!(t.is_finite());
}
}
#[test]
fn test_paired_t_test() {
let stats = X86StatisticsSupport::new();
let before = vec![10.0, 12.0, 11.0, 13.0, 14.0];
let after = vec![12.0, 14.0, 13.0, 15.0, 16.0];
let (t, p, df) = stats.paired_t_test(&before, &after);
assert!(t > 0.0);
assert!(df >= 1.0);
}
#[test]
fn test_chi_squared_test() {
let stats = X86StatisticsSupport::new();
let observed = vec![50.0, 30.0, 20.0];
let expected = vec![50.0, 30.0, 20.0];
let (chi2, p, df) = stats.chi_squared_test(&observed, &expected);
assert!(chi2.abs() < 1e-10);
assert!((p - 1.0).abs() < 1e-10);
}
#[test]
fn test_polynomial_regression() {
let stats = X86StatisticsSupport::new();
let x: Vec<f64> = (0..10).map(|i| i as f64).collect();
let y: Vec<f64> = x.iter().map(|&xi| 1.0 + 2.0 * xi + 3.0 * xi * xi).collect();
let coeffs = stats.polynomial_regression(&x, &y, 2);
assert_eq!(coeffs.len(), 3);
assert!((coeffs[0] - 1.0).abs() < 1e-6);
assert!((coeffs[1] - 2.0).abs() < 1e-6);
assert!((coeffs[2] - 3.0).abs() < 1e-6);
}
#[test]
fn test_logistic_regression() {
let stats = X86StatisticsSupport::new();
let x: Vec<f64> = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
let y: Vec<f64> = vec![0.0, 0.0, 0.5, 1.0, 1.0];
let (beta0, beta1) = stats.logistic_regression(&x, &y, 500, 0.5);
assert!(beta1 > 0.0); let p0 = stats.logistic_predict(0.0, beta0, beta1);
assert!(p0 >= 0.0 && p0 <= 1.0);
}
#[test]
fn test_conjugate_gradient() {
let opt = X86OptimizationSupport::new();
let result = opt.conjugate_gradient(
|x| {
let val = x[0].powi(2) + x[1].powi(2);
let grad = vec![2.0 * x[0], 2.0 * x[1]];
(val, grad)
},
&[5.0, 5.0],
50,
1e-8,
10,
);
assert!(result.converged);
assert!(result.x[0].abs() < 1e-4);
assert!(result.x[1].abs() < 1e-4);
}
#[test]
fn test_weighted_mean() {
let stats = X86StatisticsSupport::new();
let data = vec![1.0, 2.0, 3.0];
let weights = vec![1.0, 1.0, 1.0];
assert!((stats.weighted_mean(&data, &weights) - 2.0).abs() < 1e-10);
let w2 = vec![2.0, 1.0, 0.0];
assert!((stats.weighted_mean(&data, &w2) - 4.0 / 3.0).abs() < 1e-10);
}
#[test]
fn test_quantile() {
let stats = X86StatisticsSupport::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((stats.quantile(&data, 0.0) - 1.0).abs() < 1e-10);
assert!((stats.quantile(&data, 0.5) - 3.0).abs() < 1e-10);
assert!((stats.quantile(&data, 1.0) - 5.0).abs() < 1e-10);
}
#[test]
fn test_kendall_correlation() {
let stats = X86StatisticsSupport::new();
let x = vec![1.0, 2.0, 3.0, 4.0];
let y = vec![1.0, 2.0, 3.0, 4.0];
let tau = stats.kendall_correlation(&x, &y);
assert!((tau - 1.0).abs() < 1e-10);
}
#[test]
fn test_stat_summary_display() {
let s = StatSummary {
count: 10,
mean: 5.0,
median: 5.0,
min: 0.0,
max: 10.0,
stddev: 3.0,
variance: 9.0,
skewness: 0.0,
kurtosis: -1.2,
q1: 2.5,
q3: 7.5,
iqr: 5.0,
};
let out = format!("{}", s);
assert!(out.contains("n = 10"));
assert!(out.contains("mean = 5"));
}
#[test]
fn test_trust_region_dogleg() {
let opt = X86OptimizationSupport::new();
let result = opt.trust_region_dogleg(
|x| {
let val = x[0].powi(2) + x[1].powi(2);
let grad = vec![2.0 * x[0], 2.0 * x[1]];
let hess = vec![vec![2.0, 0.0], vec![0.0, 2.0]];
(val, grad, hess)
},
&[5.0, 5.0],
50,
1e-8,
10.0,
0.1,
);
assert!(result.converged);
assert!(result.x[0].abs() < 1e-3);
assert!(result.x[1].abs() < 1e-3);
}
#[test]
fn test_coordinate_descent() {
let opt = X86OptimizationSupport::new();
let result = opt.coordinate_descent(
|i, xi, x| {
let mut val = 0.0;
for (j, &xj) in x.iter().enumerate() {
let v = if j == i { xi } else { xj };
val += (v - 1.0).powi(2);
}
val
},
&[0.0, 0.0, 0.0],
100,
1e-4,
);
assert!(result.converged);
for xi in &result.x {
assert!((xi - 1.0).abs() < 1e-2);
}
}
}