haskell AD模块:Mode类型类的困难

时间:2018-07-27 14:52:33

标签: haskell

我是Haskell的新手,并且很难理解如何使用自动微分模块AD进行类型检查来获取程序。

我的目标是实现隐式Euler ODE求解器,该求解器利用牛顿法对离散方程进行数值求解。 (我试图将其推广到ODE系统,但是对于我的问题,仅考虑一个ODE的情况就足够了)

我实现了如下的牛顿方法:

import Numeric.AD
import Numeric.AD.Rank1.Forward (Forward, diff')

newton :: (Fractional a, Eq a, Ord a) => (Forward a -> Forward a) -> a -> a -> a
newton equation guess tolerance
    | abs (equation (auto guess)) < auto tolerance = guess
    | otherwise = newton equation newGuess tolerance
    where
        newGuess = guess - correction
        (y,y') = diff' equation guess
        correction = y/y'

在可以将其用于

的意义上,此功能有效
mySqrtOfTwo = newton (\(x) -> x^2 - 2) 1 0.001

但是,如果我尝试将其用于其他功能,例如

impEuler f (x, y) newx = (newx, newy) 
    where
        newy = newton fDisc y 1e-3
        fDisc yUnknown = yUnknown - y + (newx - x) * (f (x,yUnknown))

我收到错误

• Occurs check: cannot construct the infinite type: b ~ Forward b
• In the second argument of ‘newton’, namely ‘y’
      In the expression: newton fDisc y 1e-3
      In an equation for ‘newy’: newy = newton fDisc y 1e-3

我想我理解为什么会出现此错误,但我不明白为什么仅在将newton函数用于另一个函数而不是直接调用时才会发生。此外,我想知道解决这个问题的正确方法是什么。

我考虑过以某种不同的方式实现功能newton,即它的类型

newton :: (Fractional a, Eq a, Ord a) => (a -> a) -> a -> a -> a

但是我不知道该怎么做,如果这是一种好的样式,我也不知道。

为简化此问题:我知道我可以使用autoaForward a,但是我不知道怎么走,如果这样甚至可能。

编辑:按照@leftroundabout的建议,我实现了以下功能:

impEuler :: (Double -> Forward Double -> Forward Double) -> (Double,Double) -> Double -> Double
impEuler f (x, y) newx = newy
where newy = newton fDisc y 1e-3
      fDisc :: Forward Double -> Forward Double
      fDisc yUnknown = yUnknown - realToFrac y - realToFrac (newx - x) * f x yUnknown

这要求传递给impEuler的ode也具有类型(Double -> Forward Double -> Forward Double),我希望避免这种类型,因为我可能决定使用显式方法(不需要牛顿方法)来解决ode。因此,我添加了功能

odePromoter :: (Double -> Double -> Double) -> (Double -> Forward Double -> Forward Double)
odePromoter ode x y = realToFrac (ode x (realToFrac y))

以便将类型(Double-> Double-> Double)的颂歌转换为类型(Double-> Forward Double-> Forward Double)之一。

1 个答案:

答案 0 :(得分:3)

关键是,fDisc必须能够支持自动区分。即它的类型必须为Forward Double -> Forward Double。但是,在

   fDisc yUnknown = yUnknown - y + (newx - x) * (f (x,yUnknown))

您有值yxnewx,它们是简单的具体数字,可能是Double。 Haskell永远不会隐式转换/提升类型,因此您需要fDisc :: Double -> Double,这意味着newton无法使用它。

解决方案:允许这些值的显式提升。一种标准的方法是realToFrac

impEuler :: (Double -> Forward Double -> Forward Double) -> (Double,Double) -> Double
impEuler f (x, y) newx = newy
 where newy = newton fDisc y 1e-3
       fDisc :: Forward Double -> Forward Double
       fDisc yUnknown = yUnknown - realToFrac y + realToFrac (newx - x) * f x yUnknown

请注意,我必须使用函数f,因此它在解决方案变量之前将time参数作为常量单独使用。