通过梯形规则的Haskell数值积分导致错误的符号

时间:2015-10-06 19:39:27

标签: haskell numerical-methods

我编写了一些代码,用于使用梯形规则以数字方式集成函数。它有效,但它产生的答案有一个错误的标志。为什么会这样?

代码是:

integration :: (Double -> Double) -> Double -> Double -> Double
integration f a b = h * (f a + f b + partial_sum)
    where 
        h = (b - a) / 1000 
        most_parts  = map f (points (1000-1) h) 
        partial_sum = sum most_parts

points  :: Double -> Double -> [Double]
points x1 x2 
    | x1 <= 0 = []
    | otherwise = (x1*x2) : points (x1-1) x2

Trapezoidal rule

代码可能不够优雅,但我只是Haskell的学生,并希望先处理当前的问题,然后编码风格很重要。

2 个答案:

答案 0 :(得分:5)

注意:这个答案写在有文化的Haskell中。以.lhs作为扩展名保存,并将其加载到GHCi中以测试解决方案。

找到罪魁祸首

首先,让我们来看看integration。在其当前形式中,它仅包含函数值f x的总和。尽管目前这些因素并不正确,但整体方法还是很好:您在网格点评估f。但是,我们可以使用以下函数来验证是否存在错误:

ghci> integration (\x -> if x >= 10 then 1 else (-1)) 10 15
-4.985

等一下。 [{1}}在[10,15]中甚至不是负数。这表明您使用了错误的网格点。

重新访问网格点

即使您已将该文章链接起来,让我们看看梯形规则(public domain, original file by Oleg Alexandrov)的示例性使用:

trapezoidal rule

虽然这并没有使用统一网格,但我们假设6个网格点与网格距离x等距。这些点的h = (b - a) / 5坐标是什么?

x

如果我们使用set x_0 = a + 0 * h (== a) x_1 = a + 1 * h x_2 = a + 2 * h x_3 = a + 3 * h x_4 = a + 4 * h x_5 = a + 5 * h (== b) a = 10(因此b = 15),我们应该使用h = 1。我们来检查您的[10, 11, 12, 13, 14, 15]。在这种情况下,您将使用points并以points 5 1结束。

出现错误。 [5,4,3,2,1]并不尊重边界。我们可以使用points

轻松解决此问题
pointsWithOffset

这样,我们仍然可以使用您当前的> points :: Double -> Double -> [Double] > points x1 x2 > | x1 <= 0 = [] > | otherwise = (x1*x2) : points (x1-1) x2 > > pointsWithOffset :: Double -> Double -> Double -> [Double] > pointsWithOffset x1 x2 offset = map (+offset) (points x1 x2) 定义生成从pointsx1(几乎)的网格点。如果我们将0integration一起使用,我们最终会使用

pointsWithOffset

捆绑松散的结尾

但是,这并没有考虑到你在梯形规则中使用了两次所有内点。如果我们添加因子,我们最终会

integration :: (Double -> Double) -> Double -> Double -> Double
integration f a b = h * (f a + f b + partial_sum)
    where 
        h = (b - a) / 1000 
        most_parts  = map f (pointsWithOffset (1000-1) h a)  
        partial_sum = sum most_parts

这为我们的测试函数提供了正确的值。

运动

您当前的版本仅支持> integration :: (Double -> Double) -> Double -> Double -> Double > integration f a b = > h / 2 * (f a + f b + 2 * partial_sum) > -- ^^^ ^^^ > where > h = (b - a) / 1000 > most_parts = map f (pointsWithOffset (1000-1) h a) > partial_sum = sum most_parts 网格点。添加1000参数,以便可以更改网格点的数量:

Int

此外,尝试以不同的方式撰写integration :: Int -> (Double -> Double) -> Double -> Double -> Double integration n f a b = -- ... ,例如从points转到a,使用btakeWhile,甚至是列表理解。

答案 1 :(得分:2)

是的,确实是积分加上你有一些因素是错误的(内部点乘以2) - 这是你的代码的固定版本:

integration :: (Double -> Double) -> Double -> Double -> Double
integration f a b = h * (f a + f b + innerSum) / 2
    where 
        h = (b - a) / 1000 
        innerPts  = map ((2*) . f . (a+)) (points (1000-1) h) 
        innerSum = sum innerPts

points  :: Double -> Double -> [Double]
points i x 
    | i <= 0 = []
    | otherwise = (i*x) : points (i-1) x

给出合理的近似值(至1000点):

λ> integration (const 2) 1 2
2.0
λ> integration id 1 2
1.5

λ> integration (\x -> x*x) 1 2
2.3333334999999975

λ> 7/3
2.3333333333333335