之前用最小二乘库levenberg-marquardt
写了多项式的函数拟合,这个库要求手写Jacobian矩阵。最近论坛发了个帖子,推荐了一个多变量微积分的新库multicalc
,我就想试试写一个任意函数拟合。
我理解作者想要最大化求解效率,但是他要求的函数签名着实有点难实现,如下:
pub fn get<T: ComplexFloat, const NUM_FUNCS: usize, const NUM_VARS: usize>(
function_matrix: &[&dyn Fn(&[T; NUM_VARS]) -> T; NUM_FUNCS],
vector_of_points: &[T; NUM_VARS],
) -> Result<[[T; NUM_VARS]; NUM_FUNCS], ErrorCode>
最小二乘那个库要求实现一个trait,包含如下函数:
fn jacobian(&self) -> Option<Matrix<f64>>
这可把我难倒了,我目前的想法是:
pub struct CurveFit<F: Fn([f64; N], [f64; M]) -> f64, const N: usize, const M: usize> {
func: F,
xdata: Vector<[f64; N]>,
ydata: Vector<f64>,
params: Vector<f64>,
}
fn jacobian(&self) -> Option<Matrix<f64>> {
let params = self.get_params();
let default_func = |params: &[f64; M]| -> f64 {
panic!("Incompleted jacobian matrix function in curve fitting.");
};
let mut func: [&dyn Fn(&[f64; M]) -> f64; N] = [&default_func; N];
let mut func_vec = vec![];
for i in 0..N {
let f = |params: &[f64; M]| -> f64 {
-(self.func)(self.xdata[i], *params)
};
let boxed_f: Box<dyn Fn(&[f64; M]) -> f64> = Box::new(f);
func_vec.push(boxed_f);
}
let j: [[f64; M]; N] = jacobian::get(&func, ¶ms).unwrap();
let flat_j: Vec<f64> = j.iter().flat_map(|&slice| slice.into_iter()).collect();
let j = Matrix::from_vec(N, M, flat_j);
Some(j)
}
其中,[f64; N]是输入的x,[f64; M]是待拟合的系数。
首先Vec转array就很麻烦,我就想着直接创建一个array去覆盖。但是提示我变量i
生命周期不够长。其实我想在循环里直接绑定&dyn,然后又被生命周期卡住了,后我才想着用box。结果还是卡住。有啥好的解决办法吗?
update:
fn jacobian(&self) -> Option<Matrix<f64>> {
let params = self.get_params();
let default_func = |_: &[f64; M]| -> f64 {
panic!("Incompleted jacobian matrix function in curve fitting.");
};
let mut func: [&dyn Fn(&[f64; M]) -> f64; L] = [&default_func; L];
let mut func_vec = vec![];
for i in 0..L {
let x = self.xdata[i];
let f = move |params: &[f64; M]| -> f64 {
-(self.func)(x, *params)
};
let boxed_f: Box<dyn Fn(&[f64; M]) -> f64> = Box::new(f);
func_vec.push(boxed_f);
}
for i in 0..L {
func[i] = func_vec[i].as_ref();
}
let j: [[f64; M]; L] = jacobian::get(&func, ¶ms).unwrap();
let flat_j: Vec<f64> = j.iter().flat_map(|&slice| slice.into_iter()).collect();
let j = Matrix::from_vec(L, M, flat_j);
Some(j)
}
目前是这么解决了,不知道有没有啥更优雅的办法。顺便update解决了一个bug,就是Jacobian的行数是L,不是N,N是x的维度。但是又带来了一个新问题:由于struct没有显含const L: usize
的field,所以必须手动标注,如let curve_tift: CurveFit<&dyn Fn([f64; 1], [f64; 3]) -> f64, 1, 11, 3> = CurveFit::new(&func, x, y, None);
,有啥优雅的办法,在new的时候让struct知道L的信息吗?L好像不能当做field。
评论区
写评论嗯,应该是可以的,当初就想着找个数学库,没想那么多,现在的版本挺好,不改了
--
👇
eweca-d: 我看python的scipy就有基于LM法的,其实我看了下mathru里直接集成有LM法,还不要求输入雅可比矩阵,或者你直接构建二乘法去调用它会更方便的。
--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。
另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。
我看python的scipy就有基于LM法的,其实我看了下mathru里直接集成有LM法,还不要求输入雅可比矩阵,或者你直接构建二乘法去调用它会更方便的。
--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。
另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。
我看了下那篇文章,挺有意思的,但是我可能还是想要尽量使用现成的库来组合。我二乘法直接用的库,只是现在缺个求雅可比矩阵的函数,其实我手写也不费啥功夫,就是差分而已,但这不是有现成的么,就想着用用看了哈哈。
--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。
另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。
没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。
另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。
我看了下这个库,你是用
Levenberg-Marquardt algorithm
这个算的么?这个好像已经内嵌了Jacobian的数值解了是么?如果用这个实现好像很容易。--
👇
asuper: 我刚好也用rust实现了多项式拟合,用的数学库是mathru,拟合的代码是参考 https://zhuanlan.zhihu.com/p/268884807 自己用rust改写的
我刚好也用rust实现了多项式拟合,用的数学库是mathru,拟合的代码是参考 https://zhuanlan.zhihu.com/p/268884807 自己用rust改写的