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

通过梯形规则进行的Haskell数值积分导致错误符号,haskell,numerical-methods,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

我写了一些代码,用梯形法则对函数进行数值积分。它是有效的,但它产生的答案有一个错误的符号。为什么会这样

代码是:

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
integration::(Double->Double)->Double->Double
积分f a b=h*(f a+f b+部分和)
哪里
h=(b-a)/1000
most_零件=地图f(点(1000-1)h)
部分和=大部分部分的和
积分::加倍->加倍->[加倍]
点x1-x2

|x1是的,确实是点加上一些因素错了(内部点乘以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
注意:这个答案是用识字的哈斯克尔写的。使用
.lhs
将其保存为扩展名,并将其加载到GHCi中以测试解决方案

找到罪犯 首先,让我们来看看<代码>集成< /代码>。在其当前形式中,它只包含函数值的总和
fx
。尽管这些因素目前并不正确,但总体方法是正确的:在网格点处评估
f
。但是,我们可以使用以下函数来验证是否存在错误:

ghci> integration (\x -> if x >= 10 then 1 else (-1)) 10 15
-4.985
等一下
x在[10,15]中甚至都不是负数。这表明您使用了错误的栅格点

重新访问网格点 尽管您已经链接了这篇文章,我们还是来看看梯形规则()的一个示例用法:

尽管这并不使用统一的网格,但让我们假设6个网格点与网格距离
h=(b-a)/5
相等。这些点的
x
坐标是多少

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)
如果我们使用set
a=10
b=15
(因此
h=1
),我们应该得到
[10,11,12,13,14,15]
。让我们检查您的
点。在这种情况下,您将使用
点51
,并以
[5,4,3,2,1]
结束

这就是错误<代码>点
不尊重边界。我们可以使用
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)
收尾 但是,这并没有考虑到在梯形规则中使用所有内部点两次。如果我们加上这些因素,我们最终会得到

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 = -- ...

此外,尝试以不同的方式编写
,例如从
a
b
,使用
takeWhile
迭代
,甚至是列表理解。

清除编码风格的问题将更容易看到正在发生的事情,并且更容易孤立地进行测试。正确。我应该学会编写好代码并进行测试。但这可能会在以后出现。为什么要将
x1*x2
放在
点列表中?这对我来说没有意义。你可以使用范围理解,虽然浮动范围有点。。。奇怪的请参阅(我在这里使用了梯形积分的示例,以说明为什么浮点的奇怪行为有一定意义)。让我们有3点。你应该有`(b-a)/4*(f_1+2f_2+f_3)。与您的解决方案进行比较。您的点似乎不正确-例如,第一个点将是
999*(b-a)/1000
,这是关于
(b-a)
,最后一个点将是
0
,但这些点不应该是从
a
b
?(又名:你错过了一个
+a
)-假设剩下的都没有问题,那么你将从
0
集成到
b-a
,而不是通过这种方式从
a
集成到
b
!您的
看起来相同。我认为这两种方法都是不可靠的,因为它们都是双精度的,但我认为点列表的顺序对这个算法并不重要。@dfeuer:他将
(+a)
移到了
集成中。是的。。。这更容易(也就是说,我懒得在点上添加另一个参数)这真的不是关于任何顺序-我唯一可能搞砸的是,也许我在这里选择了错误的间隔-我真的很困,我不会这样做(点),但这真的应该接近维基文章(没有那么难)@Chiffa:嗯,这太过分了,但您可以创建随机多项式,对它们进行积分,并检查您的
积分是否与正确的结果相差太多。在浮点计数时避免奇怪的围栏问题的一个好方法是
映射(ArbiaryTransformation.fromIntegral)
。当然,如果使用可变宽度间隔,这些都会发生变化。这是一个很好的答案。我很遗憾只能打1分。还有,我应该看看字面上的哈斯克尔。作为一名学生,我还不知道那是什么。@Chiffa:你知道你通常如何使用
--
{-…-}
来发表评论吗?识字编程基本上颠覆了一切。所有内容都解释为注释,除非您使用特殊语法(在本例中为
>…
,也称为Bird样式)。我的函数是一个练习的结果。当然,一个真实的、设计良好的函数应该有一个关于点数的参数。至于
iterate
,我还没有了解它。@Chiffa:备注:我只在StackOverflow上使用literate Haskell,这样就可以将整个答案复制并粘贴到编辑器/文件中。我通常不会在自己的项目中使用它。
integration :: Int -> (Double -> Double) -> Double -> Double -> Double
integration n f a b = -- ...