numpy SciPy的scipy.optimize.minimize不服从约束

qzlgjiam  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(126)

我尝试使用SciPy的optimize.minimize来拟合抛物线,根据一些约束条件来最小化一些分散的数据:曲线下的面积以及曲线通过数据的起点和终点。在这里,curve_fit可以正常工作,但我希望在其他情况下也使用带有约束的minimize。约束版本似乎不成功(可能是因为xy的值范围...?)。有什么办法可以帮助它收敛吗?

import scipy
import numpy as np
import matplotlib.pyplot as plt

x = np.array([258.6669469, 258.831, 259.129, 259.428 , 259.726, 260.025, 260.324, 260.622,261.15238824])
y = np.array([0, -0.062, -0.139, -0.181, -0.197, -0.193, -0.17 , -0.129, 0])

def Func(x,a,b,c):
    return a*x**2 + b*x + c

def FuncCons(x,params):
    return params[2]*x**2 + params[1]*x + params[0]

def ConstraintIntegral(params):
    integral = scipy.integrate.quad(FuncCons, x[0], x[-1], args=(params,))[0]
    return integral- np.trapz(y,x)

def ConstraintBegin(params):
    return y[0] - FuncCons(x[0],params)

def ConstraintEnd(params):
    return y[-1] - FuncCons(x[-1],params)

def Objective(params,x,y):
    y_pred = FuncCons(x,params)
    return np.sum((y_pred - y) ** 2) # least squares

cons = [{'type':'eq', 'fun': ConstraintIntegral},
        {'type':'eq', 'fun': ConstraintBegin},
        {'type':'eq', 'fun': ConstraintEnd}]

sigma = np.ones(len(x))
sigma[[0, -1]] = 0.0001 # first and last points

popt1, _ = scipy.optimize.curve_fit(Func, x, y, sigma=sigma)

new = scipy.optimize.minimize(Objective, x0=popt1, args=(x,y), constraints=cons)
popt3 = new.x

y_fit1 = Func(x, *popt1)  
y_fit3 = FuncCons(x, popt3) 

fig, ax = plt.subplots(1)
ax.scatter(x,y)
ax.plot(x,y_fit1, color='g', alpha=0.75, label='curve_fit')
ax.plot(x,y_fit3, color='r', alpha=0.75, label='generally constrained')
plt.legend()

字符串

vshtjzan

vshtjzan1#

这不应使用迭代拟合;该问题是精确确定的,并且应该通过分析计算:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit

def quadratic(x: np.ndarray, a: float, b: float, c: float) -> np.ndarray:
    return a*x**2 + b*x + c

def estimate(x: np.ndarray, y: np.ndarray, p: float, q: float) -> np.ndarray:
    """Calculate parabolic parameters exactly through both roots and a point close to the vertex"""
    iv = y.argmin()  # index of point closest to vertex
    vx = x[iv]       # x-value near to vertex
    vy = y[iv]       # y-value near to vertex

    return np.linalg.solve(
        a=(
            (  p*p,  p, 1),  # p root
            (  q*q,  q, 1),  # q root
            (vx*vx, vx, 1),  # near the vertex
        ),
        b=(0, 0, vy),
    )

def analytic_integral(a: float, b: float, c: float, p: float, q: float) -> float:
    """Definite integral of quadratic(a, b, c) from p to q"""
    return (
        a*(q**3 - p**3)/3
        + 0.5*b*(q**2 - p**2)
        + c*(q - p)
    )

def solve(p: float, q: float, integ: float) -> tuple[float, float, float]:
    # to fix the integral
    b = integ/(
        0.5*(q**2 - p**2)
        - (
            q*p*(q - p)
            + (q**3 - p**3)/3
        )/(q + p)
    )
    # to fix the first and second roots
    a = -b/(q + p)
    c = a*p*q
    return a, b, c

def main() -> None:
    x = np.array((258.6669469, 258.831, 259.129, 259.428, 259.726,
                  260.025, 260.324, 260.622, 261.15238824))
    y = np.array((0, -0.062, -0.139, -0.181, -0.197, -0.193, -0.17, -0.129, 0))

    # empirical integral, to be preserved
    integ = np.trapz(y, x)

    # assume that the first and last data points are the roots
    p = x[0]
    q = x[-1]

    # estimate through three points, disregarding integral
    popt_est = estimate(x, y, p, q)
    # generic unconstrained fit
    popt_approx, _ = curve_fit(f=quadratic, xdata=x, ydata=y, p0=popt_est)

    df = pd.DataFrame(
        {
            'estimated': popt_est,
            'approx': popt_approx,
            'exact': solve(p, q, integ),  # through both roots, preserving integral
        },
        index=pd.Index(name='parameter', data=('a', 'b', 'c')),
    ).T
    params = df.values.T

    dev = quadratic(x[:, np.newaxis], *params) - y[:, np.newaxis]
    df['integral'] = analytic_integral(*params, p, q)
    df['integral_err'] = df.integral - integ
    df['root1_err'] = quadratic(p, *params)
    df['root2_err'] = quadratic(q, *params)
    df['sqerr'] = (dev*dev).sum(axis=0)
    pd.set_option('display.float_format', '{:.6e}'.format)
    print(df.T)

    xlong = np.linspace(p, q, 1000)
    fig, ax = plt.subplots()
    ax.scatter(x, y, label='Empirical data')

    for title, param in df[['a', 'b', 'c']].iterrows():
        ax.plot(xlong, quadratic(xlong, *param), label=title)
    ax.legend()
    plt.show()

if __name__ == '__main__':
    main()

个字符
x1c 0d1x的数据

相关问题