use core::marker::PhantomData;
use super::spec::{Dimensionality, HasSpec, ProblemSpec, Properties, Reference};
use crate::{CostFunction, Gradient};
pub fn goldstein_price(x: &[f64]) -> f64 {
debug_assert_eq!(x.len(), 2);
let (a, b) = (x[0], x[1]);
let u = a + b + 1.0;
let p = 19.0 - 14.0 * a + 3.0 * a * a - 14.0 * b + 6.0 * a * b + 3.0 * b * b;
let v = 2.0 * a - 3.0 * b;
let q = 18.0 - 32.0 * a + 12.0 * a * a + 48.0 * b - 36.0 * a * b + 27.0 * b * b;
let big_a = 1.0 + u * u * p;
let big_b = 30.0 + v * v * q;
big_a * big_b
}
pub fn goldstein_price_gradient(x: &[f64], out: &mut [f64]) {
debug_assert_eq!(x.len(), 2);
debug_assert_eq!(out.len(), 2);
let (a, b) = (x[0], x[1]);
let u = a + b + 1.0;
let p = 19.0 - 14.0 * a + 3.0 * a * a - 14.0 * b + 6.0 * a * b + 3.0 * b * b;
let dp = -14.0 + 6.0 * a + 6.0 * b;
let big_a = 1.0 + u * u * p;
let dadx = 2.0 * u * p + u * u * dp;
let dady = dadx;
let v = 2.0 * a - 3.0 * b;
let q = 18.0 - 32.0 * a + 12.0 * a * a + 48.0 * b - 36.0 * a * b + 27.0 * b * b;
let dqdx = -32.0 + 24.0 * a - 36.0 * b;
let dqdy = 48.0 - 36.0 * a + 54.0 * b;
let big_b = 30.0 + v * v * q;
let dbdx = 4.0 * v * q + v * v * dqdx;
let dbdy = -6.0 * v * q + v * v * dqdy;
out[0] = dadx * big_b + big_a * dbdx;
out[1] = dady * big_b + big_a * dbdy;
}
pub struct GoldsteinPrice<P = Vec<f64>>(PhantomData<fn() -> P>);
impl<P> GoldsteinPrice<P> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<P> Default for GoldsteinPrice<P> {
fn default() -> Self {
Self::new()
}
}
pub static GOLDSTEIN_PRICE_SPEC: ProblemSpec = ProblemSpec {
name: "Goldstein-Price",
dim: Dimensionality::Fixed(2),
properties: Properties {
smooth: true,
differentiable: true,
convex: false,
unimodal: false,
separable: false,
scalable: false,
},
references: &[Reference {
citation: "Goldstein & Price (1971)",
title: "On Descent from Local Minima",
source: "Mathematics of Computation, 25(115), 569–574",
doi: Some("10.2307/2005219"),
url: None,
}],
description: "Smooth 2D polynomial benchmark with a single global \
minimum at (x, y) = (0, −1), value 3. Usual search domain \
is x, y ∈ [-2, 2]; values reach ~10⁶ near the corners, so \
the wide dynamic range stresses step-size control on \
first-order methods.",
};
impl<P> HasSpec for GoldsteinPrice<P> {
const SPEC: &'static ProblemSpec = &GOLDSTEIN_PRICE_SPEC;
}
impl CostFunction for GoldsteinPrice<Vec<f64>> {
type Param = Vec<f64>;
type Output = f64;
fn cost(&self, x: &Vec<f64>) -> f64 {
goldstein_price(x)
}
}
impl Gradient for GoldsteinPrice<Vec<f64>> {
type Param = Vec<f64>;
type Gradient = Vec<f64>;
fn gradient(&self, x: &Vec<f64>) -> Vec<f64> {
let mut out = vec![0.0; x.len()];
goldstein_price_gradient(x, &mut out);
out
}
}
#[cfg(feature = "nalgebra")]
mod nalgebra_impl {
use super::{goldstein_price, goldstein_price_gradient, GoldsteinPrice};
use crate::{CostFunction, Gradient};
use nalgebra::DVector;
impl CostFunction for GoldsteinPrice<DVector<f64>> {
type Param = DVector<f64>;
type Output = f64;
fn cost(&self, x: &DVector<f64>) -> f64 {
goldstein_price(x.as_slice())
}
}
impl Gradient for GoldsteinPrice<DVector<f64>> {
type Param = DVector<f64>;
type Gradient = DVector<f64>;
fn gradient(&self, x: &DVector<f64>) -> DVector<f64> {
let mut out = DVector::zeros(x.len());
goldstein_price_gradient(x.as_slice(), out.as_mut_slice());
out
}
}
}
#[cfg(feature = "ndarray")]
mod ndarray_impl {
use super::{goldstein_price, goldstein_price_gradient, GoldsteinPrice};
use crate::{CostFunction, Gradient};
use ndarray::Array1;
impl CostFunction for GoldsteinPrice<Array1<f64>> {
type Param = Array1<f64>;
type Output = f64;
fn cost(&self, x: &Array1<f64>) -> f64 {
goldstein_price(x.as_slice().expect("Array1 is contiguous"))
}
}
impl Gradient for GoldsteinPrice<Array1<f64>> {
type Param = Array1<f64>;
type Gradient = Array1<f64>;
fn gradient(&self, x: &Array1<f64>) -> Array1<f64> {
let mut out = Array1::zeros(x.len());
goldstein_price_gradient(
x.as_slice().expect("Array1 is contiguous"),
out.as_slice_mut().expect("Array1 is contiguous"),
);
out
}
}
}
#[cfg(feature = "faer")]
mod faer_impl {
use super::GoldsteinPrice;
use crate::{CostFunction, Gradient};
use faer::Col;
impl CostFunction for GoldsteinPrice<Col<f64>> {
type Param = Col<f64>;
type Output = f64;
fn cost(&self, x: &Col<f64>) -> f64 {
debug_assert_eq!(x.nrows(), 2);
let (a, b) = (x[0], x[1]);
let u = a + b + 1.0;
let p = 19.0 - 14.0 * a + 3.0 * a * a - 14.0 * b + 6.0 * a * b + 3.0 * b * b;
let v = 2.0 * a - 3.0 * b;
let q = 18.0 - 32.0 * a + 12.0 * a * a + 48.0 * b - 36.0 * a * b + 27.0 * b * b;
(1.0 + u * u * p) * (30.0 + v * v * q)
}
}
impl Gradient for GoldsteinPrice<Col<f64>> {
type Param = Col<f64>;
type Gradient = Col<f64>;
fn gradient(&self, x: &Col<f64>) -> Col<f64> {
debug_assert_eq!(x.nrows(), 2);
let (a, b) = (x[0], x[1]);
let u = a + b + 1.0;
let p = 19.0 - 14.0 * a + 3.0 * a * a - 14.0 * b + 6.0 * a * b + 3.0 * b * b;
let dp = -14.0 + 6.0 * a + 6.0 * b;
let big_a = 1.0 + u * u * p;
let dadx = 2.0 * u * p + u * u * dp;
let dady = dadx;
let v = 2.0 * a - 3.0 * b;
let q = 18.0 - 32.0 * a + 12.0 * a * a + 48.0 * b - 36.0 * a * b + 27.0 * b * b;
let dqdx = -32.0 + 24.0 * a - 36.0 * b;
let dqdy = 48.0 - 36.0 * a + 54.0 * b;
let big_b = 30.0 + v * v * q;
let dbdx = 4.0 * v * q + v * v * dqdx;
let dbdy = -6.0 * v * q + v * v * dqdy;
let g0 = dadx * big_b + big_a * dbdx;
let g1 = dady * big_b + big_a * dbdy;
Col::<f64>::from_fn(2, |i| if i == 0 { g0 } else { g1 })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn goldstein_price_minimum_is_three_at_known_optimum() {
assert!((goldstein_price(&[0.0, -1.0]) - 3.0).abs() < 1e-12);
}
#[test]
fn goldstein_price_known_value_at_origin() {
assert!((goldstein_price(&[0.0, 0.0]) - 600.0).abs() < 1e-9);
}
#[test]
fn gradient_zero_at_minimum() {
let mut g = vec![0.0; 2];
goldstein_price_gradient(&[0.0, -1.0], &mut g);
for v in g {
assert!(v.abs() < 1e-9);
}
}
#[test]
fn gradient_matches_finite_difference() {
let x = [-1.2, 0.7];
let mut g = vec![0.0; x.len()];
goldstein_price_gradient(&x, &mut g);
let h = 1e-6;
for i in 0..x.len() {
let mut xp = x;
let mut xm = x;
xp[i] += h;
xm[i] -= h;
let fd = (goldstein_price(&xp) - goldstein_price(&xm)) / (2.0 * h);
let rel = (g[i] - fd).abs() / (1.0 + fd.abs());
assert!(rel < 1e-5, "i={i}, g={}, fd={fd}", g[i]);
}
}
#[test]
fn spec_is_wired_up_via_has_spec_trait() {
let spec = <GoldsteinPrice<Vec<f64>> as HasSpec>::SPEC;
assert_eq!(spec.name, "Goldstein-Price");
assert!(spec.properties.smooth);
assert!(spec.properties.differentiable);
assert!(matches!(spec.dim, Dimensionality::Fixed(2)));
assert!(!spec.references.is_empty());
}
}