Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
OptimisticValueIterationHelper.cpp
Go to the documentation of this file.
2
3#include <type_traits>
4
11
12namespace storm::solver::helper {
13
14template<bool Relative, typename ValueType>
15static ValueType diff(ValueType const& oldValue, ValueType const& newValue) {
16 if constexpr (Relative) {
17 return storm::utility::abs<ValueType>((newValue - oldValue) / newValue);
18 } else {
19 return storm::utility::abs<ValueType>(newValue - oldValue);
20 }
21}
22
23template<typename ValueType, storm::OptimizationDirection Dir, bool Relative>
25 public:
26 GSVIBackend(ValueType const& precision) : precision{precision} {
27 // intentionally empty
28 }
29
31 isConverged = true;
32 }
33
34 void firstRow(ValueType&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
35 best = std::move(value);
36 }
37
38 void nextRow(ValueType&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
39 best &= value;
40 }
41
42 void applyUpdate(ValueType& currValue, [[maybe_unused]] uint64_t rowGroup) {
43 if (isConverged) {
44 isConverged = storm::utility::isZero(*best) || diff<Relative>(currValue, *best) <= precision;
45 }
46 currValue = std::move(*best);
47 }
48
49 void endOfIteration() const {
50 // intentionally left empty.
51 }
52
53 bool converged() const {
54 return isConverged;
55 }
56
57 bool constexpr abort() const {
58 return false;
59 }
60
61 private:
63 ValueType const precision;
64 bool isConverged{true};
65};
66
67template<typename ValueType, bool TrivialRowGrouping>
68template<storm::OptimizationDirection Dir, bool Relative>
69SolverStatus OptimisticValueIterationHelper<ValueType, TrivialRowGrouping>::GSVI(
70 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, uint64_t& numIterations, ValueType const& precision,
71 std::function<SolverStatus(SolverStatus const&, std::vector<ValueType> const&)> const& iterationCallback) const {
72 GSVIBackend<ValueType, Dir, Relative> backend{precision};
74 while (status == SolverStatus::InProgress) {
75 ++numIterations;
76 if (viOperator->applyInPlace(operand, offsets, backend)) {
78 } else if (iterationCallback) {
79 status = iterationCallback(status, operand);
80 }
81 }
82 return status;
83}
84
85template<bool Relative, typename ValueType>
86void guessCandidate(std::pair<std::vector<ValueType>, std::vector<ValueType>>& vu, ValueType const& guessValue, std::optional<ValueType> const& lowerBound,
87 std::optional<ValueType> const& upperBound) {
88 std::function<ValueType(ValueType const&)> guess;
89 [[maybe_unused]] ValueType factor = storm::utility::one<ValueType>() + guessValue;
90 if constexpr (Relative) {
91 // the guess is given by value + |value * guessValue|. If all values are positive, this can be simplified a bit
92 if (lowerBound && *lowerBound < storm::utility::zero<ValueType>()) {
93 guess = [&guessValue](ValueType const& val) { return val + storm::utility::abs<ValueType>(val * guessValue); };
94 } else {
95 guess = [&factor](ValueType const& val) { return val * factor; };
96 }
97 } else {
98 guess = [&guessValue](ValueType const& val) { return storm::utility::isZero(val) ? storm::utility::zero<ValueType>() : val + guessValue; };
99 }
100 if (lowerBound || upperBound) {
101 std::function<ValueType(ValueType const&)> guessAndClamp;
102 if (!lowerBound) {
103 guessAndClamp = [&guess, &upperBound](ValueType const& val) { return std::min(guess(val), *upperBound); };
104 } else if (!upperBound) {
105 guessAndClamp = [&guess, &lowerBound](ValueType const& val) { return std::max(guess(val), *lowerBound); };
106 } else {
107 guessAndClamp = [&guess, &lowerBound, &upperBound](ValueType const& val) { return std::clamp(guess(val), *lowerBound, *upperBound); };
108 }
109 storm::utility::vector::applyPointwise(vu.first, vu.second, guessAndClamp);
110 } else {
111 storm::utility::vector::applyPointwise(vu.first, vu.second, guess);
112 }
113}
114
115template<typename ValueType, OptimizationDirection Dir, bool Relative>
117 public:
119 isAllUp = true;
120 isAllDown = true;
121 crossed = false;
122 errorValue = storm::utility::zero<ValueType>();
123 }
124
125 void firstRow(std::pair<ValueType, ValueType>&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
126 vBest = std::move(value.first);
127 uBest = std::move(value.second);
128 }
129
130 void nextRow(std::pair<ValueType, ValueType>&& value, [[maybe_unused]] uint64_t rowGroup, [[maybe_unused]] uint64_t row) {
131 vBest &= std::move(value.first);
132 uBest &= std::move(value.second);
133 }
134
135 void applyUpdate(ValueType& vCurr, ValueType& uCurr, [[maybe_unused]] uint64_t rowGroup) {
136 if (*vBest != storm::utility::zero<ValueType>()) {
137 errorValue &= diff<Relative>(vCurr, *vBest);
138 }
139 if (*uBest < uCurr) {
140 uCurr = *uBest;
141 isAllUp = false;
142 } else if (*uBest > uCurr) {
143 isAllDown = false;
144 }
145 vCurr = *vBest;
146 if (vCurr > uCurr) {
147 crossed = true;
148 }
149 }
150
151 void endOfIteration() const {
152 // intentionally left empty.
153 }
154
155 bool converged() const {
156 return isAllDown || isAllUp;
157 }
158
159 bool allUp() const {
160 return isAllUp;
161 }
162
163 bool allDown() const {
164 return isAllDown;
165 }
166
167 bool abort() const {
168 return crossed;
169 }
170
171 ValueType error() {
172 return *errorValue;
173 }
174
175 private:
176 bool isAllUp{true};
177 bool isAllDown{true};
178 bool crossed{false};
181};
182
183template<typename ValueType, bool TrivialRowGrouping>
186 : viOperator(viOperator) {
187 // Intentionally left empty.
188}
189
190template<typename ValueType, bool TrivialRowGrouping>
191template<OptimizationDirection Dir, bool Relative>
193 std::pair<std::vector<ValueType>, std::vector<ValueType>>& vu, std::vector<ValueType> const& offsets, uint64_t& numIterations, ValueType const& precision,
194 ValueType const& guessValue, std::optional<ValueType> const& lowerBound, std::optional<ValueType> const& upperBound,
195 std::function<SolverStatus(SolverStatus const&, std::vector<ValueType> const&)> const& iterationCallback) const {
196 ValueType currentGuessValue = guessValue;
197 for (uint64_t numTries = 1; true; ++numTries) {
198 if (SolverStatus status = GSVI<Dir, Relative>(vu.first, offsets, numIterations, currentGuessValue, iterationCallback);
199 status != SolverStatus::Converged) {
200 return status;
201 }
202 guessCandidate<Relative>(vu, precision, lowerBound, upperBound);
204 uint64_t maxIters;
205 if (storm::utility::isZero(currentGuessValue)) {
206 maxIters = std::numeric_limits<uint64_t>::max();
207 } else {
208 maxIters = numIterations + storm::utility::convertNumber<uint64_t, ValueType>(
209 storm::utility::ceil<ValueType>(storm::utility::one<ValueType>() / currentGuessValue));
210 }
211 while (numIterations < maxIters) {
212 ++numIterations;
213 if (viOperator->applyInPlace(vu, offsets, backend)) {
214 if (backend.allDown()) {
216 } else {
217 assert(backend.allUp());
218 break;
219 }
220 }
221 if (backend.abort()) {
222 break;
223 }
224 if (iterationCallback) {
225 if (auto status = iterationCallback(SolverStatus::InProgress, vu.first); status != SolverStatus::InProgress) {
226 return status;
227 }
228 }
229 }
230 STORM_LOG_WARN_COND(numTries != 20, "Optimistic Value Iteration did not terminate after 20 refinements. It might be stuck.");
231 currentGuessValue = backend.error() / storm::utility::convertNumber<ValueType, uint64_t>(2u);
232 }
233}
234
235template<typename ValueType, bool TrivialRowGrouping>
237 std::pair<std::vector<ValueType>, std::vector<ValueType>>& vu, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative,
238 ValueType const& precision, std::optional<storm::OptimizationDirection> const& dir, ValueType const& guessValue, std::optional<ValueType> const& lowerBound,
239 std::optional<ValueType> const& upperBound,
240 std::function<SolverStatus(SolverStatus const&, std::vector<ValueType> const&)> const& iterationCallback) const {
241 // Catch the case where lower- and upper bound are already close enough. (when guessing candidates, OVI handles this case not very well, in particular
242 // when lowerBound==upperBound)
243 if (lowerBound && upperBound) {
244 ValueType diff = *upperBound - *lowerBound;
245 if ((relative && diff <= precision * std::min(storm::utility::abs(*lowerBound), storm::utility::abs(*upperBound))) ||
246 (!relative && diff <= precision)) {
247 vu.first.assign(vu.first.size(), *lowerBound);
248 vu.second.assign(vu.second.size(), *upperBound);
250 }
251 }
252
253 if (!dir.has_value() || maximize(*dir)) {
254 if (relative) {
255 return OVI<OptimizationDirection::Maximize, true>(vu, offsets, numIterations, precision, guessValue, lowerBound, upperBound, iterationCallback);
256 } else {
257 return OVI<OptimizationDirection::Maximize, false>(vu, offsets, numIterations, precision, guessValue, lowerBound, upperBound, iterationCallback);
258 }
259 } else {
260 if (relative) {
261 return OVI<OptimizationDirection::Minimize, true>(vu, offsets, numIterations, precision, guessValue, lowerBound, upperBound, iterationCallback);
262 } else {
263 return OVI<OptimizationDirection::Minimize, false>(vu, offsets, numIterations, precision, guessValue, lowerBound, upperBound, iterationCallback);
264 }
265 }
266}
267
268template<typename ValueType, bool TrivialRowGrouping>
270 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, uint64_t& numIterations, bool relative, ValueType const& precision,
271 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& guessValue, std::optional<ValueType> const& lowerBound,
272 std::optional<ValueType> const& upperBound,
273 std::function<SolverStatus(SolverStatus const&, std::vector<ValueType> const&)> const& iterationCallback) const {
274 // Create two vectors v and u using the given operand plus an auxiliary vector.
275 std::pair<std::vector<ValueType>, std::vector<ValueType>> vu;
276 auto& auxVector = viOperator->allocateAuxiliaryVector(operand.size());
277 vu.first.swap(operand);
278 vu.second.swap(auxVector);
279 auto doublePrec = precision + precision;
280 if constexpr (std::is_same_v<ValueType, double>) {
281 doublePrec -= precision * 1e-6; // be slightly more precise to avoid a good chunk of floating point issues
282 }
283 auto status = OVI(vu, offsets, numIterations, relative, doublePrec, dir, guessValue ? *guessValue : doublePrec, lowerBound, upperBound, iterationCallback);
284 auto two = storm::utility::convertNumber<ValueType>(2.0);
285 // get the average of lower- and upper result
286 storm::utility::vector::applyPointwise<ValueType, ValueType, ValueType>(
287 vu.first, vu.second, vu.first, [&two](ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; });
288 // Swap operand and aux vector back to original positions.
289 vu.first.swap(operand);
290 vu.second.swap(auxVector);
291 viOperator->freeAuxiliaryVector();
292 return status;
293}
294
295template<typename ValueType, bool TrivialRowGrouping>
297 std::vector<ValueType>& operand, std::vector<ValueType> const& offsets, bool relative, ValueType const& precision,
298 std::optional<storm::OptimizationDirection> const& dir, std::optional<ValueType> const& guessValue, std::optional<ValueType> const& lowerBound,
299 std::optional<ValueType> const& upperBound,
300 std::function<SolverStatus(SolverStatus const&, std::vector<ValueType> const&)> const& iterationCallback) const {
301 uint64_t numIterations = 0;
302 return OVI(operand, offsets, numIterations, relative, precision, dir, guessValue, lowerBound, upperBound, iterationCallback);
303}
304
309
310} // namespace storm::solver::helper
void firstRow(ValueType &&value, uint64_t rowGroup, uint64_t row)
void nextRow(ValueType &&value, uint64_t rowGroup, uint64_t row)
void applyUpdate(ValueType &currValue, uint64_t rowGroup)
void firstRow(std::pair< ValueType, ValueType > &&value, uint64_t rowGroup, uint64_t row)
void applyUpdate(ValueType &vCurr, ValueType &uCurr, uint64_t rowGroup)
void nextRow(std::pair< ValueType, ValueType > &&value, uint64_t rowGroup, uint64_t row)
OptimisticValueIterationHelper(std::shared_ptr< ValueIterationOperator< ValueType, TrivialRowGrouping > > viOperator)
SolverStatus OVI(std::pair< std::vector< ValueType >, std::vector< ValueType > > &vu, std::vector< ValueType > const &offsets, uint64_t &numIterations, ValueType const &precision, ValueType const &guessValue, std::optional< ValueType > const &lowerBound={}, std::optional< ValueType > const &upperBound={}, std::function< SolverStatus(SolverStatus const &, std::vector< ValueType > const &)> const &iterationCallback={}) const
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_WARN_COND(cond, message)
Definition macros.h:38
void guessCandidate(std::pair< std::vector< ValueType >, std::vector< ValueType > > &vu, ValueType const &guessValue, std::optional< ValueType > const &lowerBound, std::optional< ValueType > const &upperBound)
static ValueType diff(ValueType const &oldValue, ValueType const &newValue)
bool constexpr maximize(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
ValueType abs(ValueType const &number)