Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SoundValueIterationHelper.cpp
Go to the documentation of this file.
2
3#include <type_traits>
4
11
12namespace storm::solver::helper {
13
14template<typename ValueType, bool TrivialRowGrouping>
17 : viOperator(viOperator) {
18 sizeOfLargestRowGroup = 1;
19 if constexpr (!TrivialRowGrouping) {
20 auto it = viOperator->getRowGroupIndices().cbegin();
21 auto itEnd = viOperator->getRowGroupIndices().cend() - 1;
22 while (it != itEnd) {
23 auto const curr = *it;
24 sizeOfLargestRowGroup = std::max(sizeOfLargestRowGroup, *(++it) - curr);
25 }
26 }
27}
28
30
31template<typename ValueType, OptimizationDirection Dir, SVIStage Stage, bool TrivialRowGrouping>
33 public:
34 static const SVIStage CurrentStage = Stage;
35 using RowValueStorageType = std::vector<std::pair<ValueType, ValueType>>;
36
37 SVIBackend(RowValueStorageType& rowValueStorage, std::optional<ValueType> const& a, std::optional<ValueType> const& b,
38 std::optional<ValueType> const& d = {})
39 : currRowValues(rowValueStorage) {
40 if (a.has_value()) {
41 aValue &= *a;
42 }
43 if (b.has_value()) {
44 bValue &= *b;
45 }
46 if (d.has_value()) {
47 dValue &= *d;
48 }
49 }
50
52 allYLessOne = true;
53 curr_a.reset();
54 curr_b.reset();
55 }
56
57 void firstRow(std::pair<ValueType, ValueType>&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
58 assert(currRowValuesIndex == 0);
59 if constexpr (!TrivialRowGrouping) {
60 bestValue.reset();
61 }
62 best = std::move(value);
63 }
64
65 void nextRow(std::pair<ValueType, ValueType>&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
66 assert(!TrivialRowGrouping);
67 assert(currRowValuesIndex < currRowValues.size());
68 if (Stage == SVIStage::Initial && bValue.empty()) {
69 if (value.second > best.second || (value.second == best.second && better(value.first, best.first))) {
70 std::swap(value, best);
71 }
72 currRowValues[currRowValuesIndex++] = std::move(value);
73 } else {
74 assert(!bValue.empty());
75 auto const& b = Stage == SVIStage::b_eq_d ? *dValue : *bValue;
76 if (bestValue.empty()) {
77 bestValue = best.first + b * best.second;
78 }
79 if (ValueType currentValue = value.first + b * value.second; bestValue &= currentValue) {
80 std::swap(value, best);
81 if (Stage != SVIStage::b_eq_d && value.second < best.second) {
82 // We need to store the 'old' best values as they might be relevant for the decision value.
83 currRowValues[currRowValuesIndex++] = std::move(value);
84 }
85 } else if (best.second > value.second) {
86 if (*bestValue == currentValue) {
87 // In this case we have the same value, but the current row is to be preferred as it has a smaller y value
88 std::swap(value, best);
89 } else if (Stage != SVIStage::b_eq_d) {
90 // In this case we have a worse weighted value
91 // However, this could be relevant for the decision value
92 currRowValues[currRowValuesIndex++] = std::move(value);
93 }
94 }
95 }
96 }
97
98 void applyUpdate(ValueType& xCurr, ValueType& yCurr, [[maybe_unused]] uint64_t rowGroup) {
99 std::swap(xCurr, best.first);
100 std::swap(yCurr, best.second);
101 if constexpr (Stage != SVIStage::b_eq_d && !TrivialRowGrouping) {
102 // Update decision value
103 while (currRowValuesIndex) {
104 if (auto const& rowVal = currRowValues[--currRowValuesIndex]; yCurr > rowVal.second) {
105 dValue &= (rowVal.first - xCurr) / (yCurr - rowVal.second);
106 }
107 }
108 } else {
109 assert(currRowValuesIndex == 0);
110 }
111
112 // keep track of bounds a,b
113 if constexpr (Stage == SVIStage::Initial) {
114 if (allYLessOne) {
115 if (yCurr < storm::utility::one<ValueType>()) {
116 ValueType val = xCurr / (storm::utility::one<ValueType>() - yCurr);
117 curr_a &= val;
118 curr_b &= val;
119 } else {
120 allYLessOne = false;
121 }
122 }
123 } else {
124 STORM_LOG_ASSERT(yCurr < storm::utility::one<ValueType>(), "Unexpected y value for this stage.");
125 ValueType val = xCurr / (storm::utility::one<ValueType>() - yCurr);
126 curr_a &= val;
127 curr_b &= val;
128 }
129 }
130
132 nextStage = Stage;
133 if (nextStage == SVIStage::Initial && allYLessOne) {
134 nextStage = SVIStage::y_less_1;
135 }
136 if (nextStage == SVIStage::y_less_1 || nextStage == SVIStage::b_eq_d) {
137 aValue &= std::move(*curr_a);
138 if (nextStage == SVIStage::y_less_1) {
139 curr_b &= dValue;
140 bValue &= std::move(*curr_b);
141 if (!dValue.empty() && *bValue == *dValue) {
142 nextStage = SVIStage::b_eq_d;
143 }
144 } else {
145 // in the b_eq_d stage, we slightly repurpose _b and _d:
146 // _b is now used to track an upper bound (which can pass _d)
147 // _d is now used for the weighting when selecting the best row
148 bValue &= std::move(*curr_b);
149 }
150 }
151 }
152
153 bool constexpr converged() const {
154 return false;
155 }
156
157 bool constexpr abort() const {
158 return false;
159 }
160
161 std::optional<ValueType> a() const {
162 return aValue.getOptionalValue();
163 }
164
165 std::optional<ValueType> b() const {
166 return bValue.getOptionalValue();
167 }
168
169 std::optional<ValueType> d() const {
170 return dValue.getOptionalValue();
171 }
172
173 bool moveToNextStage() const {
174 return nextStage != Stage;
175 }
176
177 template<SVIStage NewStage>
179 std::optional<ValueType> d;
180 if (NewStage == SVIStage::b_eq_d && !bValue.empty())
181 d = *bValue;
182 else if (NewStage != SVIStage::Initial && !dValue.empty())
183 d = *dValue;
185 }
186
187 SVIStage const& getNextStage() const {
188 return nextStage;
189 }
190
191 private:
192 static bool better(ValueType const& lhs, ValueType const& rhs) {
193 if constexpr (minimize(Dir)) {
194 return lhs < rhs;
195 } else {
196 return lhs > rhs;
197 }
198 }
199
201 using ExtremumInvDir = storm::utility::Extremum<invert(Dir), ValueType>;
202
203 ExtremumDir aValue, dValue;
204 ExtremumInvDir bValue;
205
206 SVIStage nextStage{Stage};
207
208 ExtremumDir curr_b;
209 ExtremumInvDir curr_a;
210 bool allYLessOne;
211
212 std::pair<ValueType, ValueType> best;
213 ExtremumDir bestValue;
214 RowValueStorageType& currRowValues;
215 uint64_t currRowValuesIndex{0};
216};
217
218template<typename ValueType, bool TrivialRowGrouping>
220 if (a.has_value() && b.has_value()) {
221 ValueType abAvg = (*a + *b) / storm::utility::convertNumber<ValueType, uint64_t>(2);
223 [&abAvg](ValueType const& xVal, ValueType const& yVal) -> ValueType { return xVal + abAvg * yVal; });
224 }
225}
226
227template<typename ValueType, bool TrivialRowGrouping>
229 std::vector<ValueType>& upperOut) const {
230 auto [min, max] = std::minmax(*a, *b);
231 uint64_t const size = xy.first.size();
232 for (uint64_t i = 0; i < size; ++i) {
233 // We allow setting both vectors "in-place", e.g. we might have &lowerOut == &xy.first.
234 // This requires to use temporary values.
235 ValueType xi = xy.first[i];
236 ValueType yi = xy.second[i];
237 lowerOut[i] = xi + min * yi;
238 upperOut[i] = xi + max * yi;
239 }
240}
241
242template<typename ValueType, bool TrivialRowGrouping>
244 storm::solver::TerminationCondition<ValueType> const& condition) const {
245 if (a.has_value() && b.has_value()) {
247 auto max = std::max(*a, *b);
248 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * max; }, storm::solver::SolverGuarantee::GreaterOrEqual);
250 auto min = std::min(*a, *b);
251 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * min; }, storm::solver::SolverGuarantee::GreaterOrEqual);
252 }
253 }
254 return false;
255}
256
257template<typename ValueType, bool TrivialRowGrouping>
259 std::function<void()> const& getNextConvergenceCheckState,
260 bool relative, ValueType const& precision) const {
261 if (!a.has_value() || !b.has_value())
262 return false;
263 if (*a == *b)
264 return true;
265 if (relative) {
266 auto [min, max] = std::minmax(*a, *b);
267 if (min >= storm::utility::zero<ValueType>()) {
268 ValueType const val = (max - min) / precision - min;
269 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
270 if (!storm::utility::isZero(xy.second[convergenceCheckState]) && val > xy.first[convergenceCheckState] / xy.second[convergenceCheckState]) {
271 return false;
272 }
273 }
274 } else if (max <= storm::utility::zero<ValueType>()) {
275 ValueType const val = (min - max) / precision - max;
276 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
277 if (!storm::utility::isZero(xy.second[convergenceCheckState]) && val < xy.first[convergenceCheckState] / xy.second[convergenceCheckState]) {
278 return false;
279 }
280 }
281 } else {
282 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
283 ValueType l = xy.first[convergenceCheckState] + min * xy.second[convergenceCheckState];
284 ValueType u = xy.first[convergenceCheckState] + max * xy.second[convergenceCheckState];
285 assert(u >= l);
286 if (l > storm::utility::zero<ValueType>()) {
287 if ((u - l) > l * precision) {
288 return false;
289 }
290 } else if (u < storm::utility::zero<ValueType>()) {
291 if ((l - u) < u * precision) {
292 return false;
293 }
294 } else { // l <= 0 <= u
295 if (l != u) {
296 return false;
297 }
298 }
299 }
300 }
301 } else {
302 ValueType val = precision / storm::utility::abs<ValueType>(*b - *a);
303 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
304 if (xy.second[convergenceCheckState] > val) {
305 return false;
306 }
307 }
308 }
309 return true;
310}
311
312template<typename ValueType, bool TrivialRowGrouping>
313template<typename BackendType>
315 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
316 bool relative, ValueType const& precision, BackendType&& backend, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
317 std::optional<storm::storage::BitVector> const& relevantValues, uint64_t convergenceCheckState) const {
318 if constexpr (BackendType::CurrentStage == SVIStage::Initial) {
319 xy.first.assign(xy.first.size(), storm::utility::zero<ValueType>());
320 xy.second.assign(xy.first.size(), storm::utility::one<ValueType>());
321 convergenceCheckState = relevantValues.has_value() ? relevantValues->getNextSetIndex(0ull) : 0ull;
322 }
323 std::function<void()> getNextConvergenceCheckState;
324 if (relevantValues) {
325 getNextConvergenceCheckState = [&convergenceCheckState, &relevantValues]() {
326 convergenceCheckState = relevantValues->getNextSetIndex(++convergenceCheckState);
327 };
328 } else {
329 getNextConvergenceCheckState = [&convergenceCheckState]() { ++convergenceCheckState; };
330 }
331
332 while (true) {
333 ++numIterations;
334 viOperator->applyInPlace(xy, offsets, backend);
335 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
336 if (data.checkConvergence(convergenceCheckState, getNextConvergenceCheckState, relative, precision)) {
337 return SVIData{SolverStatus::Converged, xy, backend.a(), backend.b()};
338 } else {
339 if (iterationCallback) {
340 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
341 data.status = iterationCallback(data);
342 if (data.status != SolverStatus::InProgress) {
343 return data;
344 }
345 }
346 if (backend.moveToNextStage()) {
347 switch (backend.getNextStage()) {
349 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::y_less_1>(),
350 iterationCallback, relevantValues, convergenceCheckState);
351 case SVIStage::b_eq_d:
352 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::b_eq_d>(),
353 iterationCallback, relevantValues, convergenceCheckState);
354 default:
355 STORM_LOG_ASSERT(false, "Unexpected next stage");
356 }
357 }
358 }
359 }
360}
361
362template<typename ValueType, bool TrivialRowGrouping>
363template<storm::OptimizationDirection Dir>
365 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
366 bool relative, ValueType const& precision, std::optional<ValueType> const& a, std::optional<ValueType> const& b,
367 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
369 rowValueStorage.resize(sizeOfLargestRowGroup - 1);
370 return SVI(xy, offsets, numIterations, relative, precision, SVIBackend<ValueType, Dir, SVIStage::Initial, TrivialRowGrouping>(rowValueStorage, a, b),
371 iterationCallback, relevantValues);
372}
373
374template<typename ValueType, bool TrivialRowGrouping>
376 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative,
377 ValueType const& precision, std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound,
378 std::optional<ValueType> const& upperBound, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
379 std::optional<storm::storage::BitVector> const& relevantValues) const {
380 std::pair<std::vector<ValueType> const*, ValueType> offsetsPair{&offsets, storm::utility::zero<ValueType>()};
381 if (!dir.has_value() || maximize(*dir)) {
382 // When we maximize, a is the lower bound and b is the upper bound
383 return SVI<storm::OptimizationDirection::Maximize>(xy, offsetsPair, numIterations, relative, precision, lowerBound, upperBound, iterationCallback,
384 relevantValues);
385 } else {
386 // When we minimize, b is the lower bound and a is the upper bound
387 return SVI<storm::OptimizationDirection::Minimize>(xy, offsetsPair, numIterations, relative, precision, upperBound, lowerBound, iterationCallback,
388 relevantValues);
389 }
390}
391
392template<typename ValueType, bool TrivialRowGrouping>
394 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative, ValueType const& precision,
395 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
396 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
397 // Create two vectors x and y using the given operand plus an auxiliary vector.
398 std::pair<std::vector<ValueType>, std::vector<ValueType>> xy;
399 auto& auxVector = viOperator->allocateAuxiliaryVector(operand.size());
400 xy.first.swap(operand);
401 xy.second.swap(auxVector);
402 auto doublePrec = precision + precision;
403 if constexpr (std::is_same_v<ValueType, double>) {
404 doublePrec -= precision * 1e-6; // be slightly more precise to avoid a good chunk of floating point issues
405 }
406 auto res = SVI(xy, offsets, numIterations, relative, doublePrec, dir, lowerBound, upperBound, iterationCallback, relevantValues);
407 res.trySetAverage(xy.first);
408 // Swap operand and aux vector back to original positions.
409 xy.first.swap(operand);
410 xy.second.swap(auxVector);
411 viOperator->freeAuxiliaryVector();
412 return res.status;
413}
414
415template<typename ValueType, bool TrivialRowGrouping>
417 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, bool relative, ValueType const& precision,
418 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
419 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
420 uint64_t numIterations = 0;
421 return SVI(operand, offsets, numIterations, relative, precision, dir, lowerBound, upperBound, iterationCallback, relevantValues);
422}
423
428
429} // namespace storm::solver::helper
virtual bool terminateNow(std::vector< ValueType > const &currentValues, SolverGuarantee const &guarantee=SolverGuarantee::None) const
Retrieves whether the guarantee provided by the solver for the current result is sufficient to termin...
virtual bool requiresGuarantee(SolverGuarantee const &guarantee) const =0
Retrieves whether the termination criterion requires the given guarantee in order to decide terminati...
std::optional< ValueType > d() const
SVIBackend(RowValueStorageType &rowValueStorage, std::optional< ValueType > const &a, std::optional< ValueType > const &b, std::optional< ValueType > const &d={})
void nextRow(std::pair< ValueType, ValueType > &&value, uint64_t rowGroup, uint64_t row)
std::optional< ValueType > a() const
void firstRow(std::pair< ValueType, ValueType > &&value, uint64_t rowGroup, uint64_t row)
std::vector< std::pair< ValueType, ValueType > > RowValueStorageType
void applyUpdate(ValueType &xCurr, ValueType &yCurr, uint64_t rowGroup)
std::optional< ValueType > b() const
SVIData SVI(std::pair< std::vector< ValueType >, std::vector< ValueType > > &xy, std::pair< std::vector< ValueType > const *, ValueType > const &offsets, uint64_t &numIterations, bool relative, ValueType const &precision, BackendType &&backend, std::function< SolverStatus(SVIData const &)> const &iterationCallback, std::optional< storm::storage::BitVector > const &relevantValues, uint64_t convergenceCheckState=0) const
SoundValueIterationHelper(std::shared_ptr< ValueIterationOperator< ValueType, TrivialRowGrouping > > viOperator)
This class represents the Value Iteration Operator (also known as Bellman operator).
Stores and manages an extremal (maximal or minimal) value.
Definition Extremum.h:15
std::optional< ValueType > getOptionalValue() const
Definition Extremum.cpp:117
void reset()
Forgets the extremal value so that this represents the extremum over an empty set.
Definition Extremum.cpp:126
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
bool constexpr maximize(OptimizationDirection d)
OptimizationDirection constexpr invert(OptimizationDirection d)
bool constexpr minimize(OptimizationDirection d)
void applyPointwise(std::vector< InValueType1 > const &firstOperand, std::vector< InValueType2 > const &secondOperand, std::vector< OutValueType > &target, Operation f=Operation())
Applies the given operation pointwise on the two given vectors and writes the result to the third vec...
Definition vector.h:398
bool isZero(ValueType const &a)
Definition constants.cpp:41
bool checkConvergence(uint64_t &convergenceCheckState, std::function< void()> const &getNextConvergenceCheckState, bool relative, ValueType const &precision) const
void trySetLowerUpper(std::vector< ValueType > &lowerOut, std::vector< ValueType > &upperOut) const
bool checkCustomTerminationCondition(storm::solver::TerminationCondition< ValueType > const &condition) const
std::pair< std::vector< ValueType >, std::vector< ValueType > > const & xy