【问题标题】:Problems with a function and odeint in pythonpython中的函数和odeint问题
【发布时间】:2025-11-25 08:00:01
【问题描述】:

考虑到它的巨大优势,我开始使用 python 几个月。但最近,我使用 scipy 中的 odeint 来求解一个微分方程组。但是在集成过程中,实现的功能并没有按预期工作。
在这种情况下,我想求解一个微分方程组,其中一个初始条件 (x[0]) 变化(在 4-5 之间)取决于变量在积分过程中达到的值(它在if 结构的函数)。

    #Control of oxygen
    SO2_lower=4
    SO2_upper=5
    if x[0]<=SO2_lower: 
       x[0]=SO2_upper

当函数被 odeint 使用时,函数内部的一些代码行被省略,即使函数改变了 x[0] 的值。这是我所有的代码:

    import numpy as np
    from scipy.integrate import odeint
    import matplotlib.pyplot as plt
    plt.ion()
    # Stoichiometric parameters
    YSB_OHO_Ox=0.67                           #Yield for XOHO growth per SB (Aerobic)
    YSB_Stor_Ox=0.85                          #Yield for XOHO,Stor formation per SB (Aerobic)
    YStor_OHO_Ox=0.63                         #Yield for XOHO growth per XOHO,Stor (Aerobic)
    fXU_Bio_lys=0.2                           #Fraction of XU generated in biomass decay
    iN_XU=0.02                                #N content of XU
    iN_XBio=0.07                              #N content of XBio
    iN_SB=0.03                                #N content of SB
    fSTO=0.67                                 #Stored fraction of SB

    #Kinetic parameters
    qSB_Stor=5                                #Rate constant for XOHO,Stor storage of SB
    uOHO_Max=2                                #Maximum growth rate of XOHO
    KSB_OHO=2                                 #Half-saturation coefficient for SB
    KStor_OHO=1                               #Half-saturation coefficient for XOHO,Stor/XOHO
    mOHO_Ox=0.2                               #Endogenous respiration rate of XOHO (Aerobic)
    mStor_Ox=0.2                              #Endogenous respiration rate of XOHO,Stor (Aerobic)
    KO2_OHO=0.2                               #Half-saturation coefficient for SO2
    KNHx_OHO=0.01                             #Half-saturation coefficient for SNHx

    #Other parameters
    DT=1/86400.0

    def f(x,t):
        #Control of oxygen
        SO2_lower=4
        SO2_upper=5
        if x[0]<=SO2_lower: 
           x[0]=SO2_upper
        M=np.matrix([[-(1.0-YSB_Stor_Ox),-1,iN_SB,0,0,YSB_Stor_Ox],
             [-(1.0-YSB_OHO_Ox)/YSB_OHO_Ox,-1/YSB_OHO_Ox,iN_SB/YSB_OHO_Ox-iN_XBio,0,1,0],
             [-(1.0-YStor_OHO_Ox)/YStor_OHO_Ox,0,-iN_XBio,0,1,-1/YStor_OHO_Ox],
             [-(1.0-fXU_Bio_lys),0,iN_XBio-fXU_Bio_lys*iN_XU,fXU_Bio_lys,-1,0],
             [-1,0,0,0,0,-1]])
        R=np.matrix([[DT*fSTO*qSB_Stor*(x[0]/(KO2_OHO+x[0]))*(x[1]/(KSB_OHO+x[1]))*x[4]],
             [DT*(1-fSTO)*uOHO_Max*(x[0]/(KO2_OHO+x[0]))*(x[1]/(KSB_OHO+x[1]))* (x[2]/(KNHx_OHO+x[2]))*x[4]],
             [DT*uOHO_Max*(x[0]/(KO2_OHO+x[0]))*(x[2]/(KNHx_OHO+x[2]))*((x[5]/x[4])/(KStor_OHO+(x[5]/x[4])))*(KSB_OHO/(KSB_OHO+x[1]))*x[4]],
             [DT*mOHO_Ox*(x[0]/(KO2_OHO+x[0]))*x[4]],
             [DT*mStor_Ox*(x[0]/(KO2_OHO+x[0]))*x[5]]])

        Mt=M.transpose()
        MxRm=Mt*R
        MxR=MxRm.tolist()
        return ([MxR[0][0],
                MxR[1][0],
                MxR[2][0],
                MxR[3][0],
                MxR[4][0],
                MxR[5][0]])
    #ODE solution
    t=np.linspace(0.0,3600,3600)
    #Initial conditions
    y0=np.array([5,176,5,30,100,5])
    Var=odeint(f,y0,t,args=(),h0=1,hmin=1,hmax=1,atol=1e-5,rtol=1e-5)
    Sol=Var.tolist()
    plt.plot(t,Var[:,0]) 

非常感谢提前!!!!!!

【问题讨论】:

    标签: python-3.x odeint


    【解决方案1】:

    简答:

    您不应在 ODE 函数中修改输入状态向量。请尝试以下方法并验证您的结果:

    x0 = x[0]
    if x0<=SO2_lower: 
        x0=SO2_upper
    # use x0 instead of x[0] in the rest of this function body
    

    我想这是你的问题,但我不确定,因为你没有解释结果到底出了什么问题。此外,您不会更改“初始条件”。初始条件为

    y0=np.array([5,176,5,30,100,5])
    

    您只需更改输入状态向量。

    详细回答:

    您的 odeint 积分器可能正在使用一种高阶自适应龙格-库塔方法。该算法需要多次 ODE 函数评估来计算单个积分步长,因此更改输入状态向量可能会导致未定义的结果。在 C++ boost::odeint 中,这甚至是不可能的,因为输入变量是“const”。然而,Python 不像 C++ 那样严格,我认为可能会无意中制造这种错误(不过我没有尝试过)。

    编辑:

    好的,我明白你想要达到的目标。

    你的变量 x[0] 受模代数约束,不能用形式表达

    x' = f(x,t)
    

    这是常微分方程的可能定义之一,ondeint 库旨在解决。然而,很少有可能的“黑客”可以用来绕过这个限制。

    一种可能性是使用固定步长和低阶(因为对于高阶求解器,您需要知道您实际处于算法的哪一部分,例如参见RK4)求解器并更改您的 dx[0]方程式(在您的代码中是 MxR[0][0] 元素)到:

    # at the beginning of your system
    if (x[0] > S02_lower): # everything is normal here
        x0 = x[0]
        dx0 = # normal equation for dx0
    else: # x[0] is too low, we must somehow force it to become S02_upper again
        dx0 = (x[0] - S02_upper)/step_size // assuming that x0_{n+1} = x0_{n} + dx0*step_size
        x0 = S02_upper
    # remember to use x0 in the rest of your code and also remember to return dx0
    

    但是,我不推荐这种技术,因为它会让你非常依赖算法并且你必须知道确切的步长(尽管我可能会推荐它用于饱和约束)。另一种可能性是一次执行一个集成步骤,并在每次需要时更正您的 x0:

    // 1 do_step(sys, in, dxdtin, t, out, dt);
    // 2 do something with output
    // 3 in = out
    // 4 return to 1 or finish
    

    对不起,C++ 语法,这里是详尽的文档 (C++ odeint steppers),这里是它在 python 中的等价物 (Python ode class)。 C++ odeint 接口更适合您的任务,但是您可以在 python 中实现完全相同的效果。只需寻找:

    integrate(t[, step, relax])
    set_initial_value(y[, t])
    

    在文档中。

    【讨论】:

    • 非常感谢您的回答。在我的例子中,变量 x [0] 在 4 和 5 之间变化,这就是为什么每次 x [0] 达到等于或小于 4 的值时都必须再次重置。有一些变体可以解决这个问题吗?
    • 这是完全可能的,但需要一些小技巧。请阅读上面编辑过的帖子。
    • 我会按照您的建议尝试使用 ODE。非常感谢我的朋友。