Storm 1.14.0.1
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 STORM_LOG_ASSERT(currRowValuesIndex == 0, "Expected currRowValuesIndex to be 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 STORM_LOG_ASSERT(!TrivialRowGrouping, "Expected non-trivial row grouping.");
67 STORM_LOG_ASSERT(currRowValuesIndex < currRowValues.size(), "CurrRowValuesIndex out of range.");
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 STORM_LOG_ASSERT(!bValue.empty(), "BValue should not be 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 STORM_LOG_ASSERT(currRowValuesIndex == 0, "Expected currRowValuesIndex to be 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;
184 }
186 }
187
188 SVIStage const& getNextStage() const {
189 return nextStage;
190 }
191
192 private:
193 static bool better(ValueType const& lhs, ValueType const& rhs) {
194 if constexpr (minimize(Dir)) {
195 return lhs < rhs;
196 } else {
197 return lhs > rhs;
198 }
199 }
200
202 using ExtremumInvDir = storm::utility::Extremum<invert(Dir), ValueType>;
203
204 ExtremumDir aValue, dValue;
205 ExtremumInvDir bValue;
206
207 SVIStage nextStage{Stage};
208
209 ExtremumDir curr_b;
210 ExtremumInvDir curr_a;
211 bool allYLessOne;
212
213 std::pair<ValueType, ValueType> best;
214 ExtremumDir bestValue;
215 RowValueStorageType& currRowValues;
216 uint64_t currRowValuesIndex{0};
217};
218
219template<typename ValueType, bool TrivialRowGrouping>
221 if (a.has_value() && b.has_value()) {
222 ValueType abAvg = (*a + *b) / storm::utility::convertNumber<ValueType, uint64_t>(2);
224 [&abAvg](ValueType const& xVal, ValueType const& yVal) -> ValueType { return xVal + abAvg * yVal; });
225 }
226}
227
228template<typename ValueType, bool TrivialRowGrouping>
230 std::vector<ValueType>& upperOut) const {
231 if (!a.has_value() || !b.has_value()) {
232 return false;
233 }
234 auto [min, max] = std::minmax(*a, *b);
235 uint64_t const size = xy.first.size();
236 for (uint64_t i = 0; i < size; ++i) {
237 // We allow setting both vectors "in-place", e.g. we might have &lowerOut == &xy.first.
238 // This requires to use temporary values.
239 ValueType xi = xy.first[i];
240 ValueType yi = xy.second[i];
241 lowerOut[i] = xi + min * yi;
242 upperOut[i] = xi + max * yi;
243 }
244 return true;
245}
246
247template<typename ValueType, bool TrivialRowGrouping>
249 storm::solver::TerminationCondition<ValueType> const& condition) const {
250 if (a.has_value() && b.has_value()) {
252 auto max = std::max(*a, *b);
253 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * max; }, storm::solver::SolverGuarantee::GreaterOrEqual);
255 auto min = std::min(*a, *b);
256 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * min; }, storm::solver::SolverGuarantee::LessOrEqual);
257 }
258 }
259 return false;
260}
261
262template<typename ValueType, bool TrivialRowGrouping>
264 std::function<void()> const& getNextConvergenceCheckState,
265 bool relative, ValueType const& precision) const {
266 if (!a.has_value() || !b.has_value()) {
267 return false;
268 }
269 if (*a == *b) {
270 return true;
271 }
272 if (relative) {
273 auto [min, max] = std::minmax(*a, *b);
274 if (min >= storm::utility::zero<ValueType>()) {
275 ValueType const val = (max - min) / precision - min;
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 if (max <= storm::utility::zero<ValueType>()) {
282 ValueType const val = (min - max) / precision - max;
283 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
284 if (!storm::utility::isZero(xy.second[convergenceCheckState]) && val < xy.first[convergenceCheckState] / xy.second[convergenceCheckState]) {
285 return false;
286 }
287 }
288 } else {
289 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
290 ValueType l = xy.first[convergenceCheckState] + min * xy.second[convergenceCheckState];
291 ValueType u = xy.first[convergenceCheckState] + max * xy.second[convergenceCheckState];
292 STORM_LOG_ASSERT(u >= l, "Upper bound less than lower bound.");
294 if ((u - l) > l * precision) {
295 return false;
296 }
297 } else if (u < storm::utility::zero<ValueType>()) {
298 if ((l - u) < u * precision) {
299 return false;
300 }
301 } else { // l <= 0 <= u
302 if (l != u) {
303 return false;
304 }
305 }
306 }
307 }
308 } else {
309 ValueType val = precision / storm::utility::abs<ValueType>(*b - *a);
310 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
311 if (xy.second[convergenceCheckState] > val) {
312 return false;
313 }
314 }
315 }
316 return true;
317}
318
319template<typename ValueType, bool TrivialRowGrouping>
320template<typename BackendType>
322 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
323 bool relative, ValueType const& precision, BackendType&& backend, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
324 std::optional<storm::storage::BitVector> const& relevantValues, uint64_t convergenceCheckState) const {
325 if constexpr (BackendType::CurrentStage == SVIStage::Initial) {
326 xy.first.assign(xy.first.size(), storm::utility::zero<ValueType>());
327 xy.second.assign(xy.first.size(), storm::utility::one<ValueType>());
328 convergenceCheckState = relevantValues.has_value() ? relevantValues->getNextSetIndex(0ull) : 0ull;
329 }
330 std::function<void()> getNextConvergenceCheckState;
331 if (relevantValues) {
332 getNextConvergenceCheckState = [&convergenceCheckState, &relevantValues]() {
333 convergenceCheckState = relevantValues->getNextSetIndex(++convergenceCheckState);
334 };
335 } else {
336 getNextConvergenceCheckState = [&convergenceCheckState]() { ++convergenceCheckState; };
337 }
338
339 while (true) {
340 ++numIterations;
341 viOperator->applyInPlace(xy, offsets, backend);
342 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
343 if (data.checkConvergence(convergenceCheckState, getNextConvergenceCheckState, relative, precision)) {
344 return SVIData{SolverStatus::Converged, xy, backend.a(), backend.b()};
345 } else {
346 if (iterationCallback) {
347 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
348 data.status = iterationCallback(data);
349 if (data.status != SolverStatus::InProgress) {
350 return data;
351 }
352 }
353 if (backend.moveToNextStage()) {
354 switch (backend.getNextStage()) {
356 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::y_less_1>(),
357 iterationCallback, relevantValues, convergenceCheckState);
358 case SVIStage::b_eq_d:
359 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::b_eq_d>(),
360 iterationCallback, relevantValues, convergenceCheckState);
361 default:
362 STORM_LOG_ASSERT(false, "Unexpected next stage.");
363 }
364 }
365 }
366 }
367}
368
369template<typename ValueType, bool TrivialRowGrouping>
370template<storm::OptimizationDirection Dir>
372 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
373 bool relative, ValueType const& precision, std::optional<ValueType> const& a, std::optional<ValueType> const& b,
374 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
376 rowValueStorage.resize(sizeOfLargestRowGroup - 1);
377 return SVI(xy, offsets, numIterations, relative, precision, SVIBackend<ValueType, Dir, SVIStage::Initial, TrivialRowGrouping>(rowValueStorage, a, b),
378 iterationCallback, relevantValues);
379}
380
381template<typename ValueType, bool TrivialRowGrouping>
383 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative,
384 ValueType const& precision, std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound,
385 std::optional<ValueType> const& upperBound, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
386 std::optional<storm::storage::BitVector> const& relevantValues) const {
387 std::pair<std::vector<ValueType> const*, ValueType> offsetsPair{&offsets, storm::utility::zero<ValueType>()};
388 if (!dir.has_value() || maximize(*dir)) {
389 // When we maximize, a is the lower bound and b is the upper bound
390 return SVI<storm::OptimizationDirection::Maximize>(xy, offsetsPair, numIterations, relative, precision, lowerBound, upperBound, iterationCallback,
391 relevantValues);
392 } else {
393 // When we minimize, b is the lower bound and a is the upper bound
394 return SVI<storm::OptimizationDirection::Minimize>(xy, offsetsPair, numIterations, relative, precision, upperBound, lowerBound, iterationCallback,
395 relevantValues);
396 }
397}
398
399template<typename ValueType, bool TrivialRowGrouping>
401 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative, ValueType const& precision,
402 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
403 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues,
404 storm::OptionalRef<SolutionBounds<ValueType>> solutionBounds) const {
405 // Create two vectors x and y using the given operand plus an auxiliary vector.
406 std::pair<std::vector<ValueType>, std::vector<ValueType>> xy;
407 auto& auxVector = viOperator->allocateAuxiliaryVector(operand.size());
408 xy.first.swap(operand);
409 xy.second.swap(auxVector);
410 auto doublePrec = precision + precision;
411 if constexpr (std::is_same_v<ValueType, double>) {
412 doublePrec -= precision * 1e-6; // be slightly more precise to avoid a good chunk of floating point issues
413 }
414 auto res = SVI(xy, offsets, numIterations, relative, doublePrec, dir, lowerBound, upperBound, iterationCallback, relevantValues);
415 if (solutionBounds.has_value()) {
416 // The iteration encloses the solution throughout once both scaling factors are known.
417 std::vector<ValueType>& lower = solutionBounds->lower.emplace(xy.first.size());
418 std::vector<ValueType>& upper = solutionBounds->upper.emplace(xy.first.size());
419 if (!res.trySetLowerUpper(lower, upper)) {
420 solutionBounds->clear();
421 }
422 }
423 res.trySetAverage(xy.first);
424 // Swap operand and aux vector back to original positions.
425 xy.first.swap(operand);
426 xy.second.swap(auxVector);
427 viOperator->freeAuxiliaryVector();
428 return res.status;
429}
430
431template<typename ValueType, bool TrivialRowGrouping>
433 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, bool relative, ValueType const& precision,
434 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
435 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues,
436 storm::OptionalRef<SolutionBounds<ValueType>> solutionBounds) const {
437 uint64_t numIterations = 0;
438 return SVI(operand, offsets, numIterations, relative, precision, dir, lowerBound, upperBound, iterationCallback, relevantValues, solutionBounds);
439}
440
445
446} // namespace storm::solver::helper
Helper class that optionally holds a reference to an object of type T.
Definition OptionalRef.h:48
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::vector< std::pair< ValueType, ValueType > > RowValueStorageType
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)
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:17
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
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:375
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:26
ValueType one()
Definition constants.cpp:21
TargetType convertNumber(SourceType const &number)
Sound lower and upper bounds on a solution vector.
bool checkConvergence(uint64_t &convergenceCheckState, std::function< void()> const &getNextConvergenceCheckState, bool relative, ValueType const &precision) const
bool 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