seqloc 0.6 → 0.6.1
raw patch · 4 files changed
+268/−8 lines, 4 files
Files
- seqloc.cabal +1/−1
- src/Bio/SeqLoc/LocMap.hs +131/−2
- src/Bio/SeqLoc/SpliceLocation.hs +7/−2
- test/TestMain.hs +129/−3
seqloc.cabal view
@@ -1,5 +1,5 @@ Name: seqloc-Version: 0.6+Version: 0.6.1 Cabal-Version: >= 1.14 Synopsis: Handle sequence locations for bioinformatics Description: Handle sequence locations for bioinformatics
src/Bio/SeqLoc/LocMap.hs view
@@ -1,28 +1,98 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Bio.SeqLoc.LocMap+{-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances #-}+{-| Efficient lookup based on potential location overlap++Collection of objects with locations, potentially on named sequences,+that can be queried to recover all objects whose location could+potentially overlap a query location.++|-}++module Bio.SeqLoc.LocMap (+ -- * Mapping objects to locations+ LocMap+ , emptyLM, insertLoc+ , queryLoc++ -- * Mapping objects to locations on named sequneces+ , SeqLocMap+ , emptySLM, insertSeqLoc+ , querySeqLoc++ -- * Mapping transcripts to their locations+ , transcriptSeqLocMap+ + -- * Generalization of objects with locations+ , Locatable(..)+ , WithLocation(..)+ , locatableSeqLocMap+ , queryLocatable, queryLocCompatible+ + ) where import qualified Data.HashMap.Strict as HM import Data.List+-- import Data.Maybe import qualified Bio.SeqLoc.Location as Loc import Bio.SeqLoc.OnSeq import qualified Bio.SeqLoc.Position as Pos+import qualified Bio.SeqLoc.SpliceLocation as SpLoc+import Bio.SeqLoc.Strand import Bio.SeqLoc.Transcript import qualified Bio.SeqLoc.ShiftedVector as ShV +-- | Mapping objects to sequence locations in the sense of `Loc.Location`. data LocMap a = LocMap { binSize :: !Pos.Offset, bins :: !(ShV.ShiftedVector [a]) } deriving (Show) +-- | Mapping objects to locations on named sequences data SeqLocMap a = SeqLocMap { slmBinSize :: !Pos.Offset, locmaps :: !(HM.HashMap SeqLabel (LocMap a)) } deriving (Show) +-- | Object with a genomic location expressed by `SpliceSeqLoc`.+class Locatable o where+ locate :: o -> SpliceSeqLoc++instance Locatable ContigSeqLoc where+ locate (OnSeq ref loc) = OnSeq ref (SpLoc.singleton loc)++instance Locatable SpliceSeqLoc where+ locate = id++instance Locatable Transcript where+ locate = location++-- | Simple representation of a `Locatable` object as an arbitrary+-- object adjoined with a location.+data WithLocation a = WithLocation { withoutLocation :: !a, withLocate :: !SpliceSeqLoc } deriving (Eq)++instance Locatable (WithLocation a) where+ locate = withLocate++-- | Create an empty object / location map.+--+-- Specify a characteristic size for efficient queries, which depends+-- on the underlying genome. Smaller sizes yield fewer false+-- candidates but greater memory usage and potentially slower queries. emptyLM :: Pos.Offset -> LocMap a emptyLM bsz = LocMap { binSize = bsz, bins = ShV.empty } +-- | Insert a new object / location pair and reutrn the updated map insertLoc :: (Loc.Location l) => l -> a -> LocMap a -> LocMap a insertLoc l x lm0 = lm0 { bins = ShV.modifySome (bins lm0) (locBins l lm0) (x :) } +-- | Retrieve a list of objects that could potentially overlap the+-- query location.+--+-- Some objects may not actually overlap the query location. Some+-- objects may appear more than once in the list. However, no object+-- whose location truly overlaps the query will be missing from the+-- list.+--+-- Overlap is defined by one or more nucleotides in common between the+-- bounds of the two locations, regardless of the relative strands of+-- the query and the object location. queryLoc :: (Loc.Location l) => l -> LocMap a -> [a] queryLoc l lm = concat [ (bins lm) ShV.!? b | b <- locBins l lm ] @@ -32,18 +102,77 @@ binhigh = fromIntegral $ end `div` binSize lm in [binlow..binhigh] +-- | Create an empty object / location map+--+-- Specify a characteristic size as per `emptyLM`. emptySLM :: Pos.Offset -> SeqLocMap a emptySLM bsz = SeqLocMap { slmBinSize = bsz, locmaps = HM.empty } +-- | Insert a new object / location pair and reutrn the updated map insertSeqLoc :: (Loc.Location l) => OnSeq l -> a -> SeqLocMap a -> SeqLocMap a insertSeqLoc sl x slm0 = let lm0 = HM.lookupDefault (emptyLM $ slmBinSize slm0) (onSeqLabel sl) (locmaps slm0) lm' = insertLoc (unOnSeq sl) x lm0 in slm0 { locmaps = HM.insert (onSeqLabel sl) lm' (locmaps slm0) } +-- | Retrieve a list of objects that could potentially overlap the+-- query location.+--+-- Some objects may not actually overlap the query location. Some+-- objects may appear more than once in the list. However, no object+-- whose location truly overlaps the query will be missing from the+-- list.+--+-- Overlap is defined by one or more nucleotides in common between the+-- bounds of the two locations, regardless of the relative strands of+-- the query and the object location, as well as the same name for the+-- underlying reference sequence.+--+-- To retrieve objects and test for actual overlap see the `Locatable`+-- interface and `queryLocatable` or `queryLocCompatible`. querySeqLoc :: (Loc.Location l) => OnSeq l -> SeqLocMap a -> [a] querySeqLoc sl slm = maybe [] (queryLoc (unOnSeq sl)) $ HM.lookup (onSeqLabel sl) (locmaps slm) +-- | Construct a mapping from transcripts to their genomic locations. transcriptSeqLocMap :: Pos.Offset -> [Transcript] -> SeqLocMap Transcript transcriptSeqLocMap bsz = foldl' insertTrx (emptySLM bsz) where insertTrx slm0 t = insertSeqLoc (location t) t slm0++-- | Construct a mapping from a general `Locatable` object to its+-- genomic location+locatableSeqLocMap :: (Locatable l) => Pos.Offset -> [l] -> SeqLocMap l+locatableSeqLocMap bsz = foldl' insertLocable (emptySLM bsz)+ where insertLocable slm0 t = insertSeqLoc (locate t) t slm0++-- | Retrieve a list of `Locatable` objects whose overall, contiguous+-- genomic coordinates intersect at any position the genomic interval+-- spanned by the specified `Loc.Location`. This does not require that+-- the spliced structure of the query is a subset of the spliced+-- structure of the `Locatable` nor that the query location lie+-- entirely within the hit location (contrast with+-- `queryLocCompatible`).+--+-- When a strand argument is given, restrict matches to those lying on+-- the same strand as the query location, for `Just Plus`, or the+-- opposite strand, for `Just Minus`.+queryLocatable :: (Locatable o, Loc.Location l) => Maybe Strand -> OnSeq l -> SeqLocMap o -> [o]+queryLocatable mstr qyloc = filter realHit . querySeqLoc qyloc+ where realHit sb = let sbloc = locate sb+ (qylb, qyub) = Loc.bounds . unOnSeq $ qyloc+ (sblb, sbub) = Loc.bounds . unOnSeq $ sbloc+ qystr = Loc.strand . unOnSeq $ qyloc+ sbstr = Loc.strand . unOnSeq $ sbloc+ in (qylb <= sbub) && (qyub >= sblb) && (qystr `strandCompat` sbstr)+ strandCompat = case mstr of+ Nothing -> \_ _ -> True+ Just Plus -> (==)+ Just Minus -> (/=)++-- | Retrieve a list of `Locatable` objects whose spliced structure+-- contains the query location specifically.+queryLocCompatible :: (Locatable o) => Maybe Strand -> SpliceSeqLoc -> SeqLocMap o -> [o]+queryLocCompatible mstr qyloc = filter compatHit . querySeqLoc qyloc+ where compatHit sb = maybe False strandCompat $ unOnSeq qyloc `SpLoc.locInto` (unOnSeq . locate $ sb)+ strandCompat = case mstr of+ Nothing -> const True+ Just s -> (== s) . Loc.strand
src/Bio/SeqLoc/SpliceLocation.hs view
@@ -11,8 +11,8 @@ module Bio.SeqLoc.SpliceLocation ( -- * Sequence locations SpliceLoc- , fromContigs- , locOutof, locInto+ , fromContigs, singleton+ , locOutof, locInto, contigSpan , mergeContigs, mergeAdjContigs ) where @@ -104,6 +104,11 @@ locInto :: (Location l) => SpliceLoc -> l -> Maybe SpliceLoc locInto sploc outer = mapM (flip clocInto outer) (toContigs sploc) >>= fromContigs . mergeContigs . concat . map toContigs++-- | Return the contiguous location spanned by the spliced location.+contigSpan :: SpliceLoc -> ContigLoc+contigSpan sl = let (start, end) = bounds sl+ in fromBoundsStrand start end (strand sl) mergeContigs :: [ContigLoc] -> [ContigLoc] mergeContigs [] = []
test/TestMain.hs view
@@ -2,6 +2,7 @@ module Main where +import Control.Applicative import Control.Monad import qualified Data.ByteString as BSW import qualified Data.ByteString.Char8 as BS@@ -43,6 +44,13 @@ , T "LocMap hit inside only" property_LocMap_hitIn , T "LocMap hit all" property_LocMap_hitAll , T "LocMap hit multi" property_LocMap_hitMulti++ , T "Locatable hit Contig" property_Locatable_hitContig+ , T "Locatable hit Spliced" property_Locatable_hitSpliced+ , T "Locatable hit Contig Extended" property_Locatable_locHitContig+ , T "Locatable hit Spliced Extended" property_Locatable_locHitSpliced+ , T "Locatable compat Contig Ext'd" property_Locatable_locCompatContig+ , T "Locatable compat Spliced Ext'd" property_Locatable_locCompatSpliced , T "Pos revCompl" test_Pos_revCompl , T "Pos atPos" property_Pos_atPos@@ -445,7 +453,7 @@ ploc = Loc.fromPosLen pos 1 lm = LM.insertLoc contig contig (LM.emptyLM binsz) ishit = not $ null (LM.queryLoc ploc lm)- in collect ishit $ ishit || not isin+ in ishit || not isin property_LocMap_hitMulti :: Loc.ContigLoc -> Loc.ContigLoc -> Loc.ContigLoc -> Pos.Pos -> Property property_LocMap_hitMulti ca cb cc pos =@@ -460,10 +468,128 @@ hita = ca `elem` LM.queryLoc ploc lm hitb = cb `elem` LM.queryLoc ploc lm hitc = cc `elem` LM.queryLoc ploc lm- in collect (hita, hitb, hitc) $- and [ hita || not isina,+ in and [ hita || not isina, hitb || not isinb, hitc || not isinc ]++property_Locatable_hitContig :: [Loc.ContigLoc] -> Pos.Pos -> Property+property_Locatable_hitContig cs pos =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isins = map (isJust . Loc.posInto pos) cs+ ploc = OnSeq chr $ Loc.fromPosLen pos 1+ seqlocs = map ((OnSeq chr) . SpLoc.singleton) cs+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc ploc slm) locables+ hits = map (\l -> l `elem` LM.queryLocatable Nothing ploc slm) locables+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isins binhits+ , zipWith (\isin hit -> hit == isin) isins hits+ ]++property_Locatable_hitSpliced :: [SpLoc.SpliceLoc] -> Pos.Pos -> Property+property_Locatable_hitSpliced cs pos =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isins = map (isJust . Loc.posInto pos) cs+ ploc = OnSeq chr . SpLoc.singleton $ Loc.fromPosLen pos 1+ seqlocs = map (OnSeq chr) cs+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc ploc slm) locables+ hits = map (\l -> l `elem` LM.queryLocatable Nothing ploc slm) locables+ compats = map (\l -> l `elem` LM.queryLocCompatible Nothing ploc slm) locables+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isins binhits+ , zipWith (\isin hit -> hit || not isin) isins hits+ , zipWith (\isin compat -> isin == compat) isins compats+ ]++property_Locatable_locHitContig :: [Loc.ContigLoc] -> Loc.ContigLoc -> Property+property_Locatable_locHitContig cs qy =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isInFwds = map (Loc.overlaps qy) cs+ isInRevs = map (Loc.overlaps (revCompl qy)) cs+ isIns = zipWith (||) isInFwds isInRevs+ qyseqloc = OnSeq chr qy+ seqlocs = map ((OnSeq chr) . SpLoc.singleton) cs+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc qyseqloc slm) locables+ fwdHits = map (\l -> l `elem` LM.queryLocatable (Just Plus) qyseqloc slm) locables+ revHits = map (\l -> l `elem` LM.queryLocatable (Just Minus) qyseqloc slm) locables+ allHits = zipWith (||) fwdHits revHits+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isIns binhits+ , zipWith (\isin hit -> hit == isin) isInFwds fwdHits+ , zipWith (\isin hit -> hit == isin) isInRevs revHits+ , zipWith (\isin hit -> hit == isin) isIns allHits+ ]++property_Locatable_locHitSpliced :: [SpLoc.SpliceLoc] -> Loc.ContigLoc -> Property+property_Locatable_locHitSpliced ls qy =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isInFwds = map (Loc.overlaps qy . SpLoc.contigSpan) ls+ isInRevs = map (Loc.overlaps (revCompl qy) . SpLoc.contigSpan) ls+ isIns = zipWith (||) isInFwds isInRevs+ qyseqloc = OnSeq chr qy+ seqlocs = map (OnSeq chr) ls+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc qyseqloc slm) locables+ fwdHits = map (\l -> l `elem` LM.queryLocatable (Just Plus) qyseqloc slm) locables+ revHits = map (\l -> l `elem` LM.queryLocatable (Just Minus) qyseqloc slm) locables+ allHits = zipWith (||) fwdHits revHits+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isIns binhits+ , zipWith (\isin hit -> hit == isin) isInFwds fwdHits+ , zipWith (\isin hit -> hit == isin) isInRevs revHits+ , zipWith (\isin hit -> hit == isin) isIns allHits+ ]++property_Locatable_locCompatContig :: [Loc.ContigLoc] -> Loc.ContigLoc -> Property+property_Locatable_locCompatContig cs qy =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isInFwds = map ((== (Just Plus)) . liftM Loc.strand . Loc.clocInto qy) cs+ isInRevs = map ((== (Just Minus)) . liftM Loc.strand . Loc.clocInto qy) cs+ isIns = zipWith (||) isInFwds isInRevs+ qyseqloc = OnSeq chr (SpLoc.singleton qy)+ seqlocs = map ((OnSeq chr) . SpLoc.singleton) cs+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc qyseqloc slm) locables+ fwdHits = map (\l -> l `elem` LM.queryLocCompatible (Just Plus) qyseqloc slm) locables+ revHits = map (\l -> l `elem` LM.queryLocCompatible (Just Minus) qyseqloc slm) locables+ allHits = zipWith (||) fwdHits revHits+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isIns binhits+ , zipWith (\isin hit -> hit == isin) isInFwds fwdHits+ , zipWith (\isin hit -> hit == isin) isInRevs revHits+ , zipWith (\isin hit -> hit == isin) isIns allHits+ ]++property_Locatable_locCompatSpliced :: [SpLoc.SpliceLoc] -> Loc.ContigLoc -> Property+property_Locatable_locCompatSpliced ls qy =+ forAll genPositiveOffset $ \binsz ->+ forAll genName $ \chr ->+ let isInFwds = map ((== (Just Plus)) . liftM Loc.strand . Loc.clocInto qy) ls+ isInRevs = map ((== (Just Minus)) . liftM Loc.strand . Loc.clocInto qy) ls+ isIns = zipWith (||) isInFwds isInRevs+ qyseqloc = OnSeq chr (SpLoc.singleton qy)+ seqlocs = map (OnSeq chr) ls+ locables = [ LM.WithLocation sl sl | sl <- seqlocs ]+ slm = LM.locatableSeqLocMap binsz locables+ binhits = map (\l -> l `elem` LM.querySeqLoc qyseqloc slm) locables+ fwdHits = map (\l -> l `elem` LM.queryLocCompatible (Just Plus) qyseqloc slm) locables+ revHits = map (\l -> l `elem` LM.queryLocCompatible (Just Minus) qyseqloc slm) locables+ allHits = zipWith (||) fwdHits revHits+ in and $ concat [ zipWith (\isin binhit -> binhit || not isin) isIns binhits+ , zipWith (\isin hit -> hit == isin) isInFwds fwdHits+ , zipWith (\isin hit -> hit == isin) isInRevs revHits+ , zipWith (\isin hit -> hit == isin) isIns allHits+ ]++instance (Arbitrary a) => Arbitrary (OnSeq a) where+ arbitrary = OnSeq <$> arbitrary <*> arbitrary -- Utilities