比较Python中的根查找(函数)算法

时间:2011-12-13 02:11:47

标签: python performance algorithm math numerical-methods

我想比较在python中找到函数根的不同方法(如牛顿方法或其他简单的基于calc的方法)。我不认为编写算法会有太多麻烦。什么是进行实际比较的好方法?我读了一下Big-O。这会是要走的路吗?

(任何项目的想法或曲折也将不胜感激)

感谢。

8 个答案:

答案 0 :(得分:6)

@sarnold的答案是对的 - 做一个Big-Oh分析是没有意义的。

根查找算法之间的主要区别是:

  • 收敛速度(迭代次数)
  • 每次迭代的计算工作量
  • 输入需要什么(即你需要知道一阶导数,你需要设置二分的低/低限制等)。
  • 它运行良好的功能(即在多项式上工作正常但在极点函数上失效)
  • 它对函数做出了什么假设(即连续的一阶导数或分析等)
  • 实施方法的简单程度

我认为你会发现每种方法都有一些好的品质,一些不好的品质,以及一系列最合适的选择。

答案 1 :(得分:1)

Big O notation非常适合表达算法的渐近行为,因为算法的输入“增加”。这可能不是根寻找算法的一个很好的衡量标准。

相反,我认为将实际误差降低到某个epsilonε以下所需的迭代次数将是更好的衡量标准。另一个衡量标准是将连续迭代之间的差异降低到某个epsilonε以下所需的迭代次数。 (如果您的输入没有准确的根值,则连续迭代之间的差异可能是更好的选择。您可以使用连续差异等标准来了解何时在实践中终止根查找器,这样您就可以也应该在这里使用它们。)

虽然可以通过它们之间的比率来表征不同算法所需的迭代次数(一种算法可能需要大约十倍的迭代才能达到与另一种算法相同的精度),但通常不会随着输入的变化,迭代中的“增长”。

当然,如果您的算法需要使用“更大”的输入进行更多迭代,那么Big O符号是有意义的。

答案 2 :(得分:1)

Big-O符号旨在描述算法在极限中的行为,因为n变为无穷大。这在理论研究中比在实际实验中更容易使用。我会选择要研究的东西,你可以很容易地测量和人们关心的事情,例如准确度和消耗的计算机资源(时间/内存)。

当您编写并运行计算机程序来比较两种算法时,您正在进行科学实验,就像测量光速的人,或者比较吸烟者和非吸烟者的死亡率的人,以及许多人同样的因素适用。

尝试并选择要解决的示例问题或问题,这些问题具有代表性,或者至少对您感兴趣,因为您的结果可能无法推广到您尚未实际测试过的情况。如果您从大量可能的问题中随机抽样并发现所有随机样本的行为方式大致相同,或者至少遵循相同的趋势,您可以增加结果回复的情况范围。即使理论研究表明应该有一个很好的n log n趋势,你也会有意想不到的结果,因为理论研究很少考虑突然耗尽缓存或内存不足,或者通常甚至是整数溢出等问题。

警惕错误来源,并尝试将其最小化,或让它们适用于您所比较的所有内容。当然,您希望对正在测试的所有算法使用完全相同的输入数据。对每个算法进行多次运行,并检查变量是多少 - 也许少数运行速度较慢,因为计算机一次只做其他事情。请注意,缓存可能会使后续运行的算法更快,特别是如果您在彼此之后立即运行它们。您想要的时间取决于您决定测量的内容。如果你有很多I / O需要记住现代操作系统和计算机在内存中缓存大量磁盘I / O.我曾经在每次运行后关闭并重新打开计算机电源,这是我找到的唯一方法,可以确保设备I / O缓存被刷新。

答案 3 :(得分:1)

只需更改起点,您就可以针对同一问题获得完全不同的答案。选择一个接近根的初始猜测,牛顿方法将给出一个二次收敛的结果。在问题空间的不同部分选择另一个,根查找器会疯狂地发散。

这对算法有什么看法?好还是坏?

答案 4 :(得分:0)

我建议你看看下面的Python根发现演示。 它是一个简单的代码,它们之间有一些不同的方法和比较(就收敛速度而言)。

http://www.math-cs.gordon.edu/courses/mat342/python/findroot.py

答案 5 :(得分:0)

为什么要这么麻烦?只能有一个:

    public class BrentsMethodResult
    {
        public bool TerminatedSuccessfully;
        public double Result;
        public double UpperResult;
        public double LowerResult;
    }

    public static BrentsMethodResult BrentsMethodSolve(Func<double, double> function, double lowerLimit, double upperLimit, double errorTol)
    {
        BrentsMethodResult result = new BrentsMethodResult();

        double a = lowerLimit;
        double b = upperLimit;
        double c = 0;
        double d = double.MaxValue;

        double fa = function(a);
        double fb = function(b);
        result.LowerResult = fa;
        result.UpperResult = fb;

        if (Double.IsNaN(fa) || Double.IsNaN(fb))
        {
            result.TerminatedSuccessfully = false;
            result.Result = Double.NaN;
            return result;
        }

        double fc = 0;
        double s = 0;
        double fs = 0;

        // if f(a) f(b) >= 0 then error-exit
        if (fa * fb >= 0)
        {
            result.TerminatedSuccessfully = false;
            if (Math.Abs(fa) < Math.Abs(fb))
            {
                result.Result = a;
                return result;
            }
            else
            {
                result.Result = b;
                return result;
            }
        }

        // if |f(a)| < |f(b)| then swap (a,b) end if
        if (Math.Abs(fa) < Math.Abs(fb))
        { double tmp = a; a = b; b = tmp; tmp = fa; fa = fb; fb = tmp; }

        c = a;
        fc = fa;
        bool mflag = true;
        int i = 0;

        while (Math.Abs(fb) > errorTol && Math.Abs(a - b) > errorTol)
        {
            //tol1 = 2.0 * double_Accuracy * Math.Abs(b) + 0.5 * errorTol;

            if ((fa != fc) && (fb != fc))
                // Inverse quadratic interpolation
                s = a * fb * fc / (fa - fb) / (fa - fc) + b * fa * fc / (fb - fa) / (fb - fc) + c * fa * fb / (fc - fa) / (fc - fb);
            else
                // Secant Rule
                s = b - fb * (b - a) / (fb - fa);

            double tmp2 = (3 * a + b) / 4;
            if ((!(((s > tmp2) && (s < b)) || ((s < tmp2) && (s > b)))) || (mflag && (Math.Abs(s - b) >= (Math.Abs(b - c) / 2))) || (!mflag && (Math.Abs(s - b) >= (Math.Abs(c - d) / 2))))
            {
                s = (a + b) / 2;
                mflag = true;
            }
            else
            {
                if ((mflag && (Math.Abs(b - c) < errorTol)) || (!mflag && (Math.Abs(c - d) < errorTol)))
                {
                    s = (a + b) / 2;
                    mflag = true;
                }
                else
                    mflag = false;
            }
            fs = function(s);

            if (Double.IsNaN(fs))
            {
                result.TerminatedSuccessfully = false;
                result.Result = Double.NaN;
                return result;
            }

            d = c;
            c = b;
            fc = fb;
            if (fa * fs < 0) { b = s; fb = fs; }
            else { a = s; fa = fs; }

            // if |f(a)| < |f(b)| then swap (a,b) end if
            if (Math.Abs(fa) < Math.Abs(fb))
            { double tmp = a; a = b; b = tmp; tmp = fa; fa = fb; fb = tmp; }
            i++;
            if (i > 100)
            {
                throw new Exception(String.Format("100 iterations exceeded and error is {0}", fb));
            }
        }
        result.TerminatedSuccessfully = true;
        result.Result = b;
        return result;
    }

答案 6 :(得分:0)

我刚刚完成了一个比较二分,牛顿和割线根寻找方法的项目。由于这是一个实际案例,我认为你不需要使用Big-O表示法。 Big-O表示法更适合渐近视图。你可以做的是用以下方式比较它们:

速度 - 例如,如果收集好条件,牛顿是最快的

迭代次数 - 例如,这里二分最多迭代

准确性 - 如果有多个根,或者它根本不会收敛,它会多久收敛到正确的根。

输入 - 开始时需要哪些信息。例如牛顿需要在根附近有一个X0才能收敛,它还需要一个并不总是很容易找到的衍生物。

其他 - 舍入错误

为了可视化,您可以将每个迭代的值存储在数组中并绘制它们。使用你已经了解根的功能。

答案 7 :(得分:0)

虽然这是一个很老的帖子,但我的2美分:)

一旦确定了用来比较它们的算法方法(可以说是“评估协议”),那么您可能会对在实际数据集上运行挑战者的方法感兴趣。

tutorial基于一个示例(在多个数据集上比较多项式拟合算法)说明了操作方法。

(我是作者,随时在github页面上提供反馈!)