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