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 auto [min, max] = std::minmax(*a, *b);
232 uint64_t const size = xy.first.size();
233 for (uint64_t i = 0; i < size; ++i) {
234 // We allow setting both vectors "in-place", e.g. we might have &lowerOut == &xy.first.
235 // This requires to use temporary values.
236 ValueType xi = xy.first[i];
237 ValueType yi = xy.second[i];
238 lowerOut[i] = xi + min * yi;
239 upperOut[i] = xi + max * yi;
240 }
241}
242
243template<typename ValueType, bool TrivialRowGrouping>
245 storm::solver::TerminationCondition<ValueType> const& condition) const {
246 if (a.has_value() && b.has_value()) {
248 auto max = std::max(*a, *b);
249 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * max; }, storm::solver::SolverGuarantee::GreaterOrEqual);
251 auto min = std::min(*a, *b);
252 return condition.terminateNow([&](uint64_t const& i) { return xy.first[i] + xy.second[i] * min; }, storm::solver::SolverGuarantee::GreaterOrEqual);
253 }
254 }
255 return false;
256}
257
258template<typename ValueType, bool TrivialRowGrouping>
260 std::function<void()> const& getNextConvergenceCheckState,
261 bool relative, ValueType const& precision) const {
262 if (!a.has_value() || !b.has_value()) {
263 return false;
264 }
265 if (*a == *b) {
266 return true;
267 }
268 if (relative) {
269 auto [min, max] = std::minmax(*a, *b);
270 if (min >= storm::utility::zero<ValueType>()) {
271 ValueType const val = (max - min) / precision - min;
272 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
273 if (!storm::utility::isZero(xy.second[convergenceCheckState]) && val > xy.first[convergenceCheckState] / xy.second[convergenceCheckState]) {
274 return false;
275 }
276 }
277 } else if (max <= storm::utility::zero<ValueType>()) {
278 ValueType const val = (min - max) / precision - max;
279 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
280 if (!storm::utility::isZero(xy.second[convergenceCheckState]) && val < xy.first[convergenceCheckState] / xy.second[convergenceCheckState]) {
281 return false;
282 }
283 }
284 } else {
285 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
286 ValueType l = xy.first[convergenceCheckState] + min * xy.second[convergenceCheckState];
287 ValueType u = xy.first[convergenceCheckState] + max * xy.second[convergenceCheckState];
288 STORM_LOG_ASSERT(u >= l, "Upper bound less than lower bound.");
290 if ((u - l) > l * precision) {
291 return false;
292 }
293 } else if (u < storm::utility::zero<ValueType>()) {
294 if ((l - u) < u * precision) {
295 return false;
296 }
297 } else { // l <= 0 <= u
298 if (l != u) {
299 return false;
300 }
301 }
302 }
303 }
304 } else {
305 ValueType val = precision / storm::utility::abs<ValueType>(*b - *a);
306 for (; convergenceCheckState < xy.first.size(); getNextConvergenceCheckState()) {
307 if (xy.second[convergenceCheckState] > val) {
308 return false;
309 }
310 }
311 }
312 return true;
313}
314
315template<typename ValueType, bool TrivialRowGrouping>
316template<typename BackendType>
318 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
319 bool relative, ValueType const& precision, BackendType&& backend, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
320 std::optional<storm::storage::BitVector> const& relevantValues, uint64_t convergenceCheckState) const {
321 if constexpr (BackendType::CurrentStage == SVIStage::Initial) {
322 xy.first.assign(xy.first.size(), storm::utility::zero<ValueType>());
323 xy.second.assign(xy.first.size(), storm::utility::one<ValueType>());
324 convergenceCheckState = relevantValues.has_value() ? relevantValues->getNextSetIndex(0ull) : 0ull;
325 }
326 std::function<void()> getNextConvergenceCheckState;
327 if (relevantValues) {
328 getNextConvergenceCheckState = [&convergenceCheckState, &relevantValues]() {
329 convergenceCheckState = relevantValues->getNextSetIndex(++convergenceCheckState);
330 };
331 } else {
332 getNextConvergenceCheckState = [&convergenceCheckState]() { ++convergenceCheckState; };
333 }
334
335 while (true) {
336 ++numIterations;
337 viOperator->applyInPlace(xy, offsets, backend);
338 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
339 if (data.checkConvergence(convergenceCheckState, getNextConvergenceCheckState, relative, precision)) {
340 return SVIData{SolverStatus::Converged, xy, backend.a(), backend.b()};
341 } else {
342 if (iterationCallback) {
343 SVIData data{SolverStatus::InProgress, xy, backend.a(), backend.b()};
344 data.status = iterationCallback(data);
345 if (data.status != SolverStatus::InProgress) {
346 return data;
347 }
348 }
349 if (backend.moveToNextStage()) {
350 switch (backend.getNextStage()) {
352 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::y_less_1>(),
353 iterationCallback, relevantValues, convergenceCheckState);
354 case SVIStage::b_eq_d:
355 return SVI(xy, offsets, numIterations, relative, precision, backend.template createBackendForNextStage<SVIStage::b_eq_d>(),
356 iterationCallback, relevantValues, convergenceCheckState);
357 default:
358 STORM_LOG_ASSERT(false, "Unexpected next stage.");
359 }
360 }
361 }
362 }
363}
364
365template<typename ValueType, bool TrivialRowGrouping>
366template<storm::OptimizationDirection Dir>
368 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::pair<std::vector<ValueType> const*, ValueType> const& offsets, uint64_t& numIterations,
369 bool relative, ValueType const& precision, std::optional<ValueType> const& a, std::optional<ValueType> const& b,
370 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
372 rowValueStorage.resize(sizeOfLargestRowGroup - 1);
373 return SVI(xy, offsets, numIterations, relative, precision, SVIBackend<ValueType, Dir, SVIStage::Initial, TrivialRowGrouping>(rowValueStorage, a, b),
374 iterationCallback, relevantValues);
375}
376
377template<typename ValueType, bool TrivialRowGrouping>
379 std::pair<std::vector<ValueType>, std::vector<ValueType>>& xy, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative,
380 ValueType const& precision, std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound,
381 std::optional<ValueType> const& upperBound, std::function<SolverStatus(SVIData const&)> const& iterationCallback,
382 std::optional<storm::storage::BitVector> const& relevantValues) const {
383 std::pair<std::vector<ValueType> const*, ValueType> offsetsPair{&offsets, storm::utility::zero<ValueType>()};
384 if (!dir.has_value() || maximize(*dir)) {
385 // When we maximize, a is the lower bound and b is the upper bound
386 return SVI<storm::OptimizationDirection::Maximize>(xy, offsetsPair, numIterations, relative, precision, lowerBound, upperBound, iterationCallback,
387 relevantValues);
388 } else {
389 // When we minimize, b is the lower bound and a is the upper bound
390 return SVI<storm::OptimizationDirection::Minimize>(xy, offsetsPair, numIterations, relative, precision, upperBound, lowerBound, iterationCallback,
391 relevantValues);
392 }
393}
394
395template<typename ValueType, bool TrivialRowGrouping>
397 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative, ValueType const& precision,
398 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
399 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
400 // Create two vectors x and y using the given operand plus an auxiliary vector.
401 std::pair<std::vector<ValueType>, std::vector<ValueType>> xy;
402 auto& auxVector = viOperator->allocateAuxiliaryVector(operand.size());
403 xy.first.swap(operand);
404 xy.second.swap(auxVector);
405 auto doublePrec = precision + precision;
406 if constexpr (std::is_same_v<ValueType, double>) {
407 doublePrec -= precision * 1e-6; // be slightly more precise to avoid a good chunk of floating point issues
408 }
409 auto res = SVI(xy, offsets, numIterations, relative, doublePrec, dir, lowerBound, upperBound, iterationCallback, relevantValues);
410 res.trySetAverage(xy.first);
411 // Swap operand and aux vector back to original positions.
412 xy.first.swap(operand);
413 xy.second.swap(auxVector);
414 viOperator->freeAuxiliaryVector();
415 return res.status;
416}
417
418template<typename ValueType, bool TrivialRowGrouping>
420 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, bool relative, ValueType const& precision,
421 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
422 std::function<SolverStatus(SVIData const&)> const& iterationCallback, std::optional<storm::storage::BitVector> const& relevantValues) const {
423 uint64_t numIterations = 0;
424 return SVI(operand, offsets, numIterations, relative, precision, dir, lowerBound, upperBound, iterationCallback, relevantValues);
425}
426
431
432} // 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::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:15
#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:374
bool isZero(ValueType const &a)
Definition constants.cpp:42
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:24
ValueType one()
Definition constants.cpp:19
TargetType convertNumber(SourceType const &number)
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