< 返回版块

eweca-d 发表于 2024-06-17 15:09

之前用最小二乘库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, &params).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, &params).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。

评论区

写评论
asuper 2024-06-19 09:19

嗯,应该是可以的,当初就想着找个数学库,没想那么多,现在的版本挺好,不改了

--
👇
eweca-d: 我看python的scipy就有基于LM法的,其实我看了下mathru里直接集成有LM法,还不要求输入雅可比矩阵,或者你直接构建二乘法去调用它会更方便的。

--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。

另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。

作者 eweca-d 2024-06-18 16:08

我看python的scipy就有基于LM法的,其实我看了下mathru里直接集成有LM法,还不要求输入雅可比矩阵,或者你直接构建二乘法去调用它会更方便的。

--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。

另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。

作者 eweca-d 2024-06-18 16:06

我看了下那篇文章,挺有意思的,但是我可能还是想要尽量使用现成的库来组合。我二乘法直接用的库,只是现在缺个求雅可比矩阵的函数,其实我手写也不费啥功夫,就是差分而已,但这不是有现成的么,就想着用用看了哈哈。

--
👇
asuper: 没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。

另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。

asuper 2024-06-18 14:11

没有,就只用到了基础的矩阵计算,乘法、求逆、转置,没了,你可以看下我贴的文章,就是根据公式手写的代码。

另外,mathru这个库里还有用于比较浮点数相等的宏和结构,也挺好用。

作者 eweca-d 2024-06-17 18:02

我看了下这个库,你是用Levenberg-Marquardt algorithm这个算的么?这个好像已经内嵌了Jacobian的数值解了是么?如果用这个实现好像很容易。

--
👇
asuper: 我刚好也用rust实现了多项式拟合,用的数学库是mathru,拟合的代码是参考 https://zhuanlan.zhihu.com/p/268884807 自己用rust改写的

asuper 2024-06-17 17:05

我刚好也用rust实现了多项式拟合,用的数学库是mathru,拟合的代码是参考 https://zhuanlan.zhihu.com/p/268884807 自己用rust改写的

1 共 6 条评论, 1 页