求解python中的非线性方程

时间:2013-10-23 13:17:55

标签: python numpy scipy nonlinear-functions

我有4个非线性方程,其中有三个未知数XYZ我想要求解。方程的形式如下:

F(m) = X^2 + a(m)Y^2 + b(m)XYcosZ + c(m)XYsinZ

...其中abc是常量,它们取决于四个等式中F的每个值。

解决这个问题的最佳方法是什么?

2 个答案:

答案 0 :(得分:50)

有两种方法可以做到这一点。

  1. 使用非线性解算器
  2. 线性化问题并以最小二乘意义解决问题
  3. 设置

    所以,据我了解你的问题,你知道4个不同点的F,a,b和c,你想要反演模型参数X,Y和Z.我们有3个未知数和4个观测数据点,所以问题是超定的。因此,我们将在最小二乘意义上解决。

    在这种情况下使用相反的术语更为常见,所以让我们翻转你的方程式。而不是:

    F_i = X^2 + a_i Y^2 + b_i X Y cosZ + c_i X Y sinZ
    

    让我们写一下:

    F_i = a^2 + X_i b^2 + Y_i a b cos(c) + Z_i a b sin(c)
    

    我们在4个不同点(例如F)了解XYZF_0, F_1, ... F_i

    我们只是改变变量的名称,而不是方程本身。 (这比我更容易思考。)

    线性解决方案

    实际上可以将这个等式线性化。您可以轻松解决a^2b^2a b cos(c)a b sin(c)。为了使这更容易,让我们再次重新考虑:

    d = a^2
    e = b^2
    f = a b cos(c)
    g = a b sin(c)
    

    现在方程式更简单:F_i = d + e X_i + f Y_i + g Z_i。对defg进行最小二乘线性反演很容易。然后,我们可以从

    获取abc
    a = sqrt(d)
    b = sqrt(e)
    c = arctan(g/f)
    

    好的,让我们以矩阵形式写出来。我们将翻译4个观察结果(我们将编写的代码将采用任意数量的观察结果,但现在让它保持具体):

    F_i = d + e X_i + f Y_i + g Z_i
    

    分为:

    |F_0|   |1, X_0, Y_0, Z_0|   |d|
    |F_1| = |1, X_1, Y_1, Z_1| * |e|
    |F_2|   |1, X_2, Y_2, Z_2|   |f|
    |F_3|   |1, X_3, Y_3, Z_3|   |g|
    

    或者:F = G * m(我是地球物理学家,因此我们使用G表示“格林函数”,m表示“模型参数”。通常我们使用{{1也可以使用“数据”代替d。)

    在python中,这将转换为:

    F

    非线性解决方案

    你也可以使用def invert(f, x, y, z): G = np.vstack([np.ones_like(x), x, y, z]).T m, _, _, _ = np.linalg.lstsq(G, f) d, e, f, g = m a = np.sqrt(d) b = np.sqrt(e) c = np.arctan2(g, f) # Note that `c` will be in radians, not degrees return a, b, c 来解决这个问题,就像@Joe建议的那样。 scipy.optimize中最易于访问的函数是scipy.optimize,默认情况下使用Levenberg-Marquardt方法。

    Levenberg-Marquardt是一种“爬山”算法(在这种情况下,它会走下坡路,但无论如何都会使用该术语)。从某种意义上说,您可以对模型参数进行初步猜测(默认情况下为scipy.optimize.curve_fit)并按照参数空间中scipy.optimize的斜率下坡到底部。

    警告:选择正确的非线性反演方法,初步猜测和调整方法的参数是非常“黑暗的艺术”。你只能通过这样做来学习它,并且在很多情况下,事情将无法正常工作。如果你的参数空间相当平滑(这应该是),Levenberg-Marquardt是一个很好的通用方法。除了更常见的方法(如模拟退火)之外,还有很多其他方法(包括遗传算法,神经网络等)在其他情况下更好。我不打算在这里深入研究这一部分。

    有一个常见问题是某些优化工具包试图纠正observed - predicted没有尝试处理的问题。如果您的模型参数具有不同的大小(例如scipy.optimize),则需要重新调整大小以使它们的大小相似。否则a=1, b=1000, c=1e-8的“爬山”算法(如LM)将无法准确计算局部梯度的估计值,并且会产生非常不准确的结果。目前,我假设scipy.optimizeab具有相对相似的幅度。此外,请注意,基本上所有非线性方法都需要您进行初始猜测,并且对该猜测很敏感。我将它留在下面(只需将其作为c kwarg传递给p0),因为默认curve_fit对于a, b, c = 1, 1, 1是一个相当准确的猜测。

    有了警告,a, b, c = 3, 2, 1期望传递一个函数,一组观察点(作为单个curve_fit数组)和观察值。 / p>

    所以,如果我们写这样的函数:

    ndim x npoints

    在将其传递给def func(x, y, z, a, b, c): f = (a**2 + x * b**2 + y * a * b * np.cos(c) + z * a * b * np.sin(c)) return f 之前,我们需要将其包装以接受稍微不同的参数。

    简而言之:

    curve_fit

    两种方法的独立示例:

    为了给你一个完整的实现,这是一个

    的例子
    1. 生成随机分布的点以评估函数,
    2. 评估这些点上的函数(使用set model parameters),
    3. 为结果添加噪音,
    4. 然后使用上述线性和非线性方法反演模型参数。

    5. def nonlinear_invert(f, x, y, z):
          def wrapped_func(observation_points, a, b, c):
              x, y, z = observation_points
              return func(x, y, z, a, b, c)
      
          xdata = np.vstack([x, y, z])
          model, cov = opt.curve_fit(wrapped_func, xdata, f)
          return model
      

答案 1 :(得分:2)

你可能想要使用scipy的非线性求解器,它们非常简单:http://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html