isotope 0.3.3.0 → 0.4.0.0
raw patch · 6 files changed
+91/−75 lines, 6 filesdep ~megaparsecPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: megaparsec
API changes (from Hackage documentation)
- Isotope: class ToCondensedFormuala a
- Isotope: lookupElement :: ElementSymbol -> Maybe Element
+ Isotope: class ToElementalComposition a => ToCondensedFormula a
- Isotope: class ToEmpiricalFormula a
+ Isotope: class ToElementalComposition a => ToEmpiricalFormula a
- Isotope: class ToMolecularFormula a
+ Isotope: class ToElementalComposition a => ToMolecularFormula a
- Isotope: selectIsotope :: ElementSymbol -> MassNumber -> Isotope
+ Isotope: selectIsotope :: ElementSymbol -> MassNumber -> Maybe Isotope
- Isotope: toCondensedFormula :: ToCondensedFormuala a => a -> CondensedFormula
+ Isotope: toCondensedFormula :: ToCondensedFormula a => a -> CondensedFormula
Files
- README.md +3/−3
- isotope.cabal +3/−3
- src/Isotope.hs +1/−2
- src/Isotope/Base.hs +35/−49
- src/Isotope/Parsers.hs +44/−15
- test/Isotope/BaseSpec.hs +5/−3
README.md view
@@ -9,7 +9,7 @@ * [Elemental composition and molecular, condensed and empirical formulae](#elemental-composition-and-molecular-condensed-and-empirical-formulae) * [ElementalComposition, MolecularFormula, CondensedFormula, EmpiricalFormula quasiquoters](#elementalcomposition-molecularformula-condensedformula-empiricalformula-quasiquoters) * [Conversion between ElementalComposition, MolecularFormula, CondensedFormula and EmpiricalFormula data types](#conversion-between-elementalcomposition-molecularformula-condensedformula-and-empiricalformula-data-types)- * [Operators for working with molecular formulae](#operators-for-working-with-molecular-formulae)+ * [Operators for working with formulae and masses](#operators-for-working-with-formulae-and-masses) * [ToElementalComposition type class](#elementalcomposition-type-class) * [Behaviour of ElementalComposition, MolecularFormula, CondensedFormula and EmpiricalFormula data types](#behaviour-of-elementalcomposition-molecularformula-condensedformula-and-empiricalformula-data-types) * [Additional functions accepting an ElementSymbol as input](#additional-functions-accepting-an-elementsymbol-as-input)@@ -108,7 +108,7 @@ ### `ToElementalComposition` type class -In addition to the `toElementalComposition` method, the `ToElementalComposition` type class has three other methods; `monoisotopicMass`, `nominalMass` and `averageMass`. (`toElementalComposition` is the minimal complete definition.) `ElementSymbol`, `ElementalComposition`, `MolecularFormula`, `CondensedFormula` and `EmpiricalFormula` all have instances of `ToElementalComposition`. This provides a uniform approach to working with elements, elemental compositions, molecular formulae, condensed formulae and empirical formulae.+`ToElementalComposition` is a superclass of `ToMolecularFormula`, `ToCondensedFormula` and `ToEmpiricalFormula`. In addition to the `toElementalComposition` method, the `ToElementalComposition` type class has three other methods; `monoisotopicMass`, `nominalMass` and `averageMass`. (`toElementalComposition` is the minimal complete definition.) `ElementSymbol`, `ElementalComposition`, `MolecularFormula`, `CondensedFormula` and `EmpiricalFormula` all have instances of `ToElementalComposition`. This provides a uniform approach to working with elements, elemental compositions, molecular formulae, condensed formulae and empirical formulae. ```haskell ghci> nominalMass C NominalMass {getNominalMass = 12}@@ -135,7 +135,7 @@ #### Laws for `ElementalComposition`, `MolecularFormula`, `EmpiricalFormula` and `CondensedFormula` data types -Instances of `ToElementalComposition`, `ToMolecularFormula`, `ToCondensedFormuala` and `ToEmpiricalFormula` should abide by three laws. 1) Applying `toEmpiricalFormula` to a `CondensedFormula` should give the same result as applying `toMolecularFormula` compose `toEmpiricalFormula`.+Instances of `ToElementalComposition`, `ToMolecularFormula`, `ToCondensedFormula` and `ToEmpiricalFormula` should abide by three laws. 1) Applying `toEmpiricalFormula` to a `CondensedFormula` should give the same result as applying `toMolecularFormula` compose `toEmpiricalFormula`. 2) Applying `toElementalComposition` to a `CondensedFormula` should give the same result as applying `toMolecularFormula` compose `toElementalComposition`. 3) Applying `toElementalComposition . toEmpiricalFormula` to an `EmpiricalFormula` should return the same `EmpiricalFormula`.
isotope.cabal view
@@ -1,5 +1,5 @@ name: isotope-version: 0.3.3.0+version: 0.4.0.0 synopsis: Isotopic masses and relative abundances. description: Please see README.md homepage: https://github.com/Michaelt293/Element-isotopes/blob/master/README.md@@ -20,7 +20,7 @@ , Isotope.Parsers build-depends: base >= 4.7 && < 5 , containers >= 0.5 && < 0.6- , megaparsec >= 4 && < 6+ , megaparsec >= 5 && < 6 , template-haskell , th-lift default-language: Haskell2010@@ -34,7 +34,7 @@ , isotope , hspec , QuickCheck- , megaparsec >= 4 && < 6+ , megaparsec other-modules: Isotope.BaseSpec , Isotope.ParsersSpec ghc-options: -threaded -rtsopts -with-rtsopts=-N
src/Isotope.hs view
@@ -47,7 +47,6 @@ -- * 'elements' - a map containing isotopic data for each element. , elements -- * Functions taking an 'elementSymbol' as input- , lookupElement , findElement , elementName , atomicNumber@@ -69,7 +68,7 @@ , mkMolecularFormula -- * Condensed formulae , CondensedFormula(..)- , ToCondensedFormuala(..)+ , ToCondensedFormula(..) -- * Empirical formulae , EmpiricalFormula(..) , ToEmpiricalFormula(..)
src/Isotope/Base.hs view
@@ -1,7 +1,7 @@ {-| Module : Isotope.Base-Description : Contains most of the data type declarations used in the Isotope- library.+Description : Contains the data type and type class declarations used in the+ Isotope library. Copyright : Michael Thomas License : GPL-3 Maintainer : Michael Thomas <Michaelt293@gmail.com>@@ -53,7 +53,6 @@ -- 'elements' - a map containing isotopic data for each element. , elements -- Functions taking an 'elementSymbol' as input- , lookupElement , findElement , elementName , atomicNumber@@ -69,33 +68,24 @@ , ElementalComposition(..) , ToElementalComposition(..) , mkElementalComposition- -- Molecular formulae+ -- Molecular formula , MolecularFormula(..) , ToMolecularFormula(..) , mkMolecularFormula- -- Condensed formulae+ -- Condensed formula , CondensedFormula(..)- , ToCondensedFormuala(..)+ , ToCondensedFormula(..) -- Empirical formula , EmpiricalFormula(..) , ToEmpiricalFormula(..) , mkEmpiricalFormula ) where -import Prelude hiding (lookup,filter)-import Data.Map ( Map- , fromList- , unionWith- , filter- , mapWithKey- , lookup- , (!)- , toList- )-import Data.Foldable hiding (toList)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Foldable import Data.Ord-import Data.List (elemIndex, sortBy)-import Data.Maybe (fromJust)+import Data.List import Data.Monoid --------------------------------------------------------------------------------@@ -118,7 +108,7 @@ -- | The exact mass of the most abundant isotope for an element or the sum of -- the exact masses of the most abundant isotope of each element for a--- molecular formula.+-- elemental composition. newtype MonoisotopicMass = MonoisotopicMass { getMonoisotopicMass :: Double } deriving (Show, Eq, Ord) @@ -132,8 +122,8 @@ MonoisotopicMass x |*| y = MonoisotopicMass $ x * fromIntegral y -- | The integer mass of the most abundant isotope for an element or the sum of--- integer mass of the most abundant isotope of each element for a chemical--- formula.+-- integer mass of the most abundant isotope of each element for a elemental+-- composition. newtype NominalMass = NominalMass { getNominalMass :: Int } deriving (Show, Eq, Ord) @@ -146,7 +136,7 @@ NominalMass x |-| NominalMass y = NominalMass $ x - y NominalMass x |*| y = NominalMass $ x * y --- | The average mass of an element or molecular formula based on+-- | The average mass of an element or elemental composition based on -- naturally-occurring abundances. newtype AverageMass = AverageMass { getAverageMass :: Double } deriving (Show, Eq, Ord)@@ -284,14 +274,15 @@ tupleToIsotope (nucl, mass, abun) = Isotope nucl (IsotopicMass mass) (IsotopicAbundance abun) -tupleToElement :: (AtomicNumber, ElementName, [(Nucleons, Double, Double)]) -> Element+tupleToElement+ :: (AtomicNumber, ElementName, [(Nucleons, Double, Double)]) -> Element tupleToElement (atomNum, name, isotopes) = Element atomNum name (tupleToIsotope <$> isotopes) -- | Map of the periodic table. All data on isotopic masses and abundances is -- contained within this map. elements :: Map ElementSymbol Element-elements = tupleToElement <$> fromList+elements = tupleToElement <$> Map.fromList [ (H, (1, "hydrogen", [ ((1, 0), 1.00782503223, 0.999885) , ((1, 1), 2.01410177812, 0.000115) ])) , (He, (2, "helium", [ ((2, 1), 3.0160293201, 0.00000134)@@ -585,15 +576,10 @@ -------------------------------------------------------------------------------- -- Functions taking an 'elementSymbol' as input --- | Searches elements (a map) with an 'ElementSymbol' key and returns--- information for the element (wrapped in 'Maybe').-lookupElement :: ElementSymbol -> Maybe Element-lookupElement = flip lookup elements- -- | Searches 'elements' (a map) with an 'ElementSymbol' key and returns -- information for the element. findElement :: ElementSymbol -> Element-findElement = (!) elements+findElement = (Map.!) elements -- | Returns the name for an element symbol. elementName :: ElementSymbol -> ElementName@@ -612,11 +598,11 @@ mostAbundantIsotope = elementMostAbundantIsotope . findElement -- | Selects an isotope of element based on the isotope's mass number--- ('IntegerMass'). Note: This is a partial function.-selectIsotope :: ElementSymbol -> MassNumber -> Isotope-selectIsotope sym mass = isotopeList !! indexOfIsotope+-- ('IntegerMass').+selectIsotope :: ElementSymbol -> MassNumber -> Maybe Isotope+selectIsotope sym massNum =+ find (\iso -> (massNumber . nucleons) iso == massNum) isotopeList where isotopeList = isotopes sym- indexOfIsotope = fromJust $ elemIndex mass (integerMasses sym) -- | Exact masses for all naturally-occurring isotopes for an element. isotopicMasses :: ElementSymbol -> [IsotopicMass]@@ -633,7 +619,7 @@ -------------------------------------------------------------------------------- -- Formula type class --- | Type class with two methods, 'renderFormula' and 'emptyFormula'. The+-- | Type class with two methods; 'renderFormula' and 'emptyFormula'. The -- 'renderFormula' method converts a formula to its shorthand notation. class Formula a where renderFormula :: a -> String@@ -665,12 +651,12 @@ getFormulaSum :: (Monoid a, Operators a, ToElementalComposition b) => (Element -> a) -> b -> a getFormulaSum f m = fold $- mapWithKey mapFunc (getElementalComposition (toElementalComposition m))+ Map.mapWithKey mapFunc (getElementalComposition (toElementalComposition m)) where mapFunc k v = (f . findElement) k |*| v -- | Smart constructor to make values of type 'ElementalComposition'. mkElementalComposition :: [(ElementSymbol, Int)] -> ElementalComposition-mkElementalComposition = ElementalComposition . filterZero . fromList+mkElementalComposition = ElementalComposition . filterZero . Map.fromList instance Monoid ElementalComposition where mempty = emptyFormula@@ -706,16 +692,16 @@ sortElementSymbolMap :: Map ElementSymbol Int -> [(ElementSymbol, Int)] sortElementSymbolMap m = sortBy (hillSystem fst) elementSymbolIntList where- elementSymbolIntList = toList m+ elementSymbolIntList = Map.toList m elementSymbols = fst <$> elementSymbolIntList containsC = C `elem` elementSymbols hillSystem f a b = case (f a, f b) of (C, _) -> LT (_, C) -> GT (H, b') -> if containsC then LT- else (show . elementName) H `compare` show b'+ else show H `compare` show b' (a', H) -> if containsC then GT- else show a' `compare` (show . elementName) H+ else show a' `compare` show H (a', b') -> show a' `compare` show b' --------------------------------------------------------------------------------@@ -726,21 +712,21 @@ getMolecularFormula :: Map ElementSymbol Int } deriving (Show, Read, Eq, Ord) -class ToMolecularFormula a where+class ToElementalComposition a => ToMolecularFormula a where toMolecularFormula :: a -> MolecularFormula -- The function unionWith adapted to work with 'Map ElementSymbol Int'. combineMaps :: (Int -> Int -> Int) -> Map ElementSymbol Int -> Map ElementSymbol Int -> Map ElementSymbol Int-combineMaps f m1 m2 = filterZero $ unionWith f m1 m2+combineMaps f m1 m2 = filterZero $ Map.unionWith f m1 m2 -- | Smart constructor to make values of type 'MolecularFormula'. mkMolecularFormula :: [(ElementSymbol, Int)] -> MolecularFormula-mkMolecularFormula = MolecularFormula . filterZero . fromList+mkMolecularFormula = MolecularFormula . filterZero . Map.fromList -- Helper function to remove (k, v) pairs where v == 0. filterZero :: Map k Int -> Map k Int-filterZero = filter (/= 0)+filterZero = Map.filter (/= 0) instance Monoid MolecularFormula where mempty = emptyFormula@@ -770,7 +756,7 @@ getCondensedFormula :: [Either MolecularFormula (CondensedFormula, Int)] } deriving (Show, Read, Eq, Ord) -class ToCondensedFormuala a where+class ToElementalComposition a => ToCondensedFormula a where toCondensedFormula :: a -> CondensedFormula instance Monoid CondensedFormula where@@ -804,15 +790,15 @@ getEmpiricalFormula :: Map ElementSymbol Int } deriving (Show, Read, Eq, Ord) --- | Type class with a single method, 'toEmpiricalFormula', which converts a+-- | Type class with a single method; 'toEmpiricalFormula', which converts a -- chemical data type to `EmpiricalFormula`.-class ToEmpiricalFormula a where+class ToElementalComposition a => ToEmpiricalFormula a where toEmpiricalFormula :: a -> EmpiricalFormula -- | Smart constructor to make values of type 'EmpiricalFormula'. mkEmpiricalFormula :: [(ElementSymbol, Int)] -> EmpiricalFormula mkEmpiricalFormula l =- let m = filterZero (fromList l)+ let m = filterZero (Map.fromList l) in EmpiricalFormula $ (`div` greatestCommonDenom m) <$> m instance ToEmpiricalFormula ElementalComposition where
src/Isotope/Parsers.hs view
@@ -29,12 +29,12 @@ import Isotope.Base import Language.Haskell.TH.Quote+import Language.Haskell.TH.Syntax import Language.Haskell.TH.Lift import Text.Megaparsec import Text.Megaparsec.String import qualified Text.Megaparsec.Lexer as L-import Data.String-import Data.List hiding (filter)+import Data.List import Data.Map (Map) import Data.Monoid ((<>)) @@ -61,7 +61,8 @@ -- | Parses a condensed formula, i.e., \"N(CH3)3\". condensedFormula :: Parser CondensedFormula-condensedFormula = CondensedFormula <$> many (leftCondensedFormula <|> rightCondensedFormula)+condensedFormula =+ CondensedFormula <$> many (leftCondensedFormula <|> rightCondensedFormula) where subMolecularFormula :: Parser MolecularFormula subMolecularFormula = mkMolecularFormula . pure <$> subFormula@@ -80,48 +81,76 @@ empiricalFormula = mkEmpiricalFormula <$> many subFormula -- Helper function for `ElementalComposition` quasiquoter+quoteElementalComposition :: String -> Q Exp quoteElementalComposition s = case parse (condensedFormula <* eof) "" s of- Left err -> error $ "Could not parse formula: " <> show err+ Left err -> fail $+ "Could not parse elemental formula!\n" <> parseErrorPretty err Right v -> lift $ toElementalComposition v -- Helper function for `MolecularFormula` quasiquoter+quoteMolecularFormula :: String -> Q Exp quoteMolecularFormula s = case parse (condensedFormula <* eof) "" s of- Left err -> fail $ "Could not parse formula: " <> show err+ Left err -> fail $+ "Could not parse molecular formula!\n" <> parseErrorPretty err Right v -> lift $ toMolecularFormula v -- Helper function for `CondensedFormula` quasiquoter+quoteCondensedFormula :: String -> Q Exp quoteCondensedFormula s = case parse (condensedFormula <* eof) "" s of- Left err -> error $ "Could not parse formula: " <> show err+ Left err -> fail $+ "Could not parse condensed formula!\n" <> parseErrorPretty err Right v -> lift v -- Helper function for `EmpiricalFormula` quasiquoter quoteEmpiricalFormula s = case parse (condensedFormula <* eof) "" s of- Left err -> fail $ "Could not parse formula: " <> show err+ Left err -> fail $+ "Could not parse empirical formula!\n" <> parseErrorPretty err Right v -> lift $ toEmpiricalFormula v -- | Quasiquoter for `ElementalComposition` ele :: QuasiQuoter-ele = QuasiQuoter- { quoteExp = quoteElementalComposition }+ele = QuasiQuoter {+ quoteExp = quoteElementalComposition+ , quotePat = notHandled "patterns" "elemental composition"+ , quoteType = notHandled "types" "elemental composition"+ , quoteDec = notHandled "declarations" "elemental composition"+ } -- | Quasiquoter for `MolecularFormula` mol :: QuasiQuoter-mol = QuasiQuoter- { quoteExp = quoteMolecularFormula }+mol = QuasiQuoter {+ quoteExp = quoteMolecularFormula+ , quotePat = notHandled "patterns" "molecular formula"+ , quoteType = notHandled "types" "molecular formula"+ , quoteDec = notHandled "declarations" "molecular formula"+ } -- | Quasiquoter for `CondensedFormula` con :: QuasiQuoter-con = QuasiQuoter- { quoteExp = quoteCondensedFormula }+con = QuasiQuoter {+ quoteExp = quoteCondensedFormula+ , quotePat = notHandled "patterns" "condensed formula"+ , quoteType = notHandled "types" "condensed formula"+ , quoteDec = notHandled "declarations" "condensed formula"+ } -- | Quasiquoter for `EmpiricalFormula` emp :: QuasiQuoter-emp = QuasiQuoter- { quoteExp = quoteEmpiricalFormula }+emp = QuasiQuoter {+ quoteExp = quoteEmpiricalFormula+ , quotePat = notHandled "patterns" "empirical formula"+ , quoteType = notHandled "types" "empirical formula"+ , quoteDec = notHandled "declarations" "empirical formula"+ }++-- Helper function used in QuasiQuoters+notHandled :: String -> String -> a+notHandled feature quoterName =+ error $ feature <> " are not handled by the" <> quoterName <> "quasiquoter." $(deriveLift ''ElementSymbol)
test/Isotope/BaseSpec.hs view
@@ -18,9 +18,9 @@ (\x -> length x /= 2 || (isLower . last) x) . show <$> elementSymbolList `shouldSatisfy` and - describe "lookupElement" .+ describe "elementSymbolList" . it "should not contain duplicate elements" $- lookupElement <$> elementSymbolList `shouldSatisfy` allUnique+ findElement <$> elementSymbolList `shouldSatisfy` allUnique describe "elementName" $ do it "should not be an empty string" $@@ -47,7 +47,7 @@ describe "selectIsotope" . it "calling fuction with the arguments C and 12 should select C12" $- nucleons (selectIsotope C 12) `shouldBe` (6, 6)+ nucleons <$> selectIsotope C 12 `shouldBe` Just (6, 6) describe "monoisotopicMass" . it "calling function with C should be 12.0" $@@ -93,6 +93,8 @@ renderFormula [mol|CCl4|] `shouldBe` "CCl4" it "[mol|H2O4S|] should be \"H2O4S\"" $ renderFormula [mol|H2O4S|] `shouldBe` "H2O4S"+ it "[mol|BBr3|] should be \"BBr3\"" $+ renderFormula [mol|BBr3|] `shouldBe` "BBr3" describe "renderFormula for CondensedFormula" $ do it "[con|C6H6O|] should be \"C6H6O\"" $