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.
		
		
		
		
		
			
		
			
				
					
					
						
							346 lines
						
					
					
						
							19 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							346 lines
						
					
					
						
							19 KiB
						
					
					
				| #include "src/solver/Smt2SmtSolver.h" | |
|  | |
| #include "src/settings/SettingsManager.h" | |
| #include "src/exceptions/NotSupportedException.h" | |
| #include "src/exceptions/NotImplementedException.h" | |
| #include "src/exceptions/InvalidStateException.h" | |
| #include "src/exceptions/IllegalArgumentException.h" | |
| #include "src/exceptions/IllegalFunctionCallException.h" | |
| #include "utility/macros.h" | |
| #include "adapters/CarlAdapter.h" | |
| #include "exceptions/UnexpectedException.h" | |
|  | |
| namespace storm { | |
|     namespace solver { | |
| 
 | |
|         Smt2SmtSolver::Smt2ModelReference::Smt2ModelReference(storm::expressions::ExpressionManager const& manager, storm::adapters::Smt2ExpressionAdapter& expressionAdapter) : ModelReference(manager), expressionAdapter(expressionAdapter){ | |
|             // Intentionally left empty. | |
|         } | |
| 
 | |
|         bool Smt2SmtSolver::Smt2ModelReference::getBooleanValue(storm::expressions::Variable const& variable) const { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
|         int_fast64_t Smt2SmtSolver::Smt2ModelReference::getIntegerValue(storm::expressions::Variable const& variable) const { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
|         double Smt2SmtSolver::Smt2ModelReference::getRationalValue(storm::expressions::Variable const& variable) const { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
|         Smt2SmtSolver::Smt2SmtSolver(storm::expressions::ExpressionManager& manager, bool useCarlExpressions) : SmtSolver(manager), isCommandFileOpen(false), expressionAdapter(nullptr), lastCheckAssumptions(false), lastResult(CheckResult::Unknown), useCarlExpressions(useCarlExpressions) { | |
| #ifndef STORM_HAVE_CARL | |
|             STORM_LOG_THROW(!useCarlExpressions, storm::exceptions::IllegalArgumentException, "Tried to use carl expressions but storm is not linked with CARL"); | |
| #endif | |
| #ifndef WINDOWS | |
|             processIdOfSolver=0; | |
| #endif | |
|             this->expressionAdapter = std::unique_ptr<storm::adapters::Smt2ExpressionAdapter>(new storm::adapters::Smt2ExpressionAdapter(this->getManager(), this->useReadableVarNames)); | |
|             init(); | |
|         } | |
| 
 | |
|         Smt2SmtSolver::~Smt2SmtSolver() { | |
|             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 | |
| #ifndef WINDOWS | |
|             if(processIdOfSolver!=0){ | |
|                 //Since the process has been open successfully, it means that we have to close our fds    | |
|                 close(fromSolver); | |
|                 close(toSolver); | |
|                 //todo make sure that the process exits  waitpid kill(pid, ..); | |
|             } | |
| #endif | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::push() { | |
|             expressionAdapter->increaseScope(); | |
|             writeCommand("( push 1 ) ", true); | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::pop() { | |
|             expressionAdapter->decreaseScope(); | |
|             writeCommand("( pop 1 ) ", true); | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::pop(uint_fast64_t n) { | |
|             expressionAdapter->decreaseScope(n); | |
|             writeCommand("( pop " + std::to_string(n) + " ) ", true); | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::reset() { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::add(storm::expressions::Expression const& assertion) { | |
|             STORM_LOG_THROW(!useCarlExpressions, storm::exceptions::IllegalFunctionCallException, "This solver was initialized without allowing carl expressions"); | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
| #ifdef STORM_HAVE_CARL         | |
|         void Smt2SmtSolver::add(storm::RationalFunction const& leftHandSide, storm::CompareRelation const& relation, storm::RationalFunction const& rightHandSide) { | |
|             STORM_LOG_THROW(useCarlExpressions, storm::exceptions::IllegalFunctionCallException, "This solver was initialized without allowing carl expressions"); | |
|             //if some of the occurring variables are not declared yet, we will have to. | |
|             std::set<storm::Variable> variables; | |
|             leftHandSide.gatherVariables(variables); | |
|             rightHandSide.gatherVariables(variables); | |
|             std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variables); | |
|             for (auto declaration : varDeclarations){ | |
|                 writeCommand(declaration, true); | |
|             } | |
|             writeCommand("( assert " + expressionAdapter->translateExpression(leftHandSide, relation, rightHandSide) + " )", true); | |
|         } | |
|          | |
|         void Smt2SmtSolver::add(carl::Constraint<storm::RationalFunction> const& constraint) { | |
|             add(constraint.lhs(), constraint.rel()); | |
|         } | |
|          | |
|         void Smt2SmtSolver::add(carl::Constraint<storm::RawPolynomial> const& constraint) { | |
|             //if some of the occurring variables are not declared yet, we will have to. | |
|             std::set<storm::Variable> variables = constraint.lhs().gatherVariables(); | |
|             std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variables); | |
|             for (auto declaration : varDeclarations){ | |
|                 writeCommand(declaration, true); | |
|             } | |
|             writeCommand("( assert " + expressionAdapter->translateExpression(constraint) + " )", true); | |
|         } | |
|          | |
|         void Smt2SmtSolver::add(storm::Variable const& guard, typename carl::Constraint<storm::Polynomial> const& constraint){ | |
|             STORM_LOG_THROW((guard.getType()==carl::VariableType::VT_BOOL), storm::exceptions::IllegalArgumentException, "Tried to add a guarded constraint, but the guard is not of type bool."); | |
|             //if some of the occurring variables are not declared yet, we will have to (including the guard!). | |
|             std::set<storm::Variable> variables = constraint.lhs().gatherVariables(); | |
|             variables.insert(guard); | |
|             std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variables); | |
|             for (auto declaration : varDeclarations){ | |
|                 writeCommand(declaration, true); | |
|             } | |
|             std::string guardName= carl::VariablePool::getInstance().getName(guard, this->useReadableVarNames); | |
|             writeCommand("( assert (=> " + guardName + " " + expressionAdapter->translateExpression(constraint) + " ) )", true); | |
|         } | |
|          | |
|         void Smt2SmtSolver::add(const storm::Variable& variable, bool value){ | |
|             STORM_LOG_THROW((variable.getType()==carl::VariableType::VT_BOOL), storm::exceptions::IllegalArgumentException, "Tried to add a constraint that consists of a non-boolean variable."); | |
|             std::set<storm::Variable> variableSet; | |
|             variableSet.insert(variable); | |
|             std::vector<std::string> const varDeclarations = expressionAdapter->checkForUndeclaredVariables(variableSet); | |
|             for (auto declaration : varDeclarations){ | |
|                 writeCommand(declaration, true); | |
|             } | |
|             std::string varName= carl::VariablePool::getInstance().getName(variable, this->useReadableVarNames); | |
|             if(value){ | |
|                 writeCommand("( assert " + varName + " )", true); | |
|             } | |
|             else{ | |
|                 writeCommand("( assert (not " + varName + ") )", true); | |
|             } | |
|         } | |
|          | |
| #endif | |
|  | |
|         SmtSolver::CheckResult Smt2SmtSolver::check() { | |
|             writeCommand("( check-sat )", false); | |
| #ifdef WINDOWS | |
|                 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\""); | |
|                 return SmtSolver::CheckResult::Unknown; | |
| #else | |
|                  | |
|             if (processIdOfSolver!=0){ | |
|                 auto solverOutput = readSolverOutput(); | |
|                 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."); | |
|                 solverOutput[0].erase(std::remove_if(solverOutput[0].begin(), solverOutput[0].end(), isspace), solverOutput[0].end()); //remove spaces | |
|                 if(solverOutput[0]=="sat") return SmtSolver::CheckResult::Sat; | |
|                 if(solverOutput[0]=="unsat") return SmtSolver::CheckResult::Unsat; | |
|                 if(solverOutput[0]=="unknown") return SmtSolver::CheckResult::Unknown; | |
|                 //if we reach this point, something unexpected happened. Lets return unknown and print some debug output | |
|                 STORM_LOG_DEBUG("unexpected solver output: " << solverOutput[0] << ". Returning result 'unknown'"); | |
|                 return SmtSolver::CheckResult::Unknown; | |
|             } | |
|             else{ | |
|                 STORM_LOG_WARN("No SMT-LIBv2 Solver Command specified, which means that no actual SMT solving is done... Assume that the result is \"unknown\""); | |
|                 return SmtSolver::CheckResult::Unknown; | |
|             } | |
| #endif | |
|         } | |
| 
 | |
|         SmtSolver::CheckResult Smt2SmtSolver::checkWithAssumptions(std::set<storm::expressions::Expression> const& assumptions) { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| 
 | |
| #ifndef WINDOWS | |
|  | |
|         SmtSolver::CheckResult Smt2SmtSolver::checkWithAssumptions(std::initializer_list<storm::expressions::Expression> const& assumptions) { | |
|             STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "functionality not (yet) implemented"); | |
|         } | |
| #endif | |
|  | |
|         void Smt2SmtSolver::init() { | |
|             if (storm::settings::smt2SmtSolverSettings().isSolverCommandSet()){ | |
| #ifdef WINDOWS | |
|                 STORM_LOG_WARN("opening a thread for the smt solver is not implemented on Windows. Hence, no actual solving will be done") | |
| #else | |
|                 signal(SIGPIPE, SIG_IGN); | |
|                 this->needsRestart=false; | |
|                  | |
|                 //for executing the solver we will need the path to the executable file as well as the arguments as a char* array | |
|                 //where the first argument is the path to the executable file and the last is NULL | |
|                 const std::string cmdString = storm::settings::smt2SmtSolverSettings().getSolverCommand(); | |
|                 std::vector<std::string> solverCommandVec; | |
|                 boost::split(solverCommandVec, cmdString, boost::is_any_of("\t ")); | |
|                 char** solverArgs = new char* [solverCommandVec.size()+1]; | |
|                 solverArgs[0] = const_cast<char*>(solverCommandVec[0].substr(0, cmdString.rfind('/')+1).c_str()); | |
|                 for(uint_fast64_t argumentIndex = 1; argumentIndex<solverCommandVec.size(); ++argumentIndex){ | |
|                     solverArgs[argumentIndex]=const_cast<char*>(solverCommandVec[argumentIndex].c_str()); | |
|                 } | |
|                 solverArgs[solverCommandVec.size()] = NULL; | |
|                  | |
|                 //get the pipes started | |
|                 int pipeIn[2]; | |
|                 int pipeOut[2];                 | |
|                 const int READ=0; | |
|                 const int WRITE=1; | |
|                 STORM_LOG_THROW(pipe(pipeIn) == 0 && pipe(pipeOut)==0, storm::exceptions::UnexpectedException, "Could not open pipe to new process"); | |
|                  | |
|                 //now start the child process, i.e., the solver | |
|                 pid_t pid = fork(); | |
|                 STORM_LOG_THROW(pid>=0, storm::exceptions::UnexpectedException, "Could not start new process for the smt solver"); | |
|                 if (pid == 0){ | |
|                     // Child process | |
|                     // duplicate the fd so that standard input and output will be send to our pipes | |
|                     dup2(pipeIn[READ], STDIN_FILENO);  | |
|                     dup2(pipeOut[WRITE], STDOUT_FILENO); | |
|                     dup2(pipeOut[WRITE], STDERR_FILENO); | |
|                     // we can now close everything since our child process will use std in and out to address the pipes | |
|                     close(pipeIn[READ]); | |
|                     close(pipeIn[WRITE]);  | |
|                     close(pipeOut[READ]);  | |
|                     close(pipeOut[WRITE]);  | |
|                      | |
|                     execv(solverCommandVec[0].c_str(), solverArgs); //"-smt2 -in" | |
|                     // if we reach this point, execl was not successful | |
|                     STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Could not execute the solver correctly"); | |
|                 } | |
|                 // Parent Process | |
|                 toSolver=pipeIn[WRITE]; | |
|                 fromSolver=pipeOut[READ];    | |
|                 close(pipeOut[WRITE]); | |
|                 close(pipeIn[READ]); | |
|                 processIdOfSolver=pid; | |
|                  | |
|                  | |
| #endif   | |
|             } | |
|             else{ | |
|                 STORM_LOG_WARN("No SMT-LIBv2 Solver Command specified, which means that no actual SMT solving can be done"); | |
|             } | |
| 
 | |
|             if (storm::settings::smt2SmtSolverSettings().isExportSmtLibScriptSet()){ | |
|                 STORM_LOG_DEBUG("The SMT-LIBv2 commands are exportet to the given file"); | |
|                 commandFile.open(storm::settings::smt2SmtSolverSettings().getExportSmtLibScriptPath(), std::ios::trunc); | |
|                 isCommandFileOpen=commandFile.is_open(); | |
|                 STORM_LOG_THROW(isCommandFileOpen, storm::exceptions::InvalidArgumentException, "The file where the smt2commands should be written to could not be opened"); | |
|             } | |
| 
 | |
|             //some initial commands | |
|             writeCommand("( set-option :print-success true )", true); | |
|             writeCommand("( set-logic QF_NRA )", true); | |
|             //writeCommand("( get-info :name )"); | |
|         } | |
| 
 | |
|         bool Smt2SmtSolver::isNeedsRestart() const { | |
|             return this->needsRestart; | |
|         } | |
| 
 | |
|         void Smt2SmtSolver::writeCommand(std::string smt2Command, bool expectSuccess) { | |
|             if (isCommandFileOpen) { | |
|                 commandFile << smt2Command << std::endl; | |
|             } | |
|              | |
| #ifndef WINDOWS | |
|             if (processIdOfSolver!=0){ | |
|                 if(write(toSolver, (smt2Command+"\n").c_str(), smt2Command.length()+1) < 0){ | |
|                     STORM_LOG_DEBUG("Was not able to write " << smt2Command << "to the solver."); | |
|                 } | |
|                 if(expectSuccess){ | |
|                     auto output=readSolverOutput(); | |
|                     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."); | |
|                     output[0].erase(std::remove_if(output[0].begin(), output[0].end(), isspace), output[0].end()); | |
|                     STORM_LOG_THROW(output[0]=="success", storm::exceptions::UnexpectedException, "expected <<success>> response after smt2 command " + smt2Command + ". Got <<" + output[0] + ">> instead"); | |
|                 } | |
|             } | |
| #endif | |
|         } | |
|          | |
|         std::vector<std::string> Smt2SmtSolver::readSolverOutput(bool waitForOutput){ | |
| #ifndef WINDOWS | |
|             if (processIdOfSolver==0){ | |
|                 STORM_LOG_DEBUG("failed to read solver output as the solver is not running"); | |
|                 return std::vector<std::string>(); | |
|             } | |
|             int bytesReadable; | |
|             if(waitForOutput){ | |
|                 bytesReadable=1;//just assume that there are bytes readable | |
|             } | |
|             else if(ioctl(fromSolver, FIONREAD, &bytesReadable)< 0){ //actually obtain the readable bytes | |
|                 STORM_LOG_ERROR("Could not check if the solver has output"); | |
|                 return std::vector<std::string>(); | |
|             } | |
|             std::string solverOutput=""; | |
|             const ssize_t MAX_CHUNK_SIZE=256; | |
|             char chunk[MAX_CHUNK_SIZE]; | |
|             while(bytesReadable>0){ | |
|                 ssize_t chunkSize = read(fromSolver, chunk, MAX_CHUNK_SIZE); | |
|                 STORM_LOG_THROW(chunkSize>=0, storm::exceptions::UnexpectedException, "failed to read solver output"); | |
|                 solverOutput += std::string(chunk, chunkSize); | |
|                 if(ioctl(fromSolver, FIONREAD, &bytesReadable)< 0){//obtain the new amount of readable bytes | |
|                     STORM_LOG_ERROR("Could not check if the solver has output"); | |
|                     return std::vector<std::string>(); | |
|                 } | |
|                 if(bytesReadable==0 && solverOutput.back()!='\n'){ | |
|                     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"); | |
|                     bytesReadable=1; //we expect more output! | |
|                 } | |
|             } | |
|             checkForErrorMessage(solverOutput); | |
|             std::vector<std::string> solverOutputAsVector; | |
|             if(solverOutput.length()>0){ | |
|                 solverOutput=solverOutput.substr(0,solverOutput.length()-1); | |
|                 boost::split(solverOutputAsVector, solverOutput, boost::is_any_of("\n")); | |
|                 //note: this is a little bit unsafe as \n can be contained within a solver response | |
|             } | |
|             return solverOutputAsVector; | |
| #endif | |
|         } | |
|          | |
|         void Smt2SmtSolver::checkForErrorMessage(const std::string message){ | |
|             size_t errorOccurrance = message.find("error"); | |
|             if (errorOccurrance!=std::string::npos){ | |
|                 this->needsRestart=true; | |
|                 //do not throw an exception for timeout or memout errors | |
|                 if(message.find("timeout")!=std::string::npos){ | |
|                     STORM_LOG_INFO("SMT solver answered: '" << message << "' and I am interpreting this as timeout "); | |
|                 } | |
|                 else if(message.find("memory")!=std::string::npos){ | |
|                     STORM_LOG_INFO("SMT solver answered: '" << message << "' and I am interpreting this as out of memory "); | |
|                     this->processIdOfSolver=0; | |
|                 } | |
|                 else{ | |
|                     std::string errorMsg = "An error was detected while checking the solver output. "; | |
|                     STORM_LOG_DEBUG("Detected an error message in the solver response:\n" + message); | |
|                     size_t firstQuoteSign = message.find('\"',errorOccurrance); | |
|                     if(firstQuoteSign!=std::string::npos && message.find("\\\"", firstQuoteSign-1)!=firstQuoteSign-1){ | |
|                         size_t secondQuoteSign = message.find('\"', firstQuoteSign+1); | |
|                         while(secondQuoteSign!=std::string::npos && message.find("\\\"", secondQuoteSign-1)==secondQuoteSign-1){ | |
|                             secondQuoteSign = message.find('\"',secondQuoteSign+1); | |
|                         } | |
|                         if(secondQuoteSign!=std::string::npos){ | |
|                             errorMsg += "The error message was: <<" + message.substr(errorOccurrance,secondQuoteSign-errorOccurrance+1) + ">>."; | |
|                             STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, errorMsg); | |
|                             return; | |
|                         } | |
|                     } | |
|                     errorMsg += "The error message could not be parsed correctly. Snippet:\n" + message.substr(errorOccurrance,200); | |
|                     STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, errorMsg); | |
|                 } | |
|             } | |
|         } | |
|     } | |
| }
 |