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
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