packages feed

srtree-db-0.1.3.0: app/RandomSampler.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module RandomSampler
  ( RandomSamplerOpts(..)
  , randomSamplerParser
  , runRandomSampler
  ) where

import qualified Data.Text as T
import Options.Applicative
import System.IO (hPutStrLn, hFlush, stdout, stderr)

import Data.SRTree.Print (showExpr)
import Algorithm.EqSat.Storage.Backend (SqlBackend(..), SqlValue(..), sqlToInt, sqlToMaybeDouble)
import Algorithm.EqSat.Storage.Extract (extractBestFromDB)
import Algorithm.EqSat.Storage.Schema (createSchemaFit)
import Algorithm.EqSat.Storage.SQLite ()
import Database.SQLite3 (Database, open, close, exec)
import Control.Exception (bracket)
import Control.Monad (forM_, when)
import Data.List (sortBy)
import Data.Ord (Down(..), comparing)

data RandomSamplerOpts = RandomSamplerOpts
  { rsEgraph  :: String
  , rsFitdb   :: String
  , rsDataset :: String
  , rsN       :: Int
  , rsFinite  :: Bool
  } deriving (Show)

randomSamplerParser :: Parser RandomSamplerOpts
randomSamplerParser = RandomSamplerOpts
  <$> strOption
      ( long "egraph"
      <> metavar "FILE"
      <> help "Path to e-graph database" )
  <*> strOption
      ( long "fitdb"
      <> metavar "FILE"
      <> help "Path to fit database" )
  <*> strOption
      ( long "dataset"
      <> metavar "NAME"
      <> help "Dataset name" )
  <*> option auto
      ( long "n"
      <> short 'n'
      <> metavar "INT"
      <> help "Number of expressions to sample" )
  <*> switch
      ( long "finite"
      <> short 'f'
      <> help "Only sample expressions with finite (non-NaN) fitness" )

runRandomSampler :: RandomSamplerOpts -> IO ()
runRandomSampler RandomSamplerOpts{..} = do
  when (rsN <= 0) $ do
    hPutStrLn stderr "Error: --n must be a positive integer."
    hFlush stderr
    fail "--n must be positive"

  withSQLite rsFitdb $ \fitDb -> do
    createSchemaFit fitDb
    dsRows <- queryDb fitDb "SELECT id FROM dataset WHERE name = ?"
      [SqlText (T.pack rsDataset)]
    case dsRows of
      [] -> do
        hPutStrLn stderr $ "Dataset '" ++ rsDataset ++ "' not found."
        hFlush stderr
      [[dsIdVal]] -> do
        let dsid = sqlToInt dsIdVal
            fitQuery
              | rsFinite =
                  "SELECT eid, fitness FROM dataset_fit \
                  \WHERE dataset_id = ? AND fitness IS NOT NULL \
                  \AND fitness * 0 = 0 \
                  \ORDER BY RANDOM() LIMIT ?"
              | otherwise =
                  "SELECT eid, fitness FROM dataset_fit \
                  \WHERE dataset_id = ? \
                  \ORDER BY RANDOM() LIMIT ?"
        rows <- queryDb fitDb fitQuery
          [SqlInteger (fromIntegral dsid), SqlInteger (fromIntegral rsN)]

        when (null rows) $ do
          hPutStrLn stderr $ "No fitted expressions found for dataset '" ++ rsDataset ++ "'."
          hFlush stderr

        let sampled = [ (sqlToInt eid, sqlToMaybeDouble fit)
                      | [eid, fit] <- rows
                      ]

        let sorted = sortBy (comparing (Down . snd)) sampled

        withSQLite rsEgraph $ \egDb -> do
          putStrLn "eid,fitness,expression"
          hFlush stdout
          forM_ sorted $ \(eid, mfit) -> do
            mTree <- extractBestFromDB egDb eid
            case mTree of
              Nothing -> do
                hPutStrLn stderr $ "WARNING: could not reconstruct expression for eid=" ++ show eid
                hFlush stderr
              Just tree -> do
                let expr = showExpr tree
                    fitStr = case mfit of
                      Nothing -> "NaN"
                      Just f  -> show f
                putStrLn $ show eid ++ "," ++ fitStr ++ "," ++ expr
                hFlush stdout

          let total = length sorted
              label = if rsFinite then " (finite)" else ""
          hPutStrLn stderr $ "Sampled " ++ show total ++ " expressions" ++ label ++ " from dataset '" ++ rsDataset ++ "'"
          hFlush stderr

      _ -> do
        hPutStrLn stderr $ "Dataset '" ++ rsDataset ++ "' query returned unexpected result."
        hFlush stderr

withSQLite :: String -> (Database -> IO a) -> IO a
withSQLite path f = bracket openDb close f
  where
    openDb = do
      db <- open (T.pack path)
      exec db "PRAGMA busy_timeout = 5000"
      pure db