packages feed

isotope-0.1.0.0: test/Isotope/BaseSpec.hs

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverlappingInstances #-}
module Isotope.BaseSpec (spec) where

import Isotope
import Test.Hspec
import Test.QuickCheck
import Data.Char
import Data.List
import Data.Map (Map, fromList)

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 a two character ElementSymbol should not be upper case" $
        (\x -> length x /= 2 || (isLower . last) x) . show <$> elementSymbolList `shouldSatisfy` and
    describe "lookupElement" $ do
      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" $ do
      it "should not have duplicate isotopes" $
        isotopes <$> elementSymbolList `shouldSatisfy` allUnique

    describe "mostAbundantIsotope" $ do
      it "C should be C12 (six protons and six neutrons)" $
        (nucleons . mostAbundantIsotope) C `shouldBe` (6, 6)

    describe "selectIsotope" $ do
      it "calling fuction with the arguments C and 12 should select C12" $
        nucleons (selectIsotope C 12) `shouldBe` (6, 6)

    describe "monoisotopicMass" $ do
      it "calling function with C should be 12.0" $
        monoisotopicMass C `shouldBe` 12.0

    describe "nominalMass" $ do
      it "calling function with C should return 12" $
        nominalMass C `shouldBe` 12

    describe "isotopicMasses" $ do
      it "calling function with H should return a list containing 1.007... and 2.014...." $
        isotopicMasses H `shouldBe` [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 "averageAtomicMass" $ do
      it "calling function with C should return 12.0107" $
        withinTolerance (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` [0.9893, 0.0107]
      it "sum of isotopic abundances for an element should equal 1" $
        (\sym -> withinTolerance (sumIsotopicAbundance sym) 1 0.0001) <$> elementSymbolList `shouldSatisfy` and

    describe "Monoid instance for MolecularFormula" $ do
      it "associativity" $ property $
          \a b c -> (a |+| b) |+| c == a |+| (b |+| c)
      it "identity element" $ property $
          \a -> a |+| emptyFormula == a

    describe "Addition of molecular formulae is commutative" $ do
      it "commutative" $ property $
        \a b -> a |+| b == b |+| a

    describe "properties of |+|, |*| and |-|)" $ do
      it "a |-| a = emptyFormula" $ property $
        \a -> a |-| a == emptyFormula
      it "0 |*| a == emptyFormula" $ property $
        \a -> 0 |*| a == emptyFormula
      it "a |+| a == 2 |*| a" $ property $
        \a -> a |+| a == 2 |*| a

    describe "toEmpiricalFormula" $ do
      it "Empty MolecularFormula should return an empty EmpiricalFormula" $
        toEmpiricalFormula (emptyFormula :: MolecularFormula) `shouldBe` mkEmpiricalFormula []
      it "\"C6H6\" should be \"CH\"" $
        toEmpiricalFormula [mol|C6H6|] `shouldBe` [emp|CH|]

    describe "renderFormula" $ do
      it "\"C6H6O\" should be \"C6H6O\"" $
        renderFormula [mol|C6H6O|] `shouldBe` "C6H6O"
      it "\"CCl4\" should be \"CCl4\"" $
        renderFormula [mol|CCl4|] `shouldBe` "CCl4"
      it "\"H2O4S\" should be \"H2O4S\"" $
        renderFormula [mol|H2O4S|] `shouldBe` "H2O4S"


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

sumIsotopicAbundance :: ElementSymbol -> IsotopicAbundance
sumIsotopicAbundance = sum . isotopicAbundances

instance Arbitrary ElementSymbol where
    arbitrary = oneof $ return <$> elementSymbolList

instance Arbitrary (ElementSymbol, Int) where
    arbitrary = do
      elemSym <- arbitrary :: Gen ElementSymbol
      n <- choose (1,100)
      return (elemSym, n)

instance Arbitrary MolecularFormula where
    arbitrary = do
      symNums <- arbitrary :: Gen [(ElementSymbol, Int)]
      return $ MolecularFormula . fromList $ symNums