#[cfg(not(miri))]
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use diskann_utils::views::Matrix;
use rand::{
distr::{Distribution, Uniform},
rngs::StdRng,
seq::SliceRandom,
};
use thiserror::Error;
#[cfg(not(miri))]
use crate::alloc::GlobalAllocator;
use crate::alloc::{AllocatorCore, AllocatorError};
#[derive(Debug, Clone, Copy)]
pub(crate) struct AlwaysFails;
unsafe impl AllocatorCore for AlwaysFails {
fn allocate(
&self,
_layout: std::alloc::Layout,
) -> Result<std::ptr::NonNull<[u8]>, AllocatorError> {
Err(AllocatorError)
}
unsafe fn deallocate(&self, _ptr: std::ptr::NonNull<[u8]>, _layout: std::alloc::Layout) {}
}
#[cfg(not(miri))]
#[derive(Debug, Clone)]
pub(crate) struct LimitedAllocator {
remaining: Arc<AtomicUsize>,
}
#[cfg(not(miri))]
impl LimitedAllocator {
pub(crate) fn new(allocations: usize) -> Self {
Self {
remaining: Arc::new(AtomicUsize::new(allocations)),
}
}
}
#[cfg(not(miri))]
unsafe impl AllocatorCore for LimitedAllocator {
fn allocate(
&self,
layout: std::alloc::Layout,
) -> Result<std::ptr::NonNull<[u8]>, AllocatorError> {
let mut remaining = self.remaining.load(Ordering::Relaxed);
if remaining == 0 {
return Err(AllocatorError);
}
while let Err(actual) = self.remaining.compare_exchange(
remaining,
remaining - 1,
Ordering::Relaxed,
Ordering::Relaxed,
) {
if actual == 0 {
return Err(AllocatorError);
}
remaining = actual;
}
(GlobalAllocator).allocate(layout)
}
unsafe fn deallocate(&self, ptr: std::ptr::NonNull<[u8]>, layout: std::alloc::Layout) {
unsafe { (GlobalAllocator).deallocate(ptr, layout) }
}
}
pub(crate) fn compute_relative_error(got: f32, expected: f32) -> f32 {
if got == expected {
0.0
} else {
assert!(expected != 0.0);
(got - expected).abs() / expected.abs()
}
}
pub(crate) fn compute_absolute_error(got: f32, expected: f32) -> f32 {
(got - expected).abs()
}
pub(crate) struct TestProblem {
pub(crate) data: Matrix<f32>,
pub(crate) means: Vec<f64>,
pub(crate) variances: Vec<f64>,
pub(crate) mean_norm: f64,
}
pub(crate) fn create_test_problem(nrows: usize, ncols: usize, rng: &mut StdRng) -> TestProblem {
let distribution = Uniform::new_inclusive::<i64, i64>(-10, 10).unwrap();
let means: Vec<f32> = (0..ncols)
.map(|_| distribution.sample(rng) as f32)
.collect();
let scales: Vec<f32> = (0..ncols)
.map(|_| distribution.sample(rng).abs() as f32)
.collect();
let mut offsets: Vec<f32> = (0i64..(nrows as i64))
.map(|row: i64| {
let nrows: i64 = nrows as i64;
let offset = if nrows % 2 == 0 {
2 * row - nrows + 1
} else {
row - (nrows - 1) / 2
};
offset as f32
})
.collect();
let variances = scales
.iter()
.map(|&scale| {
let scale = scale as usize;
let variance: usize = if nrows.is_multiple_of(2) {
let half: usize = (1..=nrows / 2)
.map(|i| {
let j = scale * (2 * i - 1);
j * j
})
.sum();
2 * half
} else {
let half: usize = (1..=nrows / 2)
.map(|i| {
let j = scale * i;
j * j
})
.sum();
2 * half
};
variance as f64 / nrows as f64
})
.collect();
let mut data = Matrix::<f32>::new(0.0, nrows, ncols);
for col in 0..ncols {
offsets.shuffle(rng);
for row in 0..nrows {
data[(row, col)] = means[col] + scales[col] * offsets[row];
}
}
let mean_norm = data
.row_iter()
.map(|row| {
row.iter()
.map(|&i| {
let i: f64 = i.into();
i * i
})
.sum::<f64>()
.sqrt()
})
.sum::<f64>()
/ (nrows as f64);
TestProblem {
data,
means: means.into_iter().map(|i| i as f64).collect(),
variances,
mean_norm,
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Check {
Ulp(usize),
AbsRel { abs: f32, rel: f32 },
#[cfg(not(miri))]
Skip,
}
impl Check {
pub(crate) const fn ulp(ulp: usize) -> Self {
Self::Ulp(ulp)
}
pub(crate) const fn absrel(abs: f32, rel: f32) -> Self {
Self::AbsRel { abs, rel }
}
#[cfg(not(miri))]
pub(crate) const fn skip() -> Self {
Self::Skip
}
pub(crate) fn check(&self, got: f32, expected: f32) -> Result<(), CheckFailed> {
match self {
Self::Ulp(ulp) => {
if within_ulp(got, expected, *ulp) {
Ok(())
} else {
Err(CheckFailed::Ulp {
ulp: *ulp,
got,
expected,
})
}
}
Self::AbsRel { abs, rel } => {
let abs_got = (got - expected).abs();
let max_magnitude = got.abs().max(expected.abs());
let (rel_ok, rel_got) = if max_magnitude == 0.0 {
(false, f32::INFINITY)
} else {
let rel_got = abs_got / max_magnitude;
(rel_got <= *rel, rel_got)
};
if abs_got <= *abs || rel_ok {
Ok(())
} else {
Err(CheckFailed::AbsRel {
abs_limit: *abs,
rel_limit: *rel,
abs_got,
rel_got,
got,
expected,
})
}
}
#[cfg(not(miri))]
Self::Skip => Ok(()),
}
}
}
#[derive(Debug, Clone, Copy, Error)]
pub(crate) enum CheckFailed {
#[error("not within {ulp} ulp - got {got}, expected {expected}")]
Ulp { ulp: usize, got: f32, expected: f32 },
#[error(
"not within {abs_limit}/{rel_limit} - errors {abs_got}/{rel_got} - \
got {got}, expected {expected}"
)]
AbsRel {
abs_limit: f32,
rel_limit: f32,
abs_got: f32,
rel_got: f32,
got: f32,
expected: f32,
},
}
fn within_ulp(mut got: f32, expected: f32, ulp: usize) -> bool {
if got == expected {
true
} else if got < expected {
for _ in 0..ulp {
got = got.next_up();
if got >= expected {
return true;
}
}
false
} else {
for _ in 0..ulp {
got = got.next_down();
if got <= expected {
return true;
}
}
false
}
}