packages feed

srtree-2.0.0.3: apps/srtools/IO.hs

{-# language BlockArguments #-}
{-# language LambdaCase #-}
module IO where

import System.IO ( hClose, hPutStrLn, openFile, stderr, stdout, IOMode(WriteMode), Handle )
import qualified Data.Massiv.Array as A
import Data.List ( intercalate )
import Control.Monad ( unless, forM_ )
import System.Random ( StdGen )

import Data.SRTree ( SRTree (..), Fix (..), var, floatConstsToParam, relabelVars )
import Algorithm.SRTree.Likelihoods ( Distribution (..) )
import Algorithm.SRTree.ConfidenceIntervals ( printCI, BasicStats(_stdErr, _corr), CI )
import qualified Data.SRTree.Print as P
import Data.SRTree.Eval ( compMode )

import Args ( Args(outfile, alpha,dist,niter) )
import Report
import Data.SRTree.Recursion ( cata )

import Debug.Trace ( trace, traceShow )

-- Header of CSV file
csvHeader :: String
csvHeader = intercalate "," (basicFields <> optFields <> modelFields)
{-# inline csvHeader #-}

csvHeaderSimple :: String
csvHeaderSimple = intercalate "," (basicFields <> optFields)
{-# inline csvHeaderSimple #-}

-- Open file if filename is not empty
openWriteWithDefault :: Handle -> String -> IO Handle
openWriteWithDefault dflt ""    = pure dflt
openWriteWithDefault _    fname = openFile fname WriteMode
{-# INLINE openWriteWithDefault #-}

-- procecss a single tree and return all the available stats
processTree :: Args        -- command line arguments
            -> StdGen      -- random number generator
            -> Datasets    -- datasets
            -> Fix SRTree  -- expression in tree format
            -> Int         -- index of the parsed expression 
            -> (BasicInfo, SSE, SSE, Info, (BasicStats, [CI], [CI], [CI], [CI]))
processTree args seed dset t ix = (basic, sseOrig, sseOpt, info, cis)
  where
    (tree, theta0)  = floatConstsToParam t

    basic   = getBasicStats args seed dset tree theta0 ix
    treeVal = case (_xVal dset, _yVal dset) of
                (Nothing, _) -> _expr basic
                (_, Nothing) -> _expr basic
                (Just xV, Just yV) -> _expr $ getBasicStats args seed dset{_xTr = xV, _yTr = yV} tree theta0 ix
    sseOrig = getSSE dset t
    sseOpt  = getSSE dset (_expr basic)
    info    = getInfo args dset (_expr basic) treeVal
    cis     = getCI args dset basic (alpha args)

processTreeSimple :: Args        -- command line arguments
            -> StdGen      -- random number generator
            -> Datasets    -- datasets
            -> Fix SRTree  -- expression in tree format
            -> Int         -- index of the parsed expression
            -> (BasicInfo, SSE, SSE)
processTreeSimple args seed dset t ix = (basic, sseOrig, sseOpt)
  where
    (tree, theta0)  = floatConstsToParam t

    basic   = getBasicStats args seed dset tree theta0 ix
    treeVal = case (_xVal dset, _yVal dset) of
                (Nothing, _) -> _expr basic
                (_, Nothing) -> _expr basic
                (Just xV, Just yV) -> _expr $ getBasicStats args seed dset{_xTr = xV, _yTr = yV} tree theta0 ix
    sseOrig = getSSE dset t
    sseOpt  = getSSE dset (_expr basic)

-- print the results to a csv format (except CI)
printResults :: Args -> StdGen -> Datasets -> [String] -> [Either String (Fix SRTree)] -> IO ()
printResults args seed dset varnames exprs  = do
  hStat <- openWriteWithDefault stdout (outfile args)
  hPutStrLn hStat csvHeader 
  forM_ (zip [0..] exprs) 
     \(ix, tree) -> 
         case tree of
           Left  err -> hPutStrLn stderr ("invalid expression: " <> err)
           Right t   -> let treeData = processTree args seed dset t ix
                        in hPutStrLn hStat (toCsv treeData varnames)
  unless (null (outfile args)) (hClose hStat)

printResultsSimple :: Args -> StdGen -> Datasets -> [String] -> [Either String (Fix SRTree)] -> IO ()
printResultsSimple args seed dset varnames exprs  = do
  hStat <- openWriteWithDefault stdout (outfile args)
  hPutStrLn hStat csvHeaderSimple
  forM_ (zip [0..] exprs)
     \(ix, tree) ->
         case tree of
           Left  err -> hPutStrLn stderr ("invalid expression: " <> err)
           Right t   -> let treeData = processTreeSimple args seed dset t ix
                        in hPutStrLn hStat (toCsvSimple treeData varnames)
  unless (null (outfile args)) (hClose hStat)

-- change the stats into a string
toCsv :: (BasicInfo, SSE, SSE, Info, e) -> [String] -> String
toCsv (basic, sseOrig, sseOpt, info, _) varnames = intercalate "," (sBasic <> sSSEOrig <> sSSEOpt <> sInfo)
  where
    sBasic    = [ show (_index basic), show (_fname basic), P.showExprWithVars varnames (_expr basic)
                , show (_nNodes basic), show (_nParams basic)
                , intercalate ";" (map show (_params basic))
                , show (_nEvals basic)
                ]
    sSSEOrig  = map (showF sseOrig) [_sseTr, _sseVal, _sseTe]
    sSSEOpt   = map (showF sseOpt)  [_sseTr, _sseVal, _sseTe]
    sInfo     = map (showF info) [_bic, _bicVal, _aic, _aicVal, _evidence, _evidenceVal, _mdl, _mdlFreq, _mdlLatt, _mdlVal, _mdlFreqVal, _mdlLattVal, _nllTr, _nllVal, _nllTe, _cc, _cp]
              <> [intercalate ";" (map show (_fisher info))]
    showF p f = show (f p)

toCsvSimple :: (BasicInfo, SSE, SSE) -> [String] -> String
toCsvSimple (basic, sseOrig, sseOpt) varnames = intercalate "," (sBasic <> sSSEOrig <> sSSEOpt)
  where
    sBasic    = [ show (_index basic), show (_fname basic), P.showExprWithVars varnames (_expr basic)
                , show (_nNodes basic), show (_nParams basic)
                , intercalate ";" (map show (_params basic))
                , show (_nEvals basic)
                ]
    sSSEOrig  = map (showF sseOrig) [_sseTr, _sseVal, _sseTe]
    sSSEOpt   = map (showF sseOpt)  [_sseTr, _sseVal, _sseTe]
    showF p f = show (f p)

-- get trees of transformed features
getTransformedFeatures :: Fix SRTree -> (Fix SRTree, [Fix SRTree])
getTransformedFeatures = cata $
  \case
     Var   ix                   -> (Fix $ Var ix, [])
     Param ix                   -> (Fix $ Param ix, [])
     Const  x                   -> (Fix $ Const x, [])
     Uni    f (t, vars)         -> (Fix $ Uni f t, vars)
     Bin   op (l, vs1) (r, vs2) -> case (hasNoParam l, hasNoParam r) of
                                     (False, True)  -> let vs = vs1 <> vs2
                                                       in (Fix $ Bin op l (var $ length vs), vs <> [r])
                                     (True, False)  -> let vs = vs1 <> vs2
                                                       in (Fix $ Bin op (var $ length vs) r, vs <> [l])
                                     (    _,    _)   -> (Fix $ Bin op l r, vs1 <> vs2) -- vs1 == vs2 == []

 where
   hasNoParam = cata $
     \case
        Var ix     -> True
        Param ix   -> False
        Const x    -> if floor x == ceiling x then True else False
        Uni f t    -> t
        Bin op l r -> l && r

allAreVars :: [Fix SRTree] -> Bool
allAreVars = all isOnlyVar
  where
    isOnlyVar (Fix (Var _)) = True
    isOnlyVar _             = False

-- print the information on screen (including CIs)
printResultsScreen :: Args -> StdGen -> Datasets -> [String] -> String -> [Either String (Fix SRTree)] -> IO ()
printResultsScreen args seed dset varnames targt exprs = do
  forM_ (zip [0..] exprs) 
    \(ix, tree) -> 
        case tree of
          Left  err -> do putStrLn ("invalid expression: " <> err)
          Right t   -> let treeData = processTree args seed dset t ix
                        in printToScreen ix treeData
  where
    decim :: Int -> Double -> Double
    decim n x = (fromIntegral . (round :: Double -> Integer)) (x * 10^n) / 10^n
    sdecim n  = show . decim n
    nplaces   = 4


    printToScreen ix (basic, _, sseOpt, info, (sts, cis, pis_tr, pis_val, pis_te)) =
      do let (transformedT, newvars) = getTransformedFeatures (_expr basic)
             varnames' = ['z': show ix | ix <- [0 .. length newvars - 1]]
         putStrLn $ "=================== EXPR " <> show ix <> " =================="
         putStr   $ targt <> " ~ f(" <> intercalate ", " varnames <> ") = "
         putStrLn $ P.showExprWithVars varnames (_expr basic)

         unless (allAreVars newvars) do
          putStrLn "\nExpression and transformed features: "
          putStr   $ targt <> " ~ f(" <> intercalate ", " varnames' <> ") = "
          putStrLn $ P.showExprWithVars varnames' (relabelVars transformedT)
          forM_ (zip varnames' newvars) \(vn, tv) -> do
            putStrLn $ vn <> " = " <> P.showExprWithVars varnames tv

         putStrLn "\n---------General stats:---------\n"
         putStrLn $ "Number of nodes: " <> show (_nNodes basic)
         putStrLn $ "Number of params: " <> show (_nParams basic)
         putStrLn $ "theta = " <> show (_params basic)

         putStrLn "\n----------Performance:--------\n"
         putStrLn $ "SSE (train.): " <> sdecim nplaces (_sseTr sseOpt)
         putStrLn $ "SSE (val.): " <> sdecim nplaces (_sseVal sseOpt)
         putStrLn $ "SSE (test): " <> sdecim nplaces (_sseTe sseOpt)
         putStrLn $ "NegLogLiklihood (train.): " <> sdecim nplaces (_nllTr info)
         putStrLn $ "NegLogLiklihood (val.): " <> sdecim nplaces (_nllVal info)
         putStrLn $ "NegLogLiklihood (test): " <> sdecim nplaces (_nllTe info)

         putStrLn "\n------Selection criteria:-----\n"
         putStrLn $ "BIC: " <> sdecim nplaces (_bic info)
         putStrLn $ "AIC: " <> sdecim nplaces (_aic info)
         putStrLn $ "MDL: " <> sdecim nplaces (_mdl info)
         putStrLn $ "MDL (freq.): " <> sdecim nplaces (_mdlFreq info)
         putStrLn $ "Functional complexity: " <> sdecim nplaces (_cc info)
         putStrLn $ "Parameter complexity: " <> sdecim nplaces (_cp info)

         putStrLn "\n---------Uncertainties:----------\n"
         putStrLn "Correlation of parameters: " 
         putStrLn $ show $ A.map (decim 2) (_corr sts)
         putStrLn $ "Std. Err.: " <> show (A.map (decim nplaces) (_stdErr sts))
         putStrLn "\nConfidence intervals:\n\nlower <= val <= upper"
         mapM_ (printCI nplaces) cis
         putStrLn "\nConfidence intervals (predictions training):\n\nlower <= val <= upper"
         mapM_ (printCI nplaces) pis_tr
         unless (null pis_val) do
           putStrLn "\nConfidence intervals (predictions validation):\n\nlower <= val <= upper"
           mapM_ (printCI nplaces) pis_val
         unless (null pis_te) do
           putStrLn "\nConfidence intervals (predictions test):\n\nlower <= val <= upper"
           mapM_ (printCI nplaces) pis_te
         putStrLn "============================================================="