never executed always true always false
    1 {-# LANGUAGE RecordWildCards #-}
    2 
    3 module Conjure.UI.MainHelper ( mainWithArgs, savilerowScriptName ) where
    4 
    5 import Conjure.Prelude
    6 import Conjure.Bug
    7 import Conjure.UserError
    8 import Conjure.UI ( UI(..), OutputFormat(..) )
    9 import Conjure.UI.IO ( readModel, readModelFromFile, readModelFromStdin
   10                      , readModelInfoFromFile, readParamOrSolutionFromFile
   11                      , writeModel )
   12 import Conjure.UI.Model ( parseStrategy, outputModels, modelRepresentationsJSON, prologue )
   13 import qualified Conjure.UI.Model as Config ( Config(..) )
   14 import Conjure.UI.TranslateParameter ( translateParameter )
   15 import Conjure.UI.TranslateSolution ( translateSolution )
   16 import Conjure.UI.ValidateSolution ( validateSolution )
   17 import Conjure.UI.TypeCheck ( typeCheckModel_StandAlone, typeCheckModel )
   18 import Conjure.UI.Split ( outputSplittedModels, removeUnusedDecls )
   19 import Conjure.UI.VarSymBreaking ( outputVarSymBreaking )
   20 import Conjure.UI.ParameterGenerator ( parameterGenerator )
   21 import Conjure.UI.NormaliseQuantified ( normaliseQuantifiedVariables )
   22 import Conjure.UI.SolveStats ( mkSolveStats )
   23 
   24 import Conjure.Language.Name ( Name(..) )
   25 import Conjure.Language.Definition ( Model(..), ModelInfo(..), Statement(..), Declaration(..), FindOrGiven(..) )
   26 import Conjure.Language.Type ( TypeCheckerMode(..) )
   27 import Conjure.Language.Domain ( Domain(..), Range(..) )
   28 import Conjure.Language.NameGen ( NameGen, NameGenM, runNameGen )
   29 import Conjure.Language.Pretty 
   30 import qualified Conjure.Language.ParserC as ParserC ( parseModel )
   31 import Conjure.Language.ModelDiff ( modelDiffIO )
   32 import Conjure.Rules.Definition ( viewAuto, Strategy(..) )
   33 import Conjure.Process.Enumerate ( EnumerateDomain )
   34 import Conjure.Process.Streamlining ( streamlining, streamliningToStdout )
   35 import Conjure.Language.NameResolution ( resolveNames, resolveNamesMulti )
   36 import Conjure.Process.Boost ( boost )
   37 import Conjure.Language.ModelStats ( modelDeclarationsJSON )
   38 import Conjure.Language.AdHoc ( toSimpleJSON )
   39 
   40 
   41 -- base
   42 import Control.Exception ( IOException, handle )
   43 import System.IO ( Handle, hSetBuffering, stdout, BufferMode(..), hPutStrLn, stderr )
   44 import System.Environment ( getEnvironment )
   45 import System.Info ( os )
   46 import GHC.Conc ( numCapabilities )
   47 import GHC.IO.Handle ( hIsEOF, hClose, hGetLine )
   48 import Data.Char ( isDigit )
   49 
   50 import qualified Data.Set as S                  -- containers
   51 import qualified Data.HashMap.Strict as M       -- unordered-containers
   52 
   53 -- filepath
   54 import System.FilePath ( splitFileName, takeBaseName, (<.>) )
   55 
   56 -- directory
   57 import System.Directory ( copyFile, findExecutable )
   58 
   59 -- shelly
   60 import Shelly ( runHandle, lastStderr, lastExitCode, errExit, Sh )
   61 
   62 -- text
   63 import qualified Data.Text as T ( unlines, isInfixOf )
   64 
   65 -- parallel-io
   66 import Control.Concurrent.ParallelIO.Global ( parallel, stopGlobalPool )
   67 import Conjure.LSP.LanguageServer (startServer, LSPConfig (LSPConfig))
   68 
   69 
   70 mainWithArgs :: forall m .
   71     MonadIO m =>
   72     MonadLog m =>
   73     MonadFailDoc m =>
   74     EnumerateDomain m =>
   75     NameGen m =>
   76     (?typeCheckerMode :: TypeCheckerMode) =>
   77     UI -> m ()
   78 mainWithArgs LSP{} = liftIO $ startServer LSPConfig 
   79 mainWithArgs mode@Modelling{..} = do
   80     essenceM <- readModelFromFile essence
   81     doIfNotCached          -- start the show!
   82         ( sort (mStatements essenceM)
   83         , portfolio
   84         -- when the following flags change, invalidate hash
   85         -- nested tuples, because :(
   86         , ( numberingStart
   87           , smartFilenames
   88           , strategyQ
   89           , strategyA
   90           , responses
   91           )
   92         , ( representations
   93           , representationsFinds
   94           , representationsGivens
   95           , representationsAuxiliaries
   96           , representationsQuantifieds
   97           , representationsCuts
   98           )
   99         , ( channelling
  100           , representationLevels
  101           , seed
  102           , limitModels
  103           , limitTime
  104           , outputFormat
  105           )
  106         , generateStreamliners
  107         )
  108         (outputDirectory </> ".conjure-checksum")
  109         (pp logLevel "Using cached models.")
  110         (void $ mainWithArgs_Modelling "" mode Nothing S.empty)
  111 mainWithArgs TranslateParameter{..} = do
  112     when (null eprime      ) $ userErr1 "Mandatory field --eprime"
  113     when (null essenceParam) $ userErr1 "Mandatory field --essence-param"
  114     let outputFilename = fromMaybe (dropExtension essenceParam ++ ".eprime-param") eprimeParam
  115     eprimeF <- readModelInfoFromFile eprime
  116     essenceParamF <- readParamOrSolutionFromFile eprimeF essenceParam
  117     output <- runNameGen () $ translateParameter False eprimeF essenceParamF
  118     writeModel lineWidth outputFormat (Just outputFilename) output
  119 mainWithArgs TranslateSolution{..} = do
  120     when (null eprime        ) $ userErr1 "Mandatory field --eprime"
  121     when (null eprimeSolution) $ userErr1 "Mandatory field --eprime-solution"
  122     eprimeF <- readModelInfoFromFile eprime
  123     essenceParamF <- maybe (return def) (readParamOrSolutionFromFile eprimeF) essenceParamO
  124     eprimeSolutionF <- readParamOrSolutionFromFile eprimeF eprimeSolution
  125     output <- runNameGen () $ translateSolution eprimeF essenceParamF eprimeSolutionF
  126     let outputFilename = fromMaybe (dropExtension eprimeSolution ++ ".solution") essenceSolutionO
  127     writeModel lineWidth outputFormat (Just outputFilename) output
  128 mainWithArgs ValidateSolution{..} = do
  129     when (null essence        ) $ userErr1 "Mandatory field --essence"
  130     when (null essenceSolution) $ userErr1 "Mandatory field --solution"
  131     essence2  <- readModelFromFile essence
  132     param2    <- maybe (return def) (readParamOrSolutionFromFile essence2) essenceParamO
  133     solution2 <- readParamOrSolutionFromFile essence2 essenceSolution
  134     [essence3, param3, solution3] <- runNameGen () $ resolveNamesMulti [essence2, param2, solution2]
  135     runNameGen () $ validateSolution essence3 param3 solution3
  136 mainWithArgs IDE{..} = do
  137     essence2 <-
  138         if null essence
  139             then readModelFromStdin
  140             else readModelFromFile essence
  141     void $ runNameGen () $ typeCheckModel_StandAlone essence2
  142     if
  143         | dumpDeclarations    -> liftIO $ putStrLn $ render lineWidth (modelDeclarationsJSON essence2)
  144         | dumpRepresentations -> do
  145             json <- runNameGen () $ modelRepresentationsJSON essence2
  146             liftIO $ putStrLn $ render lineWidth json
  147         | otherwise           -> writeModel lineWidth ASTJSON Nothing essence2
  148 -- mainWithArgs Pretty{..} | outputFormat == Plain= do
  149 --     (_,cts) <- liftIO $ pairWithContents essence
  150 --     res <- prettyPrintWithChecks cts
  151 --     liftIO $ putStrLn $ render lineWidth res
  152 mainWithArgs Pretty{..} = do
  153     model0 <- if or [ s `isSuffixOf` essence
  154                     | s <- [".param", ".eprime-param", ".solution", ".eprime.solution"] ]
  155                 then do
  156                     liftIO $ hPutStrLn stderr "Parsing as a parameter file"
  157                     readParamOrSolutionFromFile def essence
  158                 else readModelFromFile essence
  159     let model1 = model0
  160                     |> (if normaliseQuantified then normaliseQuantifiedVariables else id)
  161                     |> (if removeUnused then removeUnusedDecls else id)
  162     writeModel lineWidth outputFormat Nothing model1
  163 
  164 mainWithArgs Diff{..} =
  165     join $ modelDiffIO
  166         <$> readModelFromFile file1
  167         <*> readModelFromFile file2
  168 mainWithArgs TypeCheck{..} = do
  169     essenceF <- readModelFromFile essence
  170     void $ runNameGen () $ typeCheckModel_StandAlone essenceF
  171 mainWithArgs Split{..} = do
  172     model <- readModelFromFile essence
  173     outputSplittedModels outputDirectory model
  174 mainWithArgs SymmetryDetection{..} = do
  175     let jsonFilePath = if null json then essence ++ "-json" else json
  176     model <- readModelFromFile essence
  177     outputVarSymBreaking jsonFilePath model
  178 mainWithArgs ParameterGenerator{..} = do
  179     model <- readModelFromFile essence
  180     ((genModel, repairModel), classes) <- runNameGen () $ parameterGenerator minInt maxInt model
  181 
  182     let genModelOut = dropExtension essence ++ "-instanceGenerator.essence"
  183     writeModel lineWidth outputFormat (Just genModelOut) genModel
  184 
  185     let repairModelOut = dropExtension essence ++ "-instanceRepair.essence"
  186     writeModel lineWidth outputFormat (Just repairModelOut) repairModel
  187 
  188     let
  189         toIrace nm lb ub _ | lb == ub =
  190             pretty nm <+>
  191             "\"-" <> pretty nm <> " \" c" <+>
  192             prParens (pretty lb)
  193         toIrace nm lb ub (Just klass) =
  194             pretty nm <+>
  195             "\"-" <> pretty nm <> " \" " <> pretty klass <+>
  196             prettyList prParens "," [lb, ub]
  197         toIrace nm _ _ Nothing = bug ("Missing class for:" <+> pretty nm)
  198 
  199         essenceOutFileContents = render lineWidth $ vcat
  200             [ toIrace nm lb ub (lookup nm classes)
  201             | Declaration (FindOrGiven Given nm (DomainInt _ [RangeBounded lb ub])) <- mStatements genModel
  202             ]
  203     liftIO $ writeFile (genModelOut ++ ".irace") (essenceOutFileContents ++ "\n")
  204 mainWithArgs Streamlining{..} = runNameGen essence
  205     (readModelFromFile essence >>= resolveNames >>= typeCheckModel >>= streamliningToStdout)
  206 mainWithArgs AutoIG{..} | generatorToIrace = do
  207     model <- readModelFromFile essence
  208     let
  209         toIrace nm lb ub | lb == ub =
  210             pretty nm <+>
  211             "\"-" <> pretty nm <> " \" c" <+>
  212             prParens (pretty lb)
  213         toIrace nm lb ub =
  214             pretty nm <+>
  215             "\"-" <> pretty nm <> " \" i" <+>
  216             prettyList prParens "," [lb, ub]
  217     let (iraceStmts, errors) = mconcat
  218             [ case st of
  219                 Declaration (FindOrGiven Given nm domain) ->
  220                     case domain of
  221                         DomainInt _ [RangeBounded lb ub] -> ([toIrace nm lb ub], [])
  222                         _ -> ([], ["Unsupported domain for given" <+> pretty nm <> ":" <+> pretty domain])
  223                 _ -> ([], [])
  224             | st <- mStatements model
  225             ]
  226     if null errors
  227         then do
  228             let essenceOutFileContents = render lineWidth $ vcat iraceStmts
  229             liftIO $ writeFile outputFilepath (essenceOutFileContents ++ "\n")
  230         else
  231             userErr errors
  232 mainWithArgs AutoIG{..} | removeAux = do
  233     model <- readModelFromFile essence
  234     let stmts' = [ case st of
  235                     Declaration (FindOrGiven _ nm _) | "Aux" `isPrefixOf` show (pretty nm) -> Nothing
  236                                                      | otherwise -> Just st
  237                     Declaration (Letting nm _) | "Aux" `isPrefixOf` show (pretty nm) -> Nothing
  238                                                | otherwise -> Just st
  239                     _ -> Just st
  240                  | st <- mStatements model
  241                  ]
  242     writeModel lineWidth outputFormat (Just outputFilepath) model {mStatements = catMaybes stmts'}
  243 mainWithArgs AutoIG{} = userErr1 "You must pass one of --generator-to-irace or --remove-aux to this command."
  244 mainWithArgs Boost{..} = do
  245     model <- readModelFromFile essence
  246     runNameGen model $ do
  247         boosted <- boost logLevel logRuleSuccesses model
  248         writeModel lineWidth outputFormat Nothing boosted
  249 mainWithArgs config@Solve{..} = do
  250     liftIO $ createDirectoryIfMissing True outputDirectory
  251     -- some sanity checks
  252     (solverName, _smtLogicName) <- splitSolverName solver
  253     case lookup solverName solverExecutables of
  254         Nothing -> userErr1 ("Unsupported solver:" <+> pretty solver)
  255         Just ex -> do
  256             fp <- liftIO $ findExecutable ex
  257             case fp of
  258                 Nothing -> userErr1 ("Cannot find executable" <+> pretty ex <+> "(for solver" <+> pretty solver <> ")")
  259                 Just _  -> return ()
  260     case solver of
  261         "cplex" -> do
  262             env <- liftIO getEnvironment
  263             case lookup "CPLEX_PATH" env of
  264                 Nothing -> userErr1 $ vcat
  265                     [ "Set environment variable CPLEX_PATH. Something like:"
  266                     , "    CPLEX_PATH=/path/to/cplex/library conjure solve"
  267                     ]
  268                 Just{} -> return ()
  269         _ -> return ()
  270     unless (nbSolutions == "all" || all isDigit nbSolutions) $
  271         userErr1 (vcat [ "The value for --number-of-solutions must either be a number or the string \"all\"."
  272                        , "Was given:" <+> pretty nbSolutions
  273                        ])
  274     when (solver `elem` ["bc_minisat_all", "nbc_minisat_all"] && nbSolutions /= "all") $
  275         userErr1 "The solvers bc_minisat_all and nbc_minisat_all only work with --number-of-solutions=all"
  276     essenceM_beforeNR <- readModelFromFile essence
  277     essenceM <- prologue essenceM_beforeNR
  278     unless (null [ () | Objective{} <- mStatements essenceM ]) $ do -- this is an optimisation problem
  279         when (nbSolutions == "all" || nbSolutions /= "1") $
  280             userErr1 ("Not supported for optimisation problems: --number-of-solutions=" <> pretty nbSolutions)
  281     essenceParamsParsed <- forM essenceParams $ \ f -> do
  282         p <- readParamOrSolutionFromFile essenceM f
  283         return (f, p)
  284     let givens = [ nm | Declaration (FindOrGiven Given nm _) <- mStatements essenceM ]
  285               ++ [ nm | Declaration (GivenDomainDefnEnum nm) <- mStatements essenceM ]
  286     when (not (null givens) && null essenceParams) $
  287         userErr1 $ vcat
  288             [ "The problem specification is parameterised, but no *.param files are given."
  289             , "Parameters:" <+> prettyList id "," givens
  290             ]
  291     let isEmptyParam Model{mStatements=[]} = True
  292         isEmptyParam _ = False
  293         hasNonEmptyParams =
  294             null essenceParams ||                               -- either no params were given
  295             all (isEmptyParam . snd) essenceParamsParsed        -- or all those given were empty
  296     when (null givens && not hasNonEmptyParams) $
  297         userErr1 "The problem specification is _not_ parameterised, but *.param files are given."
  298 
  299     eprimes <-
  300         if not (null useExistingModels)
  301             then do
  302                 pp logLevel "Using existing models."
  303                 return useExistingModels
  304             else doIfNotCached          -- start the show!
  305                     ( sort (mStatements essenceM_beforeNR)
  306                     , portfolio
  307                     -- when the following flags change, invalidate hash
  308                     -- nested tuples, because :(
  309                     , ( numberingStart
  310                       , smartFilenames
  311                       , strategyQ
  312                       , strategyA
  313                       , responses
  314                       )
  315                     , ( representations
  316                       , representationsFinds
  317                       , representationsGivens
  318                       , representationsAuxiliaries
  319                       , representationsQuantifieds
  320                       , representationsCuts
  321                       )
  322                     , ( channelling
  323                       , representationLevels
  324                       , seed
  325                       , limitModels
  326                       , limitTime
  327                       , outputFormat
  328                       )
  329                     , generateStreamliners
  330                     )
  331                     (outputDirectory </> ".conjure-checksum")
  332                     (pp logLevel "Using cached models." >> getEprimes)
  333                     conjuring
  334 
  335     eprimesParsed <- forM eprimes $ \ f -> do
  336         p1 <- liftIO $ handle (\(_ :: IOException) -> return Nothing) (Just <$> readModelInfoFromFile (outputDirectory </> f))
  337         p2 <- liftIO $ handle (\(_ :: IOException) -> return Nothing) (Just <$> readModelInfoFromFile f)
  338         case (p1, p2) of
  339             (Just p, _) -> return (outputDirectory </> f, p)
  340             (_, Just p) -> return (f, p)
  341             _ -> userErr1 $ "Model not found:" <+> pretty f
  342 
  343     msolutions <- liftIO $ savileRows eprimesParsed essenceParamsParsed
  344     case msolutions of
  345         Left msg        -> userErr msg
  346         Right solutions -> do
  347 
  348             when (null solutions) (pp logLevel "No solutions found.")
  349 
  350             when validateSolutionsOpt $ validating solutions
  351 
  352             let params = nub [ dropExtension p | (_,p,_) <- solutions ]
  353 
  354             when copySolutions $ do
  355                 -- clean-up: move the solutions next to the essence file.
  356                 -- our intention is that a user intending to just solve something
  357                 -- should never have to look into outputDirectory.
  358 
  359                 let
  360                     copySolution :: MonadIO m => FilePath -> FilePath -> m ()
  361                     copySolution old new = do
  362                         pp logLevel ("Copying solution to:" <+> pretty new)
  363                         liftIO (copyFile old new)
  364 
  365                 let (essenceDir0, essenceFilename) = splitFileName essence
  366                 let essenceDir = if essenceDir0 == "./" then "" else essenceDir0
  367                 let essenceBasename = takeBaseName essenceFilename
  368 
  369                 let extensions = [ ".solution", ".solution.json", ".solution.minizinc"
  370                                  , ".solutions", ".solutions.json"
  371                                  ]
  372 
  373                 files <- liftIO $ getAllFiles outputDirectory
  374 
  375                 unless (length eprimes == 1) $
  376                     pp logLevel $ "Multiple eprime models found in the output directory, not copying solutions."
  377 
  378                 when (length eprimes == 1) $ do
  379                     forM_ (sort files) $ \ file ->
  380                         forM_ extensions $ \ ext ->
  381                             case stripPostfix ext (snd (splitFileName file)) of
  382                                 Nothing -> return ()
  383                                 Just base -> do
  384                                     let parts' = splitOn "-" base
  385                                     let parts = case (head parts', parts', last parts') of
  386                                                     (model, _:paramparts, stripPrefix "solution" -> msolnum) ->
  387                                                         case msolnum of
  388                                                             Nothing -> [model, intercalate "-" paramparts]
  389                                                             Just{}  -> [model, intercalate "-" (init paramparts), last paramparts]
  390                                                     _ -> parts'
  391                                     case (solutionsInOneFile, null essenceParams, nbSolutions == "1", parts) of
  392                                         -- not parameterised, but no solution numbers. must be using solutionsInOneFile.
  393                                         (True, True, _singleSolution, [_model, ""]) ->
  394                                             copySolution file $ essenceDir
  395                                                                 </> essenceBasename
  396                                                                 <.> ext
  397                                         -- not parameterised, with solution numbers
  398                                         (False, True, singleSolution, [_model, "", (stripPrefix "solution" -> Just solnum)]) ->
  399                                             if singleSolution
  400                                                 then when (solnum == "000001") $ -- only copy the first solution
  401                                                      copySolution file $ essenceDir
  402                                                                 </> essenceBasename
  403                                                                 <.> ext
  404                                                 else copySolution file $ essenceDir
  405                                                                 </> intercalate "-" [essenceBasename, solnum]
  406                                                                 <.> ext
  407                                         -- parameterised, but no solution numbers. must be using solutionsInOneFile.
  408                                         (True, False, _singleSolution, [_model, param]) | param `elem` params ->
  409                                             copySolution file $ essenceDir
  410                                                                 </> intercalate "-" [ essenceBasename
  411                                                                                     , param
  412                                                                                     ]
  413                                                                 <.> ext
  414                                         -- parameterised, with solution numbers
  415                                         (False, False, singleSolution, [_model, param, (stripPrefix "solution" -> Just solnum)])
  416                                             | or [ param `elem` params
  417                                                  , or [('/' : param) `isSuffixOf` p | p <- params] ] ->
  418                                                 if singleSolution
  419                                                     then when (solnum == "000001") $ -- only copy the first solution
  420                                                         copySolution file $ essenceDir
  421                                                                     </> intercalate "-" [ essenceBasename
  422                                                                                         , param
  423                                                                                         ]
  424                                                                     <.> ext
  425                                                     else copySolution file $ essenceDir
  426                                                                     </> intercalate "-" [ essenceBasename
  427                                                                                         , param
  428                                                                                         , solnum
  429                                                                                         ]
  430                                                                     <.> ext
  431                                         _ -> return () -- ignore, we don't know how to handle this file
  432 
  433     liftIO stopGlobalPool
  434 
  435     where
  436         conjuring :: m [FilePath]
  437         conjuring = do
  438             pp logLevel $ "Generating models for" <+> pretty essence
  439             -- remove old eprime files
  440             liftIO $ getAllFilesWithSuffix ".eprime" outputDirectory >>= mapM_ removeFileIfExists
  441             let modelling = let savedChoices = def
  442                                 estimateNumberOfModels = False
  443                             in  Modelling{..}                   -- construct a Modelling UI, copying all relevant fields
  444                                                                 -- from the given Solve UI
  445             n <- mainWithArgs_Modelling "" modelling Nothing S.empty
  446             eprimes <- getEprimes
  447             when (null eprimes) $ bug "Failed to generate models."
  448             if S.size n == 1
  449                 then pp logLevel $ "Generated models:" <+> prettyList id "," eprimes
  450                 else pp logLevel $ "Generated" <+> pretty (S.size n) <+> "models:" <+> prettyList id "," eprimes
  451             pp logLevel $ "Saved under:" <+> pretty outputDirectory
  452             return [ outputDirectory </> f | f <- eprimes ]
  453 
  454         getEprimes :: m [FilePath]
  455         getEprimes = sort . filter (".eprime" `isSuffixOf`) <$> liftIO (getDirectoryContents outputDirectory)
  456 
  457         combineResults :: [Either e [a]] -> Either e [a]
  458         combineResults = fmap concat . sequence
  459 
  460         savileRows
  461             :: [(FilePath, Model)]      -- models
  462             -> [(FilePath, Model)]      -- params
  463             -> IO (Either [Doc] [(FilePath, FilePath, Maybe FilePath)])
  464         savileRows eprimes params = fmap combineResults $
  465             if null params
  466                 then autoParallel [ savileRowNoParam config m
  467                                   | m <- eprimes
  468                                   ]
  469                 else autoParallel [ savileRowWithParams config m p
  470                                   | m <- eprimes
  471                                   , p <- params
  472                                   ]
  473 
  474         -- validating :: [(FilePath, FilePath, Maybe FilePath)] -> IO ()
  475         validating solutions =
  476             if null essenceParams
  477                 then sequence_ [ validateSolutionNoParam config sol
  478                                | (_, _, Just sol) <- solutions ]
  479                 else sequence_ [ validateSolutionWithParams config sol p
  480                                | (_, p, Just sol) <- solutions ]
  481 
  482 
  483 mainWithArgs_Modelling :: forall m .
  484     MonadIO m =>
  485     MonadLog m =>
  486     MonadFailDoc m =>
  487     EnumerateDomain m =>
  488     (?typeCheckerMode :: TypeCheckerMode) =>
  489     String ->                   -- modelNamePrefix
  490     UI ->
  491     Maybe Int ->                -- portfolioSize for the recursive call
  492     S.Set Int ->                -- modelHashesBefore
  493     m (S.Set Int)
  494 mainWithArgs_Modelling _ mode@Modelling{..} _ modelHashesBefore | Just portfolioSize <- portfolio = do
  495     pp logLevel $ "Running in portfolio mode, aiming to generate" <+> pretty portfolioSize <+> "models."
  496     let
  497         go modelsSoFar [] = do
  498             pp logLevel $ "Done, no more levels, generated" <+> pretty (S.size modelsSoFar) <+> "models."
  499             return modelsSoFar
  500         go modelsSoFar (l:ls) = do
  501             let nbModelsNeeded = portfolioSize - S.size modelsSoFar
  502             if nbModelsNeeded <= 0
  503                 then return modelsSoFar
  504                 else do
  505                     modelsSoFar' <- l (Just portfolioSize) modelsSoFar
  506                     go modelsSoFar' ls
  507 
  508     go modelHashesBefore
  509         [ mainWithArgs_Modelling "01_compact"
  510             mode { portfolio = Nothing
  511                  , strategyA = "c"
  512                  , representations = Just "c"
  513                  , representationsFinds = Just "c"
  514                  , representationsGivens = Just "s"
  515                  , representationsAuxiliaries = Just "c"
  516                  , representationsQuantifieds = Just "c"
  517                  , representationsCuts = Just "c"
  518                  , channelling = False
  519                  , representationLevels = True
  520                  , smartFilenames = True
  521                  }
  522         , mainWithArgs_Modelling "02_compact"
  523             mode { portfolio = Nothing
  524                  , strategyA = "c"
  525                  , representations = Just "c"
  526                  , representationsFinds = Just "c"
  527                  , representationsGivens = Just "c"
  528                  , representationsAuxiliaries = Just "c"
  529                  , representationsQuantifieds = Just "c"
  530                  , representationsCuts = Just "c"
  531                  , channelling = False
  532                  , representationLevels = True
  533                  , smartFilenames = True
  534                  }
  535         , mainWithArgs_Modelling "03_sparse"
  536             mode { portfolio = Nothing
  537                  , strategyA = "s"
  538                  , representations = Just "s"
  539                  , representationsFinds = Just "s"
  540                  , representationsGivens = Just "s"
  541                  , representationsAuxiliaries = Just "s"
  542                  , representationsQuantifieds = Just "s"
  543                  , representationsCuts = Just "s"
  544                  , channelling = False
  545                  , representationLevels = True
  546                  , smartFilenames = True
  547                  }
  548         , mainWithArgs_Modelling "04_nochPrunedLevels"
  549             mode { portfolio = Nothing
  550                  , strategyA = "x"
  551                  , representations = Just "x"
  552                  , representationsFinds = Just "x"
  553                  , representationsGivens = Just "s"
  554                  , representationsAuxiliaries = Just "c"
  555                  , representationsQuantifieds = Just "c"
  556                  , representationsCuts = Just "c"
  557                  , channelling = False
  558                  , representationLevels = True
  559                  , smartFilenames = True
  560                  }
  561         , mainWithArgs_Modelling "05_nochAllLevels"
  562             mode { portfolio = Nothing
  563                  , strategyA = "x"
  564                  , representations = Just "x"
  565                  , representationsFinds = Just "x"
  566                  , representationsGivens = Just "s"
  567                  , representationsAuxiliaries = Just "c"
  568                  , representationsQuantifieds = Just "c"
  569                  , representationsCuts = Just "c"
  570                  , channelling = False
  571                  , representationLevels = False
  572                  , smartFilenames = True
  573                  }
  574         , mainWithArgs_Modelling "06_chPrunedLevels"
  575             mode { portfolio = Nothing
  576                  , strategyA = "x"
  577                  , representations = Just "x"
  578                  , representationsFinds = Just "x"
  579                  , representationsGivens = Just "s"
  580                  , representationsAuxiliaries = Just "c"
  581                  , representationsQuantifieds = Just "c"
  582                  , representationsCuts = Just "c"
  583                  , channelling = True
  584                  , representationLevels = True
  585                  , smartFilenames = True
  586                  }
  587         , mainWithArgs_Modelling "07_chAllLevels"
  588             mode { portfolio = Nothing
  589                  , strategyA = "x"
  590                  , representations = Just "x"
  591                  , representationsFinds = Just "x"
  592                  , representationsGivens = Just "s"
  593                  , representationsAuxiliaries = Just "c"
  594                  , representationsQuantifieds = Just "c"
  595                  , representationsCuts = Just "c"
  596                  , channelling = True
  597                  , representationLevels = False
  598                  , smartFilenames = True
  599                  }
  600         , mainWithArgs_Modelling "08_fullPrunedLevels"
  601             mode { portfolio = Nothing
  602                  , strategyA = "x"
  603                  , representations = Just "x"
  604                  , representationsFinds = Just "x"
  605                  , representationsGivens = Just "s"
  606                  , representationsAuxiliaries = Just "x"
  607                  , representationsQuantifieds = Just "x"
  608                  , representationsCuts = Just "x"
  609                  , channelling = True
  610                  , representationLevels = True
  611                  , smartFilenames = True
  612                  }
  613         , mainWithArgs_Modelling "09_fullAllLevels"
  614             mode { portfolio = Nothing
  615                  , strategyA = "x"
  616                  , representations = Just "x"
  617                  , representationsFinds = Just "x"
  618                  , representationsGivens = Just "s"
  619                  , representationsAuxiliaries = Just "x"
  620                  , representationsQuantifieds = Just "x"
  621                  , representationsCuts = Just "x"
  622                  , channelling = True
  623                  , representationLevels = False
  624                  , smartFilenames = True
  625                  }
  626         , mainWithArgs_Modelling "10_fullParamsPrunedLevels"
  627             mode { portfolio = Nothing
  628                  , strategyA = "x"
  629                  , representations = Just "x"
  630                  , representationsFinds = Just "x"
  631                  , representationsGivens = Just "x"
  632                  , representationsAuxiliaries = Just "x"
  633                  , representationsQuantifieds = Just "x"
  634                  , representationsCuts = Just "x"
  635                  , channelling = True
  636                  , representationLevels = True
  637                  , smartFilenames = True
  638                  }
  639         , mainWithArgs_Modelling "11_fullParamsAllLevels"
  640             mode { portfolio = Nothing
  641                  , strategyA = "x"
  642                  , representations = Just "x"
  643                  , representationsFinds = Just "x"
  644                  , representationsGivens = Just "x"
  645                  , representationsAuxiliaries = Just "x"
  646                  , representationsQuantifieds = Just "x"
  647                  , representationsCuts = Just "x"
  648                  , channelling = True
  649                  , representationLevels = False
  650                  , smartFilenames = True
  651                  }
  652         ]
  653 mainWithArgs_Modelling "" mode portfolioSize modelHashesBefore =
  654     mainWithArgs_Modelling "model" mode portfolioSize modelHashesBefore
  655 mainWithArgs_Modelling modelNamePrefix mode@Modelling{..} portfolioSize modelHashesBefore | strategyA == "c" && channelling == True =
  656     mainWithArgs_Modelling modelNamePrefix mode{channelling=False} portfolioSize modelHashesBefore
  657 mainWithArgs_Modelling modelNamePrefix Modelling{..} portfolioSize modelHashesBefore = do
  658     unless (modelNamePrefix == "model") $
  659         pp logLevel $ "Portfolio level:" <+> pretty modelNamePrefix
  660     let
  661         parseStrategy_ s = maybe (userErr1 ("Not a valid strategy:" <+> pretty s))
  662                                  return
  663                                  (parseStrategy s)
  664 
  665         getConfig = do
  666             strategyQ'                  <- parseStrategy_ strategyQ
  667             strategyA'                  <- parseStrategy_ strategyA
  668             representations'            <- maybe (return strategyA')       parseStrategy_ representations
  669             representationsFinds'       <- maybe (return representations') parseStrategy_ representationsFinds
  670             representationsGivens'      <- maybe (return Sparse          ) parseStrategy_ representationsGivens
  671             representationsAuxiliaries' <- maybe (return representations') parseStrategy_ representationsAuxiliaries
  672             representationsQuantifieds' <- maybe (return representations') parseStrategy_ representationsQuantifieds
  673             representationsCuts'        <- maybe (return representations') parseStrategy_ representationsCuts
  674 
  675             case fst (viewAuto strategyQ') of
  676                 Compact -> userErr1 "The Compact heuristic isn't supported for questions."
  677                 _       -> return ()
  678 
  679             let
  680                 parseCommaSeparated :: Read a => Doc -> String -> m (Maybe [a])
  681                 parseCommaSeparated flag str =
  682                     if null str
  683                         then return Nothing
  684                         else do
  685                             let parts = splitOn "," str
  686                             let intParts = mapMaybe readMay parts
  687                             if length parts == length intParts
  688                                 then return (Just intParts)
  689                                 else userErr1 $ vcat [ "Cannot parse the value for" <+> flag
  690                                                      , "Expected a comma separated list of integers."
  691                                                      , "But got:" <+> pretty str
  692                                                      ]
  693 
  694             responsesList <- parseCommaSeparated "--responses" responses
  695             generateStreamlinersList <- parseCommaSeparated "--generate-streamliners" generateStreamliners
  696 
  697             responsesRepresentationList <- do
  698                 if null responsesRepresentation
  699                     then return Nothing
  700                     else do
  701                         let parts =
  702                                 [ case splitOn ":" pair of
  703                                     [nm, val] ->
  704                                         case readMay val of
  705                                             Just i -> Just (Name (stringToText nm), i)
  706                                             Nothing -> Nothing
  707                                     _ -> Nothing
  708                                 | pair <- splitOn "," responsesRepresentation
  709                                 ]
  710                         let partsJust = catMaybes parts
  711                         if length parts == length partsJust
  712                             then return (Just partsJust)
  713                             else userErr1 $ vcat [ "Cannot parse the value for --responses-representation."
  714                                                  , "Expected a comma separated list of variable name : integer pairs."
  715                                                  , "But got:" <+> pretty responsesRepresentation
  716                                                  ]
  717 
  718             trail <- if (followModel /= "")
  719                         then miTrailGeneralised . mInfo <$> readModelInfoFromFile followModel
  720                         else return []
  721 
  722             return Config.Config
  723                 { Config.outputDirectory            = outputDirectory
  724                 , Config.logLevel                   = logLevel
  725                 , Config.verboseTrail               = verboseTrail
  726                 , Config.rewritesTrail              = rewritesTrail
  727                 , Config.logRuleFails               = logRuleFails
  728                 , Config.logRuleSuccesses           = logRuleSuccesses
  729                 , Config.logRuleAttempts            = logRuleAttempts
  730                 , Config.logChoices                 = logChoices
  731                 , Config.followTrail                = M.fromList trail
  732                 , Config.strategyQ                  = strategyQ'
  733                 , Config.strategyA                  = strategyA'
  734                 , Config.representations            = representations'
  735                 , Config.representationsFinds       = representationsFinds'
  736                 , Config.representationsGivens      = representationsGivens'
  737                 , Config.representationsAuxiliaries = representationsAuxiliaries'
  738                 , Config.representationsQuantifieds = representationsQuantifieds'
  739                 , Config.representationsCuts        = representationsCuts'
  740                 , Config.channelling                = channelling
  741                 , Config.representationLevels       = representationLevels
  742                 , Config.limitModels                = if limitModels == Just 0 then Nothing else limitModels
  743                 , Config.numberingStart             = numberingStart
  744                 , Config.smartFilenames             = smartFilenames
  745                 , Config.lineWidth                  = lineWidth
  746                 , Config.responses                  = responsesList
  747                 , Config.responsesRepresentation    = responsesRepresentationList
  748                 , Config.generateStreamliners       = generateStreamlinersList
  749                 , Config.estimateNumberOfModels     = estimateNumberOfModels
  750                 }
  751 
  752     essenceM <- readModelFromFile essence
  753     liftIO $ hSetBuffering stdout LineBuffering
  754     liftIO $ maybe (return ()) setRandomSeed seed
  755     config <- getConfig
  756     runNameGen essenceM $ do
  757         modelWithStreamliners <-
  758             case Config.generateStreamliners config of
  759                 Nothing -> return essenceM
  760                 Just ix -> do
  761                     streamliners <- pure essenceM >>= resolveNames >>= typeCheckModel >>= streamlining
  762                     let chosen = [ streamliner | (i, streamliner) <- zip [1..] streamliners, i `elem` ix ]
  763                     return essenceM { mStatements = mStatements essenceM ++ [SuchThat [x | (_, (x, _)) <- chosen]] }
  764         outputModels portfolioSize modelHashesBefore modelNamePrefix config modelWithStreamliners
  765 mainWithArgs_Modelling _ _ _ _ = bug "mainWithArgs_Modelling"
  766 
  767 pp :: MonadIO m => LogLevel -> Doc -> m ()
  768 pp LogNone = const $ return ()
  769 pp _       = liftIO . putStrLn . renderNormal
  770 
  771 
  772 savilerowScriptName :: FilePath
  773 savilerowScriptName
  774     | os `elem` ["darwin", "linux"] = "savilerow"
  775     | os `elem` ["mingw32"] = "savilerow.bat"
  776     | otherwise = bug "Cannot detect operating system."
  777 
  778 
  779 quoteMultiWord :: String -> String
  780 quoteMultiWord s
  781     | ' ' `elem` s = "\"" ++ s ++ "\""
  782     | otherwise = s
  783 
  784 
  785 savileRowNoParam ::
  786     (?typeCheckerMode :: TypeCheckerMode) =>
  787     UI ->
  788     (FilePath, Model) ->        -- model
  789     IO (Either
  790      [Doc]                      -- user error
  791      [ ( FilePath               -- model
  792        , FilePath               -- param
  793        , Maybe FilePath         -- solution, Nothing if solutionsInOneFile=True
  794        ) ])
  795 savileRowNoParam ui@Solve{..} (modelPath, eprimeModel) = sh $ errExit False $ do
  796     pp logLevel $ hsep ["Savile Row:", pretty modelPath]
  797     let outBase = (dropExtension . snd . splitFileName) modelPath
  798     srArgs <- liftIO $ srMkArgs ui outBase modelPath
  799     let tr = translateSolution eprimeModel def
  800     let handler = liftIO . srStdoutHandler
  801                     (outBase, modelPath, "<no param file>", ui)
  802                     tr (1::Int)
  803     let runsolverArgs = maybe [] (\ limit -> ["-C", show limit]) runsolverCPUTimeLimit ++
  804                         maybe [] (\ limit -> ["-W", show limit]) runsolverWallTimeLimit ++
  805                         maybe [] (\ limit -> ["-R", show limit]) runsolverMemoryLimit ++
  806                         ["--quiet", "-v", outputDirectory </> outBase ++ ".runsolver-info"]
  807     (stdoutSR, solutions) <- partitionEithers <$>
  808         case (runsolverCPUTimeLimit, runsolverWallTimeLimit, runsolverMemoryLimit) of
  809             (Nothing, Nothing, Nothing) -> do
  810                 when (logLevel >= LogDebug) $ do
  811                     liftIO $ putStrLn "Using the following options for Savile Row:"
  812                     liftIO $ putStrLn $ "    savilerow " ++ unwords (map (quoteMultiWord . textToString) srArgs)
  813                 runHandle savilerowScriptName srArgs handler
  814             _ ->
  815                 if os /= "linux"
  816                     then return [Left "runsolver is only supported on linux"]
  817                     else do
  818                         when (logLevel >= LogDebug) $ do
  819                             liftIO $ putStrLn "Using the following options for Savile Row:"
  820                             liftIO $ putStrLn $ "    runsolver " ++ unwords (map quoteMultiWord runsolverArgs)
  821                                                  ++ " savilerow " ++ unwords (map (quoteMultiWord . textToString) srArgs)
  822                         runHandle "runsolver" (map stringToText runsolverArgs ++ [stringToText savilerowScriptName] ++ srArgs) handler
  823     srCleanUp outBase ui (stringToText $ unlines stdoutSR) solutions
  824 savileRowNoParam _ _ = bug "savileRowNoParam"
  825 
  826 
  827 savileRowWithParams ::
  828     (?typeCheckerMode :: TypeCheckerMode) =>
  829     UI ->
  830     (FilePath, Model) ->        -- model
  831     (FilePath, Model) ->        -- param
  832     IO (Either
  833      [Doc]                      -- user error
  834      [ ( FilePath               -- model
  835        , FilePath               -- param
  836        , Maybe FilePath         -- solution, Nothing if solutionsInOneFile=True
  837        ) ])
  838 savileRowWithParams ui@Solve{..} (modelPath, eprimeModel) (paramPath, essenceParam) = sh $ errExit False $ do
  839     pp logLevel $ hsep ["Savile Row:", pretty modelPath, pretty paramPath]
  840     let outBase = (dropExtension . snd . splitFileName) modelPath ++ "-" ++ dropDirs (dropExtension paramPath)
  841     let
  842         -- this is a bit tricky.
  843         -- we want to preserve user-erors, and not raise them as errors using IO.fail
  844         runTranslateParameter :: IO (Either [Doc] Model)
  845         runTranslateParameter = runUserErrorT $ ignoreLogs $ runNameGen () $
  846                                     translateParameter graphSolver eprimeModel essenceParam
  847     eprimeParam' <- liftIO runTranslateParameter
  848     case eprimeParam' of
  849         Left err -> return (Left err)
  850         Right eprimeParam -> do
  851             liftIO $ writeFile (outputDirectory </> outBase ++ ".eprime-param") (render lineWidth eprimeParam)
  852             srArgsBase <- liftIO $ srMkArgs ui outBase modelPath
  853             let srArgs = "-in-param"
  854                        : stringToText (outputDirectory </> outBase ++ ".eprime-param")
  855                        : srArgsBase
  856             let tr = translateSolution eprimeModel essenceParam
  857             let handler = liftIO . srStdoutHandler
  858                             (outBase, modelPath, paramPath, ui)
  859                             tr (1::Int)
  860             let runsolverArgs = maybe [] (\ limit -> ["-C", show limit]) runsolverCPUTimeLimit ++
  861                                 maybe [] (\ limit -> ["-W", show limit]) runsolverWallTimeLimit ++
  862                                 maybe [] (\ limit -> ["-R", show limit]) runsolverMemoryLimit ++
  863                                 ["--quiet", "-v", outputDirectory </> outBase ++ ".runsolver-info"]
  864             (stdoutSR, solutions) <- partitionEithers <$>
  865                 case (runsolverCPUTimeLimit, runsolverWallTimeLimit, runsolverMemoryLimit) of
  866                     (Nothing, Nothing, Nothing) -> do
  867                         when (logLevel >= LogDebug) $ do
  868                             liftIO $ putStrLn "Using the following options for Savile Row:"
  869                             liftIO $ putStrLn $ "    savilerow " ++ unwords (map (quoteMultiWord . textToString) srArgs)
  870                         runHandle savilerowScriptName srArgs handler
  871                     _ ->
  872                         if os /= "linux"
  873                             then return [Left "runsolver is only supported on linux"]
  874                             else do
  875                                 when (logLevel >= LogDebug) $ do
  876                                     liftIO $ putStrLn "Using the following options for Savile Row:"
  877                                     liftIO $ putStrLn $ "    runsolver " ++ unwords (map quoteMultiWord runsolverArgs)
  878                                                         ++ " savilerow " ++ unwords (map (quoteMultiWord . textToString) srArgs)
  879                                 runHandle "runsolver" (map stringToText runsolverArgs ++ [stringToText savilerowScriptName] ++ srArgs) handler
  880 
  881             srCleanUp outBase ui (stringToText $ unlines stdoutSR) solutions
  882 savileRowWithParams _ _ _ = bug "savileRowWithParams"
  883 
  884 
  885 
  886 solverExecutables :: [(String, String)]
  887 solverExecutables =
  888     [ ( "minion"          , "minion" )
  889     , ( "gecode"          , "fzn-gecode" )
  890     , ( "chuffed"         , "fzn-chuffed" )
  891     , ( "cadical"         , "cadical" )
  892     , ( "kissat"          , "kissat" )
  893     , ( "glucose"         , "glucose" )
  894     , ( "glucose-syrup"   , "glucose-syrup" )
  895     , ( "lingeling"       , "lingeling" )
  896     , ( "plingeling"      , "plingeling" )
  897     , ( "treengeling"     , "treengeling" )
  898     , ( "minisat"         , "minisat" )
  899     , ( "bc_minisat_all"  , "bc_minisat_all_release" )
  900     , ( "nbc_minisat_all" , "nbc_minisat_all_release" )
  901     , ( "open-wbo"        , "open-wbo" )
  902     , ( "or-tools"        , "fzn-cp-sat" )
  903     , ( "coin-or"         , "minizinc" )
  904     , ( "cplex"           , "minizinc" )
  905     , ( "yices"           , "yices-smt2" )
  906     , ( "boolector"       , "boolector" )
  907     , ( "z3"              , "z3" )
  908     ]
  909 
  910 
  911 smtSolvers :: [String]
  912 smtSolvers = ["boolector", "yices", "z3"]
  913 
  914 smtSolversSRFlag :: String -> String
  915 smtSolversSRFlag "boolector" = "-boolector"
  916 smtSolversSRFlag "yices" = "-yices2"
  917 smtSolversSRFlag "z3" = "-z3"
  918 smtSolversSRFlag _ = bug "smtSolversSRFlag"
  919 
  920 smtSupportedLogics :: String -> [String]
  921 smtSupportedLogics "boolector" = ["bv"]
  922 smtSupportedLogics "yices" = ["bv", "lia", "idl"]
  923 smtSupportedLogics "z3" = ["bv", "lia", "nia", "idl"]
  924 smtSupportedLogics s = bug ("Unrecognised SMT solver:" <+> pretty s)
  925 
  926 
  927 splitSolverName :: MonadUserError m => String -> m (String, String)
  928 splitSolverName solver = do
  929     let
  930 
  931     (solverName, smtLogicName) <-
  932             case splitOn "-" solver of
  933                 [solverName] | solverName `elem` smtSolvers -> return (solverName, "bv")
  934                 [solverName , logic] | solverName `elem` smtSolvers -> do
  935                     unless (logic `elem` smtSupportedLogics solverName) $
  936                         userErr1 $ vcat [ "SMT logic not supported by Savile Row:" <+> pretty logic
  937                                         , "Supported logics:" <+> prettyList id "," (smtSupportedLogics solverName)
  938                                         ]
  939                     return (solverName, logic)
  940                 _ -> return (solver, "") -- not an smt solver
  941     return (solverName, smtLogicName)
  942 
  943 
  944 srMkArgs :: UI -> FilePath -> FilePath -> IO [Text]
  945 srMkArgs Solve{..} outBase modelPath = do
  946     let genericOpts =
  947             [ "-in-eprime"      , stringToText modelPath
  948             , "-out-minion"     , stringToText $ outputDirectory </> outBase ++ ".eprime-minion"
  949             , "-out-sat"        , stringToText $ outputDirectory </> outBase ++ ".eprime-dimacs"
  950             , "-out-smt"        , stringToText $ outputDirectory </> outBase ++ ".eprime-smt"
  951             , "-out-aux"        , stringToText $ outputDirectory </> outBase ++ ".eprime-aux"
  952             , "-out-info"       , stringToText $ outputDirectory </> outBase ++ ".eprime-info"
  953             , "-out-minizinc"   , stringToText $ outputDirectory </> outBase ++ ".eprime.mzn"
  954             , "-run-solver"
  955             , "-S0"
  956             , "-solutions-to-stdout-one-line"
  957             ] ++
  958             [ "-cgroups" | cgroups ] ++
  959             ( if nbSolutions == "all"
  960                 then ["-all-solutions"]
  961                 else ["-num-solutions", stringToText nbSolutions]
  962             )
  963     (solverName, smtLogicName) <- splitSolverName solver
  964     solverSelection <- case solverName of
  965         "minion"            -> return [ "-minion" ]
  966         "gecode"            -> return [ "-gecode" ]
  967         "chuffed"           -> return [ "-chuffed"]
  968         "or-tools"          -> return [ "-or-tools"]
  969         "glucose"           -> return [ "-sat"
  970                                       , "-sat-family", "glucose"
  971                                       , "-satsolver-bin", "glucose"
  972                                       ]
  973         "glucose-syrup"     -> return [ "-sat"
  974                                       , "-sat-family", "glucose"
  975                                       , "-satsolver-bin", "glucose-syrup"
  976                                       ]
  977         "cadical"           -> return [ "-sat"
  978                                       , "-sat-family", "cadical"
  979                                       , "-satsolver-bin", "cadical"
  980                                       ]
  981         "kissat"            -> return [ "-sat"
  982                                       , "-sat-family", "cadical"
  983                                       , "-satsolver-bin", "kissat"
  984                                       ]
  985         "lingeling"         -> return [ "-sat"
  986                                       , "-sat-family", "lingeling"
  987                                       , "-satsolver-bin", "lingeling"
  988                                       ]
  989         "plingeling"        -> return [ "-sat"
  990                                       , "-sat-family", "lingeling"
  991                                       , "-satsolver-bin", "plingeling"
  992                                       ]
  993         "treengeling"       -> return [ "-sat"
  994                                       , "-sat-family", "lingeling"
  995                                       , "-satsolver-bin", "treengeling"
  996                                       ]
  997         "minisat"           -> return [ "-sat"
  998                                       , "-sat-family", "minisat"
  999                                       , "-satsolver-bin", "minisat"
 1000                                       ]
 1001         "bc_minisat_all"    -> return [ "-sat"
 1002                                       , "-sat-family", "bc_minisat_all"
 1003                                       , "-satsolver-bin", "bc_minisat_all_release"
 1004                                       ]
 1005         "nbc_minisat_all"   -> return [ "-sat"
 1006                                       , "-sat-family", "nbc_minisat_all"
 1007                                       , "-satsolver-bin", "nbc_minisat_all_release"
 1008                                       ]
 1009         "open-wbo"          -> return [ "-maxsat"
 1010                                       , "-satsolver-bin", "open-wbo"
 1011                                       ]
 1012         "coin-or"           -> return [ "-minizinc"
 1013                                       , "-solver-options", "--solver COIN-BC"
 1014                                       ]
 1015         "cplex"             -> do
 1016             env <- getEnvironment
 1017             case lookup "CPLEX_PATH" env of
 1018                 Nothing -> userErr1 $ vcat
 1019                     [ "Set environment variable CPLEX_PATH. Something like:"
 1020                     , "    CPLEX_PATH=/path/to/cplex/library conjure solve"
 1021                     ]
 1022                 Just cplex_path ->
 1023                     return [ "-minizinc"
 1024                            , "-solver-options", stringToText ("--solver CPLEX --cplex-dll " ++ cplex_path)
 1025                            ]
 1026         _ | solverName `elem` smtSolvers
 1027                             -> return [ "-smt"
 1028                                       , "-smt-nested" -- alternative is -smt-flat
 1029                                       , stringToText ("-smt-" ++ smtLogicName)
 1030                                       , "-smtsolver-bin"
 1031                                       , case lookup solverName solverExecutables of
 1032                                           Nothing -> bug ("solverExecutables" <+> pretty solverName)
 1033                                           Just ex -> stringToText ex
 1034                                       , stringToText (smtSolversSRFlag solverName)
 1035                                       ]
 1036         _ -> userErr1 ("Unknown solver:" <+> pretty solver)
 1037 
 1038     return $ genericOpts
 1039           ++ solverSelection
 1040           ++ map stringToText (concatMap words savilerowOptions)
 1041           ++ if null solverOptions
 1042                     then []
 1043                     else [ "-solver-options"
 1044                          , stringToText (unwords (concatMap words solverOptions))
 1045                          ]
 1046 srMkArgs _ _ _ = bug "srMkArgs"
 1047 
 1048 
 1049 srStdoutHandler ::
 1050     (FilePath, FilePath, FilePath, UI) ->
 1051     (Model -> NameGenM (IdentityT IO) Model) ->
 1052     Int ->
 1053     Handle ->
 1054     IO [Either String (FilePath, FilePath, Maybe FilePath)]
 1055 srStdoutHandler
 1056         args@(outBase , modelPath, paramPath , Solve{..})
 1057         tr solutionNumber h = do
 1058     eof <- hIsEOF h
 1059     if eof
 1060         then do
 1061             hClose h
 1062             return []
 1063         else do
 1064             line <- hGetLine h
 1065             when (logLevel >= LogDebug) $ do
 1066                 pp logLevel ("SR:" <+> pretty line)
 1067             case stripPrefix "Solution: " line of
 1068                 Nothing -> do
 1069                     if isPrefixOf "Created output file for domain filtering" line
 1070                         then pp logLevel $ hsep ["Running minion for domain filtering."]
 1071                         else if isPrefixOf "Created output" line
 1072                             then pp logLevel $ hsep ["Running solver:", pretty solver]
 1073                             else return ()
 1074                     fmap (Left line :)
 1075                          (srStdoutHandler args tr solutionNumber h)
 1076                 Just solutionText -> do
 1077                     eprimeSol  <- readModel ParserC.parseModel (Just id) ("<memory>", stringToText solutionText)
 1078                     essenceSol <- ignoreLogs $ runNameGen () $ tr eprimeSol
 1079                     case solutionsInOneFile of
 1080                         False -> do
 1081                             let mkFilename ext = outputDirectory </> outBase
 1082                                                         ++ "-solution" ++ padLeft 6 '0' (show solutionNumber)
 1083                                                         ++ ext
 1084                             let filenameEprimeSol  = mkFilename ".eprime-solution"
 1085                             let filenameEssenceSol = mkFilename ".solution"
 1086                             writeModel lineWidth Plain (Just filenameEprimeSol) eprimeSol
 1087                             writeModel lineWidth Plain (Just filenameEssenceSol) essenceSol
 1088                             case outputFormat of
 1089                                 JSON -> writeModel lineWidth outputFormat (Just (filenameEssenceSol ++ ".json")) essenceSol
 1090                                 JSONStream -> writeModel 0 outputFormat (Just (filenameEssenceSol ++ ".json")) essenceSol
 1091                                 _ -> return ()
 1092                             when (outputFormat == MiniZinc) $
 1093                                 writeModel lineWidth outputFormat (Just (filenameEssenceSol ++ ".minizinc")) essenceSol
 1094                             fmap (Right (modelPath, paramPath, Just filenameEssenceSol) :)
 1095                                  (srStdoutHandler args tr (solutionNumber+1) h)
 1096                         True -> do
 1097                             let mkFilename ext = outputDirectory </> outBase
 1098                                                         ++ ext
 1099                             let filenameEprimeSol  = mkFilename ".eprime-solutions"
 1100                             let filenameEssenceSol = mkFilename ".solutions"
 1101                             let filenameEssenceSolJSON = mkFilename ".solutions.json"
 1102                             -- remove the solutions files before writing the first solution
 1103                             if solutionNumber == 1
 1104                                 then do
 1105                                     removeFileIfExists filenameEprimeSol
 1106                                     removeFileIfExists filenameEssenceSol
 1107                                     removeFileIfExists filenameEssenceSolJSON
 1108                                     case outputFormat of
 1109                                         JSON -> writeFile filenameEssenceSolJSON "[\n"
 1110                                         _ -> return ()
 1111                                 else
 1112                                     case outputFormat of
 1113                                         JSON -> appendFile filenameEssenceSolJSON ",\n"
 1114                                         _ -> return ()
 1115                             appendFile filenameEprimeSol  ("$ Solution: " ++ padLeft 6 '0' (show solutionNumber) ++ "\n")
 1116                             appendFile filenameEprimeSol  (render lineWidth eprimeSol  ++ "\n\n")
 1117                             appendFile filenameEssenceSol ("$ Solution: " ++ padLeft 6 '0' (show solutionNumber) ++ "\n")
 1118                             appendFile filenameEssenceSol (render lineWidth essenceSol ++ "\n\n")
 1119                             when (outputFormat `elem` [JSON, JSONStream]) $ do
 1120                                 essenceSol' <- toSimpleJSON essenceSol
 1121                                 appendFile filenameEssenceSolJSON (render lineWidth essenceSol')
 1122                                 appendFile filenameEssenceSolJSON  ("\n")
 1123                             fmap (Right (modelPath, paramPath, Nothing) :)
 1124                                  (srStdoutHandler args tr (solutionNumber+1) h)
 1125 srStdoutHandler _ _ _ _ = bug "srStdoutHandler"
 1126 
 1127 
 1128 srCleanUp :: FilePath -> UI -> Text -> [sols] -> Sh (Either [Doc] [sols])
 1129 srCleanUp outBase ui@Solve{..} stdoutSR solutions = do
 1130 
 1131     let mkFilename ext = outputDirectory </> outBase ++ ext
 1132 
 1133     -- closing the array in the all solutions json file
 1134     case outputFormat of
 1135         JSON -> case solutionsInOneFile of
 1136             False -> return ()
 1137             True -> do
 1138                 let filenameEssenceSolJSON = mkFilename ".solutions.json"
 1139                 case solutions of
 1140                     [] -> liftIO $ writeFile  filenameEssenceSolJSON "[]\n"
 1141                     _  -> liftIO $ appendFile filenameEssenceSolJSON "]\n"
 1142         _ -> return ()
 1143 
 1144     stderrSR   <- lastStderr
 1145     exitCodeSR <- lastExitCode
 1146     let combinedSR = T.unlines [stdoutSR, stderrSR]
 1147 
 1148     liftIO $ do
 1149         let savilerowInfoFilename = mkFilename ".eprime-info"
 1150         let runsolverInfoFilename = mkFilename ".runsolver-info"
 1151         let statsFilename = mkFilename ".stats.json"
 1152         savilerowInfoContent <- liftIO $ readFileIfExists savilerowInfoFilename
 1153         runsolverInfoContent <- liftIO $ readFileIfExists runsolverInfoFilename
 1154         stats <- mkSolveStats ui (exitCodeSR, stdoutSR, stderrSR) (fromMaybe "" savilerowInfoContent) (fromMaybe "" runsolverInfoContent)
 1155         writeFile statsFilename (render lineWidth (toJSON stats))
 1156 
 1157     if  | T.isInfixOf "Savile Row timed out." combinedSR ->
 1158             return (Left ["Savile Row timed out."])
 1159         | T.isInfixOf "where false" combinedSR ->
 1160             return (Left [vcat [ "Invalid instance, a where statement evaluated to false."
 1161                                , "(It can be an implicit where statement added by Conjure.)"
 1162                                ]])
 1163         | or [ T.isInfixOf "Exception in thread" combinedSR
 1164              , T.isInfixOf "ERROR" combinedSR
 1165              , T.isInfixOf "Sub-process exited with error code" combinedSR
 1166              ] ->
 1167              return (Left [vcat [ "Savile Row stdout:"    <+> pretty stdoutSR
 1168                                 , "Savile Row stderr:"    <+> pretty stderrSR
 1169                                 , "Savile Row exit-code:" <+> pretty exitCodeSR
 1170                                 ]])
 1171         | exitCodeSR == 0 -> return (Right solutions)
 1172         | otherwise ->
 1173             return (Left [vcat [ "Savile Row stdout:"    <+> pretty stdoutSR
 1174                                , "Savile Row stderr:"    <+> pretty stderrSR
 1175                                , "Savile Row exit-code:" <+> pretty exitCodeSR
 1176                                ]])
 1177 srCleanUp _ _ _ _ = bug "srCleanUp"
 1178 
 1179 
 1180 validateSolutionNoParam ::
 1181     MonadIO m =>
 1182     MonadLog m =>
 1183     MonadFailDoc m =>
 1184     EnumerateDomain m =>
 1185     (?typeCheckerMode :: TypeCheckerMode) =>
 1186     UI -> FilePath -> m ()
 1187 validateSolutionNoParam Solve{..} solutionPath = do
 1188     pp logLevel $ hsep ["Validating solution:", pretty solutionPath]
 1189     essenceM <- readModelFromFile essence
 1190     solution <- readParamOrSolutionFromFile essenceM solutionPath
 1191     [essenceM2, solution2] <- ignoreLogs $ runNameGen () $ resolveNamesMulti [essenceM, solution]
 1192     failToUserError $ ignoreLogs $ runNameGen () $ validateSolution essenceM2 def solution2
 1193 validateSolutionNoParam _ _ = bug "validateSolutionNoParam"
 1194 
 1195 
 1196 validateSolutionWithParams ::
 1197     MonadIO m =>
 1198     MonadLog m =>
 1199     MonadFailDoc m =>
 1200     EnumerateDomain m =>
 1201     (?typeCheckerMode :: TypeCheckerMode) =>
 1202     UI -> FilePath -> FilePath -> m ()
 1203 validateSolutionWithParams Solve{..} solutionPath paramPath = do
 1204     pp logLevel $ hsep ["Validating solution:", pretty paramPath, pretty solutionPath]
 1205     essenceM <- readModelFromFile essence
 1206     param    <- readParamOrSolutionFromFile essenceM paramPath
 1207     solution <- readParamOrSolutionFromFile essenceM solutionPath
 1208     [essenceM2, param2, solution2] <- ignoreLogs $ runNameGen () $ resolveNamesMulti [essenceM, param, solution]
 1209     failToUserError $ ignoreLogs $ runNameGen () $ validateSolution essenceM2 param2 solution2
 1210 validateSolutionWithParams _ _ _ = bug "validateSolutionWithParams"
 1211 
 1212 
 1213 doIfNotCached
 1214     :: (MonadIO m, Hashable h)
 1215     => h                        -- thing to hash
 1216     -> FilePath                 -- saved hashes file
 1217     -> m a                      -- the result from cache
 1218     -> m a                      -- the action
 1219     -> m a                      -- the results and writing the new hashes in the file
 1220 doIfNotCached (show . hash -> h) savedHashesFile getResult act = do
 1221     savedHashes <- liftIO $ readFileIfExists savedHashesFile
 1222     let skip = Just h == savedHashes                -- skip if h was the hash in the file
 1223     if skip
 1224         then getResult
 1225         else do
 1226             res <- act
 1227             liftIO $ writeFile savedHashesFile h
 1228             return res
 1229 
 1230 
 1231 autoParallel :: [IO a] -> IO [a]
 1232 autoParallel = if numCapabilities > 1 then parallel else sequence
 1233