Skip to content

Instantly share code, notes, and snippets.

@jasonmhite
Created April 12, 2016 16:32
Show Gist options
  • Select an option

  • Save jasonmhite/c4c56d4c50fc673e658b71b8286c390d to your computer and use it in GitHub Desktop.

Select an option

Save jasonmhite/c4c56d4c50fc673e658b71b8286c390d to your computer and use it in GitHub Desktop.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Text.Parsec
import Text.Parsec.String
import Text.Parsec.Char
import Control.Monad
import Data.List
import Data.Either
import System.Console.ArgParser hiding (Parser)
import Control.Applicative hiding (many)
import qualified Data.List.Split as S
import qualified Data.Map.Strict as M
data ChannelMask = ChannelMask [Bool] deriving (Show, Eq)
data Event = Event { _eventClockTick :: Int, _eventMask :: ChannelMask } deriving Show
data Record = Record { _recordEventCount :: Int, _recordEvents :: [Event] } deriving Show
data ListModeData = ListModeData { _listModeDataSegmentCount :: Int, _listModeRecords :: [Record] } deriving Show
simpleParse :: Parser a -> String -> String -> Either ParseError a
simpleParse = parse
parseChannelMask :: Parser ChannelMask
parseChannelMask = do
bits <- many1 digit
return $ ChannelMask (map ind bits)
where ind '1' = True
ind '0' = False
ind _ = error "Bad bit in channel mask"
parseTick :: Parser Int
parseTick = do
v <- many digit `sepBy` char ','
return $ (read . concat) v
parseEvent :: Parser Event
parseEvent = do
skipMany space
tick <- parseTick
skipMany space
mask <- parseChannelMask
return Event {
_eventClockTick = tick
, _eventMask = mask
}
parseEventCount :: Parser Int
parseEventCount = do
string "events:"
skipMany space
count <- many1 digit
return . read $ count
parseRecord :: Parser Record
parseRecord = do
counts <- parseEventCount
endOfLine
string "clock tick"
skipMany space
string "channel mask"
skipMany space
events <- parseEvent `sepEndBy` newline
return Record {
_recordEventCount = counts
, _recordEvents = events
}
parseSegmentCount :: Parser Int
parseSegmentCount = do
string "segments:"
skipMany space
n <- many digit
return . read $ n
readListModeData :: String -> ListModeData
readListModeData l =
let txt = map unlines $ S.splitWhen ("" ==) $ lines l
Right segments = simpleParse parseSegmentCount "<segment count>" (head txt)
parsedRecords = rights $ map (simpleParse parseRecord "<record>") (tail txt)
in ListModeData {
_listModeDataSegmentCount = segments
, _listModeRecords = parsedRecords
}
sortPredicate :: Int -> Int -> (Int, Int) -> (Int, Int) -> Bool
sortPredicate offset base (a, _) (b, _) =
quot (a - offset) base == quot (b - offset) base
type BinTally = M.Map Int Int
printTally :: BinTally -> String
printTally x = unlines $ map (\(x, y) -> show x ++ " " ++ show y) (M.toList x)
countEvents :: [Event] -> [(Int, Int)]
countEvents e =
let eventTimes = map _eventClockTick e
masks = map _eventMask e
mcounts = fmap countMasks masks
in zip eventTimes mcounts
where c True = 1
c False = 0
countMasks (ChannelMask m) = sum $ fmap c m
updateCounts :: (Int, Int) -> BinTally -> BinTally
updateCounts = uncurry (M.insertWith (+))
combineTallies :: BinTally -> BinTally -> BinTally
combineTallies = M.unionWith (+)
histogram :: Ord a => [a] -> M.Map a Int
histogram xs = M.fromList [(head l, length l) | l <- group (sort xs)]
countBetween' :: Int -> [Int] -> Int
countBetween' tot [x, y] = tot + y - x - 1
countBetween' tot (x:y:xs) = countBetween' (tot + y - x - 1) (y:xs)
countBetween :: [Int] -> Int
countBetween l = countBetween' (head l) l
sequentialBin :: Int -> [Event] -> BinTally
sequentialBin binWidth events =
let e = countEvents events
t0 = fst . head $ e
cpred = sortPredicate t0 binWidth
{-groupedCounts = (map . map) snd $ groupBy cpred e-}
groupedCounts = groupBy cpred e
ticks = map (\c -> quot ((fst . head) c - t0) binWidth) groupedCounts
zeroBins = countBetween ticks
redCounts = (map . map) snd $ groupedCounts
in M.insert 0 zeroBins $ histogram (map sum redCounts)
data CliArgs = CliArgs { _cliFname :: String, _cliBinWidth :: Int }
cliParser :: ParserSpec CliArgs
cliParser = CliArgs
`parsedBy` reqPos "filename" `Descr` "Name of file to read from"
`andBy` reqPos "bin_width" `Descr` "Width of binning window"
cliInterface :: IO (CmdLnInterface CliArgs)
cliInterface =
(`setAppDescr` "Read nPOD list mode file")
<$> (`setAppEpilog` "Prints multiplicity bins to stdout in csv format")
<$> mkApp cliParser
runTally :: CliArgs -> IO ()
runTally cli = do
file <- readFile . _cliFname $ cli
let records = readListModeData file
ev = map _recordEvents . _listModeRecords $ records
recordTallies = map (sequentialBin (_cliBinWidth cli)) ev
putStr . printTally $ foldr combineTallies M.empty recordTallies
main :: IO ()
main = cliInterface >>= flip runApp runTally
@jasonmhite

jasonmhite commented Jul 30, 2016

Copy link
Copy Markdown
Author

Hmm... note to self, probably should have used typeclasses instead of printtally, something like
the following would be more Haskelly...

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}

{- add these extensions for instancing type synonyms
 - note to self: the original type can't have an instance of show already AFAIK.
 - there has to be some way to get around that
 -}

...

-- down by the BinTally decl
type BinTally = M.Map Int Int

instance Show BinTally where
    show x = unlines $ map (\(x, y) -> show x ++ " " ++ show y) (M.toList x)

...

-- down in runTally, last line becomes
    print $ foldr combineTallies M.empty recordTallies

Edit: apparently not on the show override thing. Seems like I'm maybe abusing show as is? Dunno but I don't care.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment