9个版本 (破坏性)

新版本 0.7.1 2024年8月23日
0.7.0 2024年8月23日
0.6.0 2024年8月17日
0.5.0 2024年8月15日
0.1.0 2024年7月13日

#240 in 数学

Download history 219/week @ 2024-07-12 326/week @ 2024-07-19 441/week @ 2024-07-26 89/week @ 2024-08-02 65/week @ 2024-08-09 271/week @ 2024-08-16

每月下载 873次
4 个Crates中使用了(直接使用2个)

MIT/Apache

1MB
1K SLoC

Rust中函数最小化,简化版

GitHub Release GitHub last commit GitHub Actions Workflow Status GitHub License Crates.io Version docs.rs Codecov

ganesh(/ɡəˈneɪʃ/),以印度智慧之神的名字命名,提供了一些常见的最小化算法以及一个简单、基于特质的接口来创建自己的扩展。这个crate旨在尽可能简单。用户需要在某些结构体上实现Function特质,该特质将接受一个参数向量并返回一个单值Result ($f(\mathbb{R}^n) \to \mathbb{R}$)。用户可以选择提供一个梯度函数以加快某些算法的速度,但提供了一个默认的中心有限差分实现,以便所有算法都能直接使用。

[!警告] 这个crate仍处于早期开发阶段,API不稳定。在1.0.0版本发布之前(希望之后不会太多),它可能会(并可能确实会)受到破坏性更改。

目录

关键特性

  • 简单但强大的特质导向库,试图遵循Unix哲学“做一件事,做好一件事”。
  • 泛型以允许在提供的算法中使用不同的数值类型。
  • 简单易用、默认设置合理的算法。
  • 特质使得开发未来的算法简单且一致。

快速入门

此crate在test_functions模块中提供了一些常见的测试函数。考虑以下Rosenbrock函数的实现

use std::convert::Infallible;
use ganesh::prelude::*;

pub struct Rosenbrock {
    pub n: usize,
}
impl Function<f64, (), Infallible> for Rosenbrock {
    fn evaluate(&self, x: &[f64], _user_data: &mut ()) -> Result<f64, 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))
            .sum())
    }
}

为了最小化此函数,我们可以考虑使用Nelder-Mead算法

use ganesh::prelude::*;
use ganesh::algorithms::NelderMead;

let problem = Rosenbrock { n: 2 };
let nm = NelderMead::default();
let mut m = Minimizer::new(nm, 2);
let x0 = &[2.0, 2.0];
m.minimize(&problem, x0, &mut ()).unwrap();
println!("{}", m.status);

这将输出

MSG:       term_f = STDDEV
X:         [0.9999999946231828, 0.9999999884539057]
F(X):      0.00000000000000009170942877687133
N_EVALS:   160
CONVERGED: true

边界

ganesh中的所有最小化器都拥有一个功能,允许通常在无界参数空间中运行的算法只在边界框内返回结果。这是通过参数变换实现的,与LMFITMINUIT使用的相同。当用户在边界内输入参数时,无界算法可以将这些值转换为一系列无界的“内部”参数。然而,在调用函数时,这些内部参数通过以下变换重新转换为有界的“外部”参数:

上界和下界

x_\text{int} = \arcsin\left(2\frac{x_\text{ext} - x_\text{min}}{x_\text{max} - x_\text{min}} - 1\right)
x_\text{ext} = x_\text{min} + \left(\sin(x_\text{int}) + 1\right)\frac{x_\text{max} - x_\text{min}}{2}

仅上界

x_\text{int} = \sqrt{(x_\text{max} - x_\text{ext} + 1)^2 - 1}
x_\text{ext} = x_\text{max} + 1 - \sqrt{x_\text{int}^2 + 1}

仅下界

x_\text{int} = \sqrt{(x_\text{ext} - x_\text{min} + 1)^2 - 1}
x_\text{ext} = x_\text{min} - 1 + \sqrt{x_\text{int}^2 + 1}

LMFITMINUIT的文档所述,应谨慎使用这些边界。它们将线性问题转化为非线性问题,可能会干扰误差传播,甚至影响拟合收敛,更不用说增加函数复杂性了。如果需要输出协方差矩阵的方法,在有限制的情况下需要调整,而MINUIT建议在没有边界的最小值附近进行第二次拟合,以确保正确的误差传播。

未来计划

  • 最终,我打算实现BGFS及其变体、MCMC算法以及一些更现代的无梯度优化技术。
  • 我可能错过了许多优化和算法扩展,因为我只是想首先让它运行起来。
  • 测试套件

依赖项

~3MB
~57K SLoC