use core::fmt;
use rayon::prelude::*;
use crate::math::{RandType, TensorRandError, TensorRandFiller, VectorList};
use crate::rng::ResolvedRng;
use crate::threading::parallel_chunk_len;
const DOMAIN_UNIFORM_COMPONENT: u64 = 0x6f42_8f31_98f7_067a;
const DOMAIN_UNIFORM_CENTERED_COMPONENT: u64 = 0xb4ac_a77c_e8df_b5f1;
const DOMAIN_GAUSSIAN_COMPONENT: u64 = 0x15eb_0cc7_aac5_7548;
const DOMAIN_JITTER_COMPONENT: u64 = 0xcbef_a4d0_a194_0f13;
#[derive(Debug, Clone, PartialEq)]
pub enum VectorSamplingMethod<'a> {
Uniform { low: f64, high: f64 },
UniformCentered {
box_size: &'a [f64],
},
GaussianPerAxis {
mean: &'a [f64],
std: &'a [f64],
},
JitteredLattice {
spacings: &'a [f64],
sigmas: &'a [f64],
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum VectorSamplingError {
InvalidUniformBounds {
low: f64,
high: f64,
},
InvalidParameterLength {
parameter: &'static str,
expected: usize,
got: usize,
},
InvalidParameterValue {
parameter: &'static str,
index: usize,
value: f64,
rule: &'static str,
},
Rng(TensorRandError),
}
impl fmt::Display for VectorSamplingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidUniformBounds { low, high } => write!(
f,
"uniform sampling bounds must be finite with low < high; got low={low}, high={high}"
),
Self::InvalidParameterLength {
parameter,
expected,
got,
} => write!(
f,
"sampling parameter `{parameter}` has length {got}; expected {expected}"
),
Self::InvalidParameterValue {
parameter,
index,
value,
rule,
} => write!(
f,
"sampling parameter `{parameter}` has invalid value {value} at index {index}; expected {rule}"
),
Self::Rng(error) => write!(f, "vector sampling RNG error: {error}"),
}
}
}
impl std::error::Error for VectorSamplingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Rng(error) => Some(error),
_ => None,
}
}
}
pub fn sample_vectors(
vectors: &mut VectorList<f64>,
method: VectorSamplingMethod<'_>,
rng: ResolvedRng,
) -> Result<(), VectorSamplingError> {
let dim = vectors.dim();
let n = vectors.num_vectors();
let mut values = vec![0.0; dim * n];
match method {
VectorSamplingMethod::Uniform { low, high } => {
if !low.is_finite() || !high.is_finite() || low >= high {
return Err(VectorSamplingError::InvalidUniformBounds { low, high });
}
let mut filler = TensorRandFiller::new(RandType::Uniform { low, high }, rng)
.map_err(VectorSamplingError::Rng)?;
filler
.try_fill_slice_once(&mut values, dim, DOMAIN_UNIFORM_COMPONENT)
.map_err(VectorSamplingError::Rng)?;
}
VectorSamplingMethod::UniformCentered { box_size } => {
validate_len("box_size", box_size.len(), dim)?;
validate_finite_nonnegative("box_size", box_size)?;
let mut filler = TensorRandFiller::new(
RandType::Uniform {
low: 0.0,
high: 1.0,
},
rng,
)
.map_err(VectorSamplingError::Rng)?;
filler
.try_fill_slice_once(&mut values, dim, DOMAIN_UNIFORM_CENTERED_COMPONENT)
.map_err(VectorSamplingError::Rng)?;
values
.par_chunks_mut(dim)
.with_min_len(parallel_chunk_len(n).unwrap_or(1))
.for_each(|row| {
for k in 0..dim {
let half_span = 0.5 * box_size[k];
row[k] = (2.0 * row[k] - 1.0) * half_span;
}
});
}
VectorSamplingMethod::GaussianPerAxis { mean, std } => {
validate_len("mean", mean.len(), dim)?;
validate_len("std", std.len(), dim)?;
validate_finite("mean", mean)?;
validate_finite_nonnegative("std", std)?;
let mut filler = TensorRandFiller::new(
RandType::Normal {
mean: 0.0,
std: 1.0,
},
rng,
)
.map_err(VectorSamplingError::Rng)?;
filler
.try_fill_slice_once(&mut values, dim, DOMAIN_GAUSSIAN_COMPONENT)
.map_err(VectorSamplingError::Rng)?;
values
.par_chunks_mut(dim)
.with_min_len(parallel_chunk_len(n).unwrap_or(1))
.for_each(|row| {
for k in 0..dim {
row[k] = mean[k] + row[k] * std[k];
}
});
}
VectorSamplingMethod::JitteredLattice { spacings, sigmas } => {
validate_len("spacings", spacings.len(), dim)?;
validate_len("sigmas", sigmas.len(), dim)?;
validate_finite_nonnegative("spacings", spacings)?;
validate_finite_nonnegative("sigmas", sigmas)?;
let mut filler = TensorRandFiller::new(
RandType::Normal {
mean: 0.0,
std: 1.0,
},
rng,
)
.map_err(VectorSamplingError::Rng)?;
filler
.try_fill_slice_once(&mut values, dim, DOMAIN_JITTER_COMPONENT)
.map_err(VectorSamplingError::Rng)?;
let side = ((n as f64).powf(1.0 / dim as f64).ceil() as usize).max(1);
values
.par_chunks_mut(dim)
.with_min_len(parallel_chunk_len(n).unwrap_or(1))
.enumerate()
.for_each(|(vector_idx, row)| {
let mut lattice_idx = vector_idx;
for k in 0..dim {
let grid_coord = lattice_idx % side;
lattice_idx /= side;
let base = grid_coord as f64 * spacings[k];
row[k] = base + row[k] * sigmas[k];
}
});
}
}
vectors.replace_values(values);
Ok(())
}
fn validate_len(
parameter: &'static str,
got: usize,
expected: usize,
) -> Result<(), VectorSamplingError> {
if got != expected {
return Err(VectorSamplingError::InvalidParameterLength {
parameter,
expected,
got,
});
}
Ok(())
}
fn validate_finite(parameter: &'static str, values: &[f64]) -> Result<(), VectorSamplingError> {
for (index, &value) in values.iter().enumerate() {
if !value.is_finite() {
return Err(VectorSamplingError::InvalidParameterValue {
parameter,
index,
value,
rule: "finite",
});
}
}
Ok(())
}
fn validate_finite_nonnegative(
parameter: &'static str,
values: &[f64],
) -> Result<(), VectorSamplingError> {
for (index, &value) in values.iter().enumerate() {
if !value.is_finite() || value < 0.0 {
return Err(VectorSamplingError::InvalidParameterValue {
parameter,
index,
value,
rule: "finite and non-negative",
});
}
}
Ok(())
}