1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! basin — a numerical optimization library.
//!
//! The framework lives in [`core`]: problem traits the user implements
//! ([`CostFunction`], [`Gradient`], [`BoxConstraints`],
//! [`LinearInequalityConstraints`], [`LinearEqualityConstraints`],
//! [`LinearConstraints`], [`NonlinearInequalityConstraints`]), state shapes
//! solvers iterate over ([`State`], [`GradientState`], [`SimplexState`]),
//! the [`Solver`] trait, a pluggable termination layer
//! ([`TerminationCriterion`]), and a read-only observer layer
//! ([`Observe`]). Concrete solvers are in [`solver`]; line searches in
//! [`line_search`].
//!
//! Start at [`Executor`] for the user-facing driver, or [`core`] for the
//! trait taxonomy and the iteration-loop contract.
//!
//! See `CONTRIBUTING.md` at the repo root for the design tenets that shape
//! these APIs (notably tenet 3 on framework-level termination, tenet 4
//! on first-class constraints, and tenet 5 on backend tiering).
//!
//! # Example
//!
//! Implement [`CostFunction`] (and [`Gradient`] when the solver needs
//! derivatives), then hand the problem, a solver, and an initial state to
//! the [`Executor`]:
//!
//! ```
//! use basin::{BasicState, CostFunction, Executor, Gradient, GradientDescent, GradientTolerance};
//!
//! struct Sphere;
//! impl CostFunction for Sphere {
//! type Param = Vec<f64>;
//! type Output = f64;
//! type Error = std::convert::Infallible;
//! fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
//! Ok(x.iter().map(|xi| xi * xi).sum())
//! }
//! }
//! impl Gradient for Sphere {
//! type Gradient = Vec<f64>;
//! fn gradient(&self, x: &Vec<f64>) -> Result<Vec<f64>, std::convert::Infallible> {
//! Ok(x.iter().map(|xi| 2.0 * xi).collect())
//! }
//! }
//!
//! let result = Executor::new(Sphere, GradientDescent::new(0.1), BasicState::new(vec![1.0, 1.0]))
//! .max_iter(1_000)
//! .terminate_on(GradientTolerance(1e-8))
//! .run()
//! .unwrap();
//! assert!(result.cost() < 1e-12);
//! ```
//!
//! # Error model
//!
//! basin distinguishes *three* outcomes a run can produce. The split is a
//! stable part of the public contract — downstream code can rely on it:
//!
//! - **Soft reject** — return `Ok(f64::INFINITY)` from [`CostFunction::cost`]
//! to reject a *single point* without stopping the solve. Line searches treat
//! `+∞` as worse and retreat; population solvers treat it as worst fitness.
//! This is the channel for "this `x` is outside my domain, but the solve
//! should continue."
//! - **Clean stop** — the run ends *normally* with a
//! [`TerminationReason`], either
//! because a [`TerminationCriterion`] fired or because the [`Solver`] reported
//! a mid-iteration stop. [`Executor::run`] returns
//! `Ok(`[`OptimizationResult`]`)` carrying that reason. This is **not** an
//! error.
//! - **Hard abort** — return `Err(_)` from a problem-trait method to terminate
//! the *entire* solve. The error is your own type and bubbles out of
//! [`Executor::run`] untouched, typed as `Result<_, P::Error>`. Use it when
//! the failure is not about a particular `x` — a downstream service vanished,
//! the user pressed cancel, an early-stop condition in your own problem state
//! fired.
//!
//! ## One error type, threaded through
//!
//! The hard-abort error is chosen *once*, on the problem
//! ([`CostFunction::Error`], or
//! [`Residual::Error`] for nonlinear
//! least squares). Every downstream trait mirrors it: [`Solver::Error`] and
//! [`LineSearch::Error`] are set to `P::Error`, so a custom problem error flows
//! through the solver and line search out to the caller with no conversion glue.
//! Problems that cannot fail pick [`std::convert::Infallible`]; its niche
//! optimization keeps `Result<f64, Infallible>` the same layout as a bare `f64`,
//! so the happy path stays zero-cost.
//!
//! The [`problem`](crate::core::problem) module docs carry the per-trait detail.
//!
//! # Backends
//!
//! Parameters and linear algebra are generic over the backend. `Vec<f64>` needs
//! no features; nalgebra, ndarray, and faer are enabled one feature each, each
//! pinning a single major version. basin pins one major version per backend;
//! each basin 1.x release supports exactly these versions:
//!
//! | Backend | Feature | Version |
//! | ---------- | ------------ | ---------------------------------- |
//! | nalgebra | `nalgebra` | 0.34 (with `nalgebra-sparse` 0.11) |
//! | ndarray | `ndarray` | 0.17 |
//! | faer | `faer` | 0.24 |
//!
//! `Vec<f64>` is the built-in default backend, so no feature is needed for that.
//!
//! A backend major-version bump is a breaking change and ships only in a basin
//! major release; within the 1.x series these pins are fixed.
/// Catalogue of test problems used by the example tests and benchmarks.
/// Concrete solver implementations.
pub use crateAugmentedLagrangian;
pub use crateLogBarrier;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crateSolver;
pub use crateFaerQuasiNewtonState;
pub use crateNalgebraQuasiNewtonState;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crateBfgs;
pub use crate;
pub use crate;