packages feed

srtree-2.0.1.2: apps/srtools/Args.hs

module Args where

import Data.Char ( toLower, toUpper )
import Data.List ( intercalate )
import Algorithm.SRTree.Likelihoods ( Distribution (..) )
import Algorithm.SRTree.ConfidenceIntervals ( PType (..) )
import Options.Applicative
import Text.ParseSR ( SRAlgs (..) )
import Text.Read ( readMaybe )

-- Data type to store command line arguments
data Args = Args
    {   from        :: SRAlgs
      , infile      :: String
      , outfile     :: String
      , dataset     :: String
      , test        :: String
      , niter       :: Int
      , hasHeader   :: Bool
      , simpl       :: Bool
      , dist        :: Distribution
      , restart     :: Bool
      , rseed       :: Int
      , toScreen    :: Bool
      , useProfile  :: Bool
      , simple      :: Bool
      , alpha       :: Double
      , ptype       :: PType
    } deriving Show

-- parser of command line arguments
opt :: Parser Args
opt = Args
   <$> option sralgsReader
       ( long "from"
       <> short 'f'
       <> metavar ("[" <> intercalate "|" sralgsHelp <> "]")
       <> help "Input expression format" )
   <*> strOption
       ( long "input"
       <> short 'i'
       <> metavar "INPUT-FILE"
       <> showDefault
       <> value ""
       <> help "Input file containing expressions. \
               \ Empty string gets expression from stdin." )
   <*> strOption
       ( long "output"
       <> short 'o'
       <> metavar "OUTPUT-FILE"
       <> showDefault
       <> value ""
       <> help "Output file to store the stats in CSV format. \
                \ Empty string prints expressions to stdout." )
   <*> strOption
       ( long "dataset"
       <> short 'd'
       <> metavar "DATASET-FILENAME"
       <> help "Filename of the dataset used for optimizing the parameters. \
               \ Empty string omits stats that make use of the training data. \
               \ It will auto-detect and handle gzipped file based on gz extension. \
               \ It will also auto-detect the delimiter.\n\
               \ The filename can include extra information: \
               \ filename.csv:start:end:target:cols:yerr:xerr where start and end \
               \ corresponds to the range of rows that should be used for fitting,\
               \ target is the column index (or name) of the target variable and cols\
               \ is a comma separated list of column indices or names of the variables\
               \ in the same order as used by the symbolic model.\
               \ The yerr field corresponds to the column with the error of the target,\
               \ while xerr a comma separated indices of the columns with the error of the\
               \ variables. If nothing passed, it will ignore measurement errors." )
   <*> strOption
       ( long "test"
       <> metavar "TEST"
       <> showDefault
       <> value ""
       <> help "Filename of the test dataset.\
               \ Empty string omits stats that make use of the training data.\
               \ It can have additional information as in the training set,\
               \ but the validation range will be discarded." )
   <*> option auto
       ( long "niter"
       <> metavar "NITER"
       <> showDefault
       <> value 10
       <> help "Number of iterations for the optimization algorithm.")
   <*> switch
       ( long "hasheader"
       <> help "Uses the first row of the csv file as header.")
   <*> switch
        ( long "simplify"
        <> help "Apply basic simplification." )
   <*> option distRead
        ( long "distribution"
        <> metavar ("[" <> intercalate "|" distHelp <> "]")
        <> showDefault
        <> value Gaussian
        <> help "Minimize negative log-likelihood following one of\
                \ the avaliable distributions.\
                \ The default is Gaussian."
        )
   <*> switch
        ( long "restart"
        <> help "If set, it samples the initial values of\
                 \ the parameters using a Gaussian distribution N(0, 1),\
                 \ otherwise it uses the original values of the expression." )
   <*> option auto
       ( long "seed"
       <> metavar "SEED"
       <> showDefault
       <> value (-1)
       <> help "Random seed to initialize the parameters values.\
                \ Used only if restart is enabled.")
   <*> switch
        ( long "report"
        <> help "If set, reports the analysis in a user-friendly\
                \ format instead of csv. It will also include\
                \ confidence interval for the parameters and predictions" )
   <*> switch
        ( long "profile"
        <> help "If set, it will use profile likelihood to calculate the CIs." )
   <*> switch
       ( long "simple"
       <> help "If set, calculates only SSE.")
   <*> option auto
       ( long "alpha"
       <> metavar "ALPHA"
       <> showDefault
       <> value 0.05
       <> help "Significance level for confidence intervals.")
    <*> option auto
        ( long "ptype"
        <> metavar "[Bates | ODE | Constrained]"
        <> showDefault
        <> value Constrained
        <> help "Profile Likelihood method. Default: Constrained. NOTE: Constrained method only calculates the endpoint."
        )

-- helper functions to show the possible options
mkDescription :: Show a => [a] -> [String]
mkDescription = map (envelope '\'' . map toLower . show) 
  where
    envelope :: a -> [a] -> [a]
    envelope c xs = c : xs <> [c]
{-# INLINE mkDescription #-}

sralgsHelp :: [String]
sralgsHelp = mkDescription [toEnum 0 :: SRAlgs ..]
{-# INLINE sralgsHelp #-}

distHelp :: [String]
distHelp = mkDescription [toEnum 0 :: Distribution ..]
{-# INLINE distHelp #-}

-- helper functions to parse the options
mkReader :: Read a => String -> (a -> b) -> String -> ReadM b
mkReader err val sr = eitherReader 
                    $ case readMaybe sr of
                        Nothing -> pure (Left err)
                        Just x  -> pure (Right (val x))

sralgsReader :: ReadM SRAlgs
sralgsReader =
  str >>= (mkReader errMsg id . map toUpper)
  where
    errMsg = "unknown algorithm. Available options are " <> intercalate "," sralgsHelp

--s2Reader :: ReadM (Maybe Double)
--s2Reader =
--  str >>= \s -> mkReader ("wrong format " <> s) Just s

distRead :: ReadM Distribution
distRead =
  str >>= \s -> mkReader ("unsupported distribution " <> s) id (capitalize s)
  where
    capitalize ""     = ""
    capitalize (c:cs) = toUpper c : if length cs == 2 then map toUpper cs else map toLower cs