isotope-0.3.3.0: test/Isotope/BaseSpec.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleInstances #-}
module Isotope.BaseSpec (spec) where
import Isotope
import Test.Hspec
import Test.QuickCheck
import Control.Monad
import Data.Char
import Data.List
spec :: Spec
spec = do
describe "ElementSymbol" $ do
it "should not have more than two characters" $
((<= 2) . length . show) <$> elementSymbolList `shouldSatisfy` and
it "second character of an ElementSymbol should not be upper case" $
(\x -> length x /= 2 || (isLower . last) x) . show <$>
elementSymbolList `shouldSatisfy` and
describe "lookupElement" .
it "should not contain duplicate elements" $
lookupElement <$> elementSymbolList `shouldSatisfy` allUnique
describe "elementName" $ do
it "should not be an empty string" $
elementName <$> elementSymbolList `shouldSatisfy` notElem ""
it "should not start with a capital letter" $
isLower . head . elementName <$> elementSymbolList `shouldSatisfy` and
it "element names should not be duplicated" $
elementName <$> elementSymbolList `shouldSatisfy` allUnique
describe "atomicNumber" $ do
it "should be between 1 and 92" $
(\x -> x >= 1 && x <= 92) . atomicNumber <$>
elementSymbolList `shouldSatisfy` and
it "should not have duplicated atomic numbers" $
atomicNumber <$> elementSymbolList `shouldSatisfy` allUnique
describe "isotopes" .
it "should not have duplicate isotopes" $
isotopes <$> elementSymbolList `shouldSatisfy` allUnique
describe "mostAbundantIsotope" .
it "C should be C12 (six protons and six neutrons)" $
(nucleons . mostAbundantIsotope) C `shouldBe` (6, 6)
describe "selectIsotope" .
it "calling fuction with the arguments C and 12 should select C12" $
nucleons (selectIsotope C 12) `shouldBe` (6, 6)
describe "monoisotopicMass" .
it "calling function with C should be 12.0" $
monoisotopicMass C `shouldBe` MonoisotopicMass 12.0
describe "nominalMass" .
it "calling function with C should return 12" $
nominalMass C `shouldBe` NominalMass 12
describe "isotopicMasses" .
it "H should return a list containing 1.007... and 2.014...." $
isotopicMasses H `shouldBe` IsotopicMass <$> [1.00782503223, 2.01410177812]
describe "integerMasses" $ do
it "calling function with H should return [1, 2]" $
integerMasses H `shouldBe` [1, 2]
it "proton number should be equal to atomic number" $
all protonNumEqAtomicNum elementSymbolList
describe "averageMass" .
it "calling function with C should return 12.0107" $
withinTolerance (getAverageMass (averageMass C)) 12.0107 0.0001 `shouldBe` True
describe "isotopicAbundances" $ do
it "calling function with C should return [0.9893, 0.0107]" $
isotopicAbundances C `shouldBe` IsotopicAbundance <$> [0.9893, 0.0107]
it "sum of isotopic abundances for an element should equal 1" $
(\sym -> withinTolerance (sum (getIsotopicAbundance <$> isotopicAbundances sym)) 1 0.0001) <$>
elementSymbolList `shouldSatisfy` and
describe "renderFormula for ElementalComposition" $ do
it "[ele|C6H6O|] should be \"C6H6O\"" $
renderFormula [ele|C6H6O|] `shouldBe` "C6H6O"
it "[ele|CCl4|] should be \"CCl4\"" $
renderFormula [ele|CCl4|] `shouldBe` "CCl4"
it "[ele|H2O4S|] should be \"H2O4S\"" $
renderFormula [ele|H2O4S|] `shouldBe` "H2O4S"
describe "renderFormula for MolecularFormula" $ do
it "[mol|C6H6O|] should be \"C6H6O\"" $
renderFormula [mol|C6H6O|] `shouldBe` "C6H6O"
it "[mol|CCl4|] should be \"CCl4\"" $
renderFormula [mol|CCl4|] `shouldBe` "CCl4"
it "[mol|H2O4S|] should be \"H2O4S\"" $
renderFormula [mol|H2O4S|] `shouldBe` "H2O4S"
describe "renderFormula for CondensedFormula" $ do
it "[con|C6H6O|] should be \"C6H6O\"" $
renderFormula [con|C6H6O|] `shouldBe` "C6H6O"
it "[con|N(CH3)3|] should be \"N(CH3)3\"" $
renderFormula [con|N(CH3)3|] `shouldBe` "N(CH3)3"
it "[con|C6H5OH|] should be \"C6H5OH\"" $
renderFormula [con|C6H5OH|] `shouldBe` "C6H5OH"
describe "renderFormula for EmpiricalFormula" $ do
it "[emp|C6H6O|] should be \"C6H6O\"" $
renderFormula [emp|C6H6O|] `shouldBe` "C6H6O"
it "[emp|CCl4|] should be \"CCl4\"" $
renderFormula [emp|CCl4|] `shouldBe` "CCl4"
it "[emp|H2O4S|] should be \"H2O4S\"" $
renderFormula [emp|H2O4S|] `shouldBe` "H2O4S"
describe "ToElementalComposition - ElementalComposition instance" $ do
it "toElementalComposition" . property $
\ec -> toElementalComposition ec == (ec :: ElementalComposition)
it "monoisotopic mass of ethanol" $
withinTolerance (getMonoisotopicMass (monoisotopicMass [ele|C2H6O|])) 46.04186 0.0001
`shouldBe` True
it "average mass of ethanol" $
withinTolerance (getAverageMass (averageMass [ele|C2H6O|])) 46.06844 0.0001
`shouldBe` True
it "nominalMass mass of ethanol" $
nominalMass [ele|C2H6O|] `shouldBe` NominalMass 46
describe "mkElementalComposition" $ do
it "zero is filtered out" $
mkElementalComposition [(C, 0), (H, 0), (O, 0)] `shouldBe` emptyFormula
it "should give the correct formula" $
mkElementalComposition [(C, 2), (H, 6), (O, 1)] `shouldBe` [ele|C2H6O|]
describe "ToElementalComposition - ElementSymbol instance" .
it "monoisotopicMass" . property $
\sym -> monoisotopicMass sym == monoisotopicMass (mkElementalComposition [(sym, 1)])
describe "Monoid instance for MolecularFormula" $ do
it "associativity" . property $
\a b c -> (a |+| b) |+| c == a |+| (b |+| c :: MolecularFormula)
it "right identity" . property $
\a -> (a :: MolecularFormula) |+| emptyFormula == a
it "left identity" . property $
\a -> (emptyFormula :: MolecularFormula) |+| a == a
describe "Addition of molecular formulae is commutative" .
it "commutative" . property $
\a b -> (a |+| b) == (b |+| a :: MolecularFormula)
describe "properties of |+|, |*| and |-|)" $ do
it "a |-| a = emptyFormula" . property $
\a -> a |-| a == (emptyFormula :: MolecularFormula)
it "0 |*| a == emptyFormula" . property $
\a -> a |*| 0 == (emptyFormula :: MolecularFormula)
it "a |+| a == 2 |*| a" . property $
\a -> a |+| a == (a :: MolecularFormula) |*| 2
describe "ToElementalComposition - MolecularFormula instance" $ do
it "[mol|C2H6O|] should be [ele|C2H6O|]" $
toElementalComposition [mol|C2H6O|] `shouldBe` [ele|C2H6O|]
it "empty MolecularFormula should return an empty ElementalComposition" $
toElementalComposition (emptyFormula :: ElementalComposition) `shouldBe`
mkElementalComposition []
describe "Monoid instance for CondensedFormula" $ do
it "associativity" . property $
\a b c -> (a `mappend` b) `mappend` c ==
a `mappend` (b `mappend` c :: CondensedFormula)
it "right identity" . property $
\a -> (a :: CondensedFormula) `mappend` mempty == a
it "left identity" . property $
\a -> emptyFormula `mappend` (a :: CondensedFormula) == a
describe "ToElementalComposition - CondensedFormula instance" $ do
it "empty CondensedFormula should return an empty ElementalComposition" $
toElementalComposition (emptyFormula :: CondensedFormula) `shouldBe`
[ele||]
it "[con|N(CH3)3|] should be [ele|C3NH9|]" $
toElementalComposition [con|N(CH3)3|] `shouldBe` [ele|C3NH9|]
describe "ToMolecularFormula - CondensedFormula instance" $ do
it "empty CondensedFormula should return an empty MolecularFormula" $
toMolecularFormula (emptyFormula :: CondensedFormula) `shouldBe`
mkMolecularFormula []
it "[mol|C6H6|] should be [emp|CH|]" $
toMolecularFormula [con|N(CH3)3|] `shouldBe` [mol|C3NH9|]
describe "mkEmpiricalFormula" $ do
it "zero is filtered out" $
mkEmpiricalFormula [(C, 0), (H, 0), (O, 0)] `shouldBe` emptyFormula
it "should give the correct formula" $
mkEmpiricalFormula [(C, 6), (H, 6)] `shouldBe` [emp|CH|]
describe "ToEmpiricalFormula - ElementalComposition instance" $ do
it "empty ElementalComposition should return an empty EmpiricalFormula" $
toEmpiricalFormula (emptyFormula :: ElementalComposition) `shouldBe`
mkEmpiricalFormula []
it "[ele|C6H6|] should be [emp|CH|]" $
toEmpiricalFormula [ele|C6H6|] `shouldBe` [emp|CH|]
describe "ToEmpiricalFormula - MolecularFormula instance" $ do
it "empty MolecularFormula should return an empty EmpiricalFormula" $
toEmpiricalFormula (emptyFormula :: MolecularFormula) `shouldBe`
mkEmpiricalFormula []
it "[mol|C6H6|] should be [emp|CH|]" $
toEmpiricalFormula [mol|C6H6|] `shouldBe` [emp|CH|]
describe "ToEmpiricalFormula - CondensedFormula instance" $ do
it "empty CondensedFormula should return an empty EmpiricalFormula" $
toEmpiricalFormula (emptyFormula :: CondensedFormula) `shouldBe`
mkEmpiricalFormula []
it "[con|C6H6|] should be [emp|CH|]" $
toEmpiricalFormula [con|C6H6|] `shouldBe` [emp|CH|]
describe "ToElementalComposition - EmpiricalFormula instance" $ do
it "empty EmpiricalFormula should return an empty EmpiricalFormula" $
toElementalComposition (emptyFormula :: EmpiricalFormula) `shouldBe`
mkElementalComposition []
it "[emp|CH|] should be [ele|CH|]" $
toElementalComposition [emp|CH|] `shouldBe` [ele|CH|]
describe "Laws for ElementalComposition, MolecularFormula, EmpiricalFormula and CondensedFormula data types" $ do
it "applying toEmpiricalFormula to a CondensedFormula should give the same result as applying toMolecularFormula compose toEmpiricalFormula" . property $
\c -> toEmpiricalFormula c == (toEmpiricalFormula . toMolecularFormula) (c :: CondensedFormula)
it "applying toElementalComposition to a CondensedFormula should give the same result as applying toMolecularFormula compose toElementalComposition" . property $
\c -> toElementalComposition c == (toElementalComposition . toMolecularFormula) (c :: CondensedFormula)
it "applying toElementalComposition compose toEmpiricalFormula to an EmpiricalFormula should return the same EmpiricalFormula" . property $
\e -> (toEmpiricalFormula . toElementalComposition) e == (e :: EmpiricalFormula)
allUnique :: (Eq a) => [a] -> Bool
allUnique l = l == nub l
withinTolerance :: (Num a, Ord a) => a -> a -> a -> Bool
withinTolerance n1 n2 err = abs (n1 - n2) < err
protonNumEqAtomicNum :: ElementSymbol -> Bool
protonNumEqAtomicNum sym =
and $ (== atomicNumber sym) . fst . nucleons <$> isotopes sym
elemSymIntPairGen :: Gen (ElementSymbol, Int)
elemSymIntPairGen = do
elemSym <- arbitrary
n <- choose (1,100)
return (elemSym, n)
elemSymIntPairListGen :: Gen [(ElementSymbol, Int)]
elemSymIntPairListGen = listOf elemSymIntPairGen
instance Arbitrary ElementSymbol where
arbitrary = oneof $ return <$> elementSymbolList
instance Arbitrary ElementalComposition where
arbitrary = mkElementalComposition <$> elemSymIntPairListGen
instance Arbitrary MolecularFormula where
arbitrary = mkMolecularFormula <$> elemSymIntPairListGen
instance Arbitrary CondensedFormula where
arbitrary = do
n <- choose (0, 4)
condForm <- vectorOf n
(oneof [leftCondensedFormulaGen, sized arbRightCondensedFormulaGen])
return $ CondensedFormula condForm
where
leftCondensedFormulaGen :: Gen (Either MolecularFormula (CondensedFormula, Int))
leftCondensedFormulaGen = Left <$> arbitrary
arbRightCondensedFormulaGen :: Int -> Gen (Either MolecularFormula (CondensedFormula, Int))
arbRightCondensedFormulaGen 0 = do
m <- choose (1, 4)
condForm <- leftCondensedFormulaGen
return $ Right (CondensedFormula [condForm], m)
arbRightCondensedFormulaGen n | n > 0 = do
m <- choose (1, 4)
v <- choose (1, 3)
let n' = n `div` (v + 1)
condForm' <- replicateM v (arbRightCondensedFormulaGen n')
return $ Right (CondensedFormula condForm', m)
instance Arbitrary EmpiricalFormula where
arbitrary = mkEmpiricalFormula <$> elemSymIntPairListGen