使用Numerical.Integration.Tanh Sinh进行N维积分

时间:2014-05-16 19:40:17

标签: haskell functional-programming

我在Haskell中使用Numeric.Integration.TanhSinh进行数值积分。这定义了一个函数

parTrap :: (Double -> Double) -> Double -> Double -> [Result]

其中第一个参数是要集成的1维函数,然后是上限和下限。我有一个包装器,将此功能转换为

ttrap f xmin xmax = (ans, err)
   where
      res = absolute 1e-6 $ parTrap f xmin xmax
      ans = result res
      err = errorEstimate res

要集成二维功能,我可以使用

 ttrap2 f y1 y2 g1 g2 = ttrap h y1 y2 -- f ylower yupper (fn for x lower) (fn for x upper) 
       where
           h y = fst $ ttrap (flip f y) (g1 y) (g2 y)

ttrap2_fixed f y1 y2 x1 x2 = ttrap2 f y1 y2 (const x1) (const x2)

ttrap2_fixed的想法是我现在可以在函数为(Double - > Double - > Double)的情况下进行双积分,并且边界为y1 y2 x1 x2

使用这种模式,我可以定义更高阶的积分函数

ttrap3_fixed :: (Double -> Double -> Double -> Double) -> Double -> Double -> Double -> Double -> Double -> Double -> Double
ttrap3_fixed f z1 z2 y1 y2 x1 x2 = fst $ ttrap h z1 z2
  where
    h z  = fst $ ttrap2_fixed (f z)  x1 x2 y1 y2


ttrap4_fixed :: (Double -> Double -> Double -> Double -> Double) -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double
ttrap4_fixed f w1 w2 z1 z2 y1 y2 x1 x2 = fst $ ttrap h w1 w2
  where
    h w  = ttrap3_fixed (f w) z1 z2 x1 x2 y1 y2


ttrap5_fixed :: (Double -> Double -> Double -> Double -> Double -> Double) -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double
ttrap5_fixed f u1 u2 w1 w2 z1 z2 y1 y2 x1 x2 = fst $ ttrap h u1 u2
  where
    h u  = ttrap4_fixed (f u) w1 w2 z1 z2 x1 x2 y1 y2

但是,我想整合一个类型为

的函数
f :: [Double] -> Double

理念是函数的维度可以在程序中变化。理想情况下,我想要一个类型为

的函数
int_listfn :: ([Double] -> Double) -> [(Double, Double)] -> Double

我可以将多维函数集成到边界元组列表中。作为其中的一部分,似乎我需要使用诸如延续传递样式之类的东西来构建积分器函数,但这是我遇到的问题。提前谢谢。


我想要整合的f的例子就像是

> let f [x,y,z] = x**3.0 * sin(y) + (1.0/z)

考虑边界

> let bounds = [(1.0,2.0),(2.0,4.0), (0.0,3.0)]
> int_listfn f bounds

这相当于计算 enter image description here


编辑:添加另一个示例函数

f1 :: Double -> Double
f1 x = 1.0 * x


fn_maker :: [Double -> Double] -> ([Double] -> Double)
fn_maker inlist = myfn
  where
    myfn xlist = product $ zipWith (\f x -> f x) inlist xlist


m = 4

f_list = fn_maker (replicate f1 m)

f_list的类型为[Double] -> Double,相当于f x y z w = x * y * z * w。我认为类型[Double] -> Double是合适的,因为函数m的维度是一个参数。也许我需要改变设计。


将函数列为curried函数,@ fizruk我一直在我的代码中使用它并且似乎工作,虽然我需要根据边界列表的大小跟踪调用哪个函数

static_1 :: ([Double] -> Double) -> (Double -> Double)
static_1 f = f'
  where
    f' x = f [x]


static_2 :: ([Double] -> Double) -> (Double -> Double -> Double)
static_2 f = f'
  where
    f' x y = f [x,y]


static_3 :: ([Double] -> Double) -> (Double -> Double -> Double -> Double)
static_3 f = f'
  where
    f' x y z = f [x,y,z]


static_4 :: ([Double] -> Double) -> (Double -> Double -> Double -> Double -> Double)
static_4 f = f'
  where
    f' x y z w = f [x,y,z,w]


static_5 :: ([Double] -> Double) -> (Double -> Double -> Double -> Double -> Double -> Double)
static_5 f = f'
  where
    f' x y z w u = f [x,y,z,w,u]


int_listfn :: ([Double] -> Double) -> [(Double, Double)] -> Double
int_listfn f bounds  
  | f_dim == 1 = fst $ ttrap (static_1 f) (fst (bounds !! 0)) (snd (bounds !! 0))
  | f_dim == 2 = fst $ ttrap2_fixed  (static_2 f) (fst (bounds !! 0)) (snd (bounds !! 0)) (fst (bounds !! 1)) (snd (bounds !! 1))
  | f_dim == 3 = ttrap3_fixed  (static_3 f) (fst (bounds !! 0)) (snd (bounds !! 0)) (fst (bounds !! 1)) (snd (bounds !! 1)) (fst (bounds !! 2)) (snd (bounds !! 2))
  | f_dim == 4 = ttrap4_fixed  (static_4 f) (fst (bounds !! 0)) (snd (bounds !! 0)) (fst (bounds !! 1)) (snd (bounds !! 1)) (fst (bounds !! 2)) (snd (bounds !! 2)) (fst (bounds !! 3)) (snd (bounds !! 3))
  | f_dim == 5 = ttrap5_fixed  (static_5 f) (fst (bounds !! 0)) (snd (bounds !! 0)) (fst (bounds !! 1)) (snd (bounds !! 1)) (fst (bounds !! 2)) (snd (bounds !! 2)) (fst (bounds !! 3)) (snd (bounds !! 3)) (fst (bounds !! 3)) (snd (bounds !! 3))
  | otherwise = error "Unsupported integral size"
  where
    f_dim = length bounds

2 个答案:

答案 0 :(得分:2)

想法

为方便起见,我介绍了这些类型的别名:

type Res   = (Double, Double)
type Bound = (Double, Double)

让我们仔细看看ttrap_fixedN的类型:

ttrap :: (Double -> Double) -> Double -> Double -> Res
ttrap_fixed2 :: (Double -> Double -> Double) -> Double -> Double -> Double -> Double -> Res

显然,我们可以配对边界以获得更短更清晰的版本:

ttrap :: (Double -> Double) -> Bound -> Res
ttrap_fixed2 :: (Double -> Double -> Double) -> Bound -> Bounds -> Res

此外,我们可以Bounds>一起收集N 1

ttrap_fixed2 :: (Double -> Double -> Double) -> (Bound, Bound) -> Res
ttrap_fixed3 :: (Double -> Double -> Double -> Double) -> (Bound, Bound, Bound) -> Res

注意我们如何使所有ttrap_fixedN函数精确地获取2个参数。还要注意第二个参数的类型(带有Bound s的元组的arity)取决于第一个(要集成的函数的arity)。

现在应该清楚,一般ttrap_fixed函数将取决于给定函数的arity,我们需要类型类来实现这种多态性。除了integrate方法(这是一般ttrap_fixed),该类需要associated type synonym作为第二个参数:

class Integrable r where
  type Bounds r :: *
  integrate :: r -> Bounds r -> Res

解决方案草图

我们需要以下扩展程序:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}

import Numeric.Integration.TanhSinh

这是我将使用的问题中的唯一功能:

ttrap :: (Double -> Double) -> Double -> Double -> Res
ttrap f xmin xmax = (ans, err)
   where
      res = absolute 1e-6 $ parTrap f xmin xmax
      ans = result res
      err = errorEstimate res

定义Integrable类型类:

class Integrable r where
  type Bounds r :: *
  integrate :: r -> Bounds r -> Res

它的实例

instance Integrable Double where
  type Bounds Double = ()
  integrate x _ = (x, 0)

instance Integrable r => Integrable (Double -> r) where
  type Bounds (Double -> r) = (Bound, Bounds r)
  integrate f ((xmin, xmax), args) = ttrap g xmin xmax
    where
      g x = fst $ integrate (f x) args

去测试一下:

main :: IO ()
main = do
  let f :: Double -> Double -> Double -> Double
      f x y z = x ** 3.0 * sin(y) + (1.0/z)
      zbounds = (1.0, 2.0)
      ybounds = (2.0, 4.0)
      xbounds = (0.0, 3.0)
      res = integrate f (xbounds, (ybounds, (zbounds, ())))
  print res -- prints (8.9681929657648,4.732074732061164e-10)

结束Bounds

您可能已经注意到,我们现在没有非常方便的嵌套元组Bound。如果integrate f返回一个curried函数会很好,例如:

integrate (*) :: Bound -> Bound -> Res

而不是

integrate (*) :: (Bound, (Bound, ())) -> Res

不幸的是,我没有找到任何简单的方法来重构Integrable以允许它。但是,我们可以用另一个类型类hackery来解决这个问题。我们的想法是引入一个多态curryBounds

class CurryBounds bs where
  type Curried bs a :: *
  curryBounds :: (bs -> a) -> Curried bs a

实例很简单:

instance CurryBounds () where
  type Curried () a = a
  curryBounds f = f ()

instance CurryBounds bs => CurryBounds (b, bs) where
  type Curried (b, bs) a = b -> Curried bs a
  curryBounds f = \x -> curryBounds (\xs -> f (x, xs))

现在我们可以定义更好的integrate版本:

integrate' :: (Integrable r, CurryBounds (Bounds r)) => r -> Curried (Bounds r) Res
integrate' = curryBounds . integrate

实施例

>>> let f x y z = x**3.0 * sin(y) + (1.0/z) :: Double
>>> integrate' f (0, 3) (2, 4) (1, 2)
(8.9681929657648,4.732074732061164e-10)

答案 1 :(得分:1)

Fizruk的回答似乎是正确的(虽然我没有事实检查过细节)......但我想我应该添加一套补充说明来解释你为什么要做的事情不会工作。

要集成的函数类型具有类型

[Double]->Double

这并不是你想做的事情。您希望f具有预设的域维度,但在Haskell的世界中,您提供的类型签名实际上将包含任意数量的值....以下是f的有效一致定义

f [] = 1.0
f [x] = x+1
f [x, y] = x+y

如果您只是将此值传递给集成函数,则无法知道您要指定的定义中的哪一行(您可以将此维度作为额外参数添加,但使用它有点过分一个可以采用任意长度数组的机制,然后使用参数来确定长度....虽然考虑替代方案,这可能是最好的解决方案。)

您可以使用元组代替。这将解决维度问题,但编写跨任意长度元组的代码很难,通常需要一些GHC扩展。

我喜欢Fizruk的方法,它干净而且非常时尚,你可以使用完全咖喱的功能,因为它们的目的是被使用....唯一的缺点是你需要学习更多高级Haskell了解它是如何工作的(虽然这可能对你有利)。


@SteveJB指出维度是作为边界参数的长度给出的(这是我错过的一个显而易见的事实)....但我仍然认为数组方法存在一些问题。

首先,我自然会想到在这里使用递归(即 - 在N维中积分,在间隔切片上求和N-1维),但这对于数组函数和大小的边界来说真的很难。一旦你剥离了最外层的边界,尺寸就会改变,错误的"子功能"在f中将被使用。你可以通过拉出包装器函数的边界大小来解决这个问题,但是内部函数需要一个维度参数,无论如何:)。此外,咖喱"咖喱" [Double] - >类型的函数使用它的方式是Double。最终你可能需要传递所有切片位置。

同样,我想强调你可以使这项工作,但它可能比Fizruk做的更麻烦。