如何创建与 scipy 一起使用的 n 个参数的方程生成器?

问题描述

我正在将我的代码从 MatLab 移植到 Python,我做了一个巧妙的技巧,但无法重现:

function [Equation,EquationComponents] = BezierEquation(n)
syms t x01 x02 x03 x04 x05 x06 x07 x08 x09 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28 x29 x30 x31 x32 x33 x34 x35 x36 x37 x38 x39 x40 x41;
xVar=[x01,x02,x03,x04,x05,x06,x07,x08,x09,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41];
for i = 0:n
    B(:,i+1)= nchoosek(n,i);
    Pol(:,i+1)= (1-t)^(n-i)*t^i;
    xVar2(:,i+1)=xVar(:,i+1);
end
EquationComponents=[xVar2;B;Pol];
Equation=sum(B.*xVar2.*Pol);
end

它的作用是生成一个具有 n 个参数的 n 次贝塞尔方程。用 n=30 或 n=40 手动编写这个方程会很痛苦。

我目前正在尝试对 scipy 做同样的事情并将其用于 curve_fit,但我不明白如何创建可变数量参数的方程。我目前有此代码,其中包含 n=5 的可工作的手写示例。如何为任何 n 生成? curve_fit 似乎不明白 co 不是标量。

import numpy as np
from scipy.special import comb
from scipy.optimize import curve_fit

class Bezier(object):
    def __init__(self,n):
        self.n = n
        self.i = np.arange(0,n + 1)
        self.n_minus_i = np.flip(self.i)
        self.b = comb(n,self.i)

    def generate_equation(self,x,co):
        b = self.b
        i = self.i
        eq = []
        for j,B in enumerate(b):
            eq.append(B * (1 - x)**(self.n - i[j]) * x**i[j] * co[j])
        return np.sum(eq)

    def equation_5(self,a,b,c,d,e,f):
        i = np.arange(0,6)
        B = comb(5,i)
        return a*B[0]*(1-x)**(5-i[0])*x**i[0] + b*B[1]*(1-x)**(5-i[1])*x**i[1] + c*B[2]*(1-x)**(5-i[2])*x**i[2] + d*B[3]*(1-x)**(5-i[3])*x**i[3] + e*B[4]*(1-x)**(5-i[4])*x**i[4] + f*B[5]*(1-x)**(5-i[5])*x**i[5]

更新:

通过查看 sympy 库,我用它做了一个简单的解决方案。我正在分享它,但我想保持这个问题的开放性,以寻求没有同情的解决方案。也许使用 numpy 数组而不是变量,或者是否有办法通过解包 n 个参数来创建 lambda 函数。相当于在 lambdify([x,*list_of_params],equation,'numpy') 中解包但没有同情的东西。

    import numpy as np
    from scipy.special import comb
    from scipy.optimize import curve_fit
    from sympy import symbols
    from sympy import lambdify
    
def bezier_generator(n):
    if n > 15:
        return
    i = np.arange(0,n + 1)
    b = comb(n,i)
    x,c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15 = symbols(
        "x,c15")
    co = [c0,c15]

    eq = np.sum(b * (1 - x) ** (n - i) * x ** i * co[:n + 1])
    func = lambdify([x,*co[:n + 1]],eq,'numpy')
    return func

解决方法

如果我理解正确:

我想在没有同情的情况下保持这个问题的解决方案。也许使用 numpy 数组而不是变量

然后听起来你想从简单的 Python 实现开始(将二项式函数外包给一个已经实现它的库,比如 scipy),并提升仍然不足以完成你最终用它做的事情的部分:

from scipy.stats import binom

def evaluate_bezier_at(t,coords):
    mt = 1 - t
    sum = 0
    n = len(coords)
    for (i,w_i) in enumerate(coords):
        sum += w_i * binom(n,i) * (mt ** (n-i)) * (t ** i)
    return sum

(当然还有速度优化,但如果您使用 30 度以上的 Bezier 曲线,我假设您不会编写需要达到最佳性能代码的代码:没有人需要 Beziers这么高的学位=)