packages feed

aern2-mp-0.2.2.0: src/AERN2/MP/Enclosure.hs

{-# OPTIONS_GHC -Wno-orphans #-}
{-|
    Module      :  AERN2.MP.Enclosure
    Description :  Enclosure operations
    Copyright   :  (c) Michal Konecny
    License     :  BSD3

    Maintainer  :  mikkonecny@gmail.com
    Stability   :  experimental
    Portability :  portable

    Enclosure classes and operations.
-}
module AERN2.MP.Enclosure
(
  IsBall(..), ballFunctionUsingLipschitz
  , IsInterval(..), endpointL, endpointR
  , fromEndpointsAsIntervals, endpointsAsIntervals, endpointLAsInterval, endpointRAsInterval
  , intervalFunctionByEndpoints, intervalFunctionByEndpointsUpDown
  , CanPlusMinus(..), (+-)
  , CanTestContains(..), CanMapInside(..), specCanMapInside
  , CanIntersectAsymmetric(..), CanIntersect
  , CanIntersectBy, CanIntersectSameType
  , CanIntersectCNBy, CanIntersectCNSameType
  , CanUnionAsymmetric(..), CanUnion
  , CanUnionBy, CanUnionSameType
  , CanUnionCNBy, CanUnionCNSameType
  )
where

import MixedTypesNumPrelude
-- import qualified Prelude as P

-- import Control.Arrow

import Test.Hspec
import Test.QuickCheck

import qualified Numeric.CollectErrors as CN

import AERN2.Kleenean
import AERN2.MP.ErrorBound
-- import AERN2.MP.Accuracy

{- ball-specific operations -}

class IsBall t where
  type CentreType t
  centre :: t -> CentreType t
  centreAsBallAndRadius :: t-> (t,ErrorBound)
  centreAsBall :: t -> t
  centreAsBall = fst . centreAsBallAndRadius
  radius :: t -> ErrorBound
  radius = snd . centreAsBallAndRadius
  updateRadius :: (ErrorBound -> ErrorBound) -> (t -> t)
  {-|  When the radius of the ball is implicitly contributed to by imprecision in the centre
     (eg if the centre is a polynomial with inexact coefficients), move all that imprecision
     to the explicit radius, making the centre exact.  This may lose some information,
     but as a ball is equivalent to the original.
     For MPBall this function is pointless because it is equivalent to the identity.  -}
  makeExactCentre :: t -> t
  makeExactCentre v =
    updateRadius (+r) c
    where
    (c, r) = centreAsBallAndRadius v

instance (IsBall t => IsBall (CN t)) where
    type CentreType (CN t) = CN (CentreType t)
    centre = fmap centre
    updateRadius f = fmap (updateRadius f)
    centreAsBallAndRadius = error $ "centreAsBallAndRadius not defined for CN types"

{-|
    Computes a ball function @f@ on the centre and updating the error bound using a Lipschitz constant.
-}
ballFunctionUsingLipschitz ::
  (IsBall t, HasEqCertainly t t)
  =>
  (t -> t) {-^ @fThin@: a version of @f@ that works well on thin balls -} ->
  (t -> ErrorBound) {-^ @fLip@: a Lipschitz function of @f@ over large balls -} ->
  (t -> t) {-^ @f@ on *large* balls -}
ballFunctionUsingLipschitz fThin fLip x
  | r == 0 = fThin c
  | otherwise = updateRadius (+ (fLip x)*r) (fThin c)
  where
  (c, r) = centreAsBallAndRadius x


{- interval-specific operations -}
class IsInterval i where
  type IntervalEndpoint i
  endpoints :: i -> (IntervalEndpoint i, IntervalEndpoint i)
  fromEndpoints :: IntervalEndpoint i -> IntervalEndpoint i -> i

instance (IsInterval t) => (IsInterval (CN t)) where
    type (IntervalEndpoint (CN t)) = CN (IntervalEndpoint t)
    fromEndpoints l u = CN.lift2 fromEndpoints l u
    endpoints = CN.liftPair endpoints

endpointL :: (IsInterval i) => i -> IntervalEndpoint i
endpointL = fst . endpoints

endpointR :: (IsInterval i) => i -> IntervalEndpoint i
endpointR = snd . endpoints

endpointsAsIntervals :: 
  (IsInterval i) => i -> (i,i)
endpointsAsIntervals x = (lI,rI)
  where
  lI = fromEndpoints l l
  rI = fromEndpoints r r
  (l,r) = endpoints x

endpointLAsInterval :: (IsInterval i) => i -> i
endpointLAsInterval = fst . endpointsAsIntervals

endpointRAsInterval :: (IsInterval i) => i -> i
endpointRAsInterval = snd . endpointsAsIntervals

fromEndpointsAsIntervals :: 
  (IsInterval i, CanMinMaxSameType (IntervalEndpoint i)) =>
  i -> i -> i
fromEndpointsAsIntervals l r = 
  fromEndpoints lMP uMP
  where
  lMP = min llMP rlMP
  uMP = max luMP ruMP
  (llMP, luMP) = endpoints l
  (rlMP, ruMP) = endpoints r

{- plusMinus (+-) operator -}

class CanPlusMinus t1 t2 where
  type PlusMinusType t1 t2
  type PlusMinusType t1 t2 = t1
  {-| Operator for constructing or enlarging enclosures such as balls or intervals -}
  plusMinus :: t1 -> t2 -> PlusMinusType t1 t2

infixl 6  +-

{-| Operator for constructing or enlarging enclosures such as balls or intervals -}
(+-) :: (CanPlusMinus t1 t2) => t1 -> t2 -> PlusMinusType t1 t2
(+-) = plusMinus


{-|
    Computes a *monotone* ball function @f@ on intervals using the interval endpoints.
-}
intervalFunctionByEndpoints ::
  (IsInterval t, CanMinMaxSameType (IntervalEndpoint t), HasEqCertainly t t)
  =>
  (t -> t) {-^ @fThin@: a version of @f@ that works well on thin intervals -} ->
  (t -> t) {-^ @f@ on *large* intervals -}
intervalFunctionByEndpoints fThin x
  | l !==! u = fThin l
  | otherwise = fromEndpointsAsIntervals (fThin l) (fThin u)
  where
  (l,u) = endpointsAsIntervals x

{-|
    Computes a *monotone* ball function @f@ on intervals using the interval endpoints.
-}
intervalFunctionByEndpointsUpDown ::
  (IsInterval t)
  =>
  (IntervalEndpoint t -> IntervalEndpoint t) {-^ @fDown@: a version of @f@ working on endpoints, rounded down -} ->
  (IntervalEndpoint t -> IntervalEndpoint t) {-^ @fUp@: a version of @f@ working on endpoints, rounded up -} ->
  (t -> t) {-^ @f@ on intervals rounding *outwards* -}
intervalFunctionByEndpointsUpDown fDown fUp x =
  fromEndpoints (fDown l) (fUp u)
  where
  (l,u) = endpoints x


{- containment -}

class CanTestContains dom e where
  {-| Test if @e@ is inside @dom@. -}
  contains :: dom {-^ @dom@ -} -> e  {-^ @e@ -} -> Bool

class CanMapInside dom e where
  {-| Return some value contained in @dom@.
      The returned value does not have to equal the given @e@
      even if @e@ is already inside @dom@.
      All elements of @dom@ should be covered with roughly the same probability
      when calling this function for evenly distributed @e@'s.

      This function is intended mainly for generating values inside @dom@
      for randomised tests.
  -}
  mapInside :: dom {-^ @dom@ -} -> e  {-^ @e@ -} -> e

specCanMapInside ::
  (CanMapInside d e, CanTestContains d e
  , Arbitrary d, Arbitrary e, Show d, Show e)
  =>
  T d -> T e -> Spec
specCanMapInside (T dName :: T d) (T eName :: T e) =
  it ("CanMapInside " ++ dName ++ " " ++ eName) $ do
    property $
      \ (d :: d) (e :: e) ->
        contains d $ mapInside d e

{- intersection -}

type CanIntersect e1 e2 =
  (CanIntersectAsymmetric e1 e2, CanIntersectAsymmetric e1 e2
  , IntersectionType e1 e2 ~ IntersectionType e2 e1)

{-| A set intersection (usually partial) -}
class CanIntersectAsymmetric e1 e2 where
  type IntersectionType e1 e2
  type IntersectionType e1 e2 = CN e1
  intersect :: e1 -> e2 -> IntersectionType e1 e2

type CanIntersectBy e1 e2 =
  (CanIntersect e1 e2, IntersectionType e1 e2 ~ e1)

type CanIntersectSameType e1 =
  (CanIntersectBy e1 e1)

type CanIntersectCNBy e1 e2 =
  (CanIntersect e1 e2, IntersectionType e1 e2 ~ CN e1)

type CanIntersectCNSameType e1 =
  (CanIntersectCNBy e1 e1)

instance
  CanIntersectAsymmetric Bool Bool
  where
  intersect b1 b2
    | b1 == b2 = cn b1
    | otherwise =
      CN.noValueNumErrorCertain $ CN.NumError "empty Boolean intersection"

instance
  CanIntersectAsymmetric Kleenean Kleenean
  where
  intersect CertainTrue CertainFalse =
    CN.noValueNumErrorCertain $ CN.NumError "empty Kleenean intersection"
  intersect CertainFalse CertainTrue =
    CN.noValueNumErrorCertain $ CN.NumError "empty Kleenean intersection"
  intersect TrueOrFalse k2 = cn k2
  intersect k1 _ = cn k1

instance 
  (CanIntersectAsymmetric a b, IntersectionType a b ~ CN c)
  =>
  CanIntersectAsymmetric (CN a) (CN b) 
  where
  type IntersectionType (CN a) (CN b) = IntersectionType a b
  intersect = CN.lift2CN intersect

instance
  (CanIntersectAsymmetric (CN Bool) (CN b))
  =>
  CanIntersectAsymmetric Bool (CN b)
  where
  type IntersectionType Bool (CN b) = IntersectionType (CN Bool) (CN b)
  intersect b1 = intersect (cn b1)

instance
  (CanIntersectAsymmetric (CN a) (CN Bool))
  =>
  CanIntersectAsymmetric (CN a) Bool
  where
  type IntersectionType (CN a) Bool = IntersectionType (CN a) (CN Bool)
  intersect b1 b2 = intersect b1 (cn b2)

instance
  (CanIntersectAsymmetric (CN Kleenean) (CN b))
  =>
  CanIntersectAsymmetric Kleenean (CN b)
  where
  type IntersectionType Kleenean (CN b) = IntersectionType (CN Kleenean) (CN b)
  intersect k1 = intersect (cn k1)

instance
  (CanIntersectAsymmetric (CN a) (CN Kleenean))
  =>
  CanIntersectAsymmetric (CN a) Kleenean
  where
  type IntersectionType (CN a) Kleenean = IntersectionType (CN a) (CN Kleenean)
  intersect k1 k2 = intersect k1 (cn k2)

{- set union -}

type CanUnion e1 e2 =
  (CanUnionAsymmetric e1 e2, CanUnionAsymmetric e1 e2
  , UnionType e1 e2 ~ UnionType e2 e1)

{-| A set union (usually partial) -}
class CanUnionAsymmetric e1 e2 where
  type UnionType e1 e2
  type UnionType e1 e2 = CN e1
  union :: e1 -> e2 -> UnionType e1 e2

type CanUnionBy e1 e2 =
  (CanUnion e1 e2, UnionType e1 e2 ~ e1)

type CanUnionSameType e1 =
  (CanUnionBy e1 e1)

type CanUnionCNBy e1 e2 =
  (CanUnion e1 e2, UnionType e1 e2 ~ CN e1)

type CanUnionCNSameType e1 =
  (CanUnionCNBy e1 e1)

instance 
  (CanUnionAsymmetric a b, UnionType a b ~ CN c)
  =>
  CanUnionAsymmetric (CN a) (CN b) 
  where
  type UnionType (CN a) (CN b) = UnionType a b
  union = CN.lift2CN union

instance (CanUnionSameType t, CN.CanTakeCNErrors t) =>
  HasIfThenElse Kleenean t
  where
  type IfThenElseType Kleenean t = t
  ifThenElse CertainTrue e1 _  = e1
  ifThenElse CertainFalse _ e2 = e2
  ifThenElse TrueOrFalse e1 e2 = e1 `union` e2