Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from expipiplus1/converge
Add Converge class
- Loading branch information
Showing
3 changed files
with
201 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE RankNTypes #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
|
||
-- | The Converge type class. | ||
module Data.CReal.Converge | ||
( Converge(..) | ||
) where | ||
|
||
import Control.Arrow ((&&&)) | ||
import Data.Coerce (coerce) | ||
import Data.CReal.Internal (CReal(..), atPrecision, crMemoize) | ||
import Data.Function (on) | ||
import Data.Proxy (Proxy) | ||
import GHC.TypeLits (someNatVal, SomeNat(..)) | ||
|
||
-- $setup | ||
-- >>> :set -XFlexibleContexts | ||
-- >>> import Data.CReal.Internal | ||
|
||
-- | If a type is an instance of Converge then it represents a stream of values | ||
-- which are increasingly accurate approximations of a desired value | ||
class Converge a where | ||
-- | The type of the value the stream converges to. | ||
type Element a | ||
|
||
-- | 'converge' is a function that returns the value the stream is converging | ||
-- to. If given a stream which doens't converge to a single value then | ||
-- 'converge' will not terminate. | ||
-- | ||
-- If the stream is empty then it should return nothing. | ||
-- | ||
-- >>> let initialGuess = 1 :: Double | ||
-- >>> let improve x = (x + 121 / x) / 2 | ||
-- >>> converge (iterate improve initialGuess) | ||
-- Just 11.0 | ||
-- | ||
-- >>> converge [] :: Maybe [Int] | ||
-- Nothing | ||
converge :: a -> Maybe (Element a) | ||
|
||
-- 'convergeErr' is a function that returns the value the stream is | ||
-- converging to. It also takes a function err which returns a value which | ||
-- varies monotonically with the error of the value in the stream. This can | ||
-- be used to ensure that when 'convergeErr' terminates when given a | ||
-- non-converging stream or a stream which enters a cycle close to the | ||
-- solution. See the documentation for the CReal instance for a caveat with | ||
-- that implementation. | ||
-- | ||
-- It's often the case that streams generated with approximation | ||
-- functions such as Newton's method will generate worse approximations for | ||
-- some number of steps until they find the "zone of convergence". For these | ||
-- cases it's necessary to drop some values of the stream before handing it | ||
-- to convergeErr. | ||
-- | ||
-- For example trying to find the root of the following funciton @f@ with a | ||
-- poor choice of starting point. Although this doesn't find the root, it | ||
-- doesn't fail to terminate. | ||
-- >>> let f x = x ^ 3 - 2 * x + 2 | ||
-- >>> let f' x = 3 * x ^ 2 - 2 | ||
-- >>> let initialGuess = 0.1 | ||
-- >>> let improve x = x - f x / f' x | ||
-- >>> let err x = abs (f x) | ||
-- >>> convergeErr err (iterate improve initialGuess) | ||
-- Just 0.1 | ||
convergeErr :: Ord (Element a) => (Element a -> Element a) -> a -> Maybe (Element a) | ||
|
||
-- | Every list of equatable values is an instance of 'Converge'. 'converge' | ||
-- returns the first element which is equal to the succeeding element in the | ||
-- list. If the list ends before the sequence converges the last value is | ||
-- returned. | ||
instance {-# OVERLAPPABLE #-} Eq a => Converge [a] where | ||
type Element [a] = a | ||
|
||
converge = lastMay . takeWhilePairwise (/=) | ||
{-# INLINE converge #-} | ||
|
||
convergeErr err xs = fmap snd . lastMay . takeWhilePairwise ((>) `on` fst) $ es | ||
where es = (err &&& id) <$> xs | ||
{-# INLINE convergeErr #-} | ||
|
||
-- | The overlapping instance for @'CReal' n@ has a slightly different | ||
-- behavior. The instance for 'Eq' will cause 'converge' to return a value when | ||
-- the list converges to within 2^-n (due to the 'Eq' instance for @'CReal' n@) | ||
-- despite the precision the value is requested at by the surrounding | ||
-- computation. This instance will return a value approximated to the correct | ||
-- precision. | ||
-- | ||
-- It's important to note when the error function reaches zero this function | ||
-- behaves like 'converge' as it's not possible to determine the precision at | ||
-- which the error function should be evaluated at. | ||
-- | ||
-- Find where log x = π using Newton's method | ||
-- >>> let initialGuess = 1 | ||
-- >>> let improve x = x - x * (log x - pi) | ||
-- >>> let Just y = converge (iterate improve initialGuess) | ||
-- >>> showAtPrecision 10 y | ||
-- "23.1406" | ||
-- >>> showAtPrecision 50 y | ||
-- "23.1406926327792686" | ||
instance {-# OVERLAPPING #-} Converge [CReal n] where | ||
type Element [CReal n] = CReal n | ||
|
||
converge [] = Nothing | ||
converge xs = | ||
Just $ crMemoize (\p -> | ||
case someNatVal (toInteger p) of | ||
Nothing -> error "Data.CReal.Converge p should be non negative" | ||
Just (SomeNat (_ :: Proxy p')) -> | ||
let modifyPrecision = coerce :: [CReal n] -> [CReal p'] | ||
in (last . takeWhilePairwise (/=) . modifyPrecision $ xs) `atPrecision` p) | ||
{-# INLINE converge #-} | ||
|
||
convergeErr _ [] = Nothing | ||
convergeErr err xs = | ||
Just $ crMemoize (\p -> | ||
case someNatVal (toInteger p) of | ||
Nothing -> error "Data.CReal.Converge p should be non negative" | ||
Just (SomeNat (_ :: Proxy p')) -> | ||
let modifyPrecision = coerce :: [CReal n] -> [CReal p'] | ||
modifyFunPrecision = coerce :: (CReal n -> CReal n) -> CReal p' -> CReal p' | ||
es = (modifyFunPrecision err &&& id) <$> modifyPrecision xs | ||
continue (e1, x1) (e2, x2) = if e1 == 0 then x1 /= x2 else e1 > e2 | ||
in (snd . last . takeWhilePairwise continue $ es) `atPrecision` p) | ||
{-# INLINE convergeErr #-} | ||
|
||
takeWhilePairwise :: (a -> a -> Bool) -> [a] -> [a] | ||
takeWhilePairwise p (x1:x2:xs) = if x1 `p` x2 | ||
then x1 : takeWhilePairwise p (x2:xs) | ||
else [x1] | ||
takeWhilePairwise _ xs = xs | ||
|
||
lastMay :: [a] -> Maybe a | ||
lastMay [] = Nothing | ||
lastMay xs = Just (last xs) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters