{-|
Module      : ITEA
Description : Main program to run ITEA with a configuration file.
Copyright   : (c) Fabricio Olivetti de Franca, 2020
License     : GPL-3
Maintainer  : fabricio.olivetti@gmail.com
Stability   : experimental
Portability : POSIX

Main program to run ITEA with a configuration file.
-}
module RunConfig where

import System.Directory

import Data.ConfigFile
import Data.Either.Utils

import ITEA.Regression
import ITEA.Config
import IT.Regression

-- | Algorithm Choice 
--
-- ITEA is the mutation-based Symbolic Regression for IT representation
-- FI2POP splits the population as feasible and infeasible to deal with constraints
data Alg = ITEA | FI2POP deriving (Int -> Alg -> ShowS
[Alg] -> ShowS
Alg -> String
(Int -> Alg -> ShowS)
-> (Alg -> String) -> ([Alg] -> ShowS) -> Show Alg
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Alg] -> ShowS
$cshowList :: [Alg] -> ShowS
show :: Alg -> String
$cshow :: Alg -> String
showsPrec :: Int -> Alg -> ShowS
$cshowsPrec :: Int -> Alg -> ShowS
Show, ReadPrec [Alg]
ReadPrec Alg
Int -> ReadS Alg
ReadS [Alg]
(Int -> ReadS Alg)
-> ReadS [Alg] -> ReadPrec Alg -> ReadPrec [Alg] -> Read Alg
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Alg]
$creadListPrec :: ReadPrec [Alg]
readPrec :: ReadPrec Alg
$creadPrec :: ReadPrec Alg
readList :: ReadS [Alg]
$creadList :: ReadS [Alg]
readsPrec :: Int -> ReadS Alg
$creadsPrec :: Int -> ReadS Alg
Read)

-- | Read the option 'x' located at section 'cat' from configuration 'cp'.
getSetting :: Get_C a => ConfigParser -> SectionSpec -> OptionSpec -> a
getSetting :: ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
cat String
x = Either CPError a -> a
forall e a. Show e => Either e a -> a
forceEither (Either CPError a -> a) -> Either CPError a -> a
forall a b. (a -> b) -> a -> b
$ ConfigParser -> String -> String -> Either CPError a
forall a (m :: * -> *).
(Get_C a, MonadError CPError m) =>
ConfigParser -> String -> String -> m a
get ConfigParser
cp String
cat String
x

-- | get a setting value if it exists in the config file, or return a default value 
getWithDefault :: Get_C a => a -> ConfigParser -> SectionSpec -> OptionSpec -> a
getWithDefault :: a -> ConfigParser -> String -> String -> a
getWithDefault a
def ConfigParser
cp String
cat String
x = 
  case ConfigParser -> String -> String -> Either CPError a
forall a (m :: * -> *).
(Get_C a, MonadError CPError m) =>
ConfigParser -> String -> String -> m a
get ConfigParser
cp String
cat String
x of
    Right a
y -> a
y      
    Left  (NoOption String
_, String
_) -> a
def
    Left  (NoSection String
_, String
_) -> a
def
    Left (CPErrorData
e,String
_) -> String -> a
forall a. HasCallStack => String -> a
error (CPErrorData -> String
forall a. Show a => a -> String
show CPErrorData
e)

-- | Read the config file and run the algorithm.
runWithConfig :: String -> IO ()
runWithConfig :: String -> IO ()
runWithConfig String
fname = do
  ConfigParser
cp <- Either CPError ConfigParser -> ConfigParser
forall e a. Show e => Either e a -> a
forceEither (Either CPError ConfigParser -> ConfigParser)
-> IO (Either CPError ConfigParser) -> IO ConfigParser
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ConfigParser -> String -> IO (Either CPError ConfigParser)
forall (m :: * -> *).
MonadError CPError m =>
ConfigParser -> String -> IO (m ConfigParser)
readfile ConfigParser
emptyCP String
fname
  let
    (Int
expmin, Int
expmax)   = ConfigParser -> String -> String -> (Int, Int)
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Mutation"  String
"exponents"
    (Int
termmin, Int
termmax) = ConfigParser -> String -> String -> (Int, Int)
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Mutation"  String
"termlimit"
    nzExps :: Int
nzExps             = ConfigParser -> String -> String -> Int
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Mutation"  String
"nonzeroexps"
    tfuncs :: [Transformation]
tfuncs             = ConfigParser -> String -> String -> [Transformation]
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Mutation"  String
"transfunctions"
    perf_mes :: [String]
perf_mes           = ConfigParser -> String -> String -> [String]
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Mutation"  String
"measures"
    
    trainname :: String
trainname          = ConfigParser -> String -> ShowS
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"IO"   String
"train"
    testname :: String
testname           = ConfigParser -> String -> ShowS
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"IO"   String
"test"
    task :: Task
task               = ConfigParser -> String -> String -> Task
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"IO"   String
"task"
    logg :: Output
logg               = ConfigParser -> String -> String -> Output
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"IO"   String
"log"
    
    nPop :: Int
nPop               = ConfigParser -> String -> String -> Int
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Algorithm"   String
"npop"
    nGens :: Int
nGens              = ConfigParser -> String -> String -> Int
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Algorithm"   String
"ngens"
    alg :: Alg
alg                = ConfigParser -> String -> String -> Alg
forall a. Get_C a => ConfigParser -> String -> String -> a
getSetting ConfigParser
cp String
"Algorithm"   String
"algorithm"
    
    penalty :: Penalty
penalty            = Penalty -> ConfigParser -> String -> String -> Penalty
forall a. Get_C a => a -> ConfigParser -> String -> String -> a
getWithDefault Penalty
NoPenalty ConfigParser
cp String
"Constraints" String
"penalty"
    shapes :: [Shape]
shapes             = [Shape] -> ConfigParser -> String -> String -> [Shape]
forall a. Get_C a => a -> ConfigParser -> String -> String -> a
getWithDefault [] ConfigParser
cp String
"Constraints" String
"shapes"
    domains :: Maybe [(Double, Double)]
domains            = Maybe [(Double, Double)]
-> ConfigParser -> String -> String -> Maybe [(Double, Double)]
forall a. Get_C a => a -> ConfigParser -> String -> String -> a
getWithDefault Maybe [(Double, Double)]
forall a. Maybe a
Nothing ConfigParser
cp String
"Constraints" String
"domains"
    
    -- validate the configurations
    mutCfg :: MutationCfg
mutCfg =  UncheckedMutationCfg -> MutationCfg
forall a b. Valid a b => a -> b
validateConfig
           (UncheckedMutationCfg -> MutationCfg)
-> UncheckedMutationCfg -> MutationCfg
forall a b. (a -> b) -> a -> b
$  Int -> Int -> UncheckedMutationCfg
exponents Int
expmin Int
expmax
           UncheckedMutationCfg
-> UncheckedMutationCfg -> UncheckedMutationCfg
forall a. Semigroup a => a -> a -> a
<> Int -> Int -> UncheckedMutationCfg
termLimit Int
termmin Int
termmax
           UncheckedMutationCfg
-> UncheckedMutationCfg -> UncheckedMutationCfg
forall a. Semigroup a => a -> a -> a
<> Int -> UncheckedMutationCfg
nonzeroExps Int
nzExps
           UncheckedMutationCfg
-> UncheckedMutationCfg -> UncheckedMutationCfg
forall a. Semigroup a => a -> a -> a
<> [Transformation] -> UncheckedMutationCfg
transFunctions [Transformation]
tfuncs
           UncheckedMutationCfg
-> UncheckedMutationCfg -> UncheckedMutationCfg
forall a. Semigroup a => a -> a -> a
<> [String] -> UncheckedMutationCfg
measures [String]
perf_mes 

    datasetCfg :: Datasets
datasetCfg =  UncheckedDatasets -> Datasets
forall a b. Valid a b => a -> b
validateConfig
               (UncheckedDatasets -> Datasets) -> UncheckedDatasets -> Datasets
forall a b. (a -> b) -> a -> b
$  String -> UncheckedDatasets
trainingset String
trainname
               UncheckedDatasets -> UncheckedDatasets -> UncheckedDatasets
forall a. Semigroup a => a -> a -> a
<> String -> UncheckedDatasets
testset String
testname

  -- run ITEA with the given configuration
  case Alg
alg of
    Alg
ITEA   -> AlgRunner
-> Datasets
-> MutationCfg
-> Output
-> Int
-> Int
-> Task
-> Penalty
-> [Shape]
-> Maybe [(Double, Double)]
-> IO ()
run AlgRunner
runITEA Datasets
datasetCfg MutationCfg
mutCfg Output
logg Int
nPop Int
nGens Task
task Penalty
penalty [Shape]
shapes Maybe [(Double, Double)]
domains
    Alg
FI2POP -> AlgRunner
-> Datasets
-> MutationCfg
-> Output
-> Int
-> Int
-> Task
-> Penalty
-> [Shape]
-> Maybe [(Double, Double)]
-> IO ()
run AlgRunner
runFI2POP Datasets
datasetCfg MutationCfg
mutCfg Output
logg Int
nPop Int
nGens Task
task Penalty
penalty [Shape]
shapes Maybe [(Double, Double)]
domains

-- | Parse the filename from the system arguments.
parseConfigFile :: [String] -> IO ()
parseConfigFile :: [String] -> IO ()
parseConfigFile [String
fname] = do Bool
exist <- String -> IO Bool
doesFileExist String
fname
                             if Bool
exist
                              then String -> IO ()
runWithConfig String
fname
                              else String -> IO ()
putStrLn String
"Config file does not exist."
parseConfigFile [String]
_       = String -> IO ()
putStrLn String
"Usage: ./itea config config-file-name"