{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Numeric.Units.Dimensional.Dynamic
(
AnyQuantity
, DynQuantity
, Demotable
, Promotable
, HasDynamicDimension(..), DynamicDimension(..)
, promoteQuantity, demoteQuantity
, (*~), (/~), invalidQuantity, polydimensionalZero
, AnyUnit
, demoteUnit, promoteUnit, demoteUnit'
, siUnit, anyUnitName
, (*), (/), (^), recip, applyPrefix
) where
import Control.DeepSeq
import Control.Monad
import Data.Data
import Data.ExactPi
import Data.Kind
import Data.Semigroup (Semigroup(..))
import Data.Monoid (Monoid(..))
import GHC.Generics
import Prelude (Eq(..), Num, Fractional, Floating, Show(..), Bool(..), Maybe(..), (.), ($), (++), (&&), id, otherwise, error)
import qualified Prelude as P
import Numeric.Units.Dimensional hiding ((*~), (/~), (*), (/), (^), recip, nroot, siUnit)
import qualified Numeric.Units.Dimensional as Dim
import Numeric.Units.Dimensional.Coercion
import Numeric.Units.Dimensional.UnitNames (UnitName, baseUnitName)
import qualified Numeric.Units.Dimensional.UnitNames.InterchangeNames as I
import qualified Numeric.Units.Dimensional.UnitNames as N
import Numeric.Units.Dimensional.Dimensions.TermLevel (HasDynamicDimension(..), DynamicDimension(..), matchDimensions, isCompatibleWith)
import qualified Numeric.Units.Dimensional.Dimensions.TermLevel as D
class Demotable (q :: Type -> Type) where
demotableOut :: q a -> AnyQuantity a
class Promotable (q :: Type -> Type) where
promotableIn :: AnyQuantity a -> q a
promotableOut :: q a -> DynQuantity a
demoteQuantity :: (Demotable q, Promotable d) => q a -> d a
demoteQuantity = promotableIn . demotableOut
promoteQuantity :: forall a d q.(Promotable q, KnownDimension d) => q a -> Maybe (Quantity d a)
promoteQuantity = promoteQ . promotableOut
where
dim' = dimension (Proxy :: Proxy d)
promoteQ (DynQuantity d v) | d `isCompatibleWith` dim' = Just . Quantity $ v
| otherwise = Nothing
instance (KnownDimension d) => Demotable (Quantity d) where
demotableOut q@(Quantity x) = AnyQuantity (dimension q) x
data AnyQuantity a = AnyQuantity !Dimension' !a
deriving (Eq, Data, Generic, Generic1, Typeable)
instance (Show a) => Show (AnyQuantity a) where
show (AnyQuantity d a) | d == D.dOne = show a
| otherwise = show a ++ " " ++ (show . baseUnitName $ d)
instance HasDynamicDimension (AnyQuantity a) where
instance HasDimension (AnyQuantity a) where
dimension (AnyQuantity d _) = d
instance NFData a => NFData (AnyQuantity a)
instance Promotable AnyQuantity where
promotableIn = id
promotableOut (AnyQuantity d a) = DynQuantity (SomeDimension d) a
instance Demotable AnyQuantity where
demotableOut = id
instance Num a => Semigroup (AnyQuantity a) where
(AnyQuantity d1 a1) <> (AnyQuantity d2 a2) = AnyQuantity (d1 D.* d2) (a1 P.* a2)
instance Num a => Monoid (AnyQuantity a) where
mempty = demoteQuantity (1 Dim.*~ one)
mappend = (Data.Semigroup.<>)
data DynQuantity a = DynQuantity !DynamicDimension a
deriving (Data, Generic, Generic1, Typeable)
instance Eq a => Eq (DynQuantity a) where
(DynQuantity NoDimension _) == (DynQuantity NoDimension _) = True
(DynQuantity NoDimension _) == _ = False
_ == (DynQuantity NoDimension _) = False
(DynQuantity d1 v1) == (DynQuantity d2 v2) = d1 == d2 && v1 == v2
instance NFData a => NFData (DynQuantity a)
instance Show a => Show (DynQuantity a) where
show (DynQuantity NoDimension _) = "invalidQuantity"
show (DynQuantity AnyDimension v) = show v
show (DynQuantity (SomeDimension d) v) = show $ AnyQuantity d v
instance Promotable DynQuantity where
promotableIn (AnyQuantity d a) = DynQuantity (SomeDimension d) a
promotableOut = id
instance HasDynamicDimension (DynQuantity a) where
dynamicDimension (DynQuantity d _) = d
instance Num a => Num (DynQuantity a) where
x + y = liftDQ2 matchDimensions (P.+) x y
x - y = liftDQ2 matchDimensions (P.-) x y
x * y = liftDQ2 (valid2 (D.*)) (P.*) x y
negate = liftDQ id P.negate
abs = liftDQ id P.abs
signum = liftDQ (constant D.dOne) P.signum
fromInteger = demoteQuantity . (Dim.*~ one) . P.fromInteger
instance Fractional a => Fractional (DynQuantity a) where
x / y = liftDQ2 (valid2 (D./)) (P./) x y
recip = liftDQ (valid D.recip) P.recip
fromRational = demoteQuantity . (Dim.*~ one) . P.fromRational
instance Floating a => Floating (DynQuantity a) where
pi = demoteQuantity pi
exp = liftDimensionless P.exp
log = liftDimensionless P.log
sqrt = liftDQ (whenValid $ D.nroot 2) P.sqrt
(**) = liftDQ2 (matchDimensions3 $ SomeDimension D.dOne) (P.**)
logBase = liftDQ2 (matchDimensions3 $ SomeDimension D.dOne) P.logBase
sin = liftDimensionless P.sin
cos = liftDimensionless P.cos
tan = liftDimensionless P.tan
asin = liftDimensionless P.asin
acos = liftDimensionless P.acos
atan = liftDimensionless P.atan
sinh = liftDimensionless P.sinh
cosh = liftDimensionless P.cosh
tanh = liftDimensionless P.tanh
asinh = liftDimensionless P.asinh
acosh = liftDimensionless P.acosh
atanh = liftDimensionless P.atanh
instance Num a => Semigroup (DynQuantity a) where
(<>) = (P.*)
instance Num a => Monoid (DynQuantity a) where
mempty = demoteQuantity (1 Dim.*~ one)
mappend = (Data.Semigroup.<>)
invalidQuantity :: DynQuantity a
invalidQuantity = DynQuantity NoDimension $ error "Attempt to evaluate the value of an invalid quantity."
polydimensionalZero :: (Num a) => DynQuantity a
polydimensionalZero = DynQuantity AnyDimension 0
liftDimensionless :: (a -> a) -> DynQuantity a -> DynQuantity a
liftDimensionless = liftDQ (matchDimensions $ SomeDimension D.dOne)
liftDQ :: (DynamicDimension -> DynamicDimension)
-> (a -> a)
-> DynQuantity a -> DynQuantity a
liftDQ fd fv (DynQuantity d v) = case fd d of
NoDimension -> invalidQuantity
d' -> DynQuantity d' $ fv v
liftDQ2 :: (DynamicDimension -> DynamicDimension -> DynamicDimension)
-> (a -> a -> a)
-> DynQuantity a -> DynQuantity a -> DynQuantity a
liftDQ2 fd fv (DynQuantity d1 v1) (DynQuantity d2 v2) = case fd d1 d2 of
NoDimension -> invalidQuantity
d' -> DynQuantity d' $ fv v1 v2
valid :: (Dimension' -> Dimension') -> DynamicDimension -> DynamicDimension
valid _ AnyDimension = AnyDimension
valid f (SomeDimension d) = SomeDimension (f d)
valid _ NoDimension = NoDimension
whenValid :: (Dimension' -> Maybe Dimension') -> DynamicDimension -> DynamicDimension
whenValid _ AnyDimension = AnyDimension
whenValid f (SomeDimension d) | Just d' <- f d = SomeDimension d'
whenValid _ _ = NoDimension
constant :: Dimension' -> DynamicDimension -> DynamicDimension
constant d AnyDimension = SomeDimension d
constant d (SomeDimension _) = SomeDimension d
constant _ _ = NoDimension
valid2 :: (Dimension' -> Dimension' -> Dimension') -> DynamicDimension -> DynamicDimension -> DynamicDimension
valid2 _ AnyDimension (SomeDimension _) = AnyDimension
valid2 _ (SomeDimension _) AnyDimension = AnyDimension
valid2 _ AnyDimension AnyDimension = AnyDimension
valid2 f (SomeDimension d1) (SomeDimension d2) = SomeDimension (f d1 d2)
valid2 _ _ _ = NoDimension
matchDimensions3 :: DynamicDimension -> DynamicDimension -> DynamicDimension -> DynamicDimension
matchDimensions3 x y z = matchDimensions x (matchDimensions y z)
data AnyUnit = AnyUnit Dimension' (UnitName 'NonMetric) ExactPi
deriving (Generic, Typeable)
instance Show AnyUnit where
show (AnyUnit _ n e) = show n ++ " =def= " ++ show e ++ " of the SI base unit"
instance HasDynamicDimension AnyUnit where
instance HasDimension AnyUnit where
dimension (AnyUnit d _ _) = d
instance I.HasInterchangeName AnyUnit where
interchangeName (AnyUnit _ n _) = I.interchangeName n
instance Semigroup AnyUnit where
(<>) = (Numeric.Units.Dimensional.Dynamic.*)
instance Monoid AnyUnit where
mempty = demoteUnit' one
mappend = (Data.Semigroup.<>)
anyUnitName :: AnyUnit -> UnitName 'NonMetric
anyUnitName (AnyUnit _ n _) = n
siUnit :: Dimension' -> AnyUnit
siUnit d = AnyUnit d (baseUnitName d) 1
demoteUnit :: forall m d a.(KnownDimension d) => Unit m d a -> AnyUnit
demoteUnit u = AnyUnit dim (name $ weaken u) (exactValue u)
where
dim = dimension (Proxy :: Proxy d)
demoteUnit' :: (KnownDimension d) => Unit m d ExactPi -> AnyUnit
demoteUnit' = demoteUnit
promoteUnit :: forall d.(KnownDimension d) => AnyUnit -> Maybe (Unit 'NonMetric d ExactPi)
promoteUnit (AnyUnit dim n e) | dim == dim' = Just $ mkUnitR n e Dim.siUnit
| otherwise = Nothing
where
dim' = dimension (Proxy :: Proxy d)
recip :: AnyUnit -> AnyUnit
recip (AnyUnit d n e) = AnyUnit (D.recip d) (N.nOne N./ n) (P.recip e)
(*) :: AnyUnit -> AnyUnit -> AnyUnit
(AnyUnit d1 n1 e1) * (AnyUnit d2 n2 e2) = AnyUnit (d1 D.* d2) (n1 N.* n2) (e1 P.* e2)
(/) :: AnyUnit -> AnyUnit -> AnyUnit
(AnyUnit d1 n1 e1) / (AnyUnit d2 n2 e2) = AnyUnit (d1 D./ d2) (n1 N./ n2) (e1 P./ e2)
(^) :: (P.Integral a) => AnyUnit -> a -> AnyUnit
(AnyUnit d n e) ^ x = AnyUnit (d D.^ P.fromIntegral x) (n N.^ P.fromIntegral x) (e P.^^ x)
applyPrefix :: N.Prefix -> AnyUnit -> Maybe AnyUnit
applyPrefix p (AnyUnit d n e) = do
n' <- N.strengthen n
let n'' = N.applyPrefix p n'
let e' = (P.fromRational $ N.scaleFactor p) P.* e
return $ AnyUnit d n'' e'
(*~) :: (Floating a, Promotable q) => a -> AnyUnit -> q a
x *~ (AnyUnit d _ e) = promotableIn $ AnyQuantity d (x P.* approximateValue e)
(/~) :: (Floating a, Promotable q) => q a -> AnyUnit -> Maybe a
x /~ (AnyUnit d _ e) = case promotableOut x of
DynQuantity d' x' | d' `isCompatibleWith` d -> Just $ x' P./ approximateValue e
| otherwise -> Nothing