通过梯形规则进行的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)
如果我们使用seta=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 = -- ...