Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ValueIterationOperator.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <boost/range/adaptor/reversed.hpp>
5#include <boost/range/irange.hpp>
6#include <functional>
7#include <optional>
8#include <utility>
9#include <vector>
10
20
21namespace storm {
22class Environment;
23
24namespace storage {
25template<typename T>
26class SparseMatrix;
27}
28
29namespace solver::helper {
30
39template<typename ValueType, bool TrivialRowGrouping, typename SolutionType>
41 public:
43
51 template<bool Backward = true>
52 void setMatrix(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
53
60 void setMatrixForwards(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
61
68 void setMatrixBackwards(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
69
99 template<typename OperandType, typename OffsetType, typename BackendType>
100 bool apply(OperandType const& operandIn, OperandType& operandOut, OffsetType const& offsets, BackendType& backend) const {
101 // We assume in the normal apply case that we can just maximize.
102 return applyRobust<OptimizationDirection::Maximize>(operandIn, operandOut, offsets, backend);
103 }
104
105 template<OptimizationDirection RobustDir, typename OperandType, typename OffsetType, typename BackendType>
106 bool applyRobust(OperandType const& operandIn, OperandType& operandOut, OffsetType const& offsets, BackendType& backend) const {
107 if (hasSkippedRows) {
108 if (backwards) {
109 return apply<OperandType, OffsetType, BackendType, true, true, RobustDir>(operandOut, operandIn, offsets, backend);
110 } else {
111 return apply<OperandType, OffsetType, BackendType, false, true, RobustDir>(operandOut, operandIn, offsets, backend);
112 }
113 } else {
114 if (backwards) {
115 return apply<OperandType, OffsetType, BackendType, true, false, RobustDir>(operandOut, operandIn, offsets, backend);
116 } else {
117 return apply<OperandType, OffsetType, BackendType, false, false, RobustDir>(operandOut, operandIn, offsets, backend);
118 }
119 }
120 }
121
125 template<typename OperandType, typename OffsetType, typename BackendType>
126 bool applyInPlace(OperandType& operand, OffsetType const& offsets, BackendType& backend) const {
127 return apply(operand, operand, offsets, backend);
128 }
129
130 template<OptimizationDirection RobustDir, typename OperandType, typename OffsetType, typename BackendType>
131 bool applyInPlaceRobust(OperandType& operand, OffsetType const& offsets, BackendType& backend) const {
132 return applyRobust<RobustDir>(operand, operand, offsets, backend);
133 }
134
142 void setIgnoredRows(bool useLocalRowIndices, std::function<bool(IndexType, IndexType)> const& ignore);
143
147 void unsetIgnoredRows();
148
152 std::vector<IndexType> const& getRowGroupIndices() const;
153
160 std::vector<SolutionType>& allocateAuxiliaryVector(uint64_t size, std::optional<SolutionType> const& initialValue = {});
161
165 void freeAuxiliaryVector();
166
167 private:
172 template<typename OperandType, typename OffsetType, typename BackendType, bool Backward, bool SkipIgnoredRows, OptimizationDirection RobustDirection>
173 bool apply(OperandType& operandOut, OperandType const& operandIn, OffsetType const& offsets, BackendType& backend) const {
174 auto const outSize = TrivialRowGrouping ? getSize(operandOut) : rowGroupIndices->size() - 1;
175 STORM_LOG_ASSERT(TrivialRowGrouping || getSize(operandOut) >= outSize, "Dimension mismatch");
176 backend.startNewIteration();
177 auto matrixValueIt = matrixValues.cbegin();
178 auto matrixColumnIt = matrixColumns.cbegin();
179 for (auto groupIndex : indexRange<Backward>(0, outSize)) {
180 STORM_LOG_ASSERT(matrixColumnIt != matrixColumns.end(), "VI Operator in invalid state.");
181 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
182 if constexpr (TrivialRowGrouping) {
183 // Ugly special case
184 if constexpr (std::is_same<BackendType, RobustSchedulerTrackingBackend<double, RobustDirection, TrivialRowGrouping>>::value) {
185 // Intentionally different method name
186 backend.processRobustRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, groupIndex), groupIndex,
187 applyCache.robustOrder);
188 } else {
189 // Generic nextRow interface
190 backend.firstRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, groupIndex), groupIndex, groupIndex);
191 }
192 } else {
193 IndexType rowIndex = (*rowGroupIndices)[groupIndex];
194 if constexpr (SkipIgnoredRows) {
195 rowIndex += skipMultipleIgnoredRows(matrixColumnIt, matrixValueIt);
196 }
197 backend.firstRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, rowIndex), groupIndex, rowIndex);
198 while (*matrixColumnIt < StartOfRowGroupIndicator) {
199 ++rowIndex;
200 if (!SkipIgnoredRows || !skipIgnoredRow(matrixColumnIt, matrixValueIt)) {
201 backend.nextRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, rowIndex), groupIndex, rowIndex);
202 }
203 }
204 }
205 if constexpr (isPair<OperandType>::value) {
206 backend.applyUpdate(operandOut.first[groupIndex], operandOut.second[groupIndex], groupIndex);
207 } else {
208 backend.applyUpdate(operandOut[groupIndex], groupIndex);
209 }
210 if (backend.abort()) {
211 return backend.converged();
212 }
213 }
214 STORM_LOG_ASSERT(matrixColumnIt + 1 == matrixColumns.cend(), "Unexpected position of matrix column iterator.");
215 STORM_LOG_ASSERT(matrixValueIt == matrixValues.cend(), "Unexpected position of matrix column iterator.");
216 backend.endOfIteration();
217 return backend.converged();
218 }
219
220 // Auxiliary methods to deal with various OperandTypes and OffsetTypes
221
222 template<typename OpT>
223 OpT initializeRowRes(std::vector<OpT> const&, OpT const& offsets, uint64_t offsetIndex) const {
224 return offsets;
225 }
226
227 template<typename OpT, typename OffT>
228 OpT initializeRowRes(std::vector<OpT> const&, std::vector<OffT> const& offsets, uint64_t offsetIndex) const {
229 return offsets[offsetIndex];
230 }
231
232 template<typename OpT1, typename OpT2, typename OffT>
233 std::pair<OpT1, OpT2> initializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::vector<OffT> const& offsets,
234 uint64_t offsetIndex) const {
235 return {offsets[offsetIndex], offsets[offsetIndex]};
236 }
237
238 template<typename OpT1, typename OpT2, typename OffT1, typename OffT2>
239 std::pair<OpT1, OpT2> initializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::pair<std::vector<OffT1> const*, OffT2> const& offsets,
240 uint64_t offsetIndex) const {
241 return {(*offsets.first)[offsetIndex], offsets.second};
242 }
243
244 template<OptimizationDirection RobustDirection, typename OpT, typename OffT>
245 OpT robustInitializeRowRes(std::vector<OpT> const&, OffT const& offsets, uint64_t offsetIndex) const {
246 if constexpr (RobustDirection == OptimizationDirection::Maximize) {
247 return offsets.upper();
248 } else {
249 return offsets.lower();
250 }
251 }
252
253 template<OptimizationDirection RobustDirection, typename OpT, typename OffT>
254 OpT robustInitializeRowRes(std::vector<OpT> const&, std::vector<OffT> const& offsets, uint64_t offsetIndex) const {
255 if constexpr (RobustDirection == OptimizationDirection::Maximize) {
256 return offsets[offsetIndex].upper();
257 } else {
258 return offsets[offsetIndex].lower();
259 }
260 }
261
262 template<OptimizationDirection RobustDirection, typename OpT1, typename OpT2, typename OffT>
263 std::pair<OpT1, OpT2> robustInitializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::vector<OffT> const& offsets,
264 uint64_t offsetIndex) const {
265 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
266
267 return {offsets[offsetIndex].upper(), offsets[offsetIndex].upper()};
268 }
269
270 template<OptimizationDirection RobustDirection, typename OpT1, typename OpT2, typename OffT1, typename OffT2>
271 std::pair<OpT1, OpT2> robustInitializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&,
272 std::pair<std::vector<OffT1> const*, OffT2> const& offsets, uint64_t offsetIndex) const {
273 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
274
275 return {(*offsets.first)[offsetIndex], offsets.second};
276 }
277
281 template<OptimizationDirection RobustDirection, typename OperandType, typename OffsetType>
282 auto applyRow(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
283 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
284 if constexpr (storm::IsIntervalType<ValueType>) {
285 return applyRowRobust<RobustDirection>(matrixColumnIt, matrixValueIt, operand, offsets, offsetIndex);
286 } else {
287 return applyRowStandard(matrixColumnIt, matrixValueIt, operand, offsets, offsetIndex);
288 }
289 }
290
291 template<typename OperandType, typename OffsetType>
292 auto applyRowStandard(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
293 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
294 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
295 auto result{initializeRowRes(operand, offsets, offsetIndex)};
296 for (++matrixColumnIt; *matrixColumnIt < StartOfRowIndicator; ++matrixColumnIt, ++matrixValueIt) {
297 if constexpr (isPair<OperandType>::value) {
298 result.first += operand.first[*matrixColumnIt] * (*matrixValueIt);
299 result.second += operand.second[*matrixColumnIt] * (*matrixValueIt);
300 } else {
301 result += operand[*matrixColumnIt] * (*matrixValueIt);
302 }
303 }
304 return result;
305 }
306
307 // Aux function for applyRowRobust
308 template<OptimizationDirection RobustDirection>
309 struct AuxCompare {
310 bool operator()(const std::pair<SolutionType, std::pair<SolutionType, uint64_t>>& a,
311 const std::pair<SolutionType, std::pair<SolutionType, uint64_t>>& b) const {
312 if constexpr (RobustDirection == OptimizationDirection::Maximize) {
313 return a.first > b.first;
314 } else {
315 return a.first < b.first;
316 }
317 }
318 };
319
320 template<OptimizationDirection RobustDirection, typename OperandType, typename OffsetType>
321 auto applyRowRobust(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
322 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
323 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
324 SolutionType result{robustInitializeRowRes<RobustDirection>(operand, offsets, offsetIndex)};
325
326 applyCache.robustOrder.clear();
327
328 if (applyCache.hasOnlyConstants.size() > 0 && applyCache.hasOnlyConstants.get(offsetIndex)) {
329 for (++matrixColumnIt; *matrixColumnIt < StartOfRowIndicator; ++matrixColumnIt, ++matrixValueIt) {
330 SolutionType const lower = matrixValueIt->lower();
331 if constexpr (isPair<OperandType>::value) {
332 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
333 // Notice the unclear semantics here in terms of how to order things.
334 } else {
335 result += operand[*matrixColumnIt] * lower;
336 }
337 }
338 return result;
339 }
340
341 SolutionType remainingValue{storm::utility::one<SolutionType>()};
342 uint64_t orderCounter = 0;
343 for (++matrixColumnIt; *matrixColumnIt < StartOfRowIndicator; ++matrixColumnIt, ++matrixValueIt, ++orderCounter) {
344 SolutionType const lower = matrixValueIt->lower();
345 if constexpr (isPair<OperandType>::value) {
346 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
347 // Notice the unclear semantics here in terms of how to order things.
348 } else {
349 result += operand[*matrixColumnIt] * lower;
350 }
351 remainingValue -= lower;
352 SolutionType const diameter = matrixValueIt->upper() - lower;
354 applyCache.robustOrder.emplace_back(operand[*matrixColumnIt], std::make_pair(diameter, orderCounter));
355 }
356 }
357 if (storm::utility::isZero(remainingValue)) {
358 return result;
359 }
360
361 static AuxCompare<RobustDirection> cmp;
362 std::sort(applyCache.robustOrder.begin(), applyCache.robustOrder.end(), cmp);
363
364 for (auto const& pair : applyCache.robustOrder) {
365 SolutionType availableMass = std::min(pair.second.first, remainingValue);
366 result += availableMass * pair.first;
367 remainingValue -= availableMass;
368 if (storm::utility::isZero(remainingValue)) {
369 return result;
370 }
371 }
373 // sad states allowed (they're having a bummer summer)
374 (storm::utility::isOne(remainingValue) && applyCache.robustOrder.size() == 0),
375 "Remaining value should be zero (all prob mass taken) or it should be a sad state, but is " << remainingValue);
376 return result;
377 }
378
379 // Auxiliary helpers used for metaprogramming
380 template<bool Backward>
381 auto indexRange(IndexType start, IndexType end) const {
382 if constexpr (Backward) {
383 return boost::adaptors::reverse(boost::irange(start, end));
384 } else {
385 return boost::irange(start, end);
386 }
387 }
388
389 template<typename T>
390 uint64_t getSize(std::vector<T> const& vec) const {
391 return vec.size();
392 }
393
394 template<typename T1, typename T2>
395 uint64_t getSize(std::pair<T1, T2> const& pairOfVec) const {
396 return pairOfVec.first.size();
397 }
398
399 template<typename>
400 struct isPair : std::false_type {};
401
402 template<typename T1, typename T2>
403 struct isPair<std::pair<T1, T2>> : std::true_type {};
404
408 template<bool Backward = true>
409 void setIgnoredRows(bool useLocalRowIndices, std::function<bool(IndexType, IndexType)> const& ignore);
410
414 void moveToEndOfRow(std::vector<IndexType>::iterator& matrixColumnIt) const;
415
419 bool skipIgnoredRow(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt) const;
420
424 uint64_t skipMultipleIgnoredRows(std::vector<IndexType>::const_iterator& matrixColumnIt,
425 typename std::vector<ValueType>::const_iterator& matrixValueIt) const;
426
430 std::vector<ValueType> matrixValues;
431
436 std::vector<IndexType> matrixColumns;
437
441 std::vector<IndexType> const* rowGroupIndices;
442
446 bool backwards{true};
447
451 bool hasSkippedRows{false};
452
456 std::vector<SolutionType> auxiliaryVector;
457
461 bool auxiliaryVectorUsedExternally{false};
462
463 // Due to a GCC bug we have to add this dummy template type here
464 // https://stackoverflow.com/questions/49707184/explicit-specialization-in-non-namespace-scope-does-not-compile-in-gcc
465 template<typename ApplyValueType, typename Dummy>
466 struct ApplyCache {};
467
468 template<typename Dummy>
469 struct ApplyCache<storm::Interval, Dummy> {
470 mutable std::vector<std::pair<SolutionType, std::pair<SolutionType, uint64_t>>> robustOrder;
471 storage::BitVector hasOnlyConstants;
472 };
473
474 template<typename Dummy>
475 struct ApplyCache<storm::RationalInterval, Dummy> {
476 mutable std::vector<std::pair<SolutionType, std::pair<SolutionType, uint64_t>>> robustOrder;
477 storage::BitVector hasOnlyConstants;
478 };
479
483 ApplyCache<ValueType, int> applyCache;
484
488 static constexpr IndexType StartOfRowIndicator = 1ull << 63; // 10000..0
489
493 static constexpr IndexType StartOfRowGroupIndicator = StartOfRowIndicator + (1ull << 62); // 11000..0
494
498 static constexpr IndexType SkipNumEntriesMask = ~StartOfRowGroupIndicator; // 00111..1
499};
500
501} // namespace solver::helper
502} // namespace storm
This class represents the Value Iteration Operator (also known as Bellman operator).
std::vector< SolutionType > & allocateAuxiliaryVector(uint64_t size, std::optional< SolutionType > const &initialValue={})
Allocates additional storage that can be used e.g.
void setIgnoredRows(bool useLocalRowIndices, std::function< bool(IndexType, IndexType)> const &ignore)
Sets rows that will be skipped when applying the operator.
bool applyRobust(OperandType const &operandIn, OperandType &operandOut, OffsetType const &offsets, BackendType &backend) const
std::vector< IndexType > const & getRowGroupIndices() const
void setMatrix(storm::storage::SparseMatrix< ValueType > const &matrix, std::vector< IndexType > const *rowGroupIndices=nullptr)
Initializes this operator with the given data.
void freeAuxiliaryVector()
Clears the auxiliary vector, invalidating any references to it.
bool applyInPlaceRobust(OperandType &operand, OffsetType const &offsets, BackendType &backend) const
bool apply(OperandType const &operandIn, OperandType &operandOut, OffsetType const &offsets, BackendType &backend) const
Applies the operator with the given operands, offsets, and backend.
bool applyInPlace(OperandType &operand, OffsetType const &offsets, BackendType &backend) const
Same as apply but with operandOut==operandIn.
void setMatrixForwards(storm::storage::SparseMatrix< ValueType > const &matrix, std::vector< IndexType > const *rowGroupIndices=nullptr)
Initializes this operator with the given data for forward iterations (starting with the smallest row ...
void setMatrixBackwards(storm::storage::SparseMatrix< ValueType > const &matrix, std::vector< IndexType > const *rowGroupIndices=nullptr)
Initializes this operator with the given data for backward iterations (starting with the largest row ...
A class that holds a possibly non-square matrix in the compressed row storage format.
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
bool isOne(ValueType const &a)
Definition constants.cpp:34
bool isAlmostZero(ValueType const &a)
Definition constants.cpp:93
bool isZero(ValueType const &a)
Definition constants.cpp:39
ValueType one()
Definition constants.cpp:19
carl::Interval< storm::RationalNumber > RationalInterval
carl::Interval< double > Interval
Interval type.
constexpr bool IsIntervalType
Helper to check if a type is an interval.