use core::marker::PhantomData;
use super::spec::{Dimensionality, HasSpec, ProblemSpec, Properties, Reference};
use crate::{BoxConstraints, CostFunction};
const A: f64 = 10.0;
pub const STANDARD_LOWER: f64 = -5.12;
pub const STANDARD_UPPER: f64 = 5.12;
pub fn rastrigin(x: &[f64]) -> f64 {
let n = x.len() as f64;
let two_pi = 2.0 * core::f64::consts::PI;
let mut s = A * n;
for &v in x.iter() {
s += v * v - A * (two_pi * v).cos();
}
s
}
pub struct Rastrigin<P = Vec<f64>>(PhantomData<fn() -> P>);
impl<P> Rastrigin<P> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<P> Default for Rastrigin<P> {
fn default() -> Self {
Self::new()
}
}
pub static RASTRIGIN_SPEC: ProblemSpec = ProblemSpec {
name: "Rastrigin",
dim: Dimensionality::NDimensional { min: 1 },
properties: Properties {
smooth: true,
differentiable: true,
convex: false,
unimodal: false,
separable: true,
scalable: true,
},
references: &[
Reference {
citation: "Rastrigin (1974)",
title: "Systems of extremal control",
source: "Nauka, Moscow (in Russian)",
doi: None,
url: None,
},
Reference {
citation: "Mühlenbein, Schomisch & Born (1991)",
title: "The parallel genetic algorithm as function optimizer",
source: "Parallel Computing, 17(6–7), 619–632",
doi: Some("10.1016/S0167-8191(05)80052-3"),
url: None,
},
],
description: "Highly multimodal: parabolic bowl Σ xᵢ² modulated by a \
cosine ripple of amplitude 10, giving a dense lattice of \
local minima. Global minimum at x = (0, …, 0), value 0. \
Standard search domain is [−5.12, 5.12]ⁿ (Mühlenbein \
et al. 1991). Separable, so coordinate-wise solvers \
handle it far better than non-separable multimodal \
functions like Schwefel.",
};
impl<P> HasSpec for Rastrigin<P> {
const SPEC: &'static ProblemSpec = &RASTRIGIN_SPEC;
}
impl CostFunction for Rastrigin<Vec<f64>> {
type Param = Vec<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x))
}
}
#[cfg(feature = "nalgebra")]
mod nalgebra_impl {
use super::{rastrigin, Rastrigin};
use crate::CostFunction;
use nalgebra::DVector;
impl CostFunction for Rastrigin<DVector<f64>> {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x.as_slice()))
}
}
}
#[cfg(feature = "ndarray")]
mod ndarray_impl {
use super::{rastrigin, Rastrigin};
use crate::CostFunction;
use ndarray::Array1;
impl CostFunction for Rastrigin<Array1<f64>> {
type Param = Array1<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x.as_slice().expect("Array1 is contiguous")))
}
}
}
#[cfg(feature = "faer")]
mod faer_impl {
use super::{Rastrigin, A};
use crate::CostFunction;
use faer::Col;
impl CostFunction for Rastrigin<Col<f64>> {
type Param = Col<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
let n = x.nrows();
let two_pi = 2.0 * core::f64::consts::PI;
let mut s = A * n as f64;
for i in 0..n {
let v = x[i];
s += v * v - A * (two_pi * v).cos();
}
Ok(s)
}
}
}
pub struct RastriginBoxed<P> {
lower: P,
upper: P,
}
impl<P> RastriginBoxed<P> {
pub fn new(lower: P, upper: P) -> Self {
Self { lower, upper }
}
}
impl<P> HasSpec for RastriginBoxed<P> {
const SPEC: &'static ProblemSpec = &RASTRIGIN_SPEC;
}
impl RastriginBoxed<Vec<f64>> {
pub fn with_standard_bounds(n: usize) -> Self {
Self {
lower: vec![STANDARD_LOWER; n],
upper: vec![STANDARD_UPPER; n],
}
}
}
impl CostFunction for RastriginBoxed<Vec<f64>> {
type Param = Vec<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x))
}
}
impl BoxConstraints for RastriginBoxed<Vec<f64>> {
fn lower(&self) -> &Vec<f64> {
&self.lower
}
fn upper(&self) -> &Vec<f64> {
&self.upper
}
}
#[cfg(feature = "nalgebra")]
mod nalgebra_boxed_impl {
use super::{rastrigin, RastriginBoxed, STANDARD_LOWER, STANDARD_UPPER};
use crate::{BoxConstraints, CostFunction};
use nalgebra::DVector;
impl RastriginBoxed<DVector<f64>> {
pub fn with_standard_bounds(n: usize) -> Self {
Self {
lower: DVector::from_element(n, STANDARD_LOWER),
upper: DVector::from_element(n, STANDARD_UPPER),
}
}
}
impl CostFunction for RastriginBoxed<DVector<f64>> {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x.as_slice()))
}
}
impl BoxConstraints for RastriginBoxed<DVector<f64>> {
fn lower(&self) -> &DVector<f64> {
&self.lower
}
fn upper(&self) -> &DVector<f64> {
&self.upper
}
}
}
#[cfg(feature = "ndarray")]
mod ndarray_boxed_impl {
use super::{rastrigin, RastriginBoxed, STANDARD_LOWER, STANDARD_UPPER};
use crate::{BoxConstraints, CostFunction};
use ndarray::Array1;
impl RastriginBoxed<Array1<f64>> {
pub fn with_standard_bounds(n: usize) -> Self {
Self {
lower: Array1::from_elem(n, STANDARD_LOWER),
upper: Array1::from_elem(n, STANDARD_UPPER),
}
}
}
impl CostFunction for RastriginBoxed<Array1<f64>> {
type Param = Array1<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
Ok(rastrigin(x.as_slice().expect("Array1 is contiguous")))
}
}
impl BoxConstraints for RastriginBoxed<Array1<f64>> {
fn lower(&self) -> &Array1<f64> {
&self.lower
}
fn upper(&self) -> &Array1<f64> {
&self.upper
}
}
}
#[cfg(feature = "faer")]
mod faer_boxed_impl {
use super::{RastriginBoxed, A, STANDARD_LOWER, STANDARD_UPPER};
use crate::{BoxConstraints, CostFunction};
use faer::Col;
impl RastriginBoxed<Col<f64>> {
pub fn with_standard_bounds(n: usize) -> Self {
Self {
lower: Col::<f64>::from_fn(n, |_| STANDARD_LOWER),
upper: Col::<f64>::from_fn(n, |_| STANDARD_UPPER),
}
}
}
impl CostFunction for RastriginBoxed<Col<f64>> {
type Param = Col<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
let n = x.nrows();
let two_pi = 2.0 * core::f64::consts::PI;
let mut s = A * n as f64;
for i in 0..n {
let v = x[i];
s += v * v - A * (two_pi * v).cos();
}
Ok(s)
}
}
impl BoxConstraints for RastriginBoxed<Col<f64>> {
fn lower(&self) -> &Col<f64> {
&self.lower
}
fn upper(&self) -> &Col<f64> {
&self.upper
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rastrigin_minimum_is_zero_at_origin() {
assert!(rastrigin(&[0.0]).abs() < 1e-12);
assert!(rastrigin(&[0.0, 0.0]).abs() < 1e-12);
assert!(rastrigin(&[0.0; 10]).abs() < 1e-12);
assert!(rastrigin(&[0.0; 30]).abs() < 1e-12);
}
#[test]
fn rastrigin_known_value_at_unit_offsets() {
assert!((rastrigin(&[1.0, 1.0, 1.0]) - 3.0).abs() < 1e-9);
assert!((rastrigin(&[2.0, 2.0]) - 8.0).abs() < 1e-9);
}
#[test]
fn rastrigin_local_minimum_at_half_integer_offset() {
assert!((rastrigin(&[1.0]) - 1.0).abs() < 1e-12);
assert!((rastrigin(&[-1.0]) - 1.0).abs() < 1e-12);
}
#[test]
fn rastrigin_matches_definition_at_irregular_point() {
let f = rastrigin(&[0.3, -0.7]);
assert!((f - 26.7603398874989).abs() < 1e-9, "got {f}");
}
#[test]
fn spec_is_wired_up_via_has_spec_trait() {
let spec = <Rastrigin<Vec<f64>> as HasSpec>::SPEC;
assert_eq!(spec.name, "Rastrigin");
assert!(spec.properties.smooth);
assert!(spec.properties.differentiable);
assert!(spec.properties.separable);
assert!(spec.properties.scalable);
assert!(!spec.properties.convex);
assert!(!spec.properties.unimodal);
assert!(matches!(spec.dim, Dimensionality::NDimensional { min: 1 }));
assert!(!spec.references.is_empty());
}
#[test]
fn boxed_form_exposes_standard_bounds() {
let p = RastriginBoxed::<Vec<f64>>::with_standard_bounds(10);
let lo = <RastriginBoxed<Vec<f64>> as BoxConstraints>::lower(&p);
let hi = <RastriginBoxed<Vec<f64>> as BoxConstraints>::upper(&p);
assert_eq!(lo.len(), 10);
assert_eq!(hi.len(), 10);
for &v in lo {
assert_eq!(v, STANDARD_LOWER);
}
for &v in hi {
assert_eq!(v, STANDARD_UPPER);
}
}
#[test]
fn boxed_form_shares_cost_with_unboxed() {
let unboxed: Rastrigin<Vec<f64>> = Rastrigin::default();
let boxed = RastriginBoxed::<Vec<f64>>::with_standard_bounds(3);
let x = vec![0.3, -0.7, 1.2];
assert!((unboxed.cost(&x).unwrap() - boxed.cost(&x).unwrap()).abs() < 1e-12);
}
#[test]
fn boxed_form_reuses_rastrigin_spec() {
let spec = <RastriginBoxed<Vec<f64>> as HasSpec>::SPEC;
assert!(core::ptr::eq(spec, &RASTRIGIN_SPEC));
}
}