Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ValueIterationOperator.h
Go to the documentation of this file.
1#pragma once
2#include <functional>
3#include <optional>
4#include <utility>
5#include <vector>
6
7#include <boost/range/adaptor/reversed.hpp>
8#include <boost/range/irange.hpp>
9
13#include "storm/utility/vector.h" // TODO
14
15namespace storm {
16class Environment;
17
18namespace storage {
19template<typename T>
20class SparseMatrix;
21}
22
23namespace solver::helper {
24
33template<typename ValueType, bool TrivialRowGrouping, typename SolutionType>
35 public:
37
45 template<bool Backward = true>
46 void setMatrix(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
47
54 void setMatrixForwards(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
55
62 void setMatrixBackwards(storm::storage::SparseMatrix<ValueType> const& matrix, std::vector<IndexType> const* rowGroupIndices = nullptr);
63
93 template<typename OperandType, typename OffsetType, typename BackendType>
94 bool apply(OperandType const& operandIn, OperandType& operandOut, OffsetType const& offsets, BackendType& backend) const {
95 // We assume in the normal apply case that we can just maximize.
96 return applyRobust<OptimizationDirection::Maximize>(operandIn, operandOut, offsets, backend);
97 }
98
99 template<OptimizationDirection RobustDir, typename OperandType, typename OffsetType, typename BackendType>
100 bool applyRobust(OperandType const& operandIn, OperandType& operandOut, OffsetType const& offsets, BackendType& backend) const {
101 if (hasSkippedRows) {
102 if (backwards) {
103 return apply<OperandType, OffsetType, BackendType, true, true, RobustDir>(operandOut, operandIn, offsets, backend);
104 } else {
105 return apply<OperandType, OffsetType, BackendType, false, true, RobustDir>(operandOut, operandIn, offsets, backend);
106 }
107 } else {
108 if (backwards) {
109 return apply<OperandType, OffsetType, BackendType, true, false, RobustDir>(operandOut, operandIn, offsets, backend);
110 } else {
111 return apply<OperandType, OffsetType, BackendType, false, false, RobustDir>(operandOut, operandIn, offsets, backend);
112 }
113 }
114 }
115
119 template<typename OperandType, typename OffsetType, typename BackendType>
120 bool applyInPlace(OperandType& operand, OffsetType const& offsets, BackendType& backend) const {
121 return apply(operand, operand, offsets, backend);
122 }
123
124 template<OptimizationDirection RobustDir, typename OperandType, typename OffsetType, typename BackendType>
125 bool applyInPlaceRobust(OperandType& operand, OffsetType const& offsets, BackendType& backend) const {
126 return applyRobust<RobustDir>(operand, operand, offsets, backend);
127 }
128
136 void setIgnoredRows(bool useLocalRowIndices, std::function<bool(IndexType, IndexType)> const& ignore);
137
141 void unsetIgnoredRows();
142
146 std::vector<IndexType> const& getRowGroupIndices() const;
147
154 std::vector<SolutionType>& allocateAuxiliaryVector(uint64_t size, std::optional<SolutionType> const& initialValue = {});
155
159 void freeAuxiliaryVector();
160
161 private:
166 template<typename OperandType, typename OffsetType, typename BackendType, bool Backward, bool SkipIgnoredRows, OptimizationDirection RobustDirection>
167 bool apply(OperandType& operandOut, OperandType const& operandIn, OffsetType const& offsets, BackendType& backend) const {
168 auto const outSize = TrivialRowGrouping ? getSize(operandOut) : rowGroupIndices->size() - 1;
169 STORM_LOG_ASSERT(TrivialRowGrouping || getSize(operandOut) >= outSize, "Dimension mismatch");
170 backend.startNewIteration();
171 auto matrixValueIt = matrixValues.cbegin();
172 auto matrixColumnIt = matrixColumns.cbegin();
173 for (auto groupIndex : indexRange<Backward>(0, outSize)) {
174 STORM_LOG_ASSERT(matrixColumnIt != matrixColumns.end(), "VI Operator in invalid state.");
175 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
176 // STORM_LOG_ASSERT(matrixValueIt != matrixValues.end(), "VI Operator in invalid state.");
177 if constexpr (TrivialRowGrouping) {
178 backend.firstRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, groupIndex), groupIndex, groupIndex);
179 } else {
180 IndexType rowIndex = (*rowGroupIndices)[groupIndex];
181 if constexpr (SkipIgnoredRows) {
182 rowIndex += skipMultipleIgnoredRows(matrixColumnIt, matrixValueIt);
183 }
184 backend.firstRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, rowIndex), groupIndex, rowIndex);
185 while (*matrixColumnIt < StartOfRowGroupIndicator) {
186 ++rowIndex;
187 if (!SkipIgnoredRows || !skipIgnoredRow(matrixColumnIt, matrixValueIt)) {
188 backend.nextRow(applyRow<RobustDirection>(matrixColumnIt, matrixValueIt, operandIn, offsets, rowIndex), groupIndex, rowIndex);
189 }
190 }
191 }
192 if constexpr (isPair<OperandType>::value) {
193 backend.applyUpdate(operandOut.first[groupIndex], operandOut.second[groupIndex], groupIndex);
194 } else {
195 backend.applyUpdate(operandOut[groupIndex], groupIndex);
196 }
197 if (backend.abort()) {
198 return backend.converged();
199 }
200 }
201 STORM_LOG_ASSERT(matrixColumnIt + 1 == matrixColumns.cend(), "Unexpected position of matrix column iterator.");
202 STORM_LOG_ASSERT(matrixValueIt == matrixValues.cend(), "Unexpected position of matrix column iterator.");
203 backend.endOfIteration();
204 return backend.converged();
205 }
206
207 // Auxiliary methods to deal with various OperandTypes and OffsetTypes
208
209 template<typename OpT>
210 OpT initializeRowRes(std::vector<OpT> const&, OpT const& offsets, uint64_t offsetIndex) const {
211 return offsets;
212 }
213
214 template<typename OpT, typename OffT>
215 OpT initializeRowRes(std::vector<OpT> const&, std::vector<OffT> const& offsets, uint64_t offsetIndex) const {
216 return offsets[offsetIndex];
217 }
218
219 template<typename OpT1, typename OpT2, typename OffT>
220 std::pair<OpT1, OpT2> initializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::vector<OffT> const& offsets,
221 uint64_t offsetIndex) const {
222 return {offsets[offsetIndex], offsets[offsetIndex]};
223 }
224
225 template<typename OpT1, typename OpT2, typename OffT1, typename OffT2>
226 std::pair<OpT1, OpT2> initializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::pair<std::vector<OffT1> const*, OffT2> const& offsets,
227 uint64_t offsetIndex) const {
228 return {(*offsets.first)[offsetIndex], offsets.second};
229 }
230
231 template<OptimizationDirection RobustDirection, typename OpT, typename OffT>
232 OpT robustInitializeRowRes(std::vector<OpT> const&, std::vector<OffT> const& offsets, uint64_t offsetIndex) const {
233 return offsets[offsetIndex].upper();
234 }
235
236 template<OptimizationDirection RobustDirection, typename OpT1, typename OpT2, typename OffT>
237 std::pair<OpT1, OpT2> robustInitializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&, std::vector<OffT> const& offsets,
238 uint64_t offsetIndex) const {
239 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
240
241 return {offsets[offsetIndex].upper(), offsets[offsetIndex].upper()};
242 }
243
244 template<OptimizationDirection RobustDirection, typename OpT1, typename OpT2, typename OffT1, typename OffT2>
245 std::pair<OpT1, OpT2> robustInitializeRowRes(std::pair<std::vector<OpT1>, std::vector<OpT2>> const&,
246 std::pair<std::vector<OffT1> const*, OffT2> const& offsets, uint64_t offsetIndex) const {
247 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
248
249 return {(*offsets.first)[offsetIndex], offsets.second};
250 }
251
255 template<OptimizationDirection RobustDirection, typename OperandType, typename OffsetType>
256 auto applyRow(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
257 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
258 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
259 return applyRowRobust<RobustDirection>(matrixColumnIt, matrixValueIt, operand, offsets, offsetIndex);
260 } else {
261 return applyRowStandard(matrixColumnIt, matrixValueIt, operand, offsets, offsetIndex);
262 }
263 }
264
265 template<typename OperandType, typename OffsetType>
266 auto applyRowStandard(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
267 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
268 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
269 auto result{initializeRowRes(operand, offsets, offsetIndex)};
270 for (++matrixColumnIt; *matrixColumnIt < StartOfRowIndicator; ++matrixColumnIt, ++matrixValueIt) {
271 if constexpr (isPair<OperandType>::value) {
272 result.first += operand.first[*matrixColumnIt] * (*matrixValueIt);
273 result.second += operand.second[*matrixColumnIt] * (*matrixValueIt);
274 } else {
275 result += operand[*matrixColumnIt] * (*matrixValueIt);
276 }
277 }
278 return result;
279 }
280
281 // Aux function for applyRowRobust
282 template<OptimizationDirection RobustDirection>
283 struct AuxCompare {
284 bool operator()(const std::pair<SolutionType, SolutionType>& a, const std::pair<SolutionType, SolutionType>& b) const {
285 if constexpr (RobustDirection == OptimizationDirection::Maximize) {
286 return a.first > b.first;
287 } else {
288 return a.first < b.first;
289 }
290 }
291 };
292
293 template<OptimizationDirection RobustDirection, typename OperandType, typename OffsetType>
294 auto applyRowRobust(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt,
295 OperandType const& operand, OffsetType const& offsets, uint64_t offsetIndex) const {
296 STORM_LOG_ASSERT(*matrixColumnIt >= StartOfRowIndicator, "VI Operator in invalid state.");
297 auto result{robustInitializeRowRes<RobustDirection>(operand, offsets, offsetIndex)};
298 AuxCompare<RobustDirection> compare;
299 applyCache.robustOrder.clear();
300
301 SolutionType remainingValue{storm::utility::one<SolutionType>()};
302 for (++matrixColumnIt; *matrixColumnIt < StartOfRowIndicator; ++matrixColumnIt, ++matrixValueIt) {
303 auto const lower = matrixValueIt->lower();
304 if constexpr (isPair<OperandType>::value) {
305 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Value Iteration is not implemented with pairs and interval-models.");
306 // Notice the unclear semantics here in terms of how to order things.
307 } else {
308 result += operand[*matrixColumnIt] * lower;
309 }
310 remainingValue -= lower;
311 auto const diameter = matrixValueIt->upper() - lower;
312 if (!storm::utility::isZero(diameter)) {
313 applyCache.robustOrder.emplace_back(operand[*matrixColumnIt], diameter);
314 }
315 }
316 if (storm::utility::isZero(remainingValue) || storm::utility::isOne(remainingValue)) {
317 return result;
318 }
319
320 std::sort(applyCache.robustOrder.begin(), applyCache.robustOrder.end(), compare);
321
322 for (auto const& pair : applyCache.robustOrder) {
323 auto availableMass = std::min(pair.second, remainingValue);
324 result += availableMass * pair.first;
325 remainingValue -= availableMass;
326 if (storm::utility::isZero(remainingValue)) {
327 return result;
328 }
329 }
330 STORM_LOG_ASSERT(storm::utility::isAlmostZero(remainingValue), "Remaining value should be zero (all prob mass taken) but is " << remainingValue);
331 return result;
332 }
333
334 // Auxiliary helpers used for metaprogramming
335 template<bool Backward>
336 auto indexRange(IndexType start, IndexType end) const {
337 if constexpr (Backward) {
338 return boost::adaptors::reverse(boost::irange(start, end));
339 } else {
340 return boost::irange(start, end);
341 }
342 }
343
344 template<typename T>
345 uint64_t getSize(std::vector<T> const& vec) const {
346 return vec.size();
347 }
348
349 template<typename T1, typename T2>
350 uint64_t getSize(std::pair<T1, T2> const& pairOfVec) const {
351 return pairOfVec.first.size();
352 }
353
354 template<typename>
355 struct isPair : std::false_type {};
356
357 template<typename T1, typename T2>
358 struct isPair<std::pair<T1, T2>> : std::true_type {};
359
363 template<bool Backward = true>
364 void setIgnoredRows(bool useLocalRowIndices, std::function<bool(IndexType, IndexType)> const& ignore);
365
369 void moveToEndOfRow(std::vector<IndexType>::iterator& matrixColumnIt) const;
370
374 bool skipIgnoredRow(std::vector<IndexType>::const_iterator& matrixColumnIt, typename std::vector<ValueType>::const_iterator& matrixValueIt) const;
375
379 uint64_t skipMultipleIgnoredRows(std::vector<IndexType>::const_iterator& matrixColumnIt,
380 typename std::vector<ValueType>::const_iterator& matrixValueIt) const;
381
385 std::vector<ValueType> matrixValues;
386
391 std::vector<IndexType> matrixColumns;
392
396 std::vector<IndexType> const* rowGroupIndices;
397
401 bool backwards{true};
402
406 bool hasSkippedRows{false};
407
411 std::vector<SolutionType> auxiliaryVector;
412
416 bool auxiliaryVectorUsedExternally{false};
417
418 // Due to a GCC bug we have to add this dummy template type here
419 // https://stackoverflow.com/questions/49707184/explicit-specialization-in-non-namespace-scope-does-not-compile-in-gcc
420 template<typename ApplyValueType, typename Dummy>
421 struct ApplyCache {};
422
423 template<typename Dummy>
424 struct ApplyCache<storm::Interval, Dummy> {
425 mutable std::vector<std::pair<SolutionType, SolutionType>> robustOrder;
426 };
427
431 ApplyCache<ValueType, int> applyCache;
432
436 static constexpr IndexType StartOfRowIndicator = 1ull << 63; // 10000..0
437
441 static constexpr IndexType StartOfRowGroupIndicator = StartOfRowIndicator + (1ull << 62); // 11000..0
442
446 static constexpr IndexType SkipNumEntriesMask = ~StartOfRowGroupIndicator; // 00111..1
447};
448
449} // namespace solver::helper
450} // 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:36
bool isAlmostZero(ValueType const &a)
Definition constants.cpp:56
bool isZero(ValueType const &a)
Definition constants.cpp:41
LabParser.cpp.
Definition cli.cpp:18
carl::Interval< double > Interval