概率密度的正交例程

时间:2012-05-27 19:57:34

标签: c++ math wolfram-mathematica

我想整合来自(-\infty, a]的概率密度函数,因为cdf不能以封闭形式提供。但我不确定如何在C ++中这样做。

这项任务在Mathematica中非常简单;我需要做的就是定义函数

f[x_, lambda_, alpha_, beta_, mu_] := 
   Module[{gamma}, 
     gamma = Sqrt[alpha^2 - beta^2]; 
     (gamma^(2*lambda)/((2*alpha)^(lambda - 1/2)*Sqrt[Pi]*Gamma[lambda]))*
      Abs[x - mu]^(lambda - 1/2)*
      BesselK[lambda - 1/2, alpha Abs[x - mu]] E^(beta (x - mu))
   ];

然后调用NIntegrate例程以数字方式集成它。

F[x_, lambda_, alpha_, beta_, mu_] := 
    NIntegrate[f[t, lambda, alpha, beta, mu], {t, -\[Infinity], x}] 

现在我想在C ++中实现同样的功能。我使用gsl数字库中的例程gsl_integration_qagil。它被设计为在半无限区间(-\infty, a]上集成函数,这正是我想要的。但不幸的是,我无法让它发挥作用。

这是C ++中的密度函数,

density(double x)
{
using namespace boost::math;

if(x == _mu)
    return std::numeric_limits<double>::infinity();

    return pow(_gamma, 2*_lambda)/(pow(2*_alpha, _lambda-0.5)*sqrt(_pi)*tgamma(_lambda))* pow(abs(x-_mu), _lambda - 0.5) * cyl_bessel_k(_lambda-0.5, _alpha*abs(x - _mu)) * exp(_beta*(x - _mu));

}  

然后我尝试通过调用gsl例程来集成以获取cdf。

cdf(double x)
{
gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000);

    double result, error;      
    gsl_function F;
    F.function = &density;

    double epsabs = 0;
    double epsrel = 1e-12;

    gsl_integration_qagil (&F, x, epsabs, epsrel, 1000, w, &result, &error);

    printf("result          = % .18f\n", result);
    printf ("estimated error = % .18f\n", error);
    printf ("intervals =  %d\n", w->size);

    gsl_integration_workspace_free (w);

    return result;

}

但是gsl_integration_qagil会返回错误number of iterations was insufficient

 double mu = 0.0f;
 double lambda = 3.0f;
 double alpha = 265.0f;
 double beta = -5.0f;

 cout << cdf(0.01) << endl;

如果我增加工作空间的大小,那么bessel函数将不会评估。

我想知道是否有人可以让我对我的问题有所了解。使用x = 0.01调用上面相应的Mathematica函数F将返回0.904384

可能是密度集中在非常小的区间(即[-0.05, 0.05]之外,密度几乎为0,下面给出了一个图。如果是这样的话可以做些什么。谢谢阅读。

density

3 个答案:

答案 0 :(得分:1)

我没有尝试过C ++代码,但是通过检查Mathematica中的函数,它确实看起来非常高峰,峰值的扩展由参数lambda,alpha,beta决定。

我会做的是对pdf进行初步搜索:在x = mu的右侧和左侧看,直到找到低于给定容差的第一个值。使用这些作为cdf的边界,而不是负无穷大。

伪代码如下:

x_mu
step = 0.000001
adaptive_step(y_value) -> returns a small step size if close to 0, and larger if far.

while (pdf_current > tolerance):
  step = adaptive_step(pdf_current)
  xtest = xtest - step
  pdf_current = pdf(xtest)

left_bound = xtest

//repeat for left bound

考虑到这个功能看起来有多紧张,收紧界限可能会为你节省大量的计算机时间,这些计算机时间目前浪费在计算零上。此外,您还可以使用有界集成例程,而不是 - \ infty,b。

只是一个想法...

PS:Mathematica给我F [0.01,3,265,-5,0] = 0.884505

答案 1 :(得分:1)

我找到了关于这个glsl http://linux.math.tifr.res.in/manuals/html/gsl-ref-html/gsl-ref_16.html的完整描述,你可能会发现有用的信息。

由于我不是GSL专家,我从数学的角度来看并没有关注你的问题,而是提醒你关于浮点编程的一些关键方面。

您无法使用IEEE 754标准准确表示数字。 MathLab通过使用无限数字表示逻辑隐藏事实,为了给您提供无错误的结果,这就是为什么它与本机代码相比速度慢的原因。

我强烈建议使用FPU参与科学计算的任何人这个链接: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

假设你喜欢那篇文章,我在上面的GSL链接上注意到了这一点:“如果错误界限过于严格,例程将无法收敛”。

如果上限和下限之间的差异小于double的最小可表示值,那么您的界限可能过于严格,即 的std :: numeric_limits ::小量();

另外请记住,从第二个链接开始,对于任何C / C ++编译器实现,默认的舍入模式都是“truncate”,这会引入微妙的微积分错误,从而导致错误的结果。我确实遇到了一个简单的Liang Barsky线剪刀的问题,第一顺序!想象一下这一行的混乱:

return pow(_gamma, 2*_lambda)/(pow(2*_alpha, _lambda-0.5)*sqrt(_pi)*tgamma(_lambda))* pow(abs(x-_mu), _lambda - 0.5) * cyl_bessel_k(_lambda-0.5, _alpha*abs(x - _mu)) * exp(_beta*(x - _mu));

作为一般规则,在C / C ++中明智的做法是添加额外的变量保存中间结果,这样你就可以逐步调试,然后看到任何舍入错误,你不应该尝试像这样输入表达式任何本地编程语言。人们无法比编译器更好地优化变量。

最后,作为一般规则,除非你对微积分的动态行为有信心,否则你应该将所有东西相乘,然后划分。

祝你好运。

答案 2 :(得分:1)

Re:积分到+/-无穷大:

我会用Mathematica找到| x - μ|的经验界&GT;&GT; K,其中K表示平均值附近的“宽度”,K是α,β和λ的函数 - 例如F小于且近似等于a(x-μ) -2 或ae -b(x-μ) 2 或其他。这些函数将已知积分输出到无穷大,您可以根据经验进行评估。然后你可以用数字整合到K,并使用有界逼近从K到无穷大。

搞清楚K可能有点棘手;我对贝塞尔函数不太熟悉,所以我无法帮助你。

一般来说,我发现对于不太明显的数值计算,最好的方法是在进行数值评估之前做的分析数学运算。 (有点像自动对焦相机 - 让它靠近你想要的地方,然后让相机完成余下的工作。)