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 1.7. 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 = "1.7" 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 = "1.7" |
| nalgebra 0.32 | nalgebra = "0.32" | basin = { version = "1.7", features = ["nalgebra_v0_32"] } |
| nalgebra 0.33 | nalgebra = "0.33" | basin = { version = "1.7", features = ["nalgebra_v0_33"] } |
| nalgebra 0.34 | nalgebra = "0.34" | basin = { version = "1.7", features = ["nalgebra_v0_34"] } |
| nalgebra 0.35 | nalgebra = "0.35" | basin = { version = "1.7", features = ["nalgebra_v0_35"] } |
| ndarray 0.15 | ndarray = "0.15" | basin = { version = "1.7", features = ["ndarray_v0_15"] } |
| ndarray 0.16 | ndarray = "0.16" | basin = { version = "1.7", features = ["ndarray_v0_16"] } |
| ndarray 0.17 | ndarray = "0.17" | basin = { version = "1.7", features = ["ndarray_v0_17"] } |
| faer 0.22 | faer = { version = "0.22", default-features = false, features = ["std", "linalg"] } | basin = { version = "1.7", features = ["faer_v0_22"] } |
| faer 0.23 | faer = { version = "0.23", default-features = false, features = ["std", "linalg"] } | basin = { version = "1.7", features = ["faer_v0_23"] } |
| faer 0.24 | faer = { version = "0.24", default-features = false, features = ["std", "linalg"] } | basin = { version = "1.7", features = ["faer_v0_24"] } |
The moving aliases nalgebra_latest, ndarray_latest, and faer_latest are
convenient for new applications. 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 the newest enabled release.
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 executor; SimplexTolerance checks both simplex diameter and cost
spread, rather than Argmin’s sample standard deviation of costs alone.
use basin::{CostFunction, Executor, NelderMead, SimplexTolerance};
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(), vec![-1.2, 1.0])
.max_iter(1_000)
.terminate_on(SimplexTolerance::new(1e-8, 1e-8))
.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 BasicSimplexState::from_simplex(vertices) as the
third argument. from_start is intentionally unavailable for solvers whose
initialization needs more than one point, such as CMA-ES and population solvers.
Common API Mappings
| Argmin 0.11 | Basin 1.7 |
|---|---|
| `Executor::new(problem, solver).configure( | state |
NelderMead::new(simplex).with_sd_tolerance(tol) | NelderMead::new() with SimplexTolerance::new(tol_x, tol_f) on the executor |
LBFGS::new(MoreThuenteLineSearch::new(), m) | Lbfgs::<Unbounded>::new().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 BoxConstraints and GlobalBestPsoState::new() |
BrentRoot::new(lower, upper, tol) | BrentRoot::new(lower, upper).with_tol(tol_rel, 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 | BoxConstraints 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<Unbounded> is unconstrained, while Lbfgsb (and the
default Lbfgs) requires BoxConstraints. Moré–Thuente is Basin’s default line
search for both modes.
use basin::solver::lbfgs::Unbounded;
use basin::{CostFunction, Executor, Gradient, GradientTolerance, 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::<Unbounded>::new().with_m_capacity(7);
let result = Executor::from_start(Sphere, solver, vec![3.0, -4.0])
.max_iter(200)
.terminate_on(GradientTolerance(1e-12))
.run()
.unwrap();
assert!(result.cost() < 1e-12);
} For a nondefault line search, use Lbfgs::<Unbounded, HagerZhang>::with_line_search(HagerZhang::new()). 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_tol_grad_rel, with_tol_cost_rel, and with_tol_step_rel 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.
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, GradientTolerance,
};
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),
vec![2.0, -1.0],
)
.max_iter(100)
.terminate_on(GradientTolerance(1e-12))
.run()
.unwrap();
assert!(result.cost() < 1e-12);
} FiniteDiff forwards BoxConstraints, 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. Implement BoxConstraints, 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::{BoxConstraints, CostFunction, Executor, Gradient, Lbfgsb};
use std::convert::Infallible;
struct BoundedQuadratic {
lower: Vec<f64>,
upper: Vec<f64>,
}
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)])
}
}
impl BoxConstraints for BoundedQuadratic {
fn lower(&self) -> &Self::Param {
&self.lower
}
fn upper(&self) -> &Self::Param {
&self.upper
}
}
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, Trf, 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 BoxConstraints implementation. 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
state with explicit positions and velocities 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::{
BoxConstraints, CostFunction, Executor, GlobalBestPso,
GlobalBestPsoState, PsoBoundaryHandling,
};
use std::convert::Infallible;
struct BoundedSphere {
lower: Vec<f64>,
upper: Vec<f64>,
}
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())
}
}
impl BoxConstraints for BoundedSphere {
fn lower(&self) -> &Self::Param {
&self.lower
}
fn upper(&self) -> &Self::Param {
&self.upper
}
}
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,
GlobalBestPsoState::<Vec<f64>>::new(),
)
.max_iter(500)
.run()
.unwrap();
assert!(result.cost() < 1e-6);
} For exact continuation, the initialized GlobalBestPsoState owns the live RNG,
velocities, and personal/global bests. With serde, serialize that state and
pass it with the same problem and solver configuration to Executor::resume,
or use a solver-aware ExactCheckpointWriter.
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 chain progress in SimulatedAnnealingState, while
the exact checkpoint captures the solver and state together. Reattach
termination criteria, observers, cancellation, and the checkpoint writer when
resuming.
Observers and Cancellation
Basin observers receive read-only state and return (). They do not receive
solver-specific key-value metadata, and an observer failure cannot accidentally
kill an optimization. Use a cloned CancellationToken when a progress callback
requests a normal stop; the executor returns the best available state with TerminationReason::Cancelled.
use basin::{
CancellationToken, CostFunction, Executor, NelderMead, Observe,
ObserverMode, State, TerminationReason,
};
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.reason, TerminationReason::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, SimplexTolerance};
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(), vec![1.0, -1.0])
.max_iter(300)
.terminate_on(SimplexTolerance::new(1e-8, 1e-12))
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} nalgebra 0.32–0.35
use basin::{CostFunction, Executor, NelderMead, SimplexTolerance};
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(), x0)
.max_iter(300)
.terminate_on(SimplexTolerance::new(1e-8, 1e-12))
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} ndarray 0.15–0.17
use basin::{CostFunction, Executor, NelderMead, SimplexTolerance};
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(), x0)
.max_iter(300)
.terminate_on(SimplexTolerance::new(1e-8, 1e-12))
.run()
.unwrap();
assert!(result.cost() < 1e-10);
} faer 0.22–0.24
use basin::{CostFunction, Executor, NelderMead, SimplexTolerance};
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(), x0)
.max_iter(300)
.terminate_on(SimplexTolerance::new(1e-8, 1e-12))
.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 termination criteria.
- Solvers: supported algorithms and backend matrix.
- Full Basin API: authoritative type and method documentation.