Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Z3LpSolver.cpp
Go to the documentation of this file.
2
3#include <numeric>
4
10#include "storm/io/file.h"
17
18namespace storm {
19namespace solver {
20
21#ifdef STORM_HAVE_Z3_OPTIMIZE
22
23template<typename ValueType, bool RawMode>
24Z3LpSolver<ValueType, RawMode>::Z3LpSolver(std::string const& name, OptimizationDirection const& optDir)
25 : LpSolver<ValueType, RawMode>(optDir), isIncremental(false) {
26 z3::config config;
27 config.set("model", true);
28 context = std::unique_ptr<z3::context>(new z3::context(config));
29 solver = std::unique_ptr<z3::optimize>(new z3::optimize(*context));
30 expressionAdapter = std::unique_ptr<storm::adapters::Z3ExpressionAdapter>(new storm::adapters::Z3ExpressionAdapter(*this->manager, *context));
31}
32
33template<typename ValueType, bool RawMode>
34Z3LpSolver<ValueType, RawMode>::Z3LpSolver(std::string const& name) : Z3LpSolver(name, OptimizationDirection::Minimize) {
35 // Intentionally left empty.
36}
37
38template<typename ValueType, bool RawMode>
39Z3LpSolver<ValueType, RawMode>::Z3LpSolver(OptimizationDirection const& optDir) : Z3LpSolver("", optDir) {
40 // Intentionally left empty.
41}
42
43template<typename ValueType, bool RawMode>
44Z3LpSolver<ValueType, RawMode>::Z3LpSolver() : Z3LpSolver("", OptimizationDirection::Minimize) {
45 // Intentionally left empty.
46}
47
48template<typename ValueType, bool RawMode>
49Z3LpSolver<ValueType, RawMode>::~Z3LpSolver() {
50 // Intentionally left empty.
51}
52
53template<typename ValueType, bool RawMode>
54void Z3LpSolver<ValueType, RawMode>::update() const {
55 // Since the model changed, we erase the optimality flag.
56 lastCheckObjectiveValue.reset(nullptr);
57 lastCheckModel.reset(nullptr);
58 this->currentModelHasBeenOptimized = false;
59}
60
61template<typename ValueType, bool RawMode>
62typename Z3LpSolver<ValueType, RawMode>::Variable Z3LpSolver<ValueType, RawMode>::addVariable(std::string const& name, VariableType const& type,
63 std::optional<ValueType> const& lowerBound,
64 std::optional<ValueType> const& upperBound,
65 ValueType objectiveFunctionCoefficient) {
66 STORM_LOG_ASSERT(isIncremental || !this->manager->hasVariable(name), "Variable with name " << name << " already exists.");
67 storm::expressions::Variable newVariable = this->declareOrGetExpressionVariable(name, type);
68 if (type == VariableType::Binary) {
69 solver->add(expressionAdapter->translateExpression(newVariable.getExpression() >= this->manager->integer(0)));
70 solver->add(expressionAdapter->translateExpression(newVariable.getExpression() <= this->manager->integer(1)));
71 }
72 if (lowerBound) {
73 solver->add(expressionAdapter->translateExpression(newVariable.getExpression() >= this->manager->rational(*lowerBound)));
74 }
75 if (upperBound) {
76 solver->add(expressionAdapter->translateExpression(newVariable.getExpression() <= this->manager->rational(*upperBound)));
77 }
78 if (!storm::utility::isZero(objectiveFunctionCoefficient)) {
79 optimizationSummands.push_back(this->manager->rational(objectiveFunctionCoefficient) * newVariable);
80 }
81
82 if constexpr (RawMode) {
83 rawIndexToVariableMap.push_back(newVariable);
84 return rawIndexToVariableMap.size() - 1;
85 } else {
86 return newVariable;
87 }
88}
89
90template<typename ValueType, bool RawMode>
91void Z3LpSolver<ValueType, RawMode>::addConstraint(std::string const& name, Constraint const& constraint) {
92 if constexpr (RawMode) {
93 // Generate expression from raw constraint
94 STORM_LOG_ASSERT(constraint.lhsVariableIndices.size() == constraint.lhsCoefficients.size(), "number of variables and coefficients do not match.");
95 std::vector<storm::expressions::Expression> lhsSummands;
96 lhsSummands.reserve(constraint.lhsVariableIndices.size());
97 auto varIt = constraint.lhsVariableIndices.cbegin();
98 auto varItEnd = constraint.lhsVariableIndices.cend();
99 auto coefIt = constraint.lhsCoefficients.cbegin();
100 for (; varIt != varItEnd; ++varIt, ++coefIt) {
101 lhsSummands.push_back(rawIndexToVariableMap[*varIt] * this->manager->rational(*coefIt));
102 }
103 if (lhsSummands.empty()) {
104 lhsSummands.push_back(this->manager->rational(storm::utility::zero<ValueType>()));
105 }
107 storm::expressions::sum(lhsSummands), this->manager->rational(constraint.rhs), constraint.relationType);
108 solver->add(expressionAdapter->translateExpression(constraintExpr));
109 } else {
110 STORM_LOG_THROW(constraint.isRelationalExpression(), storm::exceptions::InvalidArgumentException, "Illegal constraint is not a relational expression.");
111 STORM_LOG_THROW(constraint.getOperator() != storm::expressions::OperatorType::NotEqual, storm::exceptions::InvalidArgumentException,
112 "Illegal constraint uses inequality operator.");
113 solver->add(expressionAdapter->translateExpression(constraint));
114 }
115}
116
117template<typename ValueType, bool RawMode>
118void Z3LpSolver<ValueType, RawMode>::addIndicatorConstraint(std::string const& name, Variable indicatorVariable, bool indicatorValue,
119 Constraint const& constraint) {
120 if constexpr (RawMode) {
121 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Indicator constraints not implemented in RawMode");
122 } else {
123 // binary variables are encoded as integer variables with domain {0,1}.
124 STORM_LOG_THROW(indicatorVariable.hasIntegerType(), storm::exceptions::InvalidArgumentException,
125 "Variable " << indicatorVariable.getName() << " is not binary.");
126 STORM_LOG_THROW(constraint.isRelationalExpression(), storm::exceptions::InvalidArgumentException, "Illegal constraint is not a relational expression.");
127 STORM_LOG_THROW(constraint.getOperator() != storm::expressions::OperatorType::NotEqual, storm::exceptions::InvalidArgumentException,
128 "Illegal constraint uses inequality operator.");
129
130 storm::expressions::Expression invertedIndicatorVal =
131 this->getConstant(indicatorValue ? storm::utility::zero<ValueType>() : storm::utility::one<ValueType>());
132 auto indicatorConstraint = (indicatorVariable.getExpression() == invertedIndicatorVal) || constraint;
133 solver->add(expressionAdapter->translateExpression(indicatorConstraint));
134 }
135}
136
137template<typename ValueType, bool RawMode>
138void Z3LpSolver<ValueType, RawMode>::optimize() const {
139 // First incorporate all recent changes.
140 this->update();
141
142 // Invoke push() as we want to be able to erase the current optimization function after checking
143 solver->push();
144
145 storm::expressions::Expression optimizationFunction = this->manager->integer(0);
146 // Solve the optimization problem depending on the optimization direction
147 if (!optimizationSummands.empty()) {
148 optimizationFunction = storm::expressions::sum(optimizationSummands);
149 }
150 z3::optimize::handle optFuncHandle = this->getOptimizationDirection() == OptimizationDirection::Minimize
151 ? solver->minimize(expressionAdapter->translateExpression(optimizationFunction))
152 : solver->maximize(expressionAdapter->translateExpression(optimizationFunction));
153
154 z3::check_result chkRes = solver->check();
155 STORM_LOG_THROW(chkRes != z3::unknown, storm::exceptions::InvalidStateException, "Unable to solve LP problem with Z3: Check result is unknown.");
156
157 // We need to store the resulting information at this point. Otherwise, the information would be lost after calling pop() ...
158
159 // Check feasibility
160 lastCheckInfeasible = (chkRes == z3::unsat);
161 if (lastCheckInfeasible) {
162 lastCheckUnbounded = false;
163 } else {
164 // Get objective result
165 lastCheckObjectiveValue = std::make_unique<z3::expr>(solver->upper(optFuncHandle));
166 // Check boundedness
167 STORM_LOG_ASSERT(lastCheckObjectiveValue->is_app(), "Failed to convert Z3 expression. Encountered unknown expression type.");
168 lastCheckUnbounded = (lastCheckObjectiveValue->decl().decl_kind() != Z3_OP_ANUM);
169 if (lastCheckUnbounded) {
170 lastCheckObjectiveValue.reset(nullptr);
171 } else {
172 // Assert that the upper approximation equals the lower one
173 STORM_LOG_ASSERT(std::string(Z3_get_numeral_string(*context, *lastCheckObjectiveValue)) ==
174 std::string(Z3_get_numeral_string(*context, solver->lower(optFuncHandle))),
175 "Lower and Upper Approximation of z3LPSolver result do not match.");
176 lastCheckModel = std::make_unique<z3::model>(solver->get_model());
177 }
178 }
179
180 solver->pop(); // removes current optimization function
181 this->currentModelHasBeenOptimized = true;
182}
183
184template<typename ValueType, bool RawMode>
185bool Z3LpSolver<ValueType, RawMode>::isInfeasible() const {
186 STORM_LOG_THROW(this->currentModelHasBeenOptimized, storm::exceptions::InvalidStateException,
187 "Illegal call to Z3LpSolver<ValueType, RawMode>::isInfeasible: model has not been optimized.");
188 return lastCheckInfeasible;
189}
190
191template<typename ValueType, bool RawMode>
192bool Z3LpSolver<ValueType, RawMode>::isUnbounded() const {
193 STORM_LOG_THROW(this->currentModelHasBeenOptimized, storm::exceptions::InvalidStateException,
194 "Illegal call to Z3LpSolver<ValueType, RawMode>::isUnbounded: model has not been optimized.");
195 return lastCheckUnbounded;
196}
197
198template<typename ValueType, bool RawMode>
199bool Z3LpSolver<ValueType, RawMode>::isOptimal() const {
200 STORM_LOG_THROW(this->currentModelHasBeenOptimized, storm::exceptions::InvalidStateException,
201 "Illegal call to Z3LpSolver<ValueType, RawMode>::isOptimal: model has not been optimized.");
202 return !lastCheckInfeasible && !lastCheckUnbounded;
203}
204
205template<typename ValueType, bool RawMode>
206storm::expressions::Expression Z3LpSolver<ValueType, RawMode>::getValue(Variable const& variable) const {
207 if (!this->isOptimal()) {
208 STORM_LOG_THROW(!this->isInfeasible(), storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from infeasible model.");
209 STORM_LOG_THROW(!this->isUnbounded(), storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from unbounded model.");
210 STORM_LOG_THROW(false, storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from unoptimized model.");
211 }
212 STORM_LOG_ASSERT(lastCheckModel, "Model has not been stored.");
213
214 if constexpr (RawMode) {
215 STORM_LOG_ASSERT(variable < rawIndexToVariableMap.size(), "Requested variable out of range.");
216 z3::expr z3Var = this->expressionAdapter->translateExpression(rawIndexToVariableMap[variable]);
217 return this->expressionAdapter->translateExpression(lastCheckModel->eval(z3Var, true));
218 } else {
219 STORM_LOG_ASSERT(variable.getManager() == this->getManager(), "Requested variable is managed by a different manager.");
220 z3::expr z3Var = this->expressionAdapter->translateExpression(variable);
221 return this->expressionAdapter->translateExpression(lastCheckModel->eval(z3Var, true));
222 }
223}
224
225template<typename ValueType, bool RawMode>
226ValueType Z3LpSolver<ValueType, RawMode>::getContinuousValue(Variable const& variable) const {
227 storm::expressions::Expression value = getValue(variable);
229 return storm::utility::convertNumber<ValueType>(value.getBaseExpression().asIntegerLiteralExpression().getValue());
230 }
231 STORM_LOG_THROW(value.getBaseExpression().isRationalLiteralExpression(), storm::exceptions::ExpressionEvaluationException,
232 "Expected a rational literal while obtaining the value of a continuous variable. Got " << value << "instead.");
233 return storm::utility::convertNumber<ValueType>(value.getBaseExpression().asRationalLiteralExpression().getValue());
234}
235
236template<typename ValueType, bool RawMode>
237int_fast64_t Z3LpSolver<ValueType, RawMode>::getIntegerValue(Variable const& variable) const {
238 storm::expressions::Expression value = getValue(variable);
239 STORM_LOG_THROW(value.getBaseExpression().isIntegerLiteralExpression(), storm::exceptions::ExpressionEvaluationException,
240 "Expected an integer literal while obtaining the value of an integer variable. Got " << value << "instead.");
242}
243
244template<typename ValueType, bool RawMode>
245bool Z3LpSolver<ValueType, RawMode>::getBinaryValue(Variable const& variable) const {
246 storm::expressions::Expression value = getValue(variable);
247 // Binary variables are in fact represented as integer variables!
248 STORM_LOG_THROW(value.getBaseExpression().isIntegerLiteralExpression(), storm::exceptions::ExpressionEvaluationException,
249 "Expected an integer literal while obtaining the value of a binary variable. Got " << value << "instead.");
250 int_fast64_t val = value.getBaseExpression().asIntegerLiteralExpression().getValue();
251 STORM_LOG_THROW((val == 0 || val == 1), storm::exceptions::ExpressionEvaluationException,
252 "Tried to get a binary value for a variable that is neither 0 nor 1.");
253 return val == 1;
254}
255
256template<typename ValueType, bool RawMode>
257ValueType Z3LpSolver<ValueType, RawMode>::getObjectiveValue() const {
258 if (!this->isOptimal()) {
259 STORM_LOG_THROW(!this->isInfeasible(), storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from infeasible model.");
260 STORM_LOG_THROW(!this->isUnbounded(), storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from unbounded model.");
261 STORM_LOG_THROW(false, storm::exceptions::InvalidAccessException, "Unable to get Z3 solution from unoptimized model.");
262 }
263 STORM_LOG_ASSERT(lastCheckObjectiveValue, "Objective value has not been stored.");
264
265 storm::expressions::Expression result = this->expressionAdapter->translateExpression(*lastCheckObjectiveValue);
267 return storm::utility::convertNumber<ValueType>(result.getBaseExpression().asIntegerLiteralExpression().getValue());
268 }
269 STORM_LOG_THROW(result.getBaseExpression().isRationalLiteralExpression(), storm::exceptions::ExpressionEvaluationException,
270 "Expected a rational literal while obtaining the objective result. Got " << result << "instead.");
271 return storm::utility::convertNumber<ValueType>(result.getBaseExpression().asRationalLiteralExpression().getValue());
272}
273
274template<typename ValueType, bool RawMode>
275void Z3LpSolver<ValueType, RawMode>::writeModelToFile(std::string const& filename) const {
276 std::ofstream stream;
277 storm::io::openFile(filename, stream);
278 stream << Z3_optimize_to_string(*context, *solver);
279 storm::io::closeFile(stream);
280}
281
282template<typename ValueType, bool RawMode>
283void Z3LpSolver<ValueType, RawMode>::push() {
284 STORM_LOG_THROW(!RawMode, storm::exceptions::NotImplementedException, "Incremental solving is not supported in Raw mode.");
285 incrementaOptimizationSummandIndicators.push_back(optimizationSummands.size());
286 solver->push();
287}
288
289template<typename ValueType, bool RawMode>
290void Z3LpSolver<ValueType, RawMode>::pop() {
291 STORM_LOG_THROW(!RawMode, storm::exceptions::NotImplementedException, "Incremental solving is not supported in Raw mode.");
292 STORM_LOG_ASSERT(!incrementaOptimizationSummandIndicators.empty(), "Tried to pop() without push()ing first.");
293 solver->pop();
294 // Delete summands of the optimization function that have been added since the last call to push()
295 optimizationSummands.resize(incrementaOptimizationSummandIndicators.back());
296 incrementaOptimizationSummandIndicators.pop_back();
297 isIncremental = true;
298}
299
300template<typename ValueType, bool RawMode>
301void Z3LpSolver<ValueType, RawMode>::setMaximalMILPGap(ValueType const&, bool) {
302 // Since the solver is always exact, setting a gap has no effect.
303 // Intentionally left empty.
304}
305
306template<typename ValueType, bool RawMode>
307ValueType Z3LpSolver<ValueType, RawMode>::getMILPGap(bool relative) const {
308 // Since the solver is precise, the milp gap is always zero.
309 return storm::utility::zero<ValueType>();
310}
311#else
312template<typename ValueType, bool RawMode>
314 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
315 "Yet, a method was called that requires this support.";
316}
317
318template<typename ValueType, bool RawMode>
320 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
321 "Yet, a method was called that requires this support.";
322}
323
324template<typename ValueType, bool RawMode>
326 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
327 "Yet, a method was called that requires this support.";
328}
329
330template<typename ValueType, bool RawMode>
332 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
333 "Yet, a method was called that requires this support.";
334}
335
336template<typename ValueType, bool RawMode>
338
339template<typename ValueType, bool RawMode>
341 std::optional<ValueType> const&, std::optional<ValueType> const&,
342 ValueType) {
343 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
344 "Yet, a method was called that requires this support.";
345}
346
347template<typename ValueType, bool RawMode>
349 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
350 "Yet, a method was called that requires this support.";
351}
352
353template<typename ValueType, bool RawMode>
355 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
356 "Yet, a method was called that requires this support.";
357}
358
359template<typename ValueType, bool RawMode>
361 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
362 "Yet, a method was called that requires this support.";
363}
364
365template<typename ValueType, bool RawMode>
367 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
368 "Yet, a method was called that requires this support.";
369}
370
371template<typename ValueType, bool RawMode>
373 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
374 "Yet, a method was called that requires this support.";
375}
376
377template<typename ValueType, bool RawMode>
379 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
380 "Yet, a method was called that requires this support.";
381}
382
383template<typename ValueType, bool RawMode>
385 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
386 "Yet, a method was called that requires this support.";
387}
388
389template<typename ValueType, bool RawMode>
391 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
392 "Yet, a method was called that requires this support.";
393}
394
395template<typename ValueType, bool RawMode>
397 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
398 "Yet, a method was called that requires this support.";
399}
400
401template<typename ValueType, bool RawMode>
403 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
404 "Yet, a method was called that requires this support.";
405}
406
407template<typename ValueType, bool RawMode>
409 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
410 "Yet, a method was called that requires this support.";
411}
412
413template<typename ValueType, bool RawMode>
415 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
416 "Yet, a method was called that requires this support.";
417}
418
419template<typename ValueType, bool RawMode>
420void Z3LpSolver<ValueType, RawMode>::writeModelToFile(std::string const& filename) const {
421 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
422 "Yet, a method was called that requires this support.";
423}
424
425template<typename ValueType, bool RawMode>
427 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
428 "Yet, a method was called that requires this support.";
429}
430
431template<typename ValueType, bool RawMode>
433 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
434 "Yet, a method was called that requires this support.";
435}
436
437template<typename ValueType, bool RawMode>
439 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
440 "Yet, a method was called that requires this support.";
441}
442
443template<typename ValueType, bool RawMode>
444ValueType Z3LpSolver<ValueType, RawMode>::getMILPGap(bool relative) const {
445 throw storm::exceptions::NotImplementedException() << "This version of storm was compiled without Z3 or the version of Z3 does not support optimization. "
446 "Yet, a method was called that requires this support.";
447}
448#endif
449
450template class Z3LpSolver<double, false>;
452template class Z3LpSolver<double, true>;
454} // namespace solver
455} // namespace storm
IntegerLiteralExpression const & asIntegerLiteralExpression() const
RationalLiteralExpression const & asRationalLiteralExpression() const
virtual bool isRationalLiteralExpression() const
virtual bool isIntegerLiteralExpression() const
BaseExpression const & getBaseExpression() const
Retrieves the base expression underlying this expression object.
int_fast64_t getValue() const
Retrieves the value of the integer literal.
storm::RationalNumber getValue() const
Retrieves the value of the double literal.
storm::expressions::Expression getExpression() const
Retrieves an expression that represents the variable.
Definition Variable.cpp:34
A class that implements the LpSolver interface using Z3.
Definition Z3LpSolver.h:24
typename LpSolver< ValueType, RawMode >::Variable Variable
Definition Z3LpSolver.h:27
typename LpSolver< ValueType, RawMode >::Constraint Constraint
Definition Z3LpSolver.h:29
Z3LpSolver()
Constructs a solver without a name.
typename LpSolver< ValueType, RawMode >::VariableType VariableType
Definition Z3LpSolver.h:26
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
SFTBDDChecker::ValueType ValueType
Expression makeBinaryRelationExpression(Expression const &first, Expression const &second, RelationType const &reltype)
Expression sum(std::vector< storm::expressions::Expression > const &expressions)
void closeFile(std::ofstream &stream)
Close the given file after writing.
Definition file.h:47
void openFile(std::string const &filepath, std::ofstream &filestream, bool append=false, bool silent=false)
Open the given file for writing.
Definition file.h:18
SettingsManager const & manager()
Retrieves the settings manager.
bool isZero(ValueType const &a)
Definition constants.cpp:39
ValueType one()
Definition constants.cpp:19