Getting Started

Install

Add Basin to your project:

cargo add basin

Basin works on plain Vec<f64> out of the box. Linear-algebra backends are opt-in. For example, to use the latest supported nalgebra backend (required by some solvers, see Solvers):

cargo add basin --features nalgebra

The moving aliases are nalgebra, ndarray, and faer. Exact version features are also available for nalgebra 0.32–0.35, ndarray 0.15–0.17, and faer 0.22–0.24. The deprecated nalgebra_latest, ndarray_latest, and faer_latest features remain as synonyms. When several releases of one backend are enabled, Basin implements each independently. The package MSRV is Rust 1.87; nalgebra 0.35 and nalgebra require Rust 1.89.

In Basin 2.0, no features are enabled by default. Enable problems with cargo add basin --features problems to use the standard test-function corpus in basin::problems. Basin 1.x enables this feature by default; on that release line, default-features = false excludes it.

A Simple Example

Implement CostFunction for your objective. Add a Gradient impl when the solver needs derivatives (gradient descent does; Nelder–Mead does not). Then hand the problem, a solver, and an initial state to the Executor.

use basin::{CostFunction, Executor, FirstOrderState, Gradient, GradientDescent};
use std::convert::Infallible;

struct Rosenbrock;

impl CostFunction for Rosenbrock {
    type Param = Vec<f64>;
    type Output = f64;
    type Error = Infallible;

    fn cost(&self, x: &Vec<f64>) -> Result<f64, Self::Error> {
        Ok((1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0].powi(2)).powi(2))
    }
}

impl Gradient for Rosenbrock {
    type Gradient = Vec<f64>;

    fn gradient(&self, x: &Vec<f64>) -> Result<Vec<f64>, Self::Error> {
        Ok(vec![
            -2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0].powi(2)),
            200.0 * (x[1] - x[0].powi(2)),
        ])
    }
}

fn main() {
    let solver = GradientDescent::new(1e-3).with_absolute_gradient_tolerance(1e-6);
    let state = FirstOrderState::new(vec![-1.2, 1.0]);

    let result = Executor::new(Rosenbrock, solver, state)
        .max_iter(50_000)
        .run()
        .unwrap();

    println!("x = {:?}", result.param());
    println!("f = {}", result.cost());
    println!("stopped: {:?}", result.report.termination);
}

Seeding from a Point

Executor::new takes a fully-built state, so you control the initial iterate. For the common case (start at a point with the solver’s natural defaults), Executor::from_start takes the bare starting vector and builds the state for you, so you never name the concrete state type:

use basin::{Executor, GradientDescent};

let solver =
    GradientDescent::new(1e-3).with_absolute_gradient_tolerance(1e-6);
let result = Executor::from_start(Rosenbrock, solver, vec![-1.2, 1.0])
    .max_iter(50_000)
    .run()
    .unwrap();

Most solvers support from_start, including CmaEs and BoundedCmaEs: the starting point supplies the mean, and the solver configures the initial scale. Solvers that sample a box or need an explicit population, such as PSO, GA, DE, and random search, require an explicit state. The scalar minimizers Brent, BrentDerivative, and GoldenSection also require a state because they start from a bracket. Use Executor::new for those solvers.

Convergence and Run Limits

Configure numerical convergence on the solver. Configure iteration, evaluation, and time limits on the executor, along with targets, stagnation stops, and application conditions:

use basin::{Executor, GradientDescent};

let solver = GradientDescent::new(1e-3)
    .with_absolute_gradient_tolerance(1e-6)
    .with_relative_cost_change_tolerance(None);
let result = Executor::from_start(problem, solver, x0)
    .max_iter(50_000)
    .max_cost_evals(100_000)
    .run()
    .unwrap();

Optional tolerances accept a finite nonnegative scalar or None. Repeated setters replace the same test; distinct enabled tests usually combine with OR. Nelder–Mead’s enabled simplex size and cost-spread tests combine with AND. Algorithm-specific defaults and safeguards still apply. Read each solver’s rustdoc for the norm and scaling used by a test.

Defaults vary by solver. In particular, NelderMead enables paired simplex size and cost-spread tests by default in Basin 2.0, including its projected variant. Absolute and relative tolerances work together within each test. An unchanged best vertex alone does not establish collapse, and collapse on a flat objective or after clipping does not establish stationarity. See the API reference and Basin 2.0 migration guide for settings, scaling limits, and complete opt-out. Basin 1.x retains its opt-in behavior.

Choose tolerances for the scale of your objective and parameters. An absolute gradient threshold changes meaning when you rescale the problem. For least squares, the LevenbergMarquardt convergence guide shows how to select orthogonality and relative model-reduction tests through existing setters. Adding a relative check leaves any absolute check enabled unless you explicitly disable it with None.

Budgets are checked after initialization and between iterations, so evaluation limits can be exceeded within an iteration. result.report.termination reports why the run ended. Reaching a budget or reporting a numerical stall does not establish convergence or rule out an accurate solution. Inspect the returned solution alongside the reason. For an application condition, use stop_when(|state| ...), returning Some(basin::ApplicationStop::new("my_stop")) to stop or None to continue.

Basin 2.0 removes the former terminate_on facility and abbreviated tolerance setters. See Migrating to Basin 2.0.

Next