use rand::{Rng, RngExt};
use rand_distr::{Distribution, StandardNormal, uniform::SampleUniform};
use super::Scalar;
use super::cl_scaling::{
BoxAffineScaling, cl_scaling_pair, max_feasible_step_component,
project_strictly_inside_component,
};
use super::sample::{SampleStandardNormal, SampleUniformBox, assert_finite_box};
use super::{
ClampInPlace, ComponentDivAssign, ComponentMaxAssign, ComponentMulAssign, Dot,
FloorZerosInPlace, NegInPlace, NormInfinity, NormSquared, ScaleInPlace, ScaledAdd, VectorIndex,
VectorLen,
};
impl<F: Scalar> ScaledAdd<F> for Vec<F> {
fn scaled_add(&mut self, scalar: F, other: &Self) {
assert_eq!(self.len(), other.len(), "scaled_add: length mismatch");
for (x, y) in self.iter_mut().zip(other.iter()) {
*x = *x + scalar * *y;
}
}
}
impl<F: Scalar> NormSquared<F> for Vec<F> {
fn norm_squared(&self) -> F {
self.iter().map(|x| *x * *x).sum()
}
}
impl<F: Scalar> NormInfinity<F> for Vec<F> {
fn norm_infinity(&self) -> F {
self.iter().map(|x| x.abs()).fold(F::zero(), F::max)
}
}
impl<F: Scalar> Dot<F> for Vec<F> {
fn dot(&self, other: &Self) -> F {
assert_eq!(self.len(), other.len(), "dot: length mismatch");
self.iter().zip(other.iter()).map(|(a, b)| *a * *b).sum()
}
}
impl<F: Scalar> NegInPlace for Vec<F> {
fn neg_in_place(&mut self) {
for x in self.iter_mut() {
*x = -*x;
}
}
}
impl<F: Scalar> ScaleInPlace<F> for Vec<F> {
fn scale_in_place(&mut self, scalar: F) {
for x in self.iter_mut() {
*x = *x * scalar;
}
}
}
impl<F: Scalar> ComponentMulAssign for Vec<F> {
fn component_mul_assign(&mut self, other: &Self) {
assert_eq!(
self.len(),
other.len(),
"component_mul_assign: length mismatch"
);
for (x, y) in self.iter_mut().zip(other.iter()) {
*x = *x * *y;
}
}
}
impl<F: Scalar> ComponentMaxAssign for Vec<F> {
fn component_max_assign(&mut self, other: &Self) {
assert_eq!(
self.len(),
other.len(),
"component_max_assign: length mismatch"
);
for (x, y) in self.iter_mut().zip(other.iter()) {
*x = x.max(*y);
}
}
}
impl<F: Scalar> FloorZerosInPlace<F> for Vec<F> {
fn floor_zeros_in_place(&mut self, value: F) {
for x in self.iter_mut() {
if *x <= F::zero() {
*x = value;
}
}
}
}
impl<F: Scalar> ComponentDivAssign for Vec<F> {
fn component_div_assign(&mut self, other: &Self) {
assert_eq!(
self.len(),
other.len(),
"component_div_assign: length mismatch"
);
for (x, y) in self.iter_mut().zip(other.iter()) {
*x = *x / *y;
}
}
}
impl<F: Scalar> VectorLen for Vec<F> {
fn vec_len(&self) -> usize {
self.len()
}
}
impl<F: Scalar> VectorIndex<F> for Vec<F> {
fn get_scalar(&self, i: usize) -> F {
self[i]
}
fn set_scalar(&mut self, i: usize, value: F) {
self[i] = value;
}
}
impl<F: Scalar> SampleStandardNormal for Vec<F>
where
StandardNormal: Distribution<F>,
{
fn sample_standard_normal<R: Rng + ?Sized>(template: &Self, rng: &mut R) -> Self {
let n = template.len();
let mut out = Self::with_capacity(n);
for _ in 0..n {
out.push(StandardNormal.sample(rng));
}
out
}
}
impl<F: Scalar + SampleUniform> SampleUniformBox for Vec<F> {
fn sample_uniform_box<R: Rng + ?Sized>(lower: &Self, upper: &Self, rng: &mut R) -> Self {
assert_eq!(
lower.len(),
upper.len(),
"sample_uniform_box: bounds length mismatch"
);
assert_finite_box(lower, upper);
let n = lower.len();
let mut out = Self::with_capacity(n);
for i in 0..n {
out.push(rng.random_range(lower[i]..=upper[i]));
}
out
}
}
impl<F: Scalar> ClampInPlace for Vec<F> {
fn clamp_in_place(&mut self, lower: &Self, upper: &Self) {
assert_eq!(
self.len(),
lower.len(),
"clamp_in_place: lower length mismatch"
);
assert_eq!(
self.len(),
upper.len(),
"clamp_in_place: upper length mismatch"
);
for ((x, &lo), &hi) in self.iter_mut().zip(lower.iter()).zip(upper.iter()) {
*x = (*x).max(lo).min(hi);
}
}
}
impl<F: Scalar> BoxAffineScaling<F> for Vec<F> {
fn compute_cl_scaling(
&self,
gradient: &Self,
lower: &Self,
upper: &Self,
d_sq: &mut Self,
c_diag: &mut Self,
) {
let n = self.len();
assert_eq!(
n,
gradient.len(),
"compute_cl_scaling: gradient length mismatch"
);
assert_eq!(n, lower.len(), "compute_cl_scaling: lower length mismatch");
assert_eq!(n, upper.len(), "compute_cl_scaling: upper length mismatch");
assert_eq!(n, d_sq.len(), "compute_cl_scaling: d_sq length mismatch");
assert_eq!(
n,
c_diag.len(),
"compute_cl_scaling: c_diag length mismatch"
);
for i in 0..n {
let (d_sq_i, c_i) = cl_scaling_pair::<F>(self[i], gradient[i], lower[i], upper[i]);
d_sq[i] = d_sq_i;
c_diag[i] = c_i;
}
}
fn max_feasible_step(&self, step: &Self, lower: &Self, upper: &Self) -> F {
let n = self.len();
assert_eq!(n, step.len(), "max_feasible_step: step length mismatch");
assert_eq!(n, lower.len(), "max_feasible_step: lower length mismatch");
assert_eq!(n, upper.len(), "max_feasible_step: upper length mismatch");
let mut tau = F::infinity();
for i in 0..n {
let t = max_feasible_step_component::<F>(self[i], step[i], lower[i], upper[i]);
if t < tau {
tau = t;
}
}
tau
}
fn cl_kkt_inf_norm(&self, d_sq: &Self) -> F {
assert_eq!(self.len(), d_sq.len(), "cl_kkt_inf_norm: length mismatch");
self.iter()
.zip(d_sq.iter())
.map(|(&v, &d)| v.abs() / d)
.fold(F::zero(), |a, b| if b > a { b } else { a })
}
fn weighted_norm_squared(&self, weights: &Self) -> F {
assert_eq!(
self.len(),
weights.len(),
"weighted_norm_squared: length mismatch"
);
self.iter()
.zip(weights.iter())
.map(|(&v, &w)| v * v * w)
.sum()
}
fn project_strictly_inside(&mut self, lower: &Self, upper: &Self, rstep: F) {
let n = self.len();
assert_eq!(
n,
lower.len(),
"project_strictly_inside: lower length mismatch"
);
assert_eq!(
n,
upper.len(),
"project_strictly_inside: upper length mismatch"
);
for i in 0..n {
self[i] = project_strictly_inside_component::<F>(self[i], lower[i], upper[i], rstep);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[test]
fn sample_uniform_box_stays_within_bounds() {
let lower = vec![-1.0, 0.5, -10.0];
let upper = vec![1.0, 0.5, 10.0]; let mut rng = ChaCha8Rng::seed_from_u64(123);
for _ in 0..200 {
let x = Vec::<f64>::sample_uniform_box(&lower, &upper, &mut rng);
assert_eq!(x.len(), 3);
for i in 0..3 {
assert!(
x[i] >= lower[i] && x[i] <= upper[i],
"x[{i}] = {} not in [{}, {}]",
x[i],
lower[i],
upper[i]
);
}
assert_eq!(x[1], 0.5);
}
}
#[test]
#[should_panic(expected = "sample_uniform_box requires finite bounds")]
fn sample_uniform_box_rejects_non_finite_bounds() {
let lower = vec![0.0, f64::NEG_INFINITY, 0.0];
let upper = vec![1.0, f64::INFINITY, 1.0];
let mut rng = ChaCha8Rng::seed_from_u64(1);
let _ = Vec::<f64>::sample_uniform_box(&lower, &upper, &mut rng);
}
#[test]
fn sample_uniform_box_is_seed_reproducible() {
let lower = vec![-1.0, -1.0];
let upper = vec![1.0, 1.0];
let mut rng_a = ChaCha8Rng::seed_from_u64(7);
let mut rng_b = ChaCha8Rng::seed_from_u64(7);
let a = Vec::<f64>::sample_uniform_box(&lower, &upper, &mut rng_a);
let b = Vec::<f64>::sample_uniform_box(&lower, &upper, &mut rng_b);
assert_eq!(a, b);
}
#[test]
fn clamp_inside_box_is_identity() {
let mut x = vec![0.5, -0.5, 1.5];
x.clamp_in_place(&vec![-1.0, -1.0, 0.0], &vec![1.0, 1.0, 2.0]);
assert_eq!(x, vec![0.5, -0.5, 1.5]);
}
#[test]
fn clamp_partially_outside_pins_only_offending_components() {
let mut x = vec![-2.0, 0.5, 3.0];
x.clamp_in_place(&vec![-1.0, -1.0, -1.0], &vec![1.0, 1.0, 1.0]);
assert_eq!(x, vec![-1.0, 0.5, 1.0]);
}
#[test]
fn clamp_entirely_outside_pins_to_nearest_face() {
let mut x = vec![-10.0, 10.0];
x.clamp_in_place(&vec![-1.0, -1.0], &vec![1.0, 1.0]);
assert_eq!(x, vec![-1.0, 1.0]);
}
#[test]
fn clamp_with_equal_bounds_pins_to_value() {
let mut x = vec![5.0, -5.0];
x.clamp_in_place(&vec![1.0, 2.0], &vec![1.0, 2.0]);
assert_eq!(x, vec![1.0, 2.0]);
}
#[test]
fn cl_scaling_finite_bounds_negative_gradient_uses_upper() {
let x: Vec<f64> = vec![0.5];
let g: Vec<f64> = vec![-1.0];
let lower: Vec<f64> = vec![-2.0];
let upper: Vec<f64> = vec![2.0];
let mut d_sq: Vec<f64> = vec![0.0];
let mut c: Vec<f64> = vec![0.0];
x.compute_cl_scaling(&g, &lower, &upper, &mut d_sq, &mut c);
assert!((d_sq[0] - (1.0 / 1.5)).abs() < 1e-12);
assert!((c[0] - (1.0 / 1.5)).abs() < 1e-12);
}
#[test]
fn cl_scaling_finite_bounds_positive_gradient_uses_lower() {
let x: Vec<f64> = vec![0.5];
let g: Vec<f64> = vec![2.0];
let lower: Vec<f64> = vec![-2.0];
let upper: Vec<f64> = vec![2.0];
let mut d_sq: Vec<f64> = vec![0.0];
let mut c: Vec<f64> = vec![0.0];
x.compute_cl_scaling(&g, &lower, &upper, &mut d_sq, &mut c);
assert!((d_sq[0] - (1.0 / 2.5)).abs() < 1e-12);
assert!((c[0] - (2.0 / 2.5)).abs() < 1e-12);
}
#[test]
fn cl_scaling_infinite_bounds_yields_unit_d_and_zero_c() {
let x: Vec<f64> = vec![0.0, 0.0];
let g: Vec<f64> = vec![-1.0, 1.0];
let lower: Vec<f64> = vec![f64::NEG_INFINITY, f64::NEG_INFINITY];
let upper: Vec<f64> = vec![f64::INFINITY, f64::INFINITY];
let mut d_sq: Vec<f64> = vec![0.0; 2];
let mut c: Vec<f64> = vec![0.0; 2];
x.compute_cl_scaling(&g, &lower, &upper, &mut d_sq, &mut c);
assert_eq!(d_sq, vec![1.0, 1.0]);
assert_eq!(c, vec![0.0, 0.0]);
}
#[test]
fn max_feasible_step_finds_first_binding_component() {
let x: Vec<f64> = vec![0.0, 0.0];
let step: Vec<f64> = vec![1.0, 2.0];
let lower: Vec<f64> = vec![-1.0, -1.0];
let upper: Vec<f64> = vec![1.0, 1.0];
let tau = x.max_feasible_step(&step, &lower, &upper);
assert!((tau - 0.5).abs() < 1e-12);
}
#[test]
fn max_feasible_step_with_no_binding_bound_is_infinite() {
let x: Vec<f64> = vec![0.0, 0.0];
let step: Vec<f64> = vec![0.0, 0.0];
let lower: Vec<f64> = vec![-1.0, -1.0];
let upper: Vec<f64> = vec![1.0, 1.0];
assert_eq!(x.max_feasible_step(&step, &lower, &upper), f64::INFINITY);
}
#[test]
fn cl_kkt_inf_norm_matches_max_abs_g_over_d_sq() {
let g: Vec<f64> = vec![3.0, -8.0];
let d_sq: Vec<f64> = vec![1.0, 4.0];
assert!((g.cl_kkt_inf_norm(&d_sq) - 3.0).abs() < 1e-12);
}
#[test]
fn cl_kkt_inf_norm_vanishes_at_face_active_point() {
let g: Vec<f64> = vec![-16.0, -20.0]; let d_sq: Vec<f64> = vec![1e10, 1e10]; assert!(g.cl_kkt_inf_norm(&d_sq) < 1e-8);
}
}