我是 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) 的 ode 转换为类型 (Double -> Forward Double -> Forward Double) 之一。
最佳答案
关键是,fDisc
必须能够支持自动微分。 IE。它的类型必须像 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
进行柯里化(Currying)。 ,因此它在解变量之前将时间参数单独作为常数。
关于haskell AD 模块 : difficulties with Mode typeclass,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51560542/