1use core::marker::PhantomData;
26
27use super::spec::{Dimensionality, HasSpec, ProblemSpec, Properties, Reference};
28use crate::{BoxConstraints, CostFunction};
29
30const A: f64 = 10.0;
34
35pub const STANDARD_LOWER: f64 = -5.12;
37pub const STANDARD_UPPER: f64 = 5.12;
39
40pub fn rastrigin(x: &[f64]) -> f64 {
42 let n = x.len() as f64;
43 let two_pi = 2.0 * core::f64::consts::PI;
44 let mut s = A * n;
45 for &v in x.iter() {
46 s += v * v - A * (two_pi * v).cos();
47 }
48 s
49}
50
51pub struct Rastrigin<P = Vec<f64>>(PhantomData<fn() -> P>);
60
61impl<P> Rastrigin<P> {
62 pub const fn new() -> Self {
65 Self(PhantomData)
66 }
67}
68
69impl<P> Default for Rastrigin<P> {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75pub static RASTRIGIN_SPEC: ProblemSpec = ProblemSpec {
77 name: "Rastrigin",
78 dim: Dimensionality::NDimensional { min: 1 },
79 properties: Properties {
80 smooth: true,
81 differentiable: true,
82 convex: false,
84 unimodal: false,
86 separable: true,
90 scalable: true,
91 },
92 references: &[
93 Reference {
94 citation: "Rastrigin (1974)",
95 title: "Systems of extremal control",
96 source: "Nauka, Moscow (in Russian)",
97 doi: None,
98 url: None,
99 },
100 Reference {
101 citation: "Mühlenbein, Schomisch & Born (1991)",
102 title: "The parallel genetic algorithm as function optimizer",
103 source: "Parallel Computing, 17(6–7), 619–632",
104 doi: Some("10.1016/S0167-8191(05)80052-3"),
105 url: None,
106 },
107 ],
108 description: "Highly multimodal: parabolic bowl Σ xᵢ² modulated by a \
109 cosine ripple of amplitude 10, giving a dense lattice of \
110 local minima. Global minimum at x = (0, …, 0), value 0. \
111 Standard search domain is [−5.12, 5.12]ⁿ (Mühlenbein \
112 et al. 1991). Separable, so coordinate-wise solvers \
113 handle it far better than non-separable multimodal \
114 functions like Schwefel.",
115};
116
117impl<P> HasSpec for Rastrigin<P> {
118 const SPEC: &'static ProblemSpec = &RASTRIGIN_SPEC;
119}
120
121impl CostFunction for Rastrigin<Vec<f64>> {
122 type Param = Vec<f64>;
123 type Output = f64;
124 type Error = std::convert::Infallible;
125 fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
126 Ok(rastrigin(x))
127 }
128}
129
130#[cfg(feature = "nalgebra")]
131mod nalgebra_impl {
132 use super::{Rastrigin, rastrigin};
133 use crate::CostFunction;
134 use nalgebra::DVector;
135
136 impl CostFunction for Rastrigin<DVector<f64>> {
137 type Param = DVector<f64>;
138 type Output = f64;
139 type Error = std::convert::Infallible;
140 fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
141 Ok(rastrigin(x.as_slice()))
142 }
143 }
144}
145
146#[cfg(feature = "ndarray")]
147mod ndarray_impl {
148 use super::{Rastrigin, rastrigin};
149 use crate::CostFunction;
150 use ndarray::Array1;
151
152 impl CostFunction for Rastrigin<Array1<f64>> {
153 type Param = Array1<f64>;
154 type Output = f64;
155 type Error = std::convert::Infallible;
156 fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
157 Ok(rastrigin(x.as_slice().expect("Array1 is contiguous")))
158 }
159 }
160}
161
162#[cfg(feature = "faer")]
163mod faer_impl {
164 use super::{A, Rastrigin};
165 use crate::CostFunction;
166 use faer::Col;
167
168 impl CostFunction for Rastrigin<Col<f64>> {
172 type Param = Col<f64>;
173 type Output = f64;
174 type Error = std::convert::Infallible;
175 fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
176 let n = x.nrows();
177 let two_pi = 2.0 * core::f64::consts::PI;
178 let mut s = A * n as f64;
179 for i in 0..n {
180 let v = x[i];
181 s += v * v - A * (two_pi * v).cos();
182 }
183 Ok(s)
184 }
185 }
186}
187
188pub struct RastriginBoxed<P> {
208 lower: P,
209 upper: P,
210}
211
212impl<P> RastriginBoxed<P> {
213 pub fn new(lower: P, upper: P) -> Self {
216 Self { lower, upper }
217 }
218}
219
220impl<P> HasSpec for RastriginBoxed<P> {
221 const SPEC: &'static ProblemSpec = &RASTRIGIN_SPEC;
222}
223
224impl RastriginBoxed<Vec<f64>> {
225 pub fn with_standard_bounds(n: usize) -> Self {
228 Self {
229 lower: vec![STANDARD_LOWER; n],
230 upper: vec![STANDARD_UPPER; n],
231 }
232 }
233}
234
235impl CostFunction for RastriginBoxed<Vec<f64>> {
236 type Param = Vec<f64>;
237 type Output = f64;
238 type Error = std::convert::Infallible;
239 fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
240 Ok(rastrigin(x))
241 }
242}
243
244impl BoxConstraints for RastriginBoxed<Vec<f64>> {
245 fn lower(&self) -> &Vec<f64> {
246 &self.lower
247 }
248 fn upper(&self) -> &Vec<f64> {
249 &self.upper
250 }
251}
252
253#[cfg(feature = "nalgebra")]
254mod nalgebra_boxed_impl {
255 use super::{RastriginBoxed, STANDARD_LOWER, STANDARD_UPPER, rastrigin};
256 use crate::{BoxConstraints, CostFunction};
257 use nalgebra::DVector;
258
259 impl RastriginBoxed<DVector<f64>> {
260 pub fn with_standard_bounds(n: usize) -> Self {
263 Self {
264 lower: DVector::from_element(n, STANDARD_LOWER),
265 upper: DVector::from_element(n, STANDARD_UPPER),
266 }
267 }
268 }
269
270 impl CostFunction for RastriginBoxed<DVector<f64>> {
271 type Param = DVector<f64>;
272 type Output = f64;
273 type Error = std::convert::Infallible;
274 fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
275 Ok(rastrigin(x.as_slice()))
276 }
277 }
278
279 impl BoxConstraints for RastriginBoxed<DVector<f64>> {
280 fn lower(&self) -> &DVector<f64> {
281 &self.lower
282 }
283 fn upper(&self) -> &DVector<f64> {
284 &self.upper
285 }
286 }
287}
288
289#[cfg(feature = "ndarray")]
290mod ndarray_boxed_impl {
291 use super::{RastriginBoxed, STANDARD_LOWER, STANDARD_UPPER, rastrigin};
292 use crate::{BoxConstraints, CostFunction};
293 use ndarray::Array1;
294
295 impl RastriginBoxed<Array1<f64>> {
296 pub fn with_standard_bounds(n: usize) -> Self {
299 Self {
300 lower: Array1::from_elem(n, STANDARD_LOWER),
301 upper: Array1::from_elem(n, STANDARD_UPPER),
302 }
303 }
304 }
305
306 impl CostFunction for RastriginBoxed<Array1<f64>> {
307 type Param = Array1<f64>;
308 type Output = f64;
309 type Error = std::convert::Infallible;
310 fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
311 Ok(rastrigin(x.as_slice().expect("Array1 is contiguous")))
312 }
313 }
314
315 impl BoxConstraints for RastriginBoxed<Array1<f64>> {
316 fn lower(&self) -> &Array1<f64> {
317 &self.lower
318 }
319 fn upper(&self) -> &Array1<f64> {
320 &self.upper
321 }
322 }
323}
324
325#[cfg(feature = "faer")]
326mod faer_boxed_impl {
327 use super::{A, RastriginBoxed, STANDARD_LOWER, STANDARD_UPPER};
328 use crate::{BoxConstraints, CostFunction};
329 use faer::Col;
330
331 impl RastriginBoxed<Col<f64>> {
332 pub fn with_standard_bounds(n: usize) -> Self {
335 Self {
336 lower: Col::<f64>::from_fn(n, |_| STANDARD_LOWER),
337 upper: Col::<f64>::from_fn(n, |_| STANDARD_UPPER),
338 }
339 }
340 }
341
342 impl CostFunction for RastriginBoxed<Col<f64>> {
343 type Param = Col<f64>;
344 type Output = f64;
345 type Error = std::convert::Infallible;
346 fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
347 let n = x.nrows();
348 let two_pi = 2.0 * core::f64::consts::PI;
349 let mut s = A * n as f64;
350 for i in 0..n {
351 let v = x[i];
352 s += v * v - A * (two_pi * v).cos();
353 }
354 Ok(s)
355 }
356 }
357
358 impl BoxConstraints for RastriginBoxed<Col<f64>> {
359 fn lower(&self) -> &Col<f64> {
360 &self.lower
361 }
362 fn upper(&self) -> &Col<f64> {
363 &self.upper
364 }
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
373 fn rastrigin_minimum_is_zero_at_origin() {
374 assert!(rastrigin(&[0.0]).abs() < 1e-12);
375 assert!(rastrigin(&[0.0, 0.0]).abs() < 1e-12);
376 assert!(rastrigin(&[0.0; 10]).abs() < 1e-12);
377 assert!(rastrigin(&[0.0; 30]).abs() < 1e-12);
378 }
379
380 #[test]
381 fn rastrigin_known_value_at_unit_offsets() {
382 assert!((rastrigin(&[1.0, 1.0, 1.0]) - 3.0).abs() < 1e-9);
388 assert!((rastrigin(&[2.0, 2.0]) - 8.0).abs() < 1e-9);
389 }
390
391 #[test]
392 fn rastrigin_local_minimum_at_half_integer_offset() {
393 assert!((rastrigin(&[1.0]) - 1.0).abs() < 1e-12);
399 assert!((rastrigin(&[-1.0]) - 1.0).abs() < 1e-12);
400 }
401
402 #[test]
403 fn rastrigin_matches_definition_at_irregular_point() {
404 let f = rastrigin(&[0.3, -0.7]);
414 assert!((f - 26.7603398874989).abs() < 1e-9, "got {f}");
415 }
416
417 #[test]
418 fn spec_is_wired_up_via_has_spec_trait() {
419 let spec = <Rastrigin<Vec<f64>> as HasSpec>::SPEC;
420 assert_eq!(spec.name, "Rastrigin");
421 assert!(spec.properties.smooth);
422 assert!(spec.properties.differentiable);
423 assert!(spec.properties.separable);
424 assert!(spec.properties.scalable);
425 assert!(!spec.properties.convex);
426 assert!(!spec.properties.unimodal);
427 assert!(matches!(spec.dim, Dimensionality::NDimensional { min: 1 }));
428 assert!(!spec.references.is_empty());
429 }
430
431 #[test]
432 fn boxed_form_exposes_standard_bounds() {
433 let p = RastriginBoxed::<Vec<f64>>::with_standard_bounds(10);
434 let lo = <RastriginBoxed<Vec<f64>> as BoxConstraints>::lower(&p);
435 let hi = <RastriginBoxed<Vec<f64>> as BoxConstraints>::upper(&p);
436 assert_eq!(lo.len(), 10);
437 assert_eq!(hi.len(), 10);
438 for &v in lo {
439 assert_eq!(v, STANDARD_LOWER);
440 }
441 for &v in hi {
442 assert_eq!(v, STANDARD_UPPER);
443 }
444 }
445
446 #[test]
447 fn boxed_form_shares_cost_with_unboxed() {
448 let unboxed: Rastrigin<Vec<f64>> = Rastrigin::default();
449 let boxed = RastriginBoxed::<Vec<f64>>::with_standard_bounds(3);
450 let x = vec![0.3, -0.7, 1.2];
451 assert!((unboxed.cost(&x).unwrap() - boxed.cost(&x).unwrap()).abs() < 1e-12);
452 }
453
454 #[test]
455 fn boxed_form_reuses_rastrigin_spec() {
456 let spec = <RastriginBoxed<Vec<f64>> as HasSpec>::SPEC;
457 assert!(core::ptr::eq(spec, &RASTRIGIN_SPEC));
459 }
460}