5个版本

0.2.0 2024年7月14日
0.1.3 2023年12月28日
0.1.2 2023年6月6日
0.1.1 2022年6月10日
0.1.0 2022年6月7日

#145 in 数学

47 每月下载次数
用于 comp-flow

MIT 协议

120KB
1.5K SLoC

eqsolver - Rust方程求解和优化库

这个Rust库旨在数值求解方程和优化目标函数。

该库是 被动维护 的,这意味着不会添加其他功能。但是,会回答和解决GitHub上的问题。

欢迎对这个库的贡献和反馈!

支持的方法

以下方法可用于库中。它们的描述使用函数可能的最大定义域和值域,即Rn。然而,Rn的任何(良好定义的)子集也可以工作。此外,使用多变量输入或输出的方法大量使用Rust线性代数库 nalgebra

单变量

牛顿-拉夫森法给定一元函数f(x)的导数Df(x)和初始猜测,找到该函数的根。这种方法具有二次收敛速率。
牛顿-拉夫森法(带有限差分)通过使用有限差分近似其导数Df(x),给定根的初始猜测,找到一元函数f(x)的根。这种方法具有二次收敛速率,但比非有限差分版本需要更多的计算,使得运行时间略长。
割线法给定两个唯一的起始值,找到一元函数f(x)的根。这种方法具有略低的收敛速率(等于黄金比例),但每次迭代只进行一次函数调用,使得其运行时间有时低于牛顿-拉夫森方法。

多变量

牛顿-拉夫森法(带和不含有限差分)对于函数F: Rn → Rn,该方法找到x使得F(x)是零向量,这相当于求解一个含有n个未知数的n个方程的系统。

此方法有两种版本,一种需要给出雅可比矩阵,另一种使用有限差分近似它。因此,后一种版本的运行时间略长。两种方法都需要初始猜测。

对于某些病态问题,此方法可能会失败。对于更慢但更稳健的方法,请参阅下面的Levenberg-Marquardt方法。

高斯-牛顿法(带和不含有限差分)对于函数F:Rm → Rn,这种方法寻找x,使得F(x)为零向量,这等价于解一个含有m个未知数的n个方程组。这是通过在每次迭代中解决最小二乘问题来实现的,这使得这种方法的大致时间略长于牛顿-拉夫森方法。

此方法有两种版本,一种需要给出雅可比矩阵,另一种使用有限差分近似它。因此,后一种版本的运行时间略长。两种方法都需要初始猜测。

对于某些病态问题,此方法可能会失败。对于更慢但更稳健的方法,请参阅下面的Levenberg-Marquardt方法。

Levenberg-Marquardt方法(带有限差分和不带有限差分)对于函数F:Rm → Rn,这种方法寻找x,使得F(x)为零向量,这等价于解一个含有m个未知数的n个方程组。这是通过在每次迭代中解决一个阻尼最小二乘问题(比普通最小二乘问题计算量更大)来实现的,这使得这种方法的大致时间略长于高斯-牛顿方法。

此方法有两种版本,一种需要给出雅可比矩阵,另一种使用有限差分近似它。因此,后一种版本的运行时间略长。两种方法都需要初始猜测。

目标函数的全局优化器

粒子群优化对于函数F:Rn → R,这种方法寻找x,使得对于所有y,F(x) <= F(y),即全局最小值。此方法需要一个初始猜测以及全局最小值存在的边界。

如果您知道参数的边界,请使用此方法。

交叉熵方法对于函数F:Rn → R,这种方法寻找x,使得对于所有y,F(x) <= F(y),即全局最小值。此方法需要一个初始猜测和一个Rn向量的标准差(每个参数的不确定性)。

如果您不知道参数的边界但知道每个参数的不确定性,请使用此方法。

常微分方程(或它们的系统)

存在一个针对常微分方程(ODE)的单一结构体,可以通过构建模式修改它以使用以下以下步进方法之一

欧拉前向法此方法只需要调用一次对应于方程的函数,因此速度快。然而,它的精度阶为1,对于某些函数是不稳定的。
海恩方法(龙格-库塔2)此方法需要调用两次对应于方程的函数,因此比欧拉前向法慢。这种方法具有2阶精度。
龙格-库塔4(默认)此方法需要调用四次对应于方程的函数,因此比海恩方法慢。这种方法具有4阶精度。ODE求解器使用此方法作为默认。

示例

牛顿-拉夫森方法与有限差分的示例。

use eqsolver::single_variable::FDNewton;

let f = |x: f64| x.exp() - 1./x; // e^x = 1/x
let solution = FDNewton::new(f).solve(0.5); // Starting guess is 0.5

牛顿-拉夫森方法与有限差分解方程组的示例。

use eqsolver::multivariable::MultiVarNewtonFD;
use nalgebra::{vector, Vector2};

// Want to solve x^2 - y = 1 and xy = 2
let f = |v: Vector2<f64>| vector![v[0].powi(2) - v[1] - 1., v[0] * v[1] - 2.];

let solution = MultiVarNewtonFD::new(f).solve(vector![1., 1.]); // Starting guess is (1, 1)

单个一阶常微分方程解的示例。

use eqsolver::ODESolver;

let f = |t: f64, y: f64| t * y; // y' = f(t, y) = ty
let (x0, y0) = (0., 0.2);
let x_end = 2.;
let step_size = 1e-3;

let solution = ODESolver::new(f, x0, y0, step_size).solve(x_end);

使用Levenberg-Marquardt方法解非线性最小二乘问题的示例。

use eqsolver::multivariable::LevenbergMarquardtFD;
use nalgebra::{vector, Vector2};

let c0 = [3., 5., 3.];
let c1 = [1., 0., 4.];
let c2 = [6., 2., 2.];

// Function from R2 to R3
let f = |v: Vector2<f64>| {
    vector!(
        (v[0] - c0[0]).powi(2) + (v[1] - c0[1]).powi(2) - c0[2] * c0[2],
        (v[0] - c1[0]).powi(2) + (v[1] - c1[1]).powi(2) - c1[2] * c1[2],
        (v[0] - c2[0]).powi(2) + (v[1] - c2[1]).powi(2) - c2[2] * c2[2],
    )
};

let solution_lm = LevenbergMarquardtFD::new(f)
    .solve(vector![4.5, 2.5]) // Guess
    .unwrap();

在Rastrigin函数上使用全局优化器的示例。

use eqsolver::global_optimisers::{CrossEntropy, ParticleSwarm};
use nalgebra::SVector;
use std::f64::consts::PI;

const SIZE: usize = 10;
let rastrigin = |v: SVector<f64, SIZE>| {
    v.fold(10. * SIZE as f64, |acc, x| {
        acc + x * x - 10. * f64::cos(2. * PI * x)
    })
};

let bounds = SVector::repeat(10.);
let standard_deviations = SVector::repeat(10.);
let guess = SVector::repeat(5.);

let opt_pso = ParticleSwarm::new(rastrigin, -bounds, bounds).solve(guess);
let opt_ce = CrossEntropy::new(rastrigin)
    .with_std_dev(standard_deviations)
    .solve(guess);

更多示例,请参阅示例目录

依赖关系

~4MB
~75K SLoC