Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
da9ef36
feat: add `Transform` trait to differentiate explicit bounded algorit…
denehoffman Sep 12, 2025
cb86eb4
fix: remove binary reference in Cargo.toml
denehoffman Sep 12, 2025
61bef50
test: add tests for transforms/bounds
denehoffman Sep 12, 2025
f3a0d8a
test: add tests for wrapped observers/terminators, fix minor inconsis…
denehoffman Sep 12, 2025
1d50b3f
test: remove extraneous code setting parameter names and add test for…
denehoffman Sep 12, 2025
4c02452
style: rename transform methods
denehoffman Sep 12, 2025
576fba2
feat: add and methods as shorthand for getting owned versions of ou…
denehoffman Sep 12, 2025
d3142cf
feat: many clippy lints plus an attempt at implementing transforms on…
denehoffman Sep 13, 2025
b5d79f3
feat: new draft of Transform using higher order derivatives
denehoffman Sep 16, 2025
3c0eff7
feat: make new generic cost/gradient for simulated annealing, remove …
denehoffman Sep 17, 2025
fe4a386
feat: change bounds implementation to one with better inverse, add op…
denehoffman Sep 17, 2025
32f8db2
fix: accidentally transposed the inverse Jacobian twice
denehoffman Sep 18, 2025
74f28d8
fix: revert to commented out code from debugging
denehoffman Sep 18, 2025
06971ff
feat: move DiffOps into Transform trait and update documentation with…
denehoffman Sep 18, 2025
dc12c3d
feat: add new example generating and fitting multivariate normal data
denehoffman Sep 19, 2025
7c0b491
feat: add some better styling to multivariate_normal_fit example
denehoffman Sep 19, 2025
c695705
feat: overhaul bounds methods
denehoffman Sep 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ exclude = ["src/main.rs", "*.png", "*.svg", "*.gif", "*.pkl"]
[lib]
bench = false

[[bin]]
name = "ganesh"
bench = false

[dependencies]
nalgebra = { version = "0.34.0", features = ["serde-serialize"] }
ctrlc = "3.5.0"
dyn-clone = "1.0.20"
typetag = "0.2.20"
serde = { version = "1.0.219", features = ["derive", "rc"] }
serde-pickle = "1.2.0"
spec_math = "0.1.6"
Expand Down
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ pub struct Rosenbrock {
pub n: usize,
}
impl CostFunction for Rosenbrock {
type Input = DVector<Float>;
fn evaluate(&self, x: &DVector<Float>, _args: &()) -> Result<Float, Infallible> {
Ok((0..(self.n - 1))
.map(|i| 100.0 * (x[i + 1] - x[i].powi(2)).powi(2) + (1.0 - x[i]).powi(2))
Expand All @@ -74,9 +73,9 @@ use ganesh::{Float, DVector};
use std::convert::Infallible;

fn main() -> Result<(), Infallible> {
let mut problem = Rosenbrock { n: 2 };
let problem = Rosenbrock { n: 2 };
let mut nm = NelderMead::default();
let result = nm.process(&mut problem,
let result = nm.process(&problem,
&(),
NelderMeadConfig::new([2.0, 2.0]),
NelderMead::default_callbacks())?;
Expand Down Expand Up @@ -138,30 +137,36 @@ cargo r -r --example <example_name>
```

## Bounds
All `Algorithm`s in `ganesh` can be constructed to have access to a feature which allows algorithms which usually function in unbounded parameter spaces to only return results inside a bounding box. This is done via a parameter transformation, the same one used by [`LMFIT`](https://lmfit.github.io/lmfit-py/) and [`MINUIT`](https://root.cern.ch/doc/master/classTMinuit.html). This transform is not enacted on algorithms which already have bounded implementations, like [`L-BFGS-B`](https://docs.rs/ganesh/latest/ganesh/algorithms/gradient/lbfgsb/struct.LBFGSB.html). While the user inputs parameters within the bounds, unbounded algorithms can (and in practice will) convert those values to a set of unbounded "internal" parameters. When functions are called, however, these internal parameters are converted back into bounded "external" parameters, via the following transformations:
All `Algorithm`s in `ganesh` can be constructed to have access to a feature which allows algorithms which usually function in unbounded parameter spaces to only return results inside a bounding box. This is done via a parameter transformation, similar to that used by [`LMFIT`](https://lmfit.github.io/lmfit-py/) and [`MINUIT`](https://root.cern.ch/doc/master/classTMinuit.html). This transform is not directly useful with algorithms which already have bounded implementations, like [`L-BFGS-B`](https://docs.rs/ganesh/latest/ganesh/algorithms/gradient/lbfgsb/struct.LBFGSB.html), but it can be combined with other transformations which may be useful to algorithms with bounds. While the user inputs parameters within the bounds, unbounded algorithms can (and in practice will) convert those values to a set of unbounded "internal" parameters. When functions are called, however, these internal parameters are converted back into bounded "external" parameters, via the following transformations:

Upper and lower bounds:
```math
x_\text{int} = \arcsin\left(2\frac{x_\text{ext} - x_\text{min}}{x_\text{max} - x_\text{min}} - 1\right)
x_\text{int} = \frac{u}{\sqrt{1 - u^2}}
```
```math
x_\text{ext} = x_\text{min} + \left(\sin(x_\text{int}) + 1\right)\frac{x_\text{max} - x_\text{min}}{2}
x_\text{ext} = c + w \frac{x_\text{int}}{\sqrt{x_\text{int}^2 + 1}}
```
where
```math
u = \frac{x_\text{ext} - c}{w},\ c = \frac{x_\text{min} + x_\text{max}}{2},\ w = \frac{x_\text{max} - x_\text{min}}{2}
```
Upper bound only:
```math
x_\text{int} = \sqrt{(x_\text{max} - x_\text{ext} + 1)^2 - 1}
x_\text{int} = \frac{1}{2}\left(\frac{1}{(x_\text{max} - x_\text{ext})} - (x_\text{max} - x_\text{ext}) \right)
```
```math
x_\text{ext} = x_\text{max} + 1 - \sqrt{x_\text{int}^2 + 1}
x_\text{ext} = x_\text{max} - (\sqrt{x_\text{int}^2 + 1} - x_\text{int})
```
Lower bound only:
```math
x_\text{int} = \sqrt{(x_\text{ext} - x_\text{min} + 1)^2 - 1}
x_\text{int} = \frac{1}{2}\left((x_\text{ext} - x_\text{min}) - \frac{1}{(x_\text{ext} - x_\text{min})} \right)
```
```math
x_\text{ext} = x_\text{min} - 1 + \sqrt{x_\text{int}^2 + 1}
x_\text{ext} = x_\text{min} + (\sqrt{x_\text{int}^2 + 1} + x_\text{int})
```
As noted in the documentation for both `LMFIT` and `MINUIT`, these bounds should be used with caution. They turn linear problems into nonlinear ones, which can mess with error propagation and even fit convergence, not to mention increase function complexity. Methods which output covariance matrices need to be adjusted if bounded, and `MINUIT` recommends fitting a second time near a minimum without bounds to ensure proper error propagation. Some methods, like `L-BFGS-B`, are written with implicit bounds handling, and these transformations are not performed in such cases.
While `MINUIT` and `LMFIT` recommend caution in interpreting covariance matrices obtained from
fits with bounds transforms, `ganesh` does not, since it implements higher-order derivatives on
these bounds while these other libraries use linear approximations.

## Future Plans

Expand Down
1 change: 0 additions & 1 deletion benches/derivatives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use ganesh::{

struct Rosenbrock;
impl CostFunction for Rosenbrock {
type Input = DVector<Float>;
fn evaluate(&self, x: &DVector<Float>, _: &()) -> Result<Float, Infallible> {
let mut s = 0.0 as Float;
for i in 0..(x.len() - 1) {
Expand Down
16 changes: 13 additions & 3 deletions benches/nelder_mead_benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use ganesh::{
algorithms::gradient_free::{nelder_mead::NelderMeadConfig, NelderMead},
algorithms::gradient_free::{
nelder_mead::{NelderMeadConfig, SimplexConstructionMethod},
NelderMead,
},
test_functions::rosenbrock::Rosenbrock,
traits::Algorithm,
};
Expand All @@ -9,7 +12,11 @@ fn nelder_mead_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("Nelder Mead");
for n in [2, 3, 4, 5] {
group.bench_with_input(BenchmarkId::new("Rosenbrock", n), &n, |b, ndim| {
let base_cfg = NelderMeadConfig::new(vec![5.0; *ndim]);
let base_cfg =
NelderMeadConfig::new_with_method(SimplexConstructionMethod::orthogonal(vec![
5.0;
*ndim
]));
b.iter_batched(
|| {
let problem = Rosenbrock { n: *ndim };
Expand All @@ -28,7 +35,10 @@ fn nelder_mead_benchmark(c: &mut Criterion) {
BenchmarkId::new("Rosenbrock (adaptive)", n),
&n,
|b, ndim| {
let base_cfg = NelderMeadConfig::new(vec![5.0; *ndim]).with_adaptive(*ndim);
let base_cfg = NelderMeadConfig::new_with_method(
SimplexConstructionMethod::orthogonal(vec![5.0; *ndim]),
)
.with_adaptive(*ndim);
b.iter_batched(
|| {
let problem = Rosenbrock { n: *ndim };
Expand Down
Loading
Loading