dimensional-1.2: tests/Numeric/Units/Dimensional/DynamicSpec.hs
module Numeric.Units.Dimensional.DynamicSpec where
import Numeric.Units.Dimensional.Prelude
import Numeric.Units.Dimensional.Dynamic hiding ((*),(/),(^),(*~),(/~), recip)
import Numeric.Units.Dimensional.Dimensions.TermLevel (hasSomeDimension)
import qualified Numeric.Units.Dimensional.Dynamic as Dyn
import qualified Prelude as P
import Test.Hspec
import Test.QuickCheck
spec :: Spec
spec = do
describe "Dynamic quantity promotion and demotion" $ do
it "round-trips through AnyQuantity" $ property $
\x -> let x' = x *~ kilo newton
x'' = demoteQuantity x' :: AnyQuantity Double
in Just x' == promoteQuantity x''
it "round-trips through DynQuantity" $ property $
\x -> let x' = x *~ micro watt
x'' = demoteQuantity x' :: DynQuantity Rational
in Just x' == promoteQuantity x''
it "round-trips through AnyQuantity then DynQuantity" $ property $
\x -> let x' = x *~ gram
x'' = demoteQuantity x' :: AnyQuantity Double
x''' = demoteQuantity x'' :: DynQuantity Double
in Just x' == promoteQuantity x'''
it "doesn't promote invalid quantities" $ do
(promoteQuantity invalidQuantity :: Maybe (Length Double)) `shouldBe` Nothing
it "doesn't promote AnyQuantity to the wrong dimension" $ do
let x = 12.3 *~ meter
x' = demoteQuantity x :: AnyQuantity Double
(promoteQuantity x' :: Maybe (Mass Double)) `shouldBe` Nothing
it "doesn't promote DynQuantity to the wrong dimension" $ do
let x = 12.3 *~ mole
x' = demoteQuantity x :: DynQuantity Double
(promoteQuantity x' :: Maybe (Time Double)) `shouldBe` Nothing
it "properly combines with dynamic units" $ do
let meter' = demoteUnit' meter
(promoteQuantity (139.4 Dyn.*~ meter' :: AnyQuantity Double)) `shouldBe` Just (139.4 *~ meter)
it "properly eliminates dynamic units" $ do
let ampere' = demoteUnit' ampere
i = demoteQuantity $ 47 *~ ampere :: AnyQuantity Double
i Dyn./~ ampere' `shouldBe` Just 47
it "doesn't eliminate dynamic units of the wrong dimension" $ do
let ampere' = demoteUnit' ampere
i = demoteQuantity $ 47 *~ joule :: AnyQuantity Double
i Dyn./~ ampere' `shouldBe` Nothing
describe "DynQuantity arithmetic" $ do
-- declare some static quantities and their dynamic counterparts for arithmetic tests
let x1 = 12.3 *~ meter
x2 = (-7.9) *~ meter
a = 93 *~ square (kilo meter)
m = 147 *~ kilo gram
t = 14.9 *~ second
f = 87.2 *~ milli newton
phi = 1.61803398875 *~ one
x1' = demoteQuantity x1 :: DynQuantity Double
x2' = demoteQuantity x2 :: DynQuantity Double
a' = demoteQuantity a :: DynQuantity Double
m' = demoteQuantity m :: DynQuantity Double
t' = demoteQuantity t :: DynQuantity Double
f' = demoteQuantity f :: DynQuantity Double
phi' = demoteQuantity phi :: DynQuantity Double
context "Num instance" $ do
it "matches static addition" $ do
(x1' P.+ x2') `shouldBe` demoteQuantity (x1 + x2)
it "allows addition with polydimensional zero" $ do
(t' P.+ polydimensionalZero) `shouldBe` t'
(polydimensionalZero P.+ t') `shouldBe` t'
(polydimensionalZero P.+ polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "propagates witnesses to zero during addition" $ do
-- We want to test that the witness for polymorphic zero was actually added to the other addend.
-- The reason for this property is that if the other addend is some element of the underlying type
-- which can't act as a divisor (such as a propagating nAn), then we want that information to still
-- be around when we go to promote the result.
let nan = 0 P./ 0 :: Double
x = demoteQuantity $ nan *~ meter
Just y = promoteQuantity (polydimensionalZero P.+ x) :: Maybe (Length Double)
(y /~ meter) `shouldSatisfy` P.isNaN
it "matches static subtraction" $ do
(x2' P.- x1') `shouldBe` demoteQuantity (x2 - x1)
it "allows subtraction with polydimensional zero" $ do
(m' P.- polydimensionalZero) `shouldBe` m'
(polydimensionalZero P.- m') `shouldBe` (P.negate m')
(polydimensionalZero P.- polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "matches static multiplication" $ do
promoteQuantity (x1' P.* f') `shouldBe` Just (x1 * f)
it "allows multiplication with polydimensional zero" $ do
(f' P.* polydimensionalZero) `shouldBe` polydimensionalZero
(polydimensionalZero P.* m') `shouldBe` polydimensionalZero
(polydimensionalZero P.* polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "matches static negation" $ do
(P.negate m') `shouldBe` demoteQuantity (negate m)
it "negates polydimensional zero" $ do
(P.negate polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "matches static absolute value" $ do
(P.abs x2') `shouldBe` demoteQuantity (abs x2)
it "takes absolute value of polydimensional zero" $ do
(P.abs polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "matches static signum" $ do
(P.signum x1') `shouldBe` demoteQuantity (signum x1)
(P.signum x2') `shouldBe` demoteQuantity (signum x2)
it "takes signum of polydimensional zero" $ do
(P.signum polydimensionalZero) `shouldBe` demoteQuantity (_0 :: Dimensionless Double)
it "implements fromInteger with dimensionless result" $ do
(P.fromInteger 7 :: DynQuantity Double) `shouldBe` demoteQuantity _7
context "Fractional instance" $ do
it "matches static division" $ do
((f' P.* x1') P./ t') `shouldBe` demoteQuantity ((f * x1) / t)
it "matches static reciprocal" $ do
(P.recip m') `shouldBe` demoteQuantity (recip m)
it "implements fromRational with dimensionless result" $ do
let pi' = 22 P./ 7 :: Rational
(P.fromRational pi' :: DynQuantity Rational) `shouldBe` demoteQuantity (pi' *~ one)
it "permits polydimensional zero as a dividend" $ do
(polydimensionalZero P./ m') `shouldBe` polydimensionalZero
it "propagates witnesses to zero during division" $ do
-- We want to test that the witness for polymorphic zero was actually divided by the divisor.
-- The reason for this property is that if the divisor is itself zero (but not polydimensionalZero),
-- or some other element of the underlying type which can't act as a divisor (such as a propagating nAn),
-- then we want that information to still be around when we go to promote the result.
let nan = 0 P./ 0 :: Double
x = demoteQuantity $ nan *~ meter
y = polydimensionalZero P./ x
Just y' = promoteQuantity y :: Maybe (Length Double)
(y' /~ meter) `shouldSatisfy` P.isNaN
context "Floating instance" $ do
it "implements dimensionless pi" $ do
(P.pi :: DynQuantity Double) `shouldBe` demoteQuantity pi
it "implements dimensionless sin" $ do
-- this will serve as a test for all the single-argument dimensionless functions
(P.sin phi') `shouldBe` demoteQuantity (sin phi)
it "rejects non-dimensionless arguments to sin" $ do
(P.sin m') `shouldBe` invalidQuantity
it "implements dimensionless sin of polydimensional zero" $ do
(P.sin polydimensionalZero) `shouldBe` (0 :: DynQuantity Double)
it "matches static square root" $ do
(P.sqrt a') `shouldBe` demoteQuantity (sqrt a)
it "rejects arguments to square root with non-square dimensions" $ do
(P.sqrt f') `shouldNotSatisfy` hasSomeDimension
it "takes the square root of polydimensional zero" $ do
(P.sqrt polydimensionalZero) `shouldBe` (polydimensionalZero :: DynQuantity Double)
it "matches static dimensionless exponentiation" $ do
(phi' P.** phi') `shouldBe` demoteQuantity (phi ** phi)
it "rejects non-dimensionless arguments to dimensionless exponentiation" $ do
(phi' P.** m') `shouldNotSatisfy` hasSomeDimension
(x1' P.** phi') `shouldNotSatisfy` hasSomeDimension
it "matches static logBase" $ do
(P.logBase 10 phi') `shouldBe` demoteQuantity (logBase (10 *~ one) phi)
it "rejects non-dimensionless arguments to logBase" $ do
(P.logBase 10 x1') `shouldNotSatisfy` hasSomeDimension
(P.logBase x1' 10) `shouldNotSatisfy` hasSomeDimension
describe "Dynamic units" $ do
describe "Promotion and demotion" $ do
return ()
describe "Arithmetic" $ do
return ()