mixed-types-num-0.5.0.0: src/Numeric/MixedTypes/Elementary.hs
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-|
Module : Numeric.MixedType.Elementary
Description : Bottom-up typed pi, sqrt, cos, etc
Copyright : (c) Michal Konecny
License : BSD3
Maintainer : mikkonecny@gmail.com
Stability : experimental
Portability : portable
-}
module Numeric.MixedTypes.Elementary
(
-- * Square root
CanSqrt(..), CanSqrtSameType, specCanSqrtReal
-- * Exp
, CanExp(..), CanExpSameType, specCanExpReal
-- * Log
, CanLog(..), CanLogSameType, specCanLogReal
, powUsingExpLog
-- * Sine and cosine
, CanSinCos(..), CanSinCosSameType, specCanSinCosReal
, approxPi
)
where
import Numeric.MixedTypes.PreludeHiding
import qualified Prelude as P
import Text.Printf
-- import qualified Data.List as List
import Test.Hspec
import Test.QuickCheck
import Numeric.CollectErrors ( CN )
import qualified Numeric.CollectErrors as CN
import Numeric.MixedTypes.Literals
import Numeric.MixedTypes.Bool
import Numeric.MixedTypes.Eq
import Numeric.MixedTypes.Ord
import Numeric.MixedTypes.MinMaxAbs
import Numeric.MixedTypes.AddSub
import Numeric.MixedTypes.Ring
import Numeric.MixedTypes.Field
import Numeric.MixedTypes.Power
-- import Numeric.MixedTypes.Round
import Utils.Test.EnforceRange
{---- sqrt -----}
{-|
A replacement for Prelude's `P.sqrt`. If @Floating t@,
then one can use the default implementation to mirror Prelude's @sqrt@.
-}
class CanSqrt t where
type SqrtType t
type SqrtType t = t -- default
sqrt :: t -> SqrtType t
default sqrt :: (SqrtType t ~ t, P.Floating t) => t -> SqrtType t
sqrt = P.sqrt
type CanSqrtSameType t = (CanSqrt t, SqrtType t ~ t)
{-|
HSpec properties that each implementation of CanSqrt should satisfy.
-}
specCanSqrtReal ::
_ => T t -> Spec
specCanSqrtReal (T typeName :: T t) =
describe (printf "CanSqrt %s" typeName) $ do
it "sqrt(x) >= 0" $ do
property $ \ (x :: t) ->
isCertainlyNonNegative x ==>
(sqrt x) ?>=?$ 0
it "sqrt(x)^2 = x" $ do
property $ \ (x :: t) ->
isCertainlyNonNegative x ==>
(sqrt x)^2 ?==?$ x
where
infix 4 ?==?$
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
infix 4 ?>=?$
(?>=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?>=?$) = printArgsIfFails2 "?>=?" (?>=?)
{-
Instances for Integer, Rational etc need an algebraic real or exact real type.
Such type is not provided in this package. See eg aern2-real.
-}
instance CanSqrt Double -- not exact, will not pass the tests
instance
(CanSqrt a, CanTestPosNeg a, CanMinMaxThis a Integer)
=>
CanSqrt (CN a)
where
type SqrtType (CN a) = CN (SqrtType a)
sqrt x
| isCertainlyNonNegative x = CN.lift sqrt x
| isCertainlyNegative x = CN.noValueNumErrorCertain err
| otherwise = CN.prependErrorPotential err $ CN.lift sqrt $ max x 0
where
err :: CN.NumError
err = CN.OutOfDomain "negative sqrt argument"
{---- exp -----}
{-|
A replacement for Prelude's `P.exp`. If @Floating t@,
then one can use the default implementation to mirror Prelude's @exp@.
-}
class CanExp t where
type ExpType t
type ExpType t = t -- default
exp :: t -> ExpType t
default exp :: (ExpType t ~ t, P.Floating t) => t -> ExpType t
exp = P.exp
type CanExpSameType t = (CanExp t, ExpType t ~ t)
{-|
HSpec properties that each implementation of CanExp should satisfy.
-}
specCanExpReal ::
_ => T t -> Spec
specCanExpReal (T typeName :: T t) =
describe (printf "CanExp %s" typeName) $ do
it "exp(x) >= 0" $ do
property $ \ (x_ :: t) ->
let x = enforceRange (Just (-100000), Just 100000) x_ in
exp x ?>=?$ 0
it "exp(-x) == 1/(exp x)" $ do
property $ \ (x_ :: t) ->
let x = enforceRange (Just (-100000), Just 100000) x_ in
let ex = exp x in
(ex !>! 0) ==>
(exp (-x)) ?==?$ 1/ex
it "exp(x+y) = exp(x)*exp(y)" $ do
property $ \ (x_ :: t) (y_ :: t) ->
let x = enforceRange (Just (-100000), Just 100000) x_ in
let y = enforceRange (Just (-100000), Just 100000) y_ in
(exp $ x + y) ?==?$ (exp x) * (exp y)
where
infix 4 ?==?$
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
infix 4 ?>=?$
(?>=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?>=?$) = printArgsIfFails2 "?>=?" (?>=?)
{-
Instances for Integer, Rational etc need an algebraic real or exact real type.
Such type is not provided in this package. See eg aern2-real.
-}
instance CanExp Double -- not exact, will not pass the tests
instance
(CanExp a) => CanExp (CN a)
where
type ExpType (CN a) = CN (ExpType a)
exp = CN.lift exp
{---- log -----}
{-|
A replacement for Prelude's `P.log`. If @Floating t@,
then one can use the default implementation to mirror Prelude's @log@.
-}
class CanLog t where
type LogType t
type LogType t = t -- default
log :: t -> LogType t
default log :: (LogType t ~ t, P.Floating t) => t -> LogType t
log = P.log
type CanLogSameType t = (CanLog t, LogType t ~ t)
{-|
HSpec properties that each implementation of CanLog should satisfy.
-}
specCanLogReal ::
_ => T t -> Spec
specCanLogReal (T typeName :: T t) =
describe (printf "CanLog %s" typeName) $ do
it "log(1/x) == -(log x)" $ do
property $ \ (x_ :: t) ->
let x = enforceRange (Just 0, Nothing) x_ in
x !>! 0 && (1/x) !>! 0 ==>
log (1/x) ?==?$ -(log x)
it "log(x*y) = log(x)+log(y)" $ do
property $ \ (x_ :: t) (y_ :: t) ->
let x = enforceRange (Just 0, Nothing) x_ in
let y = enforceRange (Just 0, Nothing) y_ in
x !>! 0 && y !>! 0 && x*y !>! 0 ==>
(log $ x * y) ?==?$ (log x) + (log y)
it "log(exp x) == x" $ do
property $ \ (x_ :: t) ->
let x = enforceRange (Just (-1000), Just 10000) x_ in
let ex = exp x in
(ex !>! 0) ==>
log ex ?==?$ x
where
infix 4 ?==?$
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
{-
Instances for Integer, Rational etc need an algebraic real or exact real type.
Such type is not provided in this package. See eg aern2-real.
-}
instance CanLog Double -- not exact, will not pass the tests
instance
(CanLog a, CanTestPosNeg a)
=>
CanLog (CN a)
where
type LogType (CN a) = CN (LogType a)
log x
| isCertainlyPositive x = logx
| isCertainlyNonPositive x = CN.noValueNumErrorCertain err
| otherwise = CN.noValueNumErrorPotential err
where
logx = CN.lift log x
err :: CN.NumError
err = CN.OutOfDomain "log argument not positive"
powUsingExpLog ::
(CanLogSameType t,
CanExpSameType t,
CanMulSameType t,
CanTestInteger t,
CanTestZero t,
CanRecipSameType t)
=>
t -> t -> t -> t
powUsingExpLog one b e =
case certainlyIntegerGetIt e of
Just n ->
powUsingMulRecip one b n
Nothing ->
exp ((log b) * (e))
{---- sine and cosine -----}
{-|
A replacement for Prelude's `P.cos` and `P.sin`. If @Floating t@,
then one can use the default implementation to mirror Prelude's @sin@, @cos@.
-}
class CanSinCos t where
type SinCosType t
type SinCosType t = t -- default
cos :: t -> SinCosType t
default cos :: (SinCosType t ~ t, P.Floating t) => t -> SinCosType t
cos = P.cos
sin :: t -> SinCosType t
default sin :: (SinCosType t ~ t, P.Floating t) => t -> SinCosType t
sin = P.sin
type CanSinCosSameType t = (CanSinCos t, SinCosType t ~ t)
{-|
HSpec properties that each implementation of CanSinCos should satisfy.
Derived partially from
http://math.stackexchange.com/questions/1303044/axiomatic-definition-of-sin-and-cos
-}
specCanSinCosReal ::
_ => T t -> Spec
specCanSinCosReal (T typeName :: T t) =
describe (printf "CanSinCos %s" typeName) $ do
it "-1 <= sin(x) <= 1" $ do
property $ \ (x :: t) ->
(-1) ?<=?$ (sin x) .&&. (sin x) ?<=?$ 1
it "-1 <= cos(x) <= 1" $ do
property $ \ (x :: t) ->
(-1) ?<=?$ (cos x) .&&. (cos x) ?<=?$ 1
it "cos(x)^2 + sin(x)^2 = 1" $ do
property $ \ (x :: t) ->
(sin x)^2 + (cos x)^2 ?==?$ 1
it "sin(x-y) = sin(x)cos(y) - cos(x)sin(y)" $ do
property $ \ (x :: t) (y :: t) ->
(sin $ x - y) ?==?$ (sin x)*(cos y) - (cos x)*(sin y)
it "cos(x-y) = cos(x)cos(y) + sin(x)sin(y)" $ do
property $ \ (x :: t) (y :: t) ->
(cos $ x - y) ?==?$ (cos x)*(cos y) + (sin x)*(sin y)
it "sin(x) < x < tan(x) for x in [0,pi/2]" $ do
property $ \ (x :: t) ->
x !>=! 0 && x !<=! 1.57 && (cos x) !>! 0 ==>
(sin x) ?<=?$ x .&&. (x) ?<=?$ (sin x)/(cos x)
where
infix 4 ?==?$
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
infix 4 ?<=?$
(?<=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?<=?$) = printArgsIfFails2 "?<=?" (?<=?)
{-
Instances for Integer, Rational etc need an algebraic real or exact real type.
Such type is not provided in this package. See eg aern2-real.
-}
instance CanSinCos Double -- not exact, will not pass the tests
instance
(CanSinCos a) => CanSinCos (CN a)
where
type SinCosType (CN a) = CN (SinCosType a)
sin = CN.lift sin
cos = CN.lift cos
{-|
Approximate pi, synonym for Prelude's `P.pi`.
We do not define (exect) @pi@ in this package as we have no type
that can represent it exactly.
-}
approxPi :: (P.Floating t) => t
approxPi = P.pi