You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
18 KiB

  1. #ifndef WINDOWS
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <sys/ioctl.h>
  5. #include <signal.h>
  6. #include <sys/wait.h>
  7. #include <errno.h>
  8. #endif
  9. #include "storm/solver/SmtlibSmtSolver.h"
  10. #include "storm/settings/SettingsManager.h"
  11. #include "storm/settings/modules/Smt2SmtSolverSettings.h"
  12. #include "storm/exceptions/NotSupportedException.h"
  13. #include "storm/exceptions/NotImplementedException.h"
  14. #include "storm/exceptions/InvalidStateException.h"
  15. #include "storm/exceptions/IllegalArgumentException.h"
  16. #include "storm/exceptions/IllegalFunctionCallException.h"
  17. #include "storm/utility/macros.h"
  18. #include "storm/utility/file.h"
  19. #include "storm/adapters/RationalFunctionAdapter.h"
  20. #include "storm/exceptions/UnexpectedException.h"
  21. namespace storm {
  22. namespace solver {
  23. SmtlibSmtSolver::SmtlibModelReference::SmtlibModelReference(storm::expressions::ExpressionManager const& manager) : ModelReference(manager) {
  24. // Intentionally left empty.
  25. }
  26. bool SmtlibSmtSolver::SmtlibModelReference::getBooleanValue(storm::expressions::Variable const&) const {
  27. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  28. }
  29. int_fast64_t SmtlibSmtSolver::SmtlibModelReference::getIntegerValue(storm::expressions::Variable const&) const {
  30. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  31. }
  32. double SmtlibSmtSolver::SmtlibModelReference::getRationalValue(storm::expressions::Variable const&) const {
  33. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  34. }
  35. SmtlibSmtSolver::SmtlibSmtSolver(storm::expressions::ExpressionManager& manager, bool useCarlExpressions) : SmtSolver(manager), isCommandFileOpen(false), expressionAdapter(nullptr), useCarlExpressions(useCarlExpressions) {
  36. #ifndef STORM_HAVE_CARL
  37. STORM_LOG_THROW(!useCarlExpressions, storm::exceptions::IllegalArgumentException, "Tried to use carl expressions but storm is not linked with CARL");
  38. #endif
  39. #ifndef WINDOWS
  40. processIdOfSolver=0;
  41. #endif
  42. this->expressionAdapter = std::unique_ptr<storm::adapters::Smt2ExpressionAdapter>(new storm::adapters::Smt2ExpressionAdapter(this->getManager(), this->useReadableVarNames));
  43. init();
  44. }
  45. SmtlibSmtSolver::~SmtlibSmtSolver() {
  46. writeCommand("( exit )", false); //do not wait for success because it does not matter at this point and may cause problems if the solver is not running properly
  47. #ifndef WINDOWS
  48. if(processIdOfSolver!=0){
  49. //Since the process has been opened successfully, it means that we have to close our fds
  50. close(fromSolver);
  51. close(toSolver);
  52. kill(processIdOfSolver, SIGTERM);
  53. waitpid(processIdOfSolver, nullptr, 0); //make sure the process has exited
  54. }
  55. #endif
  56. }
  57. void SmtlibSmtSolver::push() {
  58. expressionAdapter->increaseScope();
  59. writeCommand("( push 1 ) ", true);
  60. }
  61. void SmtlibSmtSolver::pop() {
  62. expressionAdapter->decreaseScope();
  63. writeCommand("( pop 1 ) ", true);
  64. }
  65. void SmtlibSmtSolver::pop(uint_fast64_t n) {
  66. expressionAdapter->decreaseScope(n);
  67. writeCommand("( pop " + std::to_string(n) + " ) ", true);
  68. }
  69. void SmtlibSmtSolver::reset() {
  70. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  71. }
  72. void SmtlibSmtSolver::add(storm::expressions::Expression const&) {
  73. STORM_LOG_THROW(!useCarlExpressions, storm::exceptions::IllegalFunctionCallException, "This solver was initialized without allowing carl expressions");
  74. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  75. }
  76. #ifdef STORM_HAVE_CARL
  77. void SmtlibSmtSolver::add(storm::RationalFunction const& leftHandSide, storm::CompareRelation const& relation, storm::RationalFunction const& rightHandSide) {
  78. STORM_LOG_THROW(useCarlExpressions, storm::exceptions::IllegalFunctionCallException, "This solver was initialized without allowing carl expressions");
  79. //if some of the occurring variables are not declared yet, we will have to.
  80. std::set<storm::RationalFunctionVariable> variables;
  81. leftHandSide.gatherVariables(variables);
  82. rightHandSide.gatherVariables(variables);
  83. std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variables);
  84. for (auto declaration : varDeclarations){
  85. writeCommand(declaration, true);
  86. }
  87. writeCommand("( assert " + expressionAdapter->translateExpression(leftHandSide, relation, rightHandSide) + " )", true);
  88. }
  89. void SmtlibSmtSolver::add(const storm::RationalFunctionVariable& variable, bool value){
  90. STORM_LOG_THROW((variable.type()==carl::VariableType::VT_BOOL), storm::exceptions::IllegalArgumentException, "Tried to add a constraint that consists of a non-boolean variable.");
  91. std::set<storm::RationalFunctionVariable> variableSet;
  92. variableSet.insert(variable);
  93. std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variableSet);
  94. for (auto declaration : varDeclarations){
  95. writeCommand(declaration, true);
  96. }
  97. std::string varName= carl::VariablePool::getInstance().getName(variable, this->useReadableVarNames);
  98. if(value){
  99. writeCommand("( assert " + varName + " )", true);
  100. }
  101. else{
  102. writeCommand("( assert (not " + varName + ") )", true);
  103. }
  104. }
  105. #endif
  106. SmtSolver::CheckResult SmtlibSmtSolver::check() {
  107. writeCommand("( check-sat )", false);
  108. #ifdef WINDOWS
  109. STORM_LOG_WARN("SMT-LIBv2 Solver can not be started on Windows as this is not yet implemented. Assume that the check-result is \"unknown\"");
  110. return SmtSolver::CheckResult::Unknown;
  111. #else
  112. if (processIdOfSolver!=0){
  113. auto solverOutput = readSolverOutput();
  114. STORM_LOG_THROW(solverOutput.size()==1, storm::exceptions::UnexpectedException, "expected a single line of output after smt2 command ( check-sat ). Got " + std::to_string(solverOutput.size()) + " lines of output instead.");
  115. solverOutput[0].erase(std::remove_if(solverOutput[0].begin(), solverOutput[0].end(), ::isspace), solverOutput[0].end()); //remove spaces
  116. if(solverOutput[0]=="sat") return SmtSolver::CheckResult::Sat;
  117. if(solverOutput[0]=="unsat") return SmtSolver::CheckResult::Unsat;
  118. if(solverOutput[0]=="unknown") return SmtSolver::CheckResult::Unknown;
  119. //if we reach this point, something unexpected happened. Lets return unknown and print some debug output
  120. STORM_LOG_DEBUG("unexpected solver output: " << solverOutput[0] << ". Returning result 'unknown'");
  121. return SmtSolver::CheckResult::Unknown;
  122. }
  123. else{
  124. STORM_LOG_WARN("No SMT-LIBv2 Solver Command specified, which means that no actual SMT solving is done... Assume that the result is \"unknown\"");
  125. return SmtSolver::CheckResult::Unknown;
  126. }
  127. #endif
  128. }
  129. SmtSolver::CheckResult SmtlibSmtSolver::checkWithAssumptions(std::set<storm::expressions::Expression> const&) {
  130. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  131. }
  132. #ifndef WINDOWS
  133. SmtSolver::CheckResult SmtlibSmtSolver::checkWithAssumptions(std::initializer_list<storm::expressions::Expression> const&) {
  134. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented");
  135. }
  136. #endif
  137. void SmtlibSmtSolver::init() {
  138. if (storm::settings::getModule<storm::settings::modules::Smt2SmtSolverSettings>().isSolverCommandSet()){
  139. #ifdef WINDOWS
  140. STORM_LOG_WARN("opening a thread for the smt solver is not implemented on Windows. Hence, no actual solving will be done")
  141. #else
  142. signal(SIGPIPE, SIG_IGN);
  143. this->needsRestart=false;
  144. //for executing the solver we will need the path to the executable file as well as the arguments as a char* array
  145. //where the first argument is the path to the executable file and the last is NULL
  146. const std::string cmdString = storm::settings::getModule<storm::settings::modules::Smt2SmtSolverSettings>().getSolverCommand();
  147. std::vector<std::string> solverCommandVec;
  148. boost::split(solverCommandVec, cmdString, boost::is_any_of("\t "));
  149. char** solverArgs = new char* [solverCommandVec.size()+1];
  150. solverArgs[0] = const_cast<char*>(solverCommandVec[0].substr(0, cmdString.rfind('/')+1).c_str());
  151. for(uint_fast64_t argumentIndex = 1; argumentIndex<solverCommandVec.size(); ++argumentIndex){
  152. solverArgs[argumentIndex]=const_cast<char*>(solverCommandVec[argumentIndex].c_str());
  153. }
  154. solverArgs[solverCommandVec.size()] = NULL;
  155. //get the pipes started
  156. int pipeIn[2];
  157. int pipeOut[2];
  158. const int READ=0;
  159. const int WRITE=1;
  160. STORM_LOG_THROW(pipe(pipeIn) == 0 && pipe(pipeOut)==0, storm::exceptions::UnexpectedException, "Could not open pipe to new process");
  161. //now start the child process, i.e., the solver
  162. pid_t pid = fork();
  163. STORM_LOG_THROW(pid>=0, storm::exceptions::UnexpectedException, "Could not start new process for the smt solver");
  164. if (pid == 0){
  165. // Child process
  166. // duplicate the fd so that standard input and output will be send to our pipes
  167. dup2(pipeIn[READ], STDIN_FILENO);
  168. dup2(pipeOut[WRITE], STDOUT_FILENO);
  169. dup2(pipeOut[WRITE], STDERR_FILENO);
  170. // we can now close everything since our child process will use std in and out to address the pipes
  171. close(pipeIn[READ]);
  172. close(pipeIn[WRITE]);
  173. close(pipeOut[READ]);
  174. close(pipeOut[WRITE]);
  175. execv(solverCommandVec[0].c_str(), solverArgs); //"-smt2 -in"
  176. // if we reach this point, execl was not successful
  177. STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Could not execute the solver correctly");
  178. }
  179. // Parent Process
  180. toSolver=pipeIn[WRITE];
  181. fromSolver=pipeOut[READ];
  182. close(pipeOut[WRITE]);
  183. close(pipeIn[READ]);
  184. processIdOfSolver=pid;
  185. #endif
  186. }
  187. else{
  188. STORM_LOG_WARN("No SMT-LIBv2 Solver Command specified, which means that no actual SMT solving can be done");
  189. }
  190. if (storm::settings::getModule<storm::settings::modules::Smt2SmtSolverSettings>().isExportSmtLibScriptSet()){
  191. STORM_LOG_DEBUG("The SMT-LIBv2 commands are exportet to the given file");
  192. storm::utility::openFile(storm::settings::getModule<storm::settings::modules::Smt2SmtSolverSettings>().getExportSmtLibScriptPath(), commandFile);
  193. isCommandFileOpen = true;
  194. // TODO also close file
  195. }
  196. //some initial commands
  197. writeCommand("( set-option :print-success true )", true);
  198. writeCommand("( set-logic QF_NRA )", true);
  199. //writeCommand("( get-info :name )");
  200. }
  201. bool SmtlibSmtSolver::isNeedsRestart() const {
  202. return this->needsRestart;
  203. }
  204. void SmtlibSmtSolver::writeCommand(std::string smt2Command, bool expectSuccess) {
  205. if (isCommandFileOpen) {
  206. commandFile << smt2Command << std::endl;
  207. }
  208. #ifndef WINDOWS
  209. if (processIdOfSolver!=0){
  210. if(write(toSolver, (smt2Command+"\n").c_str(), smt2Command.length()+1) < 0){
  211. STORM_LOG_DEBUG("Was not able to write " << smt2Command << "to the solver.");
  212. }
  213. if(expectSuccess){
  214. auto output=readSolverOutput();
  215. STORM_LOG_THROW(output.size()==1, storm::exceptions::UnexpectedException, "expected a single success response after smt2 command " + smt2Command + ". Got " + std::to_string(output.size()) + " lines of output instead.");
  216. output[0].erase(std::remove_if(output[0].begin(), output[0].end(), ::isspace), output[0].end());
  217. STORM_LOG_THROW(output[0]=="success", storm::exceptions::UnexpectedException, "expected <<success>> response after smt2 command " + smt2Command + ". Got <<" + output[0] + ">> instead");
  218. }
  219. }
  220. #endif
  221. }
  222. std::vector<std::string> SmtlibSmtSolver::readSolverOutput(bool waitForOutput){
  223. #ifndef WINDOWS
  224. if (processIdOfSolver==0){
  225. STORM_LOG_DEBUG("failed to read solver output as the solver is not running");
  226. return std::vector<std::string>();
  227. }
  228. int bytesReadable;
  229. if(waitForOutput){
  230. bytesReadable=1;//just assume that there are bytes readable
  231. }
  232. else if(ioctl(fromSolver, FIONREAD, &bytesReadable)< 0){ //actually obtain the readable bytes
  233. STORM_LOG_ERROR("Could not check if the solver has output");
  234. return std::vector<std::string>();
  235. }
  236. std::string solverOutput="";
  237. const ssize_t MAX_CHUNK_SIZE=256;
  238. char chunk[MAX_CHUNK_SIZE];
  239. while(bytesReadable>0){
  240. ssize_t chunkSize = read(fromSolver, chunk, MAX_CHUNK_SIZE);
  241. STORM_LOG_THROW(chunkSize>=0, storm::exceptions::UnexpectedException, "failed to read solver output");
  242. solverOutput += std::string(chunk, chunkSize);
  243. if(ioctl(fromSolver, FIONREAD, &bytesReadable)< 0){//obtain the new amount of readable bytes
  244. STORM_LOG_ERROR("Could not check if the solver has output");
  245. return std::vector<std::string>();
  246. }
  247. if(bytesReadable==0 && solverOutput.back()!='\n'){
  248. STORM_LOG_DEBUG("Solver Output '" << solverOutput << "' did not end with newline symbol (\\n). Since we assume that this should be the case, we will wait for more output from the solver");
  249. bytesReadable=1; //we expect more output!
  250. }
  251. if(bytesReadable>0){
  252. pid_t w=waitpid(processIdOfSolver, nullptr, WNOHANG);
  253. if(w!=0){
  254. STORM_LOG_WARN_COND(w>0, "Error when checking whether the solver is still running. Will assume that solver has terminated.");
  255. STORM_LOG_WARN("The solver exited unexpectedly when reading output: " << solverOutput);
  256. solverOutput+="terminated";
  257. this->needsRestart=true;
  258. this->processIdOfSolver=0;
  259. bytesReadable=0;
  260. }
  261. }
  262. }
  263. checkForErrorMessage(solverOutput);
  264. std::vector<std::string> solverOutputAsVector;
  265. if(solverOutput.length()>0){
  266. solverOutput=solverOutput.substr(0,solverOutput.length()-1);
  267. boost::split(solverOutputAsVector, solverOutput, boost::is_any_of("\n"));
  268. //note: this is a little bit unsafe as \n can be contained within a solver response
  269. }
  270. return solverOutputAsVector;
  271. #endif
  272. }
  273. void SmtlibSmtSolver::checkForErrorMessage(const std::string message){
  274. size_t errorOccurrance = message.find("error");
  275. //do not throw an exception for timeout or memout errors
  276. if(message.find("timeout")!=std::string::npos){
  277. STORM_LOG_INFO("SMT solver answered: '" << message << "' and I am interpreting this as timeout ");
  278. this->needsRestart=true;
  279. this->processIdOfSolver=0;
  280. }
  281. else if(message.find("memory")!=std::string::npos){
  282. STORM_LOG_INFO("SMT solver answered: '" << message << "' and I am interpreting this as out of memory ");
  283. this->needsRestart=true;
  284. this->processIdOfSolver=0;
  285. }
  286. else if(errorOccurrance!=std::string::npos){
  287. this->needsRestart=true;
  288. std::string errorMsg = "An error was detected while checking the solver output. ";
  289. STORM_LOG_DEBUG("Detected an error message in the solver response:\n" + message);
  290. size_t firstQuoteSign = message.find('\"',errorOccurrance);
  291. if(firstQuoteSign!=std::string::npos && message.find("\\\"", firstQuoteSign-1)!=firstQuoteSign-1){
  292. size_t secondQuoteSign = message.find('\"', firstQuoteSign+1);
  293. while(secondQuoteSign!=std::string::npos && message.find("\\\"", secondQuoteSign-1)==secondQuoteSign-1){
  294. secondQuoteSign = message.find('\"',secondQuoteSign+1);
  295. }
  296. if(secondQuoteSign!=std::string::npos){
  297. errorMsg += "The error message was: <<" + message.substr(errorOccurrance,secondQuoteSign-errorOccurrance+1) + ">>.";
  298. STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, errorMsg);
  299. return;
  300. }
  301. }
  302. errorMsg += "The error message could not be parsed correctly. Snippet:\n" + message.substr(errorOccurrance,200);
  303. STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, errorMsg);
  304. }
  305. }
  306. }
  307. }