Gradient descent — Rosenbrock, μ = 0.92

Rosenbrock f(x,y) = (a − x)² + b(y − x²)² with a=1, b=100. Polyak heavy-ball momentum (μ=0.92, η=1e-3) navigates the banana valley toward the minimum at (1, 1) — marked in pink. The background is a log-loss heatmap; the yellow line is the descent trail. 100 steps per frame.

view the kernel that wrote this receipt crates/mathground-view/src/graddescent.rs
//! 2-D gradient descent on the Rosenbrock landscape.
//!
//!   f(x, y) = (a − x)² + b (y − x²)²,  a = 1, b = 100
//!   ∇f     = ( −2(a − x) − 4 b x (y − x²),  2 b (y − x²) )
//!
//! Momentum (Polyak heavy-ball):
//!   v ← μ v − η ∇f
//!   x ← x + v
//!
//! Pure, deterministic. V-class L0-closed.

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GdParams {
    pub a: f32,
    pub b: f32,
    pub lr: f32,
    pub momentum: f32,
}

impl Default for GdParams {
    fn default() -> Self {
        Self { a: 1.0, b: 100.0, lr: 1.0e-3, momentum: 0.92 }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GdState {
    pub x: f32,
    pub y: f32,
    pub vx: f32,
    pub vy: f32,
    pub step: u64,
}

impl Default for GdState {
    fn default() -> Self {
        Self { x: -1.5, y: 2.5, vx: 0.0, vy: 0.0, step: 0 }
    }
}

pub fn rosenbrock(x: f32, y: f32, a: f32, b: f32) -> f32 {
    let dx = a - x;
    let dy = y - x * x;
    dx * dx + b * dy * dy
}

pub fn grad_rosenbrock(x: f32, y: f32, a: f32, b: f32) -> (f32, f32) {
    let gx = -2.0 * (a - x) - 4.0 * b * x * (y - x * x);
    let gy = 2.0 * b * (y - x * x);
    (gx, gy)
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GdStepReport {
    pub bit_ops: u64,
    pub loss: f32,
    pub grad_norm: f32,
}

pub fn gd_step(state: &mut GdState, p: &GdParams) -> GdStepReport {
    let (gx, gy) = grad_rosenbrock(state.x, state.y, p.a, p.b);
    state.vx = p.momentum * state.vx - p.lr * gx;
    state.vy = p.momentum * state.vy - p.lr * gy;
    state.x += state.vx;
    state.y += state.vy;
    state.step += 1;
    let loss = rosenbrock(state.x, state.y, p.a, p.b);
    let gn = (gx * gx + gy * gy).sqrt();
    // ~20 f32 ops/step.
    GdStepReport { bit_ops: 20 * 32, loss, grad_norm: gn }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn minimum_is_at_one_one() {
        let f = rosenbrock(1.0, 1.0, 1.0, 100.0);
        assert!(f.abs() < 1e-6, "f(1,1) = {f}");
        let (gx, gy) = grad_rosenbrock(1.0, 1.0, 1.0, 100.0);
        assert!(gx.abs() < 1e-6 && gy.abs() < 1e-6);
    }

    #[test]
    fn descent_reduces_loss() {
        let p = GdParams::default();
        let mut s = GdState::default();
        let l0 = rosenbrock(s.x, s.y, p.a, p.b);
        for _ in 0..5_000 {
            gd_step(&mut s, &p);
        }
        let l1 = rosenbrock(s.x, s.y, p.a, p.b);
        assert!(l1 < l0 * 0.05, "loss {l1} should be ≪ {l0}");
    }

    #[test]
    fn converges_near_minimum() {
        let p = GdParams::default();
        let mut s = GdState::default();
        for _ in 0..20_000 {
            gd_step(&mut s, &p);
        }
        let dist = ((s.x - 1.0).powi(2) + (s.y - 1.0).powi(2)).sqrt();
        assert!(dist < 0.5, "ended at ({}, {}), distance {dist}", s.x, s.y);
    }
}

This is the exact Rust file compiled into the WASM module the page just loaded. Every line that runs on your device is here. The receipt is a function of this code, not a bespoke benchmark.