use rand::{Rng, RngCore};
use crate::traits::SearchSpace;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Grey {
center: f64,
spread: f64,
}
impl Grey {
pub fn new(center: f64, spread: f64) -> Self {
Self {
center,
spread: spread.abs(),
}
}
pub fn from_interval(lower: f64, upper: f64) -> Self {
assert!(lower <= upper, "invalid grey interval: {lower} > {upper}");
Self {
center: 0.5 * (lower + upper),
spread: 0.5 * (upper - lower),
}
}
pub fn center(&self) -> f64 {
self.center
}
pub fn spread(&self) -> f64 {
self.spread
}
pub fn lower(&self) -> f64 {
self.center - self.spread
}
pub fn upper(&self) -> f64 {
self.center + self.spread
}
pub fn whiten(&self, lambda: f64) -> f64 {
self.lower() + lambda * (self.upper() - self.lower())
}
}
impl std::ops::Add for Grey {
type Output = Grey;
fn add(self, rhs: Grey) -> Grey {
Grey::from_interval(self.lower() + rhs.lower(), self.upper() + rhs.upper())
}
}
impl std::ops::Sub for Grey {
type Output = Grey;
fn sub(self, rhs: Grey) -> Grey {
Grey::from_interval(self.lower() - rhs.upper(), self.upper() - rhs.lower())
}
}
impl std::ops::Mul for Grey {
type Output = Grey;
fn mul(self, rhs: Grey) -> Grey {
let (a1, b1, a2, b2) = (self.lower(), self.upper(), rhs.lower(), rhs.upper());
let ps = [a1 * a2, a1 * b2, b1 * a2, b1 * b2];
let lo = ps.iter().copied().fold(f64::INFINITY, f64::min);
let hi = ps.iter().copied().fold(f64::NEG_INFINITY, f64::max);
Grey::from_interval(lo, hi)
}
}
impl std::ops::Mul<f64> for Grey {
type Output = Grey;
fn mul(self, k: f64) -> Grey {
let (p, q) = (self.lower() * k, self.upper() * k);
Grey::from_interval(p.min(q), p.max(q))
}
}
#[derive(Debug, Clone)]
pub struct GreySpace {
bounds: Vec<(f64, f64)>,
max_spread: Vec<f64>,
}
impl GreySpace {
pub fn new(bounds: Vec<(f64, f64)>, max_spread: Vec<f64>) -> Self {
assert_eq!(
bounds.len(),
max_spread.len(),
"bounds and max_spread must have the same length"
);
for (i, ((lo, hi), s)) in bounds.iter().zip(&max_spread).enumerate() {
assert!(lo <= hi, "invalid bound in variable {i}: {lo} > {hi}");
assert!(*s >= 0.0, "negative max spread in variable {i}: {s}");
}
Self { bounds, max_spread }
}
pub fn uniform(n: usize, lo: f64, hi: f64, max_spread: f64) -> Self {
Self::new(vec![(lo, hi); n], vec![max_spread; n])
}
pub fn bounds(&self) -> &[(f64, f64)] {
&self.bounds
}
pub fn n_grey(&self) -> usize {
self.bounds.len()
}
fn spread_cap(&self, var: usize, c: f64) -> f64 {
let (lo, hi) = self.bounds[var];
(c - lo).min(hi - c).min(self.max_spread[var]).max(0.0)
}
fn max_feasible_spread(&self, var: usize) -> f64 {
let (lo, hi) = self.bounds[var];
(0.5 * (hi - lo)).min(self.max_spread[var]).max(0.0)
}
}
impl SearchSpace for GreySpace {
type Scalar = Grey;
fn dim(&self) -> usize {
2 * self.bounds.len()
}
fn sample(&self, rng: &mut dyn RngCore) -> Vec<f64> {
let mut raw = Vec::with_capacity(self.dim());
for var in 0..self.bounds.len() {
let (lo, hi) = self.bounds[var];
let c = rng.gen_range(lo..=hi);
let cap = self.spread_cap(var, c);
raw.push(c);
raw.push(rng.gen_range(0.0..=cap.max(0.0)));
}
raw
}
fn sample_velocity(&self, rng: &mut dyn RngCore) -> Vec<f64> {
let mut vel = Vec::with_capacity(self.dim());
for var in 0..self.bounds.len() {
let (lo, hi) = self.bounds[var];
let crange = hi - lo;
vel.push(rng.gen_range(-crange..=crange));
let srange = self.max_feasible_spread(var);
vel.push(rng.gen_range(-srange..=srange.max(0.0)));
}
vel
}
fn clamp(&self, position: &mut [f64]) {
for var in 0..self.bounds.len() {
let (lo, hi) = self.bounds[var];
let c = position[2 * var].clamp(lo, hi);
position[2 * var] = c;
let cap = self.spread_cap(var, c);
position[2 * var + 1] = position[2 * var + 1].clamp(0.0, cap);
}
}
fn decode(&self, raw: &[f64]) -> Vec<Grey> {
raw.chunks_exact(2)
.map(|cs| Grey::new(cs[0], cs[1]))
.collect()
}
fn span(&self) -> Vec<(f64, f64)> {
let mut s = Vec::with_capacity(self.dim());
for var in 0..self.bounds.len() {
s.push(self.bounds[var]);
s.push((0.0, self.max_feasible_spread(var)));
}
s
}
}