蒙特卡罗积分高斯函数f(x)= exp(-x ^ 2/2)在C错误输出

时间:2017-05-28 05:56:23

标签: c random integration montecarlo

我写了一个短程序来近似高斯函数f(x)= exp(-x ^ 2/2)的定积分,我的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

double gaussian(double x) {
    return exp((-pow(x,2))/2);
}

int main(void) {
    srand(0);
    double valIntegral, yReal = 0, xRand, yRand, yBound;
    int xMin, xMax, numTrials, countY = 0;

    do {
        printf("Please enter the number of trials (n): ");
        scanf("%d", &numTrials);
        if (numTrials < 1) {
            printf("Exiting.\n");
            return 0;
        }  
        printf("Enter the interval of integration (a b): ");
        scanf("%d %d", &xMin, &xMax);      
        while (xMin > xMax) { //keeps looping until a valid interval is entered
            printf("Invalid interval!\n");
            printf("Enter the interval of integration (a b): ");
            scanf("%d %d", &xMin, &xMax);
        }
        //check real y upper bound
        if (gaussian((double)xMax) > gaussian((double)xMin))
            yBound = gaussian((double)xMax);
        else 
            yBound = gaussian((double)xMin);
        for (int i = 0; i < numTrials; i++) {
            xRand = (rand()% ((xMax-xMin)*1000 + 1))/1000.00 + xMin; //generate random x value between xMin and xMax to 3 decimal places             
            yRand = (rand()% (int)(yBound*1000 + 1))/1000.00; //generate random y value between 0 and yBound to 3 decimal places
            yReal = gaussian(xRand);
            if (yRand < yReal) 
                countY++;
        }
        valIntegral = (xMax-xMin)*((double)countY/numTrials);
        printf("Integral of exp(-x^2/2) on [%.3lf, %.3lf] with n = %d trials is: %.3lf\n\n", (double)xMin, (double)xMax, numTrials, valIntegral);

        countY = 0; //reset countY to 0 for the next run
    } while (numTrials >= 1);

    return 0;
}

但是,我的代码输出与解决方案不匹配。我尝试调试并打印100次试验的所有xRand,yRand和yReal值(并使用Matlab检查yReal值和特定xRand值,以防我有任何拼写错误),这些值似乎没有无论如何......我都不知道我的错误在哪里。

[0,1]的试验数= 100的正确输出是0.810,而我的是0.880;试验#的正确输出= [-1,0]上的50是0.900,而我的是0.940。谁能找到我做错的地方?非常感谢。

另一个问题是,我找不到使用以下代码的参考:

double randomNumber = rand() / (double) RAND MAX;

但它是由教师提供的,他说它会生成一个从0到1的随机数。为什么他在'/'之后使用'%'而不是"rand()"

2 个答案:

答案 0 :(得分:9)

您的代码中存在一些逻辑错误/讨论点,包括数学和编程方面。

首先,为了解决这个问题,我们在这里谈论标准高斯,即

[latex] $$ \mathcal {N} (x ~|~ \mu = 0, \sigma^2 = 1) ~=~ \frac {1} {\sqrt {2 \pi}} \exp \Biggl \{ - \frac {x^2} {2} \Biggr \} $$ [/latex]

除了line 6上的高斯定义,省略了 [latex] $$ \sqrt{2\pi} $$ [/latex] 规范化术语。鉴于您似乎期望的产出,这似乎是故意的。很公平。但是如果你想计算实际的积分,那么几乎无限的范围(例如[-1000,1000])总和为1,那么你需要那个术语

我的代码在逻辑上是否正确?

<强> 没有 即可。您的代码有两个逻辑错误:一个在line 29上(即您的if语句),一个在line 40上(即valIntegral的计算),这是直接后果第一个逻辑错误。

对于第一个错误,请考虑以下情节以了解原因:

你的蒙特卡罗过程有效地考虑了某个范围内的有界框,然后说&#34;我会随机将点放在这个框内,然后计算随机掉落的总点数的比例曲线下;然后,积分估计就是有界框本身的面积,乘以这个比例&#34;。

现在,如果两者都有 <子>   [latex] $$ x \! _ { _ { m \! i \! n }} $$ [/latex] 和 <子>   [latex] $$ x \! _ { _ { m \! a \! x }} $$ [/latex] 是对于平均值的(即0),那么你的if语句正确设置了方框的上限(即yBound)到 <子>   [latex] $$ \mathcal {N} ( x \! _ { _ {m \! a \! x}} ) $$ [/latex] 这样盒子的最上面的边界包含该曲线的最高部分。因此,例如,要估计范围[-2,-1]的积分,可以将上限设置为 <子>   [latex] $$ \mathcal {N} ( - 1 ) $$ [/latex]

同样,如果两者兼而有之 <子>   [latex] $$ x \! _ { _ { m \! i \! n }} $$ [/latex] 和 <子>   [latex] $$ x \! _ { _ { m \! a \! x }} $$ [/latex] 对于平均值的正确,然后您正确设置yBound <子>   [latex] $$ \mathcal {N} ( x \! _ { _ {m \! i \! n}} ) $$ [/latex]

然而 ,如果 <子>   [latex] $$ x ! _ { _ {m ! i ! n}} < 0 < x ! _ { _ {m ! a ! x}} $$ [/latex] ,您应该将yBound设置为 <子>   [latex] $$ x \! _ { _ { m \! i \! n }} $$ [/latex] 也不 <子>   [latex] $$ x \! _ { _ { m \! a \! x }} $$ [/latex] ,因为0点高于!所以在这种情况下,你的yBound应该只是高斯的峰值,即 <子>   [latex] $$ \mathcal {N} ( 0 ) $$ [/latex] (在 unnormalised 高斯的情况下,这取值为&#39; 1&#39;)。

因此,正确的if语句如下:

if (xMax < 0.0)
  { yBound = gaussian((double)xMax); }
else if (xMin > 0.0)
  { yBound = gaussian((double)xMin); }
else
  { yBound = gaussian(0.0); }

至于第二个逻辑错误,我们已经提到积分的值是边界框的&#34;区域&#34;乘以成功比例&#34;。但是,您似乎忽略了计算中框的高度。确实,在特殊情况下 <子>   [latex] $$ x ! _ { _ {m ! i ! n}} < 0 < x ! _ { _ {m ! a ! x}} $$ [/latex] ,非标准化高斯函数的高度默认为&#39; 1&#39;因此可以省略该项。我怀疑这就是它可能被遗漏的原因。但是,在另外两种情况下,边界框的高度必须 小于1,因此需要包含在计算中。因此line 40的正确代码应为:

valIntegral = yBound * (xMax-xMin) * (((double)countY)/numTrials);

为什么我没有得到正确的输出?

即使出现上述逻辑错误,正如我们上面所讨论的那样,特定的间隔[0,1]和[> -1,0](因为它们包括均值,因此包含正确的yBound 1)。那你为什么仍然得到一个错误的&#39;输出

答案是,你不是。你的输出是&#34;正确&#34;。除此之外,蒙特卡罗过程涉及随机性,100次试验的数量不足以导致一致的结果。如果您反复运行100次试验的相同范围,您将会看到每次都会得到非常不同的结果(但总的来说,它们会分布在正确的值周围)。运行1000000次试验,您将看到结果变得更加精确。

那个randomNumber代码是什么?

rand()函数返回[0,RAND_MAX]范围内的整数,其中RAND_MAX是系统特定的(请查看{{ 1}})。

模数方法(即man 3 rand)的工作原理如下:考虑范围[-0.1,0.3]。这个范围跨越0.4个单位。 0.4 * 1000 + 1 = 401.对于从0到%的随机数,执行RAND_MAX modulo rand()将始终产生范围内的随机数[0400]。如果再将其除以1000,则得到[0,0.4]范围内的随机数。将其添加到xmin偏移量(此处为:-0.1),您将获得[-0.1,0.3]范围内的随机数。

从理论上讲,这是有道理的。然而,不幸的是,正如在此处的另一个答案中已经指出的那样,作为一种方法,它易受模偏差的影响,因为401不一定完全可被401整除,因此该范围的顶部会导致到RAND_MAX比其他人更多地代表了一些数字。

相比之下,老师给你的方法只是说:将RAND_MAX函数的结果除以rand()这有效地将返回的随机数标准化为[0,1] 范围。这是一个更直截了当的事情,它避免了模数偏差。

因此,我实现这个的方法是将它变成一个函数:

RAND_MAX

然后也简化了你的计算:

double randomNumber(void) {
  return rand() / (double) RAND_MAX;
}

如果你使用规范化高斯,你可以看到这是一个更准确的事情,即

xRand = randomNumber() * (xMax-xMin) + xMin;
yRand = randomNumber() * yBound;

然后比较两种方法。你会看到{&#34;有效无限&#34;的double gaussian(double x) { return exp((-pow(x,2.0))/2.0) / sqrt(2.0 * M_PI); } 方法。范围(例如[-1000,1000])给出正确的结果1,而模数方法倾向于给出大于1的数字。

答案 1 :(得分:3)

你的代码没有明显的错误(尽管 是上限计算中的一个错误,正如@TasosPapastylianou指出的那样,尽管它不是你的测试用例中的问题)。在100次试验中,您对0.880的回答更接近于积分的实际值(0.855624 ...)而不是0.810,并且这些数字都不是真正的值,因此在代码中提出了一个彻头彻尾的错误。似乎在抽样误差范围内(尽管见下文)。以下是e^(-x^2/2)[0,1]进行100次试验的1000次蒙特卡洛积分(在R中完成,但使用相同算法)的直方图:

enter image description here

除非您的教师详细指定算法和种子,否则您不应期望完全相同的答案。

至于你关于rand() / (double) RAND MAX的第二个问题:它试图避免modulo bias。这样的偏差可能会影响你的代码(特别是考虑到你的方法可以舍入到3个小数位),因为它似乎高估了积分(基于运行它十几次左右)。也许您可以在代码中使用它,看看您是否获得了更好的结果。