【问题标题】:Octave's fzero() and Scipy's root() functions not producing the same resultOctave 的 fzero() 和 Scipy 的 root() 函数不会产生相同的结果
【发布时间】:2020-08-24 22:19:34
【问题描述】:

我必须找到以下等式的零:

这是一个状态方程,如果您不确切知道 EoS 是什么,这并不重要。根据上述等式的根,我计算(除其他外)气态物质在不同压力和温度下的压缩因子 Z。通过这些解决方案,我可以绘制以压力为横坐标、Zs 为纵坐标、温度为参数的曲线族。 Beta、delta、eta 和 phi 是常数,pr 和 Tr 也是如此。

在用 Newton-Raphson 方法(与其他几个 EoS 配合得很好)失败后,我决定尝试 Scipy 的 root() 函数。令我不满的是,我得到了这张图表:

我们很容易看出,这张锯齿状的图表是完全有缺陷的。我应该得到平滑的曲线。此外,Z 通常介于 0.25 和 2.0 之间。因此,等于或大于 3 的 Z 完全不符合标准。然而 Z

然后我尝试了 Octave 的 fzero() 求解器,得到了这个:

这正是我应该得到的,因为它们是具有正确/预期形状的曲线!

我的问题来了。显然 Scipy 的 root() 和 Octave 的 fzero() 基于来自 MINPACK 的相同算法hybrid。尽管如此,结果显然并不相同。有谁知道为什么吗?

我绘制了一条由 Octave(横坐标)获得的 Zs 与使用 Scipy 获得的 Zs 的曲线,并得到了这个:

底部暗示直线的点代表y = x,即 Octave 和 Scipy 在他们提出的解决方案中同意的点。其他观点完全不同,不幸的是,它们太多了,不能简单地忽略。

从现在开始我可能会一直使用 Octave,因为它可以工作,但我想继续使用 Python。

您对此有何看法?有什么建议吗?

PS:这是原始 Python 代码。它会生成此处显示的第一个图表。

import numpy
from scipy.optimize import root
import matplotlib.pyplot as plt

def fx(x, beta, delta, eta, phi, pr_, Tr_):
    tmp = phi*x**2
    etmp = numpy.exp(-tmp)
    f = x*(1.0 + beta*x + delta*x**4 + eta*x**2*(1.0 + tmp)*etmp) - pr_/Tr_
    return f

def zsbwr(pr_, Tr_, pc_, Tc_, zc_, w_, MW_, phase=0):

    d1 = 0.4912 + 0.6478*w_
    d2 = 0.3000 + 0.3619*w_
    e1 = 0.0841 + 0.1318*w_ + 0.0018*w_**2
    e2 = 0.075 + 0.2408*w_ - 0.014*w_**2
    e3 = -0.0065 + 0.1798*w_ - 0.0078*w_**2
    f = 0.770
    ee = (2.0 - 5.0*zc_)*numpy.exp(f)/(1.0 + f + 3.0*f**2 - 2*f**3)
    d = (1.0 - 2.0*zc_ - ee*(1.0 + f - 2.0*f**2)*numpy.exp(-f))/3.0
    b = zc_ - 1.0 - d - ee*(1.0 + f)*numpy.exp(-f)
    bc = b*zc_
    dc = d*zc_**4
    ec = ee*zc_**2
    phi = f*zc_**2
    beta = bc + 0.422*(1.0 - 1.0/Tr_**1.6) + 0.234*w_*(1.0- 1.0/Tr_**3)
    delta = dc*(1.0+ d1*(1.0/Tr_ - 1.0) + d2*(1.0/Tr_ - 1.0)**2)
    eta = ec + e1*(1.0/Tr_ - 1.0) + e2*(1.0/Tr_ - 1.0)**2 \
          + e3*(1.0/Tr_ - 1.0)**3

    if Tr_ > 1:
        y0 = pr_/Tr_/(1.0 + beta*pr_/Tr_)
    else:
        if phase == 0:
            y0 = pr_/Tr_/(1.0 + beta*pr_/Tr_)
        else:
            y0 = 1.0/zc_**(1.0 + (1.0 - Tr_)**(2.0/7.0))

    raiz = root(fx,y0,args=(beta, delta, eta, phi, pr_, Tr_),method='hybr',tol=1.0e-06)

    return pr_/raiz.x[0]/Tr_


if __name__ == "__main__":

    Tc = 304.13
    pc = 73.773
    omega = 0.22394
    zc = 0.2746
    MW = 44.01

    Tr = numpy.array([0.8, 0.93793103])
    pr = numpy.linspace(0.5, 14.5, 25)

    zfactor = numpy.zeros((2, 25))

    for redT in Tr:
        j = numpy.where(Tr == redT)[0][0]
        for redp in pr:
            indp = numpy.where(pr == redp)[0][0]
            zfactor[j][indp] = zsbwr(redp, redT, pc, Tc, zc, omega, MW, 0)

    for key, value in enumerate(zfactor):
        plt.plot(pr, value, '.-', linewidth=1, color='#ef082a')

    plt.figure(1, figsize=(7, 6))
    plt.xlabel('$p_R$', fontsize=16)
    plt.ylabel('$Z$', fontsize=16)
    plt.grid(color='#aaaaaa', linestyle='--', linewidth=1)
    plt.show()

现在是 Octave 脚本:

function SoaveBenedictWebbRubin

    format long;

    nTr = 11;
    npr = 43;

    ic = 1;

    nome = {"CO2"; "N2"; "H2O"; "CH4"; "C2H6"; "C3H8"};

    comp = [304.13, 73.773, 0.22394, 0.2746, 44.0100; ...
            126.19, 33.958, 0.03700, 0.2894, 28.0134; ...
            647.14, 220.640, 0.34430, 0.2294, 18.0153; ...
            190.56, 45.992, 0.01100, 0.2863, 16.0430; ...
            305.33, 48.718, 0.09930, 0.2776, 30.0700; ...
            369.83, 42.477, 0.15240, 0.2769, 44.0970];

    Tc = comp(ic,1);
    pc = comp(ic,2);
    w = comp(ic,3);
    zc = comp(ic,4);
    MW = comp(ic,5);

    Tr = linspace(0.8, 2.8, nTr);
    pr = linspace(0.2, 7.2, npr);

    figure(1, 'position',[300,150,600,500])

    for i=1:size(Tr, 2)
        icont = 1;
        zval = zeros(1, npr);
        for j=1:size(pr, 2)
            [Z, phi, density] = SBWR(Tr(i), pr(j), Tc, pc, zc, w, MW, 0);
            zval(icont) = Z;
            icont = icont + 1;
        endfor
        plot(pr,zval,'o','markerfacecolor','white','linestyle','-','markersize',3);
        hold on;
    endfor

    str = strcat("Soave-Benedict-Webb-Rubin para","\t",nome(ic));
    xlabel("p_r",'fontsize',15);
    ylabel("Z",'fontsize',15);
    title(str,'fontsize',12);
end

function [Z,phi,density] = SBWR(Tr, pr, Tc, pc, Zc, w, MW, phase)
    R = 8.3144E-5; % universal gas constant (bar·m3/(mol·K))

    % Definition of parameters
    d1 = 0.4912 + 0.6478*w;
    d2 = 0.3 + 0.3619*w;
    e1 = 0.0841 + 0.1318*w + 0.0018*w**2;
    e2 = 0.075 + 0.2408*w - 0.014*w**2;
    e3 = -0.0065 + 0.1798*w - 0.0078*w**2;
    f = 0.77;
    ee = (2.0 - 5.0*Zc)*exp(f)/(1.0 + f + 3.0*f**2 - 2.0*f**3);
    d = (1.0 - 2.0*Zc - ee*(1.0 + f - 2.0*f**2)*exp(-f))/3.0;
    b = Zc - 1.0 - d - ee*(1.0 + f)*exp(-f);
    bc = b*Zc;
    dc = d*Zc**4;
    ec = ee*Zc**2;
    ff = f*Zc**2;
    beta = bc + 0.422*(1.0 - 1.0/Tr**1.6) + 0.234*w*(1.0 - 1.0/Tr**3);
    delta = dc*(1.0 + d1*(1.0/Tr - 1.0) + d2*(1.0/Tr - 1.0)**2);
    eta = ec + e1*(1.0/Tr - 1.0) + e2*(1.0/Tr - 1.0)**2 + e3*(1.0/Tr - 1.0)**3;

    if Tr > 1
        y0 = pr/Tr/(1.0 + beta*pr/Tr);
    else
        if phase == 0
            y0 = pr/Tr/(1.0 + beta*pr/Tr);
        else
            y0 = 1.0/Zc**(1.0 + (1.0 - Tr)**(2.0/7.0));
        end
    end

    fun = @(y)y*(1.0 + beta*y + delta*y**4 + eta*y**2*(1.0 + ff*y**2)*exp(-ff*y**2)) - pr/Tr;

    options = optimset('TolX',1.0e-06);
    yi = fzero(fun,y0,options);

    Z = pr/yi/Tr;
    density = yi*pc*MW/(1000.0*R*Tc);
    phi = exp(Z - 1.0 - log(Z) + beta*yi + 0.25*delta*yi**4 - eta/ff*(exp(-ff*yi**2)*(1.0 + 0.5*ff*yi**2) - 1.0));
end

【问题讨论】:

  • 向我们展示python代码。可能有几个根要收敛
  • @ev-br 完成。希望它对你有用。否则让我知道。谢谢。
  • @ev-br 当然有多个根。令我惊讶的是,Octave 从提供的初始猜测中找到了正确的。该方法(很可能)与scipy.optimize.root() 使用的方法相同。
  • @ev-br 另外,zsbwr() 函数返回的phi 不是上面 f(x) 等式中显示的希腊字母 phi。后者应该是ff。我让我的代码有点混乱。很抱歉。
  • 你介意分享八度代码吗?

标签: python scipy octave


【解决方案1】:

首先要做的事情。您的两个文件不等价,因此很难直接比较底层算法。我在这里附上一个八度和一个 python 版本,它们可以直接比较,可以并排比较。

%%% File: SoaveBenedictWebbRubin.m:
% No package imports necessary

function SoaveBenedictWebbRubin()

    nome = {"CO2"; "N2"; "H2O"; "CH4"; "C2H6"; "C3H8"};
    comp = [ 304.13,  73.773,  0.22394,  0.2746,  44.0100;
             126.19,  33.958,  0.03700,  0.2894,  28.0134;
             647.14, 220.640,  0.34430,  0.2294,  18.0153;
             190.56,  45.992,  0.01100,  0.2863,  16.0430;
             305.33,  48.718,  0.09930,  0.2776,  30.0700;
             369.83,  42.477,  0.15240,  0.2769,  44.0970  ];

    nTr = 11;   Tr = linspace( 0.8, 2.8, nTr );
    npr = 43;   pr = linspace( 0.2, 7.2, npr );
    ic  = 1;
    Tc  = comp(ic, 1);
    pc  = comp(ic, 2);
    w   = comp(ic, 3);
    zc  = comp(ic, 4);
    MW  = comp(ic, 5);

    figure(1, 'position',[300,150,600,500])

    zvalues = zeros( nTr, npr );
    
    for i = 1 : nTr
        for j = 1 : npr
            zvalues(i,j) = zSBWR( Tr(i), pr(j), Tc, pc, zc, w, MW, 0 );
        endfor
    endfor

    hold on
    for i = 1 : nTr
        plot( pr, zvalues(i,:), 'o-', 'markerfacecolor', 'white', 'markersize', 3);
    endfor
    hold off

    xlabel( "p_r", 'fontsize', 15 );
    ylabel( "Z"  , 'fontsize', 15 );
    title( ["Soave-Benedict-Webb-Rubin para\t", nome(ic)], 'fontsize', 12 );

endfunction % main



function Z = zSBWR( Tr, pr, Tc, pc, Zc, w, MW, phase )

  % Definition of parameters
    d1 =  0.4912 + 0.6478 * w;
    d2 =  0.3    + 0.3619 * w;
    e1 =  0.0841 + 0.1318 * w + 0.0018 * w ** 2;
    e2 =  0.075  + 0.2408 * w - 0.014  * w ** 2;
    e3 = -0.0065 + 0.1798 * w - 0.0078 * w ** 2;
    f  =  0.77;
    ee = (2.0 - 5.0 * Zc) * exp( f ) / (1.0 + f + 3.0 * f ** 2 - 2.0 * f ** 3 );
    d  = (1.0 - 2.0 * Zc  - ee * (1.0 + f - 2.0 * f ** 2) * exp( -f ) ) / 3.0;
    b  = Zc - 1.0 - d - ee * (1.0 + f) * exp( -f );
    bc = b  * Zc;
    dc = d  * Zc ** 4;
    ec = ee * Zc ** 2;
    phi = f  * Zc ** 2;
    beta  = bc + 0.422 * (1.0 - 1.0 / Tr ** 1.6) + 0.234 * w * (1.0 - 1.0 / Tr ** 3);
    delta = dc * (1.0 + d1 * (1.0 / Tr - 1.0) + d2 * (1.0 / Tr - 1.0) ** 2);
    eta   = ec + e1 * (1.0 / Tr - 1.0) + e2 * (1.0 / Tr - 1.0) ** 2 + e3 * (1.0 / Tr - 1.0) ** 3;


    if Tr > 1
        y0 = pr / Tr / (1.0 + beta * pr / Tr);
    else
        if phase == 0
            y0 = pr / Tr / (1.0 + beta * pr / Tr);
        else
            y0 = 1.0 / Zc ** (1.0 + (1.0 - Tr) ** (2.0 / 7.0) );
        endif
    endif


    yi = fzero( @(y) fx(y, beta, delta, eta, phi, pr, Tr), y0, optimset( 'TolX', 1.0e-06 ) );
    Z = pr / yi / Tr;

endfunction % zSBWR




function Out = fx( y, beta, delta, eta, phi, pr, Tr )
    Out = y * (1.0 + beta * y + delta * y ** 4 + eta * y ** 2 * (1.0 + phi * y ** 2) * exp( -phi * y ** 2 ) ) - pr / Tr;
endfunction
### File: SoaveBenedictWebbRubin.py
import numpy;   from scipy.optimize import root;   import matplotlib.pyplot as plt

def SoaveBenedictWebbRubin():

    nome = ["CO2", "N2", "H2O", "CH4", "C2H6", "C3H8"]
    comp = numpy.array( [ [ 304.13,  73.773,  0.22394,  0.2746,  44.0100 ],
                          [ 126.19,  33.958,  0.03700,  0.2894,  28.0134 ],
                          [ 647.14, 220.640,  0.34430,  0.2294,  18.0153 ],
                          [ 190.56,  45.992,  0.01100,  0.2863,  16.0430 ],
                          [ 305.33,  48.718,  0.09930,  0.2776,  30.0700 ],
                          [ 369.83,  42.477,  0.15240,  0.2769,  44.0970 ] ] )

    nTr = 11;   Tr = numpy.linspace( 0.8, 2.8, nTr )
    npr = 43;   pr = numpy.linspace( 0.2, 7.2, npr )
    ic  = 0
    Tc  = comp[ic, 0]
    pc  = comp[ic, 1]
    w   = comp[ic, 2]
    zc  = comp[ic, 3]
    MW  = comp[ic, 4]

    plt.figure(1, figsize=(7, 6))

    zvalues = numpy.zeros( (nTr, npr) )

    for i in range( nTr ):
        for j in range( npr ):
            zvalues[i,j] = zsbwr( Tr[i], pr[j], pc, Tc, zc, w, MW, 0)
        # endfor
    # endfor


    for i in range(nTr):
        plt.plot(pr, zvalues[i, :], 'o-', markerfacecolor='white', markersize=3 )



    plt.xlabel( '$p_r$', fontsize = 15 )
    plt.ylabel( '$Z$'  , fontsize = 15 )
    plt.title( "Soave-Benedict-Webb-Rubin para\t" + nome[ic], fontsize = 12 );
    plt.show()
# end function main



def zsbwr( Tr, pr, pc, Tc, zc, w, MW, phase=0):

  # Definition of parameters
    d1 =  0.4912 + 0.6478 * w
    d2 =  0.3000 + 0.3619 * w
    e1 =  0.0841 + 0.1318 * w + 0.0018 * w ** 2
    e2 =  0.075  + 0.2408 * w - 0.014  * w ** 2
    e3 = -0.0065 + 0.1798 * w - 0.0078 * w ** 2
    f  = 0.770
    ee = (2.0 - 5.0 * zc) * numpy.exp( f ) / (1.0 + f + 3.0 * f ** 2 - 2 * f ** 3)
    d  = (1.0 - 2.0 * zc - ee * (1.0 + f - 2.0 * f ** 2) * numpy.exp( -f )) / 3.0
    b  = zc - 1.0 - d - ee * (1.0 + f) * numpy.exp( -f )
    bc = b * zc
    dc = d * zc ** 4
    ec = ee * zc ** 2
    phi = f * zc ** 2
    beta  = bc + 0.422 * (1.0 - 1.0 / Tr ** 1.6) + 0.234 * w * (1.0 - 1.0 / Tr ** 3)
    delta = dc * (1.0 + d1 * (1.0 / Tr - 1.0) + d2 * (1.0 / Tr - 1.0) ** 2)
    eta   = ec + e1 * (1.0 / Tr - 1.0) + e2 * (1.0 / Tr - 1.0) ** 2 + e3 * (1.0 / Tr - 1.0) ** 3


    if Tr > 1:
        y0 = pr / Tr / (1.0 + beta * pr / Tr)
    else:
        if phase == 0:
            y0 = pr / Tr / (1.0 + beta * pr / Tr)
        else:
            y0 = 1.0 / zc ** (1.0 + (1.0 - Tr) ** (2.0 / 7.0))
        # endif
    # endif


    yi = root( fx, y0, args = (beta, delta, eta, phi, pr, Tr), method = 'hybr', tol = 1.0e-06 ).x
    return pr / yi / Tr

# endfunction zsbwr




def fx(y, beta, delta, eta, phi, pr, Tr):
    return y*(1.0 + beta*y + delta*y**4 + eta*y**2*(1.0 + phi*y**2)*numpy.exp(-phi*y**2)) - pr/Tr
# endfunction fx




if __name__ == "__main__":   SoaveBenedictWebbRubin()

这证实了两个系统的输出确实存在差异,部分原因在于所使用的底层算法的输出,而不是因为程序实际上并不相同。但是,现在的比较并没有那么糟糕:

至于“算法是一样的”,其实不然。 Octave 通常会在源代码中隐藏更多技术实现细节,因此这总是值得检查的。特别是,在文件 fzero.m 中,就在 docstring 之后,它提到了以下内容:

这本质上是ACM "Algorithm 748: Enclosing Zeros of Continuous Functions" due to Alefeld, Potra and Shi, ACM Transactions on Mathematical Software, Vol. 21, No. 3, September 1995

虽然工作流程应该是一样的,但算法的结构已经进行了非平凡的改造;代替作者顺序调用构建块子程序的方法,我们在这里实现了一个 FSM 版本,每次迭代使用一个内点确定和一个括号,从而减少临时变量的数量并简化算法结构。此外,这种方法减少了对外部功能和错误处理的需求。算法也稍作修改。

而根据help(root)

备注
本节介绍可以通过“方法”参数选择的可用求解器。默认方法是hybr

方法 hybr 使用 Powell 混合方法的修改,如 在MINPACK [1]中实现。

参考文献
[1]More, Jorge J., Burton S. Garbow, and Kenneth E. Hillstrom. 1980. User Guide for MINPACK-1.

我尝试了help(root) 中提到的几个替代方案。 df-sane 似乎针对“标量”值(即像“fzero”)进行了优化。事实上,虽然不如 octave 的实现好,但这确实给出了一个稍微“理智”(双关语)的结果:

话虽如此,混合方法不会转储任何警告,但如果您使用其他一些替代方法,其中许多会告诉您您有很多有效除以零,nans,和 infs,在某些地方你不应该,这大概就是你得到如此奇怪结果的原因。所以,也许不是 octave 的算法本身“更好”,而是它在这个问题中处理“被零除”的实例稍微优雅一些​​。

我不知道您的问题的确切性质,但可能是 python 方面的算法只是希望您提供条件良好的问题。也许您在 zsbwr() 中的某些计算会导致除以零或不切实际的零等,您可以将其检测并视为特殊情况?

【讨论】:

  • 首先,非常感谢您详细而亲切的回复。确实,您的第二张图表看起来比我的要好得多,尽管蓝色曲线完全是弹道的并且表现出不切实际的行为(通常,我不希望 Z>=2,尽管这在理论上是可能的值)。我敢打赌,这条蓝色曲线是 Tr = 0.8 的曲线。而且我不认为(但不能证明)这与数字异常有关,并且正如您所说,Octave 更优雅地处理它们。 IMO 这是一个实现缺陷,因为返回的值虽然异常高,但并不像 O(10**10) 之类的那么疯狂。
  • (续)因此,Z = 7 很奇怪,但它看起来(在未经训练的人看来)是一个有教养的根。我认为我们永远不会找到这个谜题的明确答案。我能做的是,当另一个解决这样一个复杂方程的机会来临时,我要时刻关注这样的解决方案。这是一个复杂的问题。我有机会学习和使用的 EOS 比这个 SBWR 简单得多。再次,非常感谢您的回答;如果可以的话,我会投票两次。 ;-)
  • @CarlosGouveia 好吧,如果您认为它回答了您的问题,您可以接受答案:p(我想它回答了“它们不是相同的算法”部分,答案基本上是'不')。至于这些图表是否有意义,我认为合理的做法(如果你还没有的话)是简单地将 EOS 方程绘制在 x 的域上,以获得 pr 的各种值(以及固定的 @987654333 @,例如你认为有问题的那个),看看你得到的图是否有意义或直观地解释了为什么“寻根”算法在那种情况下找不到零点!
  • 我必须“正式”接受答案吗?喜欢,关闭这个话题?我怎么做?我很乐意接受你的回答,Tasos。
  • @CarlosGouveia 如果没有合适的答案,您不必“必须”接受答案,但如果答案回答了您的问题,那么最好这样做,主要是为了让问题出现关闭在问题列表中,这有助于其他人在谷歌上搜索类似的问题。您通过单击答案旁边的“勾号”(就在投票箭头下方)“接受”答案。请注意,从这个意义上说,接受答案并不会“关闭”一个主题。您可以随时改变主意并选择另一个答案作为接受的答案。
【解决方案2】:

(请将代码精简为一个最小示例,仅显示根查找部分和找到不需要的根的参数。)

然后程序是手动检查方程以找到您想要的根的定位间隔并使用它。我通常使用brentq

【讨论】:

  • 完成。我尽可能地修剪它。我还发现问题发生在 Tr = 0.8 和 Tr = 0.93793103 时,因此修剪后的代码现在只为这两个降低的温度绘制图表。出于某种深不可测的原因,SciPy 与这两个 Trs 搭配得并不好。然而,Octave 可以很好地处理它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-28
相关资源
最近更新 更多