我是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
但是我不知道该怎么做,如果这是一种好的样式,我也不知道。
为简化此问题:我知道我可以使用auto
从a
到Forward 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)之一。
答案 0 :(得分:3)
关键是,fDisc
必须能够支持自动区分。即它的类型必须为Forward Double -> Forward Double
。但是,在
fDisc yUnknown = yUnknown - y + (newx - x) * (f (x,yUnknown))
您有值y
,x
和newx
,它们是简单的具体数字,可能是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参数作为常量单独使用。