use crate::error::{NumRs2Error, Result};
use num_traits::Float;
use scirs2_core::random::{thread_rng, Distribution, Uniform};
use super::{vector_norm, ReferencePoint};
pub(super) fn generate_reference_points<T: Float + std::iter::Sum>(
n_obj: usize,
n_divisions: usize,
) -> Result<Vec<ReferencePoint<T>>> {
if n_obj == 0 {
return Err(NumRs2Error::ValueError(
"Number of objectives must be positive".to_string(),
));
}
if n_divisions == 0 {
return Err(NumRs2Error::ValueError(
"Number of divisions must be positive".to_string(),
));
}
let mut raw_points = Vec::new();
let mut current_point = vec![T::zero(); n_obj];
generate_recursive_points(
&mut raw_points,
&mut current_point,
n_obj,
n_divisions,
n_divisions,
0,
)?;
let mut reference_points = Vec::with_capacity(raw_points.len());
for point in raw_points {
let norm = vector_norm(&point)?;
let normalized = if norm > T::epsilon() {
point.iter().map(|&x| x / norm).collect()
} else {
point
};
reference_points.push(ReferencePoint {
position: normalized,
niche_count: 0,
});
}
Ok(reference_points)
}
fn generate_recursive_points<T: Float>(
points: &mut Vec<Vec<T>>,
current: &mut Vec<T>,
n_obj: usize,
n_divisions: usize,
remaining: usize,
depth: usize,
) -> Result<()> {
if depth == n_obj - 1 {
let value = T::from(remaining).ok_or_else(|| {
NumRs2Error::ConversionError("Failed to convert division count".to_string())
})?;
let divisor = T::from(n_divisions).ok_or_else(|| {
NumRs2Error::ConversionError("Failed to convert n_divisions".to_string())
})?;
current[depth] = value / divisor;
points.push(current.clone());
return Ok(());
}
for i in 0..=remaining {
let value = T::from(i).ok_or_else(|| {
NumRs2Error::ConversionError("Failed to convert division value".to_string())
})?;
let divisor = T::from(n_divisions).ok_or_else(|| {
NumRs2Error::ConversionError("Failed to convert n_divisions".to_string())
})?;
current[depth] = value / divisor;
generate_recursive_points(
points,
current,
n_obj,
n_divisions,
remaining - i,
depth + 1,
)?;
}
Ok(())
}
pub(super) fn generate_reference_points_layered<T: Float + std::iter::Sum>(
n_obj: usize,
n_divisions_outer: usize,
n_divisions_inner: usize,
) -> Result<Vec<ReferencePoint<T>>> {
if n_obj == 0 {
return Err(NumRs2Error::ValueError(
"Number of objectives must be positive".to_string(),
));
}
let mut all_points = Vec::new();
let mut outer_raw = Vec::new();
let mut current_point = vec![T::zero(); n_obj];
generate_recursive_points(
&mut outer_raw,
&mut current_point,
n_obj,
n_divisions_outer,
n_divisions_outer,
0,
)?;
all_points.extend(outer_raw);
if n_divisions_inner > 0 {
let mut inner_raw = Vec::new();
let mut current_point = vec![T::zero(); n_obj];
generate_recursive_points(
&mut inner_raw,
&mut current_point,
n_obj,
n_divisions_inner,
n_divisions_inner,
0,
)?;
let half = T::from(0.5)
.ok_or_else(|| NumRs2Error::ConversionError("Failed to convert 0.5".to_string()))?;
for point in inner_raw {
let scaled: Vec<T> = point.iter().map(|&x| x * half).collect();
all_points.push(scaled);
}
}
let mut reference_points = Vec::with_capacity(all_points.len());
for point in all_points {
let norm = vector_norm(&point)?;
let normalized = if norm > T::epsilon() {
point.iter().map(|&x| x / norm).collect()
} else {
point
};
reference_points.push(ReferencePoint {
position: normalized,
niche_count: 0,
});
}
Ok(reference_points)
}
pub(super) fn generate_reference_points_random<T: Float + std::iter::Sum>(
n_obj: usize,
n_points: usize,
) -> Result<Vec<ReferencePoint<T>>> {
if n_obj == 0 {
return Err(NumRs2Error::ValueError(
"Number of objectives must be positive".to_string(),
));
}
if n_points == 0 {
return Err(NumRs2Error::ValueError(
"Number of points must be positive".to_string(),
));
}
let mut rng = thread_rng();
let uniform = Uniform::new(0.0, 1.0).map_err(|e| {
NumRs2Error::ComputationError(format!("Uniform distribution creation failed: {}", e))
})?;
let mut reference_points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let mut point = Vec::with_capacity(n_obj);
for _ in 0..n_obj {
let u: f64 = uniform.sample(&mut rng);
let u_clamped = u.max(1e-10);
let exp_sample = -u_clamped.ln();
let value = T::from(exp_sample).ok_or_else(|| {
NumRs2Error::ConversionError("Failed to convert exponential sample".to_string())
})?;
point.push(value);
}
let sum: T = point.iter().copied().sum();
if sum > T::epsilon() {
for x in &mut point {
*x = *x / sum;
}
}
let norm = vector_norm(&point)?;
let normalized = if norm > T::epsilon() {
point.iter().map(|&x| x / norm).collect()
} else {
point
};
reference_points.push(ReferencePoint {
position: normalized,
niche_count: 0,
});
}
Ok(reference_points)
}
pub(super) fn generate_reference_points_adaptive<T: Float + std::iter::Sum>(
n_obj: usize,
n_divisions: usize,
) -> Result<Vec<ReferencePoint<T>>> {
match n_obj {
0 => Err(NumRs2Error::ValueError(
"Number of objectives must be positive".to_string(),
)),
1..=5 => {
generate_reference_points(n_obj, n_divisions)
}
6..=8 => {
let outer_divisions = n_divisions.min(4); let inner_divisions = (n_divisions / 2).max(1);
generate_reference_points_layered(n_obj, outer_divisions, inner_divisions)
}
_ => {
let n_points = (n_divisions * n_divisions).clamp(100, 1000);
generate_reference_points_random(n_obj, n_points)
}
}
}