rad (empty) → 0.1
raw patch · 5 files changed
+514/−0 lines, 5 filesdep +arraydep +basedep +containerssetup-changed
Dependencies added: array, base, containers, data-reify
Files
- LICENSE +30/−0
- Numeric/RAD.hs +446/−0
- Setup.hs +2/−0
- TODO +15/−0
- rad.cabal +21/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2010, Edward Kmett+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Edward Kmett nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Numeric/RAD.hs view
@@ -0,0 +1,446 @@+{-# LANGUAGE Rank2Types, TypeFamilies #-}+-----------------------------------------------------------------------------+-- |+-- Module : Numeric.RAD+-- Copyright : (c) Edward Kmett 2010+-- License : BSD3+-- Maintainer : ekmett@gmail.com+-- Stability : experimental+-- Portability : GHC only +--+-- Reverse Mode Automatic Differentiation via overloading to perform+-- nonstandard interpretation that replaces original numeric type with+-- a bundle that contains a value of the original type and the tape that+-- will be used to recover the value of the sensitivity.+-- +-- This package uses StableNames internally to recover sharing information from +-- the tape to avoid combinatorial explosion, and thus runs asymptotically faster+-- than it could without such sharing information, but the use of side-effects+-- contained herein is benign.+--+-- The API has been built to be close to the design of 'Numeric.FAD' from the 'fad' package+-- by Barak Pearlmutter and Jeffrey Mark Siskind and contains portions of that code, with minor liberties taken.+-- +-----------------------------------------------------------------------------++module Numeric.RAD + ( + -- * First-Order Reverse Mode Automatic Differentiation+ RAD+ , lift+ -- * First-Order Differentiation Operators+ , diffUU+ , diffUF+ , diff2UU+ , diff2UF+ -- * Common access patterns + , diff+ , diff2+ , jacobian+ , jacobian2+ , grad+ , grad2+ -- * Optimization Routines + , zeroNewton+ , inverseNewton+ , fixedPointNewton+ , extremumNewton+ , argminNaiveGradient+ ) where++import Prelude hiding (mapM)+import Control.Applicative (Applicative(..),(<$>))+import Control.Monad.ST+import Control.Monad (forM_)+import Data.List (foldl')+import Data.Array.ST+import Data.Array+import Data.Ix+import Text.Show+import Data.Graph (graphFromEdges', topSort, Vertex)+import Data.Reify (reifyGraph, MuRef(..))+import qualified Data.Reify.Graph as Reified+import Data.Traversable (Traversable, mapM)+import System.IO.Unsafe (unsafePerformIO)++newtype RAD s a = RAD (Tape a (RAD s a))++data Tape a t+ = C a + | V a Int+ | B a a a t t+ | U a a t ++instance Show a => Show (RAD s a) where+ showsPrec d = disc1 (showsPrec d)++-- | The 'lift' function injects a primal number into the RAD data type with a 0 derivative.+-- If reverse-mode AD numbers formed a monad, then 'lift' would be 'return'.+lift :: a -> RAD s a +lift = RAD . C +{-# INLINE lift #-}++primal :: RAD s a -> a+primal (RAD (C y)) = y+primal (RAD (V y _)) = y+primal (RAD (B y _ _ _ _)) = y+primal (RAD (U y _ _)) = y+{-# INLINE primal #-}++var :: a -> Int -> RAD s a +var a v = RAD (V a v)++-- TODO: A higher-order data-reify+-- mapDeRef :: (Applicative f) => (forall a . Num a => RAD s a -> f (u a)) -> a -> f (Tape a (u a))+instance MuRef (RAD s a) where+ type DeRef (RAD s a) = Tape a+ mapDeRef f (RAD (C a)) = pure (C a)+ mapDeRef f (RAD (V a v)) = pure (V a v)+ mapDeRef f (RAD (B a jb jc x1 x2)) = B a jb jc <$> f x1 <*> f x2+ mapDeRef f (RAD (U a j x)) = U a j <$> f x++on :: (a -> a -> c) -> (b -> a) -> b -> b -> c+on f g a b = f (g a) (g b)++instance Eq a => Eq (RAD s a) where+ (==) = (==) `on` primal++instance Ord a => Ord (RAD s a) where+ compare = compare `on` primal++instance Bounded a => Bounded (RAD s a) where+ maxBound = lift maxBound+ minBound = lift minBound++unary_ :: (a -> a) -> a -> RAD s a -> RAD s a+unary_ f _ (RAD (C b)) = RAD (C (f b))+unary_ f g b = RAD (U (disc1 f b) g b)+{-# INLINE unary_ #-}++unary :: (a -> a) -> (a -> a) -> RAD s a -> RAD s a+unary f _ (RAD (C b)) = RAD (C (f b))+unary f g b = RAD (U (disc1 f b) (disc1 g b) b)+{-# INLINE unary #-}++binary_ :: (a -> a -> a) -> a -> a -> RAD s a -> RAD s a -> RAD s a+binary_ f _ _ (RAD (C b)) (RAD (C c)) = RAD (C (f b c))+binary_ f gb gc b c = RAD (B (f vb vc) gb gc b c)+ where vb = primal b; vc = primal c+{-# INLINE binary_ #-}++-- binary_ with partials+binary :: (a -> a -> a) -> (a -> a) -> (a -> a) -> RAD s a -> RAD s a -> RAD s a+binary f _ _ (RAD (C b)) (RAD (C c)) = RAD (C (f b c))+binary f gb gc b c = RAD (B (f vb vc) (gb vc) (gc vb) b c)+ where vb = primal b; vc = primal c+{-# INLINE binary #-}++disc1 :: (a -> b) -> RAD s a -> b+disc1 f x = f (primal x)+{-# INLINE disc1 #-}++disc2 :: (a -> b -> c) -> RAD s a -> RAD s b -> c+disc2 f x y = f (primal x) (primal y)+{-# INLINE disc2 #-}++disc3 :: (a -> b -> c -> d) -> RAD s a -> RAD s b -> RAD s c -> d+disc3 f x y z = f (primal x) (primal y) (primal z)+{-# INLINE disc3 #-}++from :: Num a => RAD s a -> a -> RAD s a+from (RAD (C a)) x = RAD (C x)+from a x = RAD (U x 1 a)++fromBy :: Num a => RAD s a -> RAD s a -> Int -> a -> RAD s a +fromBy (RAD (C a)) _ _ x = RAD (C x)+fromBy a delta n x = RAD (B x 1 (fromIntegral n) a delta)++instance (Num a, Enum a) => Enum (RAD s a) where+ succ = unary_ succ 1+ pred = unary_ pred 1+ toEnum = lift . toEnum+ fromEnum = disc1 fromEnum+ -- the enumerated results vary with the lower bound and so their derivatives reflect that+ enumFrom a = from a <$> disc1 enumFrom a+ enumFromTo a b = from a <$> disc2 enumFromTo a b+ -- these results vary with respect to both the lower bound and the delta between that and the second argument+ enumFromThen a b = zipWith (fromBy a delta) [0..] $ disc2 enumFromThen a b where delta = b - a+ enumFromThenTo a b c = zipWith (fromBy a delta) [0..] $ disc3 enumFromThenTo a b c where delta = b - a++instance Num a => Num (RAD s a) where+ fromInteger = lift . fromInteger+ (+) = binary_ (+) 1 1 + (-) = binary_ (-) 1 (-1)+ negate = unary_ negate (-1)+ (*) = binary (*) id id+ -- incorrect if the argument is complex+ abs = unary abs signum+ signum = lift . signum . primal++-- notComplex :: Num a => a -> Bool+-- notComplex x = s == 0 || s == 1 || s == -1+-- where s = signum x ++instance Real a => Real (RAD s a) where+ toRational = disc1 toRational++instance RealFloat a => RealFloat (RAD s a) where+ floatRadix = disc1 floatRadix+ floatDigits = disc1 floatDigits+ floatRange = disc1 floatRange++ decodeFloat = disc1 decodeFloat+ encodeFloat m e = lift (encodeFloat m e)++ scaleFloat n = unary_ (scaleFloat n) (scaleFloat n 1) + isNaN = disc1 isNaN+ isInfinite = disc1 isInfinite+ isDenormalized = disc1 isDenormalized+ isNegativeZero = disc1 isNegativeZero+ isIEEE = disc1 isIEEE++ exponent x+ | m == 0 = 0 + | otherwise = n + floatDigits x+ where (m,n) = decodeFloat x ++ significand x = unary_ significand (scaleFloat (- floatDigits x) 1) x++ atan2 (RAD (C x)) (RAD (C y)) = RAD (C (atan2 x y))+ atan2 x y = RAD (B (atan2 vx vy) (vy*r) (-vx*r) x y)+ where vx = primal x+ vy = primal y+ r = recip (vx^2 + vy^2)++instance RealFrac a => RealFrac (RAD s a) where+ properFraction (RAD (C a)) = (w, RAD (C p))+ where (w, p) = properFraction a+ properFraction a = (w, RAD (U p 1 a))+ where (w, p) = properFraction (primal a)+ truncate = disc1 truncate+ round = disc1 truncate+ ceiling = disc1 truncate+ floor = disc1 truncate++instance Fractional a => Fractional (RAD s a) where+ (/) = binary (/) recip id+-- recip = unary recip (const . negate . (^2))+ fromRational r = lift $ fromRational r++instance Floating a => Floating (RAD s a) where+ pi = lift pi+ exp = unary exp id+ log = unary log recip+ sqrt = unary sqrt (recip . (2*) . sqrt)+ RAD (C x) ** RAD (C y) = lift (x ** y)+ x ** y = RAD (B (vx ** vy) (vy/vx) (log (vx)) x y)+ where vx = primal x+ vy = primal y+ sin = unary sin cos+ cos = unary cos (negate . sin)+ asin = unary asin (recip . sqrt . (1-) . (^2))+ acos = unary acos (negate . recip . sqrt . (1-) . (^2))+ atan = unary atan (recip . (1+) . (^2))+ sinh = unary sinh cosh+ cosh = unary cosh sinh+ asinh = unary asinh (recip . sqrt . (1+) . (^2))+ acosh = unary acosh (recip . sqrt . (-1+) . (^2))+ atanh = unary atanh (recip . (1-) . (^2))++-- back propagate sensitivities along the tape.+backprop :: (Ix t, Ord t, Num a) => (Vertex -> (Tape a t, t, [t])) -> STArray s t a -> Vertex -> ST s ()+backprop vmap ss v = do+ case node of+ U _ g b -> do+ da <- readArray ss i+ db <- readArray ss b+ writeArray ss b (db + g*da)+ B _ gb gc b c -> do+ da <- readArray ss i+ db <- readArray ss b+ writeArray ss b (db + gb*da)+ dc <- readArray ss c+ writeArray ss c (dc + gc*da)+ _ -> return ()+ where + (node, i, _) = vmap v+++runTape :: Num a => (Int, Int) -> RAD s a -> Array Int a +runTape vbounds tape = accumArray (+) 0 vbounds [ (id, sensitivities ! ix) | (ix, V _ id) <- xs ]+ where+ Reified.Graph xs start = unsafePerformIO $ reifyGraph tape+ (g, vmap) = graphFromEdges' (edgeSet <$> filter nonConst xs)+ sensitivities = runSTArray $ do+ ss <- newArray (sbounds xs) 0+ writeArray ss start 1+ forM_ (topSort g) $ + backprop vmap ss+ return ss+ sbounds ((a,_):as) = foldl' (\(lo,hi) (b,_) -> (min lo b, max hi b)) (a,a) as+ edgeSet (i, t) = (t, i, successors t)+ nonConst (_, C{}) = False+ nonConst _ = True+ successors (U _ _ b) = [b]+ successors (B _ _ _ b c) = [b,c]+ successors _ = []++d :: Num a => RAD s a -> a+d r = runTape (0,0) r ! 0 ++d2 :: Num a => RAD s a -> (a,a)+d2 r = (primal r, d r)+++-- | The 'diffUU' function calculates the first derivative of a+-- scalar-to-scalar function.+diffUU :: Num a => (forall s. RAD s a -> RAD s a) -> a -> a+diffUU f a = d $ f (var a 0)+++-- | The 'diffUF' function calculates the first derivative of+-- scalar-to-nonscalar function.+diffUF :: (Functor f, Num a) => (forall s. RAD s a -> f (RAD s a)) -> a -> f a+diffUF f a = d <$> f (var a 0)++-- diffMU :: Num a => (forall s. [RAD s a] -> RAD s a) -> [a] -> [a] -> a+-- TODO: finish up diffMU and their ilk++-- avoid dependency on MTL+newtype S a = S { runS :: Int -> (a,Int) } ++instance Monad S where+ return a = S (\s -> (a,s))+ S g >>= f = S (\s -> let (a,s') = g s in runS (f a) s')+ +bind :: Traversable f => f a -> (f (RAD s a), (Int,Int))+bind xs = (r,(0,s)) + where + (r,s) = runS (mapM freshVar xs) 0+ freshVar a = S (\s -> let s' = s + 1 in s' `seq` (RAD (V a s), s'))++unbind :: Functor f => f (RAD s b) -> Array Int a -> f a +unbind xs ys = fmap (\(RAD (V _ i)) -> ys ! i) xs++-- | The 'diff2UU' function calculates the value and derivative, as a+-- pair, of a scalar-to-scalar function.+diff2UU :: Num a => (forall s. RAD s a -> RAD s a) -> a -> (a, a)+diff2UU f a = d2 $ f (var a 0)++-- | Note that the signature differs from that used in Numeric.FAD, because while you can always+-- 'unzip' an arbitrary functor, not all functors can be zipped.+diff2UF :: (Functor f, Num a) => (forall s. RAD s a -> f (RAD s a)) -> a -> f (a, a)+diff2UF f a = d2 <$> f (var a 0)++-- | The 'diff' function is a synonym for 'diffUU'.+diff :: Num a => (forall s. RAD s a -> RAD s a) -> a -> a+diff = diffUU ++-- | The 'diff2' function is a synonym for 'diff2UU'.+diff2 :: Num a => (forall s. RAD s a -> RAD s a) -> a -> (a, a)+diff2 = diff2UU++-- requires the input list to be finite in length+grad :: (Traversable f, Num a) => (forall s. f (RAD s a) -> RAD s a) -> f a -> f a+grad f as = unbind s (runTape bounds $ f s)+ where (s,bounds) = bind as++-- compute the primal and gradient+grad2 :: (Traversable f, Num a) => (forall s. f (RAD s a) -> RAD s a) -> f a -> (a, f a)+grad2 f as = (primal r, unbind s (runTape bounds r))+ where (s,bounds) = bind as+ r = f s++-- | The 'jacobian' function calcualtes the Jacobian of a+-- nonscalar-to-nonscalar function, using m invocations of reverse AD,+-- where m is the output dimensionality. When the output dimensionality is+-- significantly greater than the input dimensionality you should use 'Numeric.FAD.jacobian' instead.+jacobian :: (Traversable f, Functor g, Num a) => (forall s. f (RAD s a) -> g (RAD s a)) -> f a -> g (f a)+jacobian f as = unbind s . runTape bounds <$> f s+ where (s,bounds) = bind as++-- | The 'jacobian2' function calcualtes both the result and the Jacobian of a+-- nonscalar-to-nonscalar function, using m invocations of reverse AD,+-- where m is the output dimensionality. +-- 'fmap snd' on the result will recover the result of 'jacobian'+jacobian2 :: (Traversable f, Functor g, Num a) => (forall s. f (RAD s a) -> g (RAD s a)) -> f a -> g (a, f a)+jacobian2 f as = row <$> f s+ where (s,bounds) = bind as+ row a = (primal a, unbind s (runTape bounds a))++-- | The 'zeroNewton' function finds a zero of a scalar function using+-- Newton's method; its output is a stream of increasingly accurate+-- results. (Modulo the usual caveats.)+--+-- TEST CASE:+-- @take 10 $ zeroNewton (\\x->x^2-4) 1 -- converge to 2.0@+--+-- TEST CASE+-- :module Data.Complex Numeric.RAD+-- @take 10 $ zeroNewton ((+1).(^2)) (1 :+ 1) -- converge to (0 :+ 1)@+--+zeroNewton :: Fractional a => (forall s. RAD s a -> RAD s a) -> a -> [a]+zeroNewton f x0 = iterate (\x -> let (y,y') = diff2UU f x in x - y/y') x0++-- | The 'inverseNewton' function inverts a scalar function using+-- Newton's method; its output is a stream of increasingly accurate+-- results. (Modulo the usual caveats.)+--+-- TEST CASE:+-- @take 10 $ inverseNewton sqrt 1 (sqrt 10) -- converge to 10@+--+inverseNewton :: Fractional a => (forall s. RAD s a -> RAD s a) -> a -> a -> [a]+inverseNewton f x0 y = zeroNewton (\x -> f x - lift y) x0++-- | The 'fixedPointNewton' function find a fixedpoint of a scalar+-- function using Newton's method; its output is a stream of+-- increasingly accurate results. (Modulo the usual caveats.)+fixedPointNewton :: Fractional a => (forall s. RAD s a -> RAD s a) -> a -> [a]+fixedPointNewton f = zeroNewton (\x -> f x - x)++-- | The 'extremumNewton' function finds an extremum of a scalar+-- function using Newton's method; produces a stream of increasingly+-- accurate results. (Modulo the usual caveats.)+extremumNewton :: Fractional a => (forall s t. RAD t (RAD s a) -> RAD t (RAD s a)) -> a -> [a]+extremumNewton f x0 = zeroNewton (diffUU f) x0++-- | The 'argminNaiveGradient' function performs a multivariate+-- optimization, based on the naive-gradient-descent in the file+-- @stalingrad\/examples\/flow-tests\/pre-saddle-1a.vlad@ from the+-- VLAD compiler Stalingrad sources. Its output is a stream of+-- increasingly accurate results. (Modulo the usual caveats.) +-- This is /O(n)/ faster than 'Numeric.FAD.argminNaiveGradient'+argminNaiveGradient :: (Fractional a, Ord a) => (forall s. [RAD s a] -> RAD s a) -> [a] -> [[a]]+argminNaiveGradient f x0 =+ let+ gf = grad f+ loop x fx gx eta i =+ -- should check gx = 0 here+ let+ x1 = zipWith (+) x (map ((-eta)*) gx)+ fx1 = lowerFU f x1+ gx1 = gf x1+ in+ if eta == 0 then []+ else if (fx1 > fx) then loop x fx gx (eta/2) 0+ else if all (==0) gx then []+ -- else if fx1 == fx then loop x1 fx1 gx1 eta (i+1)+ else x1:(if (i==10)+ then loop x1 fx1 gx1 (eta*2) 0+ else loop x1 fx1 gx1 eta (i+1))+ in+ loop x0 (lowerFU f x0) (gf x0) 0.1 0++{-+lowerUU :: (forall s. RAD s a -> RAD s b) -> a -> b+lowerUU f = primal . f . lift++lowerUF :: Functor f => (forall s. RAD s a -> f (RAD s b)) -> a -> f b+lowerUF f = fmap primal . f . lift++lowerFF :: (Functor f, Functor g) => (forall s. f (RAD s a) -> g (RAD s b)) -> f a -> g b+lowerFF f = fmap primal . f . fmap lift+-}++lowerFU :: Functor f => (forall s. f (RAD s a) -> RAD s b) -> f a -> b+lowerFU f = primal . f . fmap lift
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ TODO view
@@ -0,0 +1,15 @@+* Implement the diffMF, etc. functionality from Numeric.FAD++* Allow the type to vary within our RAD data type container, in the same fashion as Numeric.FAD.++ Although, while Pearlmutter and Siskind provided the functionality to permit it in derivative combinator, they provided+ no combinators to convert, for instance, a Dual tag Float to a Dual tag Double, so that extra functionality cannot+ currently be leveraged.++* Implement a reverse-mode cotangent tower.++ Can we just play back the tape in a forward-mode tower?++* Provide the ability to use reverse mode locally on FAD inputs, i.e.++ reverseCheckpoint :: (forall s. RAD s a -> RAD s a) -> FAD t a -> FAD t a
+ rad.cabal view
@@ -0,0 +1,21 @@+Name: rad+Version: 0.1+License: BSD3+License-File: LICENSE+Copyright: Edward Kmett 2010+Author: Edward Kmett 2010+Maintainer: ekmett@gmail.com+Stability: Experimental+Homepage: http://comonad.com/reader/+Synopsis: Reverse Automatic Differentiation.+Description:+ Reverse-Mode Automatic Differentiation via overloading.+ Existential type \"branding\" is used to prevent sensitivity confusion.+Category: Math+Build-Type: Simple+Build-Depends: base >= 4 && < 6,+ data-reify >= 0.5 && < 0.6, + containers >= 0.2 && < 0.3,+ array >= 0.2 && < 0.3+Exposed-Modules: Numeric.RAD+Extra-Source-Files: TODO