Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Cvc5SmtSolver.cpp
Go to the documentation of this file.
2
3#include <memory>
4
14
15namespace storm::solver {
16// Deliberately not guarded by STORM_HAVE_CVC5: the header declares this constructor unconditionally, and it only
17// touches Storm's valuation types. Guarding it would leave the definition out in a build without cvc5, so any
18// client constructing a Cvc5ModelReference there would compile but fail to link.
20 : ModelReference(manager), variableValues(std::move(variableValues)) {
21 // Intentionally left empty.
22}
23
24void Cvc5SmtSolver::Cvc5ModelReference::checkVariable([[maybe_unused]] storm::expressions::Variable const& variable) const {
25 STORM_LOG_ASSERT(variable.getManager() == this->getManager(), "Requested variable is managed by a different manager.");
26}
27
29 this->checkVariable(variable);
30 STORM_LOG_THROW(variable.getType().isBooleanType(), storm::exceptions::InvalidTypeException,
31 "Cannot retrieve the Boolean value of variable '" << variable.getName() << "' of non-Boolean type.");
32 return this->variableValues.getBooleanValue(variable);
33}
34
36 this->checkVariable(variable);
37 // A bitvector variable is an integer type in Storm and is modelled as an unbounded integer by this backend, so its
38 // value is read with the integer getter.
39 STORM_LOG_THROW(variable.getType().isIntegerType(), storm::exceptions::InvalidTypeException,
40 "Cannot retrieve the integer value of variable '" << variable.getName() << "' of non-integer type.");
41 return this->variableValues.getIntegerValue(variable);
42}
43
45 this->checkVariable(variable);
46 STORM_LOG_THROW(variable.getType().isRationalType(), storm::exceptions::InvalidTypeException,
47 "Cannot retrieve the rational value of variable '" << variable.getName() << "' of non-rational type.");
48 return this->variableValues.getRationalValue(variable);
49}
50
52 return this->variableValues.toString();
53}
54
56 : SmtSolver(manager)
57#ifdef STORM_HAVE_CVC5
58 ,
59 termManager(nullptr),
60 solver(nullptr),
61 expressionAdapter(nullptr),
62 lastCheckAssumptions(false),
63 lastResult(CheckResult::Unknown)
64#endif
65{
66#ifdef STORM_HAVE_CVC5
67 this->termManager = std::make_unique<cvc5::TermManager>();
68 this->solver = std::make_unique<cvc5::Solver>(*this->termManager);
69 this->solver->setOption("produce-models", "true");
70 this->solver->setOption("produce-unsat-assumptions", "true");
71 this->solver->setOption("incremental", "true");
72 this->expressionAdapter = std::make_unique<storm::adapters::Cvc5ExpressionAdapter>(this->getManager(), *this->solver);
73#endif
74}
75
77 // Intentionally left empty.
78}
79
81#ifdef STORM_HAVE_CVC5
82 this->solver->push();
83#else
84 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
85#endif
86}
87
89#ifdef STORM_HAVE_CVC5
90 this->solver->pop();
91#else
92 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
93#endif
94}
95
96void Cvc5SmtSolver::pop(uint_fast64_t n) {
97#ifdef STORM_HAVE_CVC5
98 this->solver->pop(static_cast<uint32_t>(n));
99#else
100 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
101#endif
102}
103
105#ifdef STORM_HAVE_CVC5
106 this->solver->resetAssertions();
107#else
108 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
109#endif
110}
111
113#ifdef STORM_HAVE_CVC5
114 this->solver->assertFormula(this->expressionAdapter->translateExpression(assertion));
115#else
116 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
117#endif
118}
119
120void Cvc5SmtSolver::addNotCurrentModel([[maybe_unused]] bool performSolverReset) {
121#ifdef STORM_HAVE_CVC5
122 STORM_LOG_THROW(this->lastResult == SmtSolver::CheckResult::Sat, storm::exceptions::InvalidStateException,
123 "Unable to create model for formula that was not determined to be satisfiable.");
124
125 // Bitvector variables are part of the model just like the other ones. They are modelled as unbounded integers in this
126 // backend, so createModelExpression describes them like any other variable. This also keeps the blocking clause
127 // from degenerating to false when they are the only variables.
128 std::vector<storm::expressions::Variable> variables;
129 for (auto const& variable : this->getManager().getVariables()) {
130 variables.push_back(variable);
131 }
132
133 cvc5::Term notThisModel = this->createModelExpression(variables);
134 notThisModel = this->termManager->mkTerm(cvc5::Kind::NOT, {notThisModel});
135
136 // cvc5 enumerates models incrementally without resetting the solver, so performSolverReset is ignored. A reset would
137 // also move all assertions to the base level and thereby discard the push/pop scopes.
138 this->solver->assertFormula(notThisModel);
139#else
140 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
141#endif
142}
143
145#ifdef STORM_HAVE_CVC5
146 this->lastCheckAssumptions = false;
147 this->lastResult = this->translateResult(this->solver->checkSat());
148 return this->lastResult;
149#else
150 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
151#endif
152}
153
155#ifdef STORM_HAVE_CVC5
156 this->lastCheckAssumptions = true;
157 std::vector<cvc5::Term> cvc5Assumptions;
158 for (storm::expressions::Expression const& assumption : assumptions) {
159 cvc5Assumptions.push_back(this->expressionAdapter->translateExpression(assumption));
160 }
161 this->lastResult = this->translateResult(this->solver->checkSatAssuming(cvc5Assumptions));
162 return this->lastResult;
163#else
164 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
165#endif
166}
167
168SmtSolver::CheckResult Cvc5SmtSolver::checkWithAssumptions(std::initializer_list<storm::expressions::Expression> const& assumptions) {
169#ifdef STORM_HAVE_CVC5
170 this->lastCheckAssumptions = true;
171 std::vector<cvc5::Term> cvc5Assumptions;
172 for (storm::expressions::Expression const& assumption : assumptions) {
173 cvc5Assumptions.push_back(this->expressionAdapter->translateExpression(assumption));
174 }
175 this->lastResult = this->translateResult(this->solver->checkSatAssuming(cvc5Assumptions));
176 return this->lastResult;
177#else
178 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
179#endif
180}
181
182#ifdef STORM_HAVE_CVC5
183storm::expressions::SimpleValuation Cvc5SmtSolver::collectModelAsValuation() const {
184 storm::expressions::SimpleValuation stormModel(this->getManager().getSharedPointer());
185 for (auto const& variableValuePair : this->collectVariableValues()) {
186 storm::expressions::Variable const& variable = variableValuePair.first;
187 cvc5::Term const& value = variableValuePair.second;
188 if (variable.getType().isBooleanType()) {
189 stormModel.setBooleanValue(variable, value == this->termManager->mkBoolean(true));
190 } else if (variable.getType().isIntegerType()) {
191 auto const& interpretation = this->expressionAdapter->translateExpression(value);
192 auto const* integerLiteral = dynamic_cast<storm::expressions::IntegerLiteralExpression const*>(&interpretation.getBaseExpression());
193 STORM_LOG_ASSERT(integerLiteral != nullptr, "Variable interpretation in model is not an integer.");
194 stormModel.setIntegerValue(variable, integerLiteral->getValue());
195 } else if (variable.getType().isRationalType()) {
196 // cvc5 reports a value that is not a rational number, such as the positive root of x^2 = 2, as a real
197 // algebraic number. Since a SimpleValuation holds a double, we approximate such a value by its lower
198 // bound, just like the Z3 backend does.
199 cvc5::Term approximableValue = value;
200 if (value.isRealAlgebraicNumber()) {
201 STORM_LOG_WARN("Interpreting algebraic numbers as rational numbers. This is not an exact computation.");
202 approximableValue = value.getRealAlgebraicNumberLowerBound();
203 }
204 auto const& interpretation = this->expressionAdapter->translateExpression(approximableValue);
205 auto const* rationalLiteral = dynamic_cast<storm::expressions::RationalLiteralExpression const*>(&interpretation.getBaseExpression());
206 STORM_LOG_ASSERT(rationalLiteral != nullptr, "Variable interpretation in model is not a rational number.");
207 if (rationalLiteral != nullptr) {
208 stormModel.setRationalValue(variable, storm::utility::convertNumber<double>(rationalLiteral->getValue()));
209 }
210 } else {
211 STORM_LOG_THROW(false, storm::exceptions::ExpressionEvaluationException, "Variable interpretation in model is not of type bool, int or rational.");
212 }
213 }
214
215 return stormModel;
216}
217#endif
218
220#ifdef STORM_HAVE_CVC5
221 STORM_LOG_THROW(this->lastResult == SmtSolver::CheckResult::Sat, storm::exceptions::InvalidStateException,
222 "Unable to create model for formula that was not determined to be satisfiable.");
223 return this->collectModelAsValuation();
224#else
225 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
226#endif
227}
228
229std::shared_ptr<SmtSolver::ModelReference> Cvc5SmtSolver::getModel() {
230#ifdef STORM_HAVE_CVC5
231 STORM_LOG_THROW(this->lastResult == SmtSolver::CheckResult::Sat, storm::exceptions::InvalidStateException,
232 "Unable to create model for formula that was not determined to be satisfiable.");
233 return std::shared_ptr<SmtSolver::ModelReference>(new Cvc5ModelReference(this->getManager(), this->collectModelAsValuation()));
234#else
235 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
236#endif
237}
238
239std::vector<storm::expressions::SimpleValuation> Cvc5SmtSolver::allSat(std::vector<storm::expressions::Variable> const& important) {
240#ifdef STORM_HAVE_CVC5
241 std::vector<storm::expressions::SimpleValuation> valuations;
242 this->allSat(important, static_cast<std::function<bool(storm::expressions::SimpleValuation&)>>(
243 [&valuations](storm::expressions::SimpleValuation const& valuation) -> bool {
244 valuations.push_back(valuation);
245 return true;
246 }));
247 return valuations;
248#else
249 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
250#endif
251}
252
253uint_fast64_t Cvc5SmtSolver::allSat(std::vector<storm::expressions::Variable> const& important,
254 std::function<bool(storm::expressions::SimpleValuation&)> const& callback) {
255#ifdef STORM_HAVE_CVC5
256 for (storm::expressions::Variable const& variable : important) {
257 STORM_LOG_THROW(variable.hasBooleanType(), storm::exceptions::InvalidArgumentException, "The important atoms for AllSat must be boolean variables.");
258 }
259
260 uint64_t numberOfModels = 0;
261 bool proceed = true;
262
263 // Save the current assertion stack, to be able to backtrack after the procedure.
264 this->push();
265
266 // Enumerate models as long as the conjunction is satisfiable and the callback has not aborted the enumeration.
267 while (proceed && this->check() == CheckResult::Sat) {
268 ++numberOfModels;
269
270 cvc5::Term modelExpr = this->createModelExpression(important);
271 storm::expressions::SimpleValuation valuation(this->getManager().getSharedPointer());
272
273 for (storm::expressions::Variable const& importantAtom : important) {
274 cvc5::Term value = this->solver->getValue(this->expressionAdapter->translateExpression(importantAtom));
275 valuation.setBooleanValue(importantAtom, value == this->termManager->mkBoolean(true));
276 }
277
278 // Check if we are required to proceed, and if so rule out the current model.
279 proceed = callback(valuation);
280 if (proceed) {
281 this->solver->assertFormula(this->termManager->mkTerm(cvc5::Kind::NOT, {modelExpr}));
282 }
283 }
284
285 // Restore the old assertion stack and return.
286 this->pop();
287 return numberOfModels;
288#else
289 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
290#endif
291}
292
293uint_fast64_t Cvc5SmtSolver::allSat(std::vector<storm::expressions::Variable> const& important,
294 std::function<bool(SmtSolver::ModelReference&)> const& callback) {
295#ifdef STORM_HAVE_CVC5
296 for (storm::expressions::Variable const& variable : important) {
297 STORM_LOG_THROW(variable.hasBooleanType(), storm::exceptions::InvalidArgumentException, "The important atoms for AllSat must be boolean variables.");
298 }
299
300 uint64_t numberOfModels = 0;
301 bool proceed = true;
302
303 // Save the current assertion stack, to be able to backtrack after the procedure.
304 this->push();
305
306 // Enumerate models as long as the conjunction is satisfiable and the callback has not aborted the enumeration.
307 while (proceed && this->check() == CheckResult::Sat) {
308 ++numberOfModels;
309
310 cvc5::Term modelExpr = this->createModelExpression(important);
311 Cvc5ModelReference modelRef(this->getManager(), this->collectModelAsValuation());
312
313 // Check if we are required to proceed, and if so rule out the current model.
314 proceed = callback(modelRef);
315 if (proceed) {
316 this->solver->assertFormula(this->termManager->mkTerm(cvc5::Kind::NOT, {modelExpr}));
317 }
318 }
319
320 this->pop();
321 return numberOfModels;
322#else
323 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
324#endif
325}
326
327std::vector<storm::expressions::Expression> Cvc5SmtSolver::getUnsatAssumptions() {
328#ifdef STORM_HAVE_CVC5
329 STORM_LOG_THROW(this->lastResult == SmtSolver::CheckResult::Unsat, storm::exceptions::InvalidStateException,
330 "Unable to generate unsatisfiable core of assumptions, because the last check did not determine the formulas to be unsatisfiable.");
331 STORM_LOG_THROW(this->lastCheckAssumptions, storm::exceptions::InvalidStateException,
332 "Unable to generate unsatisfiable core of assumptions, because the last check did not involve assumptions.");
333
334 std::vector<storm::expressions::Expression> unsatAssumptions;
335 for (auto const& cvc5Assumption : this->solver->getUnsatAssumptions()) {
336 unsatAssumptions.push_back(this->expressionAdapter->translateExpression(cvc5Assumption));
337 }
338
339 return unsatAssumptions;
340#else
341 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
342#endif
343}
344
345bool Cvc5SmtSolver::setTimeout(uint_fast64_t milliseconds) {
346#ifdef STORM_HAVE_CVC5
347 this->solver->setOption("tlimit-per", std::to_string(milliseconds));
348 return true;
349#else
350 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
351#endif
352}
353
355#ifdef STORM_HAVE_CVC5
356 this->solver->setOption("tlimit-per", "0");
357 return true;
358#else
359 STORM_LOG_THROW(false, storm::exceptions::MissingLibraryException, "Storm is compiled without cvc5 support.");
360#endif
361}
362
363#ifdef STORM_HAVE_CVC5
364SmtSolver::CheckResult Cvc5SmtSolver::translateResult(cvc5::Result const& result) const {
365 if (result.isSat()) {
367 } else if (result.isUnsat()) {
369 }
371}
372
373std::unordered_map<storm::expressions::Variable, cvc5::Term> Cvc5SmtSolver::collectVariableValues() const {
374 std::unordered_map<storm::expressions::Variable, cvc5::Term> variableValues;
375 for (auto const& variable : this->getManager().getVariables()) {
376 if (variable.getType().isBooleanType() || variable.getType().isIntegerType() || variable.getType().isRationalType()) {
377 cvc5::Term value;
378 try {
379 value = this->solver->getValue(this->expressionAdapter->translateExpression(variable));
380 } catch (std::exception const&) {
381 // If cvc5 cannot provide a value for the variable, we simply do not add it to the model.
382 continue;
383 }
384 variableValues.insert({variable, value});
385 }
386 }
387 return variableValues;
388}
389
390cvc5::Term Cvc5SmtSolver::createModelExpression(std::vector<storm::expressions::Variable> const& variables) const {
391 // The model covers all variables of the manager. A variable that never occurred in an assertion gets the default
392 // value cvc5 reports for it, so ruling out a model also rules out that value (as in a complete enumeration over
393 // the declared variables).
394 auto const modelValues = this->collectVariableValues();
395
396 cvc5::Term modelExpression = this->termManager->mkBoolean(true);
397 for (auto const& variable : variables) {
398 auto const& modelValue = modelValues.find(variable);
399 if (modelValue == modelValues.end()) {
400 // cvc5 could not provide a value for this variable, so it is not part of the model expression.
401 continue;
402 }
403 cvc5::Term variableTerm = this->expressionAdapter->translateExpression(variable);
404 modelExpression =
405 this->termManager->mkTerm(cvc5::Kind::AND, {modelExpression, this->termManager->mkTerm(cvc5::Kind::EQUAL, {variableTerm, modelValue->second})});
406 }
407 return modelExpression;
408}
409#endif
410
411} // namespace storm::solver
This class is responsible for managing a set of typed variables and all expressions using these varia...
A simple implementation of the valuation interface.
virtual void setBooleanValue(Variable const &booleanVariable, bool value) override
Sets the value of the given boolean variable to the provided value.
bool isBooleanType() const
Checks whether this type is a boolean type.
Definition Type.cpp:194
bool isIntegerType() const
Checks whether this type is an integral type.
Definition Type.cpp:198
bool isRationalType() const
Checks whether this type is a rational type.
Definition Type.cpp:234
bool hasBooleanType() const
Checks whether the variable is of boolean type.
Definition Variable.cpp:59
Type const & getType() const
Retrieves the type of the variable.
Definition Variable.cpp:50
std::string const & getName() const
Retrieves the name of the variable.
Definition Variable.cpp:46
virtual double getRationalValue(storm::expressions::Variable const &variable) const override
Cvc5ModelReference(storm::expressions::ExpressionManager const &manager, storm::expressions::SimpleValuation variableValues)
Creates a new model reference that provides access to the values that the given model assigns.
virtual bool getBooleanValue(storm::expressions::Variable const &variable) const override
virtual int_fast64_t getIntegerValue(storm::expressions::Variable const &variable) const override
virtual std::string toString() const override
virtual std::vector< storm::expressions::Expression > getUnsatAssumptions() override
If the last call to checkWithAssumptions() returned Unsat, this function can be used to retrieve a su...
virtual bool setTimeout(uint_fast64_t milliseconds) override
If supported by the solver, this will limit all subsequent satisfiability queries to the given number...
virtual void push() override
Pushes a backtracking point on the solver's stack.
virtual void addNotCurrentModel(bool performSolverReset=true) override
If supported by the solver, this function tells the SMT solver to produce a model different from the ...
virtual storm::expressions::SimpleValuation getModelAsValuation() override
If the last call to check() or checkWithAssumptions() returned Sat, this method retrieves a model tha...
virtual std::shared_ptr< SmtSolver::ModelReference > getModel() override
If the last call to check() or checkWithAssumptions() returned Sat, this method retrieves a model tha...
virtual void reset() override
Removes all assertions from the solver's stack.
virtual std::vector< storm::expressions::SimpleValuation > allSat(std::vector< storm::expressions::Variable > const &important) override
Performs AllSat over the (provided) important atoms.
virtual CheckResult checkWithAssumptions(storm::expressions::ExpressionSet const &assumptions) override
Checks whether the conjunction of assertions that are currently on the solver's stack together with t...
virtual void add(storm::expressions::Expression const &assertion) override
Adds an assertion to the solver's stack.
virtual CheckResult check() override
Checks whether the conjunction of assertions that are currently on the solver's stack is satisfiable.
virtual void pop() override
Pops a backtracking point from the solver's stack.
virtual bool unsetTimeout() override
If supported by the solver, this unsets a previous timeout.
Cvc5SmtSolver(storm::expressions::ExpressionManager &manager)
The base class for all model references.
Definition SmtSolver.h:30
ModelReference(storm::expressions::ExpressionManager const &manager)
Creates a model reference that uses the given expression manager.
Definition SmtSolver.cpp:9
storm::expressions::ExpressionManager const & getManager() const
Retrieves the expression manager associated with the solver.
Definition SmtSolver.cpp:79
SmtSolver(storm::expressions::ExpressionManager &manager)
Constructs a new Smt solver with the given options.
Definition SmtSolver.cpp:17
CheckResult
possible check results
Definition SmtSolver.h:24
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
std::set< Expression, ExpressionComparator > ExpressionSet
Definition Expression.h:515
std::set< storm::RationalFunctionVariable > getVariables(SparseMatrix< storm::RationalFunction > const &matrix)
TargetType convertNumber(SourceType const &number)