Migrating from Argmin
This guide is for applications that use Argmin 0.11 as an optimization framework and want to move their problem definitions and solver runs to Basin 3.0. The frameworks share the same broad vocabulary—a problem, solver, state, and executor—but differ in where initialization, stopping rules, errors, and constraints live.
The examples below migrate uses of Argmin solvers. A custom Argmin Solver or
state machine is a separate port because Basin does not provide a generic
adapter for those extension points.
Install Basin
Plain Vec<f64> needs no backend feature:
[dependencies]
basin = "3" For a linear-algebra backend, select the Basin feature matching the version already used by your application. The backend dependency and Basin feature must name the same release.
| Parameter type | Backend dependency | Basin dependency |
|---|---|---|
Vec<f64> | — | basin = "3" |
| nalgebra 0.32 | nalgebra = "0.32" | basin = { version = "3", features = ["nalgebra_v0_32"] } |
| nalgebra 0.33 | nalgebra = "0.33" | basin = { version = "3", features = ["nalgebra_v0_33"] } |
| nalgebra 0.34 | nalgebra = "0.34" | basin = { version = "3", features = ["nalgebra_v0_34"] } |
| nalgebra 0.35 | nalgebra = "0.35" | basin = { version = "3", features = ["nalgebra_v0_35"] } |
| ndarray 0.15 | ndarray = "0.15" | basin = { version = "3", features = ["ndarray_v0_15"] } |
| ndarray 0.16 | ndarray = "0.16" | basin = { version = "3", features = ["ndarray_v0_16"] } |
| ndarray 0.17 | ndarray = "0.17" | basin = { version = "3", features = ["ndarray_v0_17"] } |
| faer 0.22 | faer = { version = "0.22", default-features = false, features = ["std", "linalg"] } | basin = { version = "3", features = ["faer_v0_22"] } |
| faer 0.23 | faer = { version = "0.23", default-features = false, features = ["std", "linalg"] } | basin = { version = "3", features = ["faer_v0_23"] } |
| faer 0.24 | faer = { version = "0.24", default-features = false, features = ["std", "linalg"] } | basin = { version = "3", features = ["faer_v0_24"] } |
The moving aliases nalgebra, ndarray, and faer are convenient for new
applications. The *_latest spellings remain as deprecated synonyms. Exact
features are safer during a migration because they cannot silently change the
backend release at the next Basin upgrade. If dependency feature unification
enables several versions of one backend, Basin implements every enabled version
independently.
Basin 2.0 replaces the backend-specific quasi-Newton state aliases with FirstOrderState<V, F>.
BFGS owns its inverse Hessian and infers the matrix through the vector’s DenseBackend association. Each enabled backend version has its own
association; Executor::from_start infers the state from the starting vector.
The Basic Executor Migration
Here is a complete Argmin Nelder–Mead run. Argmin receives the simplex when the solver is constructed and configures the initial parameter and iteration budget through its state.
use argmin::core::{CostFunction, Error, Executor, State};
use argmin::solver::neldermead::NelderMead;
struct Rosenbrock;
impl CostFunction for Rosenbrock {
type Param = Vec<f64>;
type Output = f64;
fn cost(&self, x: &Self::Param) -> Result<Self::Output, Error> {
Ok((1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0].powi(2)).powi(2))
}
}
fn main() -> Result<(), Error> {
let x0 = vec![-1.2, 1.0];
let simplex = vec![x0.clone(), vec![-1.26, 1.0], vec![-1.2, 1.05]];
let solver = NelderMead::new(simplex).with_sd_tolerance(1e-8)?;
let result = Executor::new(Rosenbrock, solver)
.configure(|state| state.param(x0).max_iters(1_000))
.run()?;
println!("x = {:?}", result.state().get_best_param());
Ok(())
} In Basin, Executor::from_start asks the solver to construct its natural state
from a point. Nelder–Mead builds its default 5% simplex there; the Argmin
simplex above uses the same coordinate perturbations. Generic stopping rules
belong on the solver; its simplex tolerances check both simplex diameter and cost
spread, rather than Argmin’s sample standard deviation of costs alone.
In Basin 2.0 both tests have absolute and relative defaults. The example below
disables the relative settings to request absolute-only thresholds. An absolute
setter alone changes its floor while leaving the relative setting enabled;
see Migrating to Basin 2.0 for details.
use basin::{CostFunction, Executor, NelderMead};
use std::convert::Infallible;
struct Rosenbrock;
impl CostFunction for Rosenbrock {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<Self::Output, Self::Error> {
Ok((1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0].powi(2)).powi(2))
}
}
fn main() {
let result = Executor::from_start(
Rosenbrock,
(NelderMead::new())
.with_relative_simplex_size_tolerance(None)
.with_relative_simplex_cost_tolerance(None)
.with_absolute_simplex_size_tolerance(1e-8)
.with_absolute_simplex_cost_tolerance(1e-8),
vec![-1.2, 1.0],
)
.max_iter(1_000)
.run()
.unwrap();
println!("x = {:?}, f = {}", result.param(), result.cost());
assert!(result.cost() < 1e-8);
} Use Executor::new when you need a custom simplex or another fully constructed
state. For example, pass SimplexProgress::from_simplex(vertices) as the
third argument. from_start is intentionally unavailable for solvers whose
initialization samples a box or requires an explicit population, such as PSO,
GA, DE, and random search. CmaEs and BoundedCmaEs support from_start: the
starting point supplies the mean, and the solver configures the initial scale.
Common API Mappings
| Argmin 0.11 | Basin 2.0 |
|---|---|
| `Executor::new(problem, solver).configure( | state |
NelderMead::new(simplex).with_sd_tolerance(tol) | NelderMead::new().with_absolute_simplex_size_tolerance(tol_x).with_absolute_simplex_cost_tolerance(tol_f) |
LBFGS::new(MoreThuenteLineSearch::new(), m) | Lbfgs::new().unbounded().with_m_capacity(m) or bounded Lbfgsb::new() |
HagerZhangLineSearch::new() | HagerZhang::new() |
GaussNewtonLS | GaussNewton for full steps or LevenbergMarquardt for damping |
ParticleSwarm::new((lower, upper), n) | GlobalBestPso::new(seed).with_swarm_size(n) plus problem-side ConstraintSet<BoxBounds<V>> and PopulationProgress::empty() |
BrentRoot::new(lower, upper, tol) | BrentRoot::new(lower, upper).with_relative_position_tolerance(tol_rel).with_absolute_position_tolerance(tol_abs).solve(function); no Executor is needed |
CostFunction, Gradient, Hessian, Residual, Jacobian | Corresponding Basin traits, with one problem-owned error type |
| Hand-written central differences | FiniteDiff::new(problem) or the standalone finite-difference functions |
| Transforms, clamps, or penalties for box bounds | ConstraintSet<BoxBounds<V>> plus a solver that consumes bounds |
Argmin’s general Error | The application’s concrete error type in CostFunction::Error or Residual::Error |
add_observer(observer, mode) | observe_with(observer, mode) |
| Returning an observer error to stop | A CancellationToken for a clean stop or a typed problem error for a hard abort |
Tolerances with similar names need not use identical norms or stopping formulas. Preserve numerical behavior by comparing the final objective, parameters, termination reason, and evaluation counts—not merely by copying a numeric tolerance.
Brent Root Finding
Basin keeps root finding outside its optimization state model: the signed
function value and sign-changing bracket are part of RootResult, rather than
being represented as an optimization cost and box constraint. Pass the former
Argmin CostFunction::cost body as a fallible closure:
use std::convert::Infallible;
use basin::BrentRoot;
fn main() {
let result = BrentRoot::new(0.0, 2.0)
.solve(|x| Ok::<_, Infallible>(x * x - 2.0))
.unwrap();
assert!(result.converged());
assert!((result.root() - 2.0_f64.sqrt()).abs() < 1e-10);
} An endpoint root is a successful zero-iteration result. Reversed or
same-sign brackets, non-finite values, and callback errors are distinct BrentRootError variants; reaching the iteration limit is a clean RootResult with RootTerminationReason::MaxIter.
L-BFGS and L-BFGS-B
Argmin’s LBFGS is unconstrained. Basin exposes both algorithms through one
type-state API: Lbfgs<V, F, Unbounded> is unconstrained, while Lbfgsb (and the
default Lbfgs) requires ConstraintSet<BoxBounds<V>>. Moré–Thuente is Basin’s default line
search for both modes.
use basin::{CostFunction, Executor, Gradient, Lbfgs};
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x.iter().map(|xi| xi * xi).sum())
}
}
impl Gradient for Sphere {
type Gradient = Vec<f64>;
fn gradient(&self, x: &Self::Param) -> Result<Self::Gradient, Self::Error> {
Ok(x.iter().map(|xi| 2.0 * xi).collect())
}
}
fn main() {
let solver = Lbfgs::new().unbounded().with_m_capacity(7);
let result = Executor::from_start(
Sphere,
(solver).with_absolute_gradient_tolerance(1e-12),
vec![3.0, -4.0],
)
.max_iter(200)
.run()
.unwrap();
assert!(result.cost() < 1e-12);
} For a nondefault line search, use Lbfgs::with_line_search(HagerZhang::new()).unbounded(). The delta, sigma, epsilon, theta, gamma, initial step, step bounds, and
evaluation budget have direct Basin counterparts. Argmin also exposes an eta setting on its Hager–Zhang line search, but does not use it there; the parameter
belongs to the Hager–Zhang conjugate-gradient update, so Basin’s line search
intentionally omits it. A compatibility adapter can safely discard that
setting. For bounds, use Lbfgsb as shown below. Basin does not currently
replace Argmin’s OWL-QN mode for L1 regularization.
Nonlinear Least Squares
Basin models nonlinear least squares directly through Residual and Jacobian.
Its state cost is ½‖r(x)‖². Choose GaussNewton when a full Gauss–Newton step
is appropriate; choose LevenbergMarquardt when you need the damping that
usually motivates an Argmin GaussNewtonLS line search.
use basin::{DenseMatrix, Executor, GaussNewton, Jacobian, Residual};
use std::convert::Infallible;
struct AffineResidual;
impl Residual for AffineResidual {
type Param = Vec<f64>;
type Output = Vec<f64>;
type Error = Infallible;
fn residual(&self, x: &Self::Param) -> Result<Self::Output, Self::Error> {
Ok(vec![x[0] - 1.0, x[1] - 2.0])
}
}
impl Jacobian for AffineResidual {
type Jacobian = DenseMatrix<f64>;
fn jacobian(&self, _x: &Self::Param) -> Result<Self::Jacobian, Self::Error> {
Ok(DenseMatrix::from_row_slice(2, 2, &[1.0, 0.0, 0.0, 1.0]))
}
}
fn main() {
let solver = GaussNewton::<Vec<f64>, DenseMatrix<f64>>::new();
let result = Executor::from_start(AffineResidual, solver, vec![0.0, 0.0])
.max_iter(20)
.run()
.unwrap();
assert!(result.cost() < 1e-20);
} LevenbergMarquardt::<Vec<f64>, DenseMatrix<f64>>::new() is a drop-in solver
change for the same problem and initial point. Its with_gradient_orthogonality_tolerance, with_relative_model_reduction_tolerance, and with_relative_step_tolerance methods expose the corresponding
MINPACK-style stopping tests.
Typed Errors
Argmin problem methods all return its general argmin::core::Error. In Basin, CostFunction owns an Error associated type, and Gradient and Hessian inherit it. A least-squares problem similarly owns its error through Residual.
This lets an application preserve domain-specific failures without boxing or
stringifying them.
use basin::{CostFunction, Executor, NelderMead};
#[derive(Debug, PartialEq)]
enum ObjectiveError {
WrongDimension,
}
struct FallibleSphere;
impl CostFunction for FallibleSphere {
type Param = Vec<f64>;
type Output = f64;
type Error = ObjectiveError;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
if x.len() != 2 {
return Err(ObjectiveError::WrongDimension);
}
Ok(x.iter().map(|xi| xi * xi).sum())
}
}
fn main() -> Result<(), ObjectiveError> {
let result = Executor::from_start(FallibleSphere, NelderMead::new(), vec![1.0, -1.0])
.max_iter(200)
.run()?;
assert!(result.cost() < 1e-8);
Ok(())
} Use Infallible for a total objective. An application that already uses a
general error container can select that type instead. A problem error is a hard
abort: Executor::run returns Err and no final observer fires. Solvers that
validate constraints return ConstraintSolveError<P::Error>: its Evaluation variant preserves the original problem error, while InvalidConstraints reports malformed constraint declarations or callback outputs.
Finite Differences
When the old integration manually approximates derivatives, first migrate only
the value function and wrap it in FiniteDiff. The default is a central
gradient, a central Hessian, and a forward Jacobian. Method, function_precision, and with_step customize those choices.
use basin::{CostFunction, Executor, FiniteDiff, GradientDescent};
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x.iter().map(|xi| xi * xi).sum())
}
}
fn main() {
let problem = FiniteDiff::new(Sphere);
let result = Executor::from_start(
problem,
(GradientDescent::new(0.25)).with_absolute_gradient_tolerance(1e-12),
vec![2.0, -1.0],
)
.max_iter(100)
.run()
.unwrap();
assert!(result.cost() < 1e-12);
} FiniteDiff preserves the canonical constraint set, so adding numerical derivatives does not
erase bounds. Under the optional parallel feature, its coordinate-wise
evaluations can run in parallel.
Bounds and Constraints
Bounds describe the problem in Basin. Declare a box-only set through Constraints, then choose a
solver whose type advertises that it consumes bounds. The compiler rejects a
bounded solver paired with a problem that does not expose them.
use basin::{CostFunction, Executor, Gradient, Lbfgsb};
use std::convert::Infallible;
struct BoundedQuadratic {
lower: Vec<f64>,
upper: Vec<f64>,
}
impl basin::Constraints for BoundedQuadratic {
type Set = basin::ConstraintSet<basin::BoxBounds<Vec<f64>>>;
fn constraints(
&self,
) -> <Self::Set as basin::ConstraintDescription>::Borrowed<'_> {
basin::ConstraintSet::new().with_bounds(basin::BoxBounds::new(
Some(&self.lower),
Some(&self.upper),
))
}
}
impl CostFunction for BoundedQuadratic {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok((x[0] - 2.0).powi(2) + (x[1] + 1.0).powi(2))
}
}
impl Gradient for BoundedQuadratic {
type Gradient = Vec<f64>;
fn gradient(&self, x: &Self::Param) -> Result<Self::Gradient, Self::Error> {
Ok(vec![2.0 * (x[0] - 2.0), 2.0 * (x[1] + 1.0)])
}
}
fn main() {
let problem = BoundedQuadratic {
lower: vec![-1.0, -0.5],
upper: vec![1.0, 2.0],
};
let solver = Lbfgsb::new().with_m_capacity(7);
let result = Executor::from_start(problem, solver, vec![0.0, 0.0])
.max_iter(200)
.run()
.unwrap();
assert!((result.param()[0] - 1.0).abs() < 1e-8);
assert!((result.param()[1] + 0.5).abs() < 1e-8);
} Other direct consumers include projected NelderMead, BoundedLevenbergMarquardt, Bobyqa, and
bounded global solvers. Linear and nonlinear constraints use their corresponding
problem traits; barrier and augmented-Lagrangian adapters are explicit opt-ins
when an inner solver is otherwise unconstrained.
Particle Swarm Optimization
Argmin 0.11’s ParticleSwarm and Basin’s GlobalBestPso share the synchronous,
coordinate-wise inertia update and the default coefficients w=1/(2 ln 2), c1=c2=1/2+ln 2. Basin names the global-best topology
explicitly because Standard PSO 2006 and 2011 require changing random
neighborhoods, and SPSO-2011 also changes the motion distribution; those are
future separate solvers, not hidden strategy modes.
Move the box from Argmin’s solver constructor to the problem’s Constraints declaration. Set PsoBoundaryHandling::Preserve to match
Argmin’s clamp-position/retain-velocity behavior; Basin defaults to absorbing a
boundary crossing. Basin’s default initialization follows the Standard PSO 2006
profile, v=(u-x)/2, rather than Argmin’s v~U(-span, span), so use a warm
population with explicit positions and set with_initial_velocities(Some(velocities)) on the solver when testing a migrated setup.
Even then, compare numerical outcomes rather than requiring seeded trajectory
identity: the two crates use different RNG versions and vector-sampling
semantics.
use basin::{CostFunction, Executor, GlobalBestPso, PopulationProgress, PsoBoundaryHandling};
use std::convert::Infallible;
struct BoundedSphere {
lower: Vec<f64>,
upper: Vec<f64>,
}
impl basin::Constraints for BoundedSphere {
type Set = basin::ConstraintSet<basin::BoxBounds<Vec<f64>>>;
fn constraints(
&self,
) -> <Self::Set as basin::ConstraintDescription>::Borrowed<'_> {
basin::ConstraintSet::new().with_bounds(basin::BoxBounds::new(
Some(&self.lower),
Some(&self.upper),
))
}
}
impl CostFunction for BoundedSphere {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x.iter().map(|xi| xi * xi).sum())
}
}
fn main() {
let problem = BoundedSphere {
lower: vec![-5.0; 2],
upper: vec![5.0; 2],
};
let solver = GlobalBestPso::new(42)
.with_swarm_size(40)
.with_boundary_handling(PsoBoundaryHandling::Preserve);
let result = Executor::new(problem, solver, PopulationProgress::<Vec<f64>>::empty())
.max_iter(500)
.run()
.unwrap();
assert!(result.cost() < 1e-6);
} The solver owns the live RNG, velocities, and personal and global bests. PopulationProgress owns the observable members, costs, and incumbent metadata.
For exact continuation, retain both through run_with_solver().into_checkpoint() and use Executor::resume_from_checkpoint, or serialize them together through ExactCheckpointWriter. A progress-only snapshot starts a fresh run that
reevaluates its members and resets particle histories.
Simulated Annealing Is Deliberately Basin-Native
Basin now supports arbitrary continuous or discrete parameter types through SimulatedAnnealing and a user-supplied Neighbor trait or closure. Migration
is not trajectory-compatible with Argmin 0.11, because the algorithms differ in
two material ways:
- Basin always uses the classical Metropolis probability
exp(-(f_new - f_old) / T)for a strictly uphill proposal and accepts equal costs. Argmin applies a logistic probability to every non-improving proposal, including equality. - Basin has no implicit cooling default. Choose geometric, reciprocal, or
normalized-log cooling explicitly, and use
with_steps_per_temperaturewhen several proposals should equilibrate at each level. Basin’s normalized-log schedule starts at exactlyT0; it does not rise aboveT0on its second indexed value.
Argmin’s with_reannealing_fixed, with_reannealing_accepted, and with_reannealing_best builder names carry over directly. The three triggers
compose: Basin restarts the schedule when any enabled threshold is reached and
resets all reannealing progress. Here, accepted-stall counts consecutive
rejected proposals, while best-stall counts proposals without a new global
best.
These choices follow Kirkpatrick, Gelatt & Vecchi’s classical acceptance rule (DOI 10.1126/science.220.4598.671). Hajek’s logarithmic convergence result requires a finite-state reversible chain and a problem-dependent coefficient (DOI 10.1287/moor.13.2.311), so it does not justify a universal schedule default.
For long runs, enable serde and attach an ExactCheckpointWriter with Executor::checkpoint_with. It writes the solver, state, and authoritative
evaluation counters together. read_exact_checkpoint validates the format,
Basin version, and concrete types; pass the result to Executor::resume_from_checkpoint, which restores the iteration boundary
without rerunning solver initialization. Simulated annealing retains its
stateful neighbor, RNG, and cooling history in the solver. ProposalState records its accepted point, cost, incumbent, counts, and acceptance history.
The exact checkpoint captures both. State-only snapshots start a fresh chain
through Executor::new. Reattach
execution controls, observers, cancellation, and the checkpoint writer when
resuming.
Observers and Cancellation
Basin’s Observe<S> observers receive read-only state and return ().
For solver diagnostics, use observe_solver with a closure borrowing the state and concrete solver, or implement ObserveSolver<S, So>.
Both kinds share the same observation boundaries, modes, and registration order.
Handle logging errors inside the observer. Use a cloned CancellationToken when a progress callback requests a normal stop; the executor returns the best
available state with Termination::Cancelled.
use basin::{
CancellationToken, CostFunction, Executor, NelderMead, Observe, ObserverMode, State,
Termination,
};
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x.iter().map(|xi| xi * xi).sum())
}
}
struct Progress {
cancel: CancellationToken,
}
impl<S: State<Float = f64>> Observe<S> for Progress {
fn observe_iter(&mut self, state: &S) {
println!("iteration {}, cost {}", state.iter(), state.cost());
if state.iter() >= 5 {
self.cancel.cancel();
}
}
}
fn main() {
let token = CancellationToken::new();
let observer = Progress {
cancel: token.clone(),
};
let result = Executor::from_start(Sphere, NelderMead::new(), vec![2.0, -1.0])
.max_iter(100)
.with_cancellation_token(token)
.observe_with(observer, ObserverMode::Always)
.run()
.unwrap();
assert_eq!(result.report.termination, Termination::Cancelled);
assert_eq!(result.iter(), 5);
} Cancellation is cooperative and checked between top-level iterations. If an individual cost or derivative evaluation must abort immediately, return the problem’s typed error instead.
Complete Backend Examples
These small programs exercise the same derivative-free solve on every advertised backend release. Pair each program with the exact dependency row at the top of this page. The documentation check compiles and runs the nalgebra program against 0.32, 0.33, 0.34, and 0.35; the ndarray program against 0.15, 0.16, and 0.17; and the faer program against 0.22, 0.23, and 0.24.
Vec<f64>
use basin::{CostFunction, Executor, NelderMead};
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Vec<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x[0] * x[0] + x[1] * x[1])
}
}
fn main() {
let result = Executor::from_start(
Sphere,
(NelderMead::new())
.with_absolute_simplex_size_tolerance(1e-8)
.with_absolute_simplex_cost_tolerance(1e-12),
vec![1.0, -1.0],
)
.max_iter(300)
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} nalgebra 0.32–0.35
use basin::{CostFunction, Executor, NelderMead};
use nalgebra::DVector;
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = DVector<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x[0] * x[0] + x[1] * x[1])
}
}
fn main() {
let x0 = DVector::from_vec(vec![1.0, -1.0]);
let result = Executor::from_start(
Sphere,
(NelderMead::new())
.with_absolute_simplex_size_tolerance(1e-8)
.with_absolute_simplex_cost_tolerance(1e-12),
x0,
)
.max_iter(300)
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} ndarray 0.15–0.17
use basin::{CostFunction, Executor, NelderMead};
use ndarray::Array1;
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Array1<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x[0] * x[0] + x[1] * x[1])
}
}
fn main() {
let x0 = Array1::from_vec(vec![1.0, -1.0]);
let result = Executor::from_start(
Sphere,
(NelderMead::new())
.with_absolute_simplex_size_tolerance(1e-8)
.with_absolute_simplex_cost_tolerance(1e-12),
x0,
)
.max_iter(300)
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} faer 0.22–0.24
use basin::{CostFunction, Executor, NelderMead};
use faer::Col;
use std::convert::Infallible;
struct Sphere;
impl CostFunction for Sphere {
type Param = Col<f64>;
type Output = f64;
type Error = Infallible;
fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
Ok(x[0] * x[0] + x[1] * x[1])
}
}
fn main() {
let x0 = Col::from_fn(2, |i| [1.0, -1.0][i]);
let result = Executor::from_start(
Sphere,
(NelderMead::new())
.with_absolute_simplex_size_tolerance(1e-8)
.with_absolute_simplex_cost_tolerance(1e-12),
x0,
)
.max_iter(300)
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} Check Compatibility Before Removing Argmin
Basin’s Brent minimizes a scalar objective, while BrentRoot solves a
bracketed scalar equation through the direct API above. Basin does not offer a
generic adapter for an Argmin solver implementation. Its Hager–Zhang
configuration maps closely to Argmin’s, but the implementations are not
promised to produce identical trial trajectories.
Like Argmin, Basin’s exact checkpoints serialize a solver and state together;
Basin also records the authoritative evaluation counters. The older CheckpointWriter still snapshots only state and therefore provides a warm
start, not a general promise of an identical future trajectory. State-only
exact resume is explicitly supported by simulated annealing and global-best
PSO because their states own every evolving stochastic component. Check a
solver’s API before making the same promise for another stochastic or
population solver.
During a real migration, keep both integrations behind features until tests compare numerical results, termination behavior, evaluation counts, and runtime. Preserve the current algorithm first; evaluate a different Basin solver only after the like-for-like path passes.
Next
- Getting Started: state construction and shared solver convergence settings.
- Solvers: supported algorithms and problem requirements.
- Full Basin API: authoritative type and method documentation.