packages feed

currycarbon-0.1.1.0: src/Currycarbon/CLI/RunCalibrate.hs

{-# LANGUAGE BangPatterns #-}

module Currycarbon.CLI.RunCalibrate
    (CalibrateOptions (..), runCalibrate) where

import           Currycarbon.CalCurves.Intcal20
import           Currycarbon.Calibration.Calibration
import           Currycarbon.Parsers
import           Currycarbon.Types
import           Currycarbon.Utils

import           Control.Monad      (when, unless)
import           Data.Either        (rights, lefts, isRight)
import           Data.Foldable      (forM_)
import           Data.Maybe         (fromJust, isJust)
import           System.IO          (hPutStrLn, stderr)

-- | A data type to represent the options to the CLI module function runCalibrate
data CalibrateOptions = CalibrateOptions {
        _calibrateUncalC14 :: [UncalC14]  -- ^ Uncalibrated dates that should be calibrated
      , _calibrateUncalC14File :: [FilePath] -- ^ List of files with uncalibrated dates to be calibrated
      , _calibrateCalCurveFile :: Maybe FilePath -- ^ Path to a .14c file
      , _calibrateCalibrationMethod :: CalibrationMethod -- ^ Calibration algorithm that should be used
      , _calibrateAllowOutside :: Bool -- ^ Allow calibration to run outside of the range of the calibration curve 
      , _calibrateDontInterpolateCalCurve :: Bool -- ^ Don't interpolate the calibration curve
      , _calibrateQuiet :: Bool -- ^ Suppress the printing of calibration results to the command line
      , _calibrateDensityFile :: Maybe FilePath -- ^ Path to an output file (see CLI documentation)
      , _calibrateHDRFile :: Maybe FilePath -- ^ Path to an output file
      , _calibrateCalCurveSegmentFile :: Maybe FilePath -- ^ Path to an output file 
      , _calibrateCalCurveMatrixFile :: Maybe FilePath -- ^ Path to an output file 
    }

-- | Interface function to trigger calibration from the command line
runCalibrate :: CalibrateOptions -> IO ()
runCalibrate (CalibrateOptions uncalDates uncalFile calCurveFile method allowOutside noInterpolate quiet densityFile hdrFile calCurveSegmentFile calCurveMatrixFile) = do
    -- compile dates
    entitiesFromFile <- mapM readUncalC14FromFile uncalFile
    let uncalDatesRenamed = replaceEmptyNames $ uncalDates ++ concat entitiesFromFile
    if null uncalDatesRenamed
    then hPutStrLn stderr "Nothing to calibrate. See currycarbon -h for help"
    else do
        -- calibration
        hPutStrLn stderr "Calibrating..."
        calCurve <- maybe (return intcal20) readCalCurveFromFile calCurveFile
        let calConf = defaultCalConf {
              _calConfMethod = method
            , _calConfAllowOutside = allowOutside
            , _calConfInterpolateCalCurve = not noInterpolate
            }
        let errorOrCalPDFs = calibrateDates calConf calCurve uncalDatesRenamed
        handleDates True calCurve $ zip uncalDatesRenamed errorOrCalPDFs
        where
            handleDates :: Bool -> CalCurveBP -> [(UncalC14, Either CurrycarbonException CalPDF)] -> IO ()
            handleDates _ _ [] = hPutStrLn stderr "Done."
            handleDates True calCurve (x:xs) = case x of
                (_, Left ex)        -> printEx ex                          >> handleDates True  calCurve xs
                (uncal, Right cPDF) -> handleFirstDate calCurve uncal cPDF >> handleDates False calCurve xs
            handleDates False calCurve (x:xs) = case x of
                (_, Left ex)        -> printEx ex                          >> handleDates False calCurve xs
                (uncal, Right cPDF) -> handleOtherDate uncal cPDF          >> handleDates False calCurve xs
            handleFirstDate :: CalCurveBP -> UncalC14 -> CalPDF -> IO ()
            handleFirstDate calCurve uncal calPDF = do
                -- calcurve segment or calcurve matrix file
                if isJust calCurveSegmentFile || isJust calCurveMatrixFile 
                then do
                    hPutStrLn stderr $ 
                        "The calCurveSegment file and the calCurveMatrix file only consider the first date, " ++
                        renderUncalC14 uncal
                    let calCurveSegment = prepareCalCurveSegment (not noInterpolate) $ getRelevantCalCurveSegment uncal calCurve
                    when (isJust calCurveSegmentFile) $ 
                        writeCalCurve (fromJust calCurveSegmentFile) calCurveSegment
                    when (isJust calCurveMatrixFile) $ 
                        writeCalCurveMatrix (fromJust calCurveMatrixFile) $ 
                        makeCalCurveMatrix (uncalToPDF uncal) calCurveSegment
                else do
                    -- other output
                    let calC14 = refineCalDate calPDF
                    unless quiet              $ putStrLn $ renderCalDatePretty (uncal, calPDF, calC14)
                    when (isJust hdrFile)     $ writeCalC14 (fromJust hdrFile) calC14
                    when (isJust densityFile) $ writeCalPDF (fromJust densityFile) calPDF
            handleOtherDate :: UncalC14 -> CalPDF -> IO ()
            handleOtherDate uncal calPDF = do
                let calC14 = refineCalDate calPDF
                unless quiet              $ putStrLn $ renderCalDatePretty (uncal, calPDF, calC14)
                when (isJust hdrFile)     $ appendCalC14 (fromJust hdrFile) calC14
                when (isJust densityFile) $ appendCalPDF (fromJust densityFile) calPDF
            printEx :: CurrycarbonException -> IO ()
            printEx ex = hPutStrLn stderr $ renderCurrycarbonException ex

-- | Helper function to replace empty input names with a sequence of numbers, 
-- to get each input date an unique identifier
replaceEmptyNames :: [UncalC14] -> [UncalC14]
replaceEmptyNames xs =
    zipWith replaceName xs [1..]
    where
        replaceName :: UncalC14 -> Int -> UncalC14
        replaceName (UncalC14 name mean std) number =
            if name == "unknownSampleName"
            then UncalC14 (show number) mean std
            else UncalC14 name mean std