Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
BaierUpperRewardBoundsComputer.cpp
Go to the documentation of this file.
2
10
12
14
15template<typename ValueType>
17 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
18 std::vector<ValueType> const& rewards,
19 std::vector<ValueType> const& oneStepTargetProbabilities,
20 std::function<uint64_t(uint64_t)> const& stateToScc)
21 : transitionMatrix(transitionMatrix),
22 backwardTransitions(&backwardTransitions),
23 stateToScc(stateToScc),
24 rewards(rewards),
25 oneStepTargetProbabilities(oneStepTargetProbabilities) {
26 // Intentionally left empty.
27}
28
29template<typename ValueType>
31 std::vector<ValueType> const& rewards,
32 std::vector<ValueType> const& oneStepTargetProbabilities,
33 std::function<uint64_t(uint64_t)> const& stateToScc)
34 : transitionMatrix(transitionMatrix),
35 backwardTransitions(nullptr),
36 stateToScc(stateToScc),
37 rewards(rewards),
38 oneStepTargetProbabilities(oneStepTargetProbabilities) {
39 // Intentionally left empty.
40}
41
42template<typename ValueType>
44 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, std::vector<ValueType> const& oneStepTargetProbabilities) {
45 return computeUpperBoundOnExpectedVisitingTimes(transitionMatrix, transitionMatrix.transpose(true), oneStepTargetProbabilities);
46}
47
48template<typename ValueType>
50 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
51 std::vector<ValueType> const& oneStepTargetProbabilities) {
52 std::vector<uint64_t> stateToScc =
54 return computeUpperBoundOnExpectedVisitingTimes(transitionMatrix, backwardTransitions, oneStepTargetProbabilities,
55 [&stateToScc](uint64_t s) { return stateToScc[s]; });
56}
57
58template<typename ValueType>
60 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
61 std::vector<ValueType> const& oneStepTargetProbabilities, std::function<uint64_t(uint64_t)> const& stateToScc) {
62 // This first computes for every state s a non-zero lower bound d_s for the probability that starting at s, we never reach s again
63 // An upper bound on the expected visiting times is given by 1/d_s
64 // More precisely, we maintain a set of processed states.
65 // Given a processed states s, let T be the union of the target states and the states processed *before* s.
66 // Then, the value d_s for s is a lower bound for the probability that from the next step on we always stay in T.
67 // Since T does not contain s, d_s is thus also a lower bound for the probability that we never reach s again.
68 // Very roughly, the procedure can be seen as a quantitative variant of 'performProb1A'.
69 // Note: We slightly deviate from the description of Baier et al. http://doi.org/10.1007/978-3-319-63387-9_8.
70 // While they only consider processed states from a previous iteration step, we immediately consider them once they are processed
71
72 auto const numStates = transitionMatrix.getRowGroupCount();
73 assert(transitionMatrix.getRowCount() == oneStepTargetProbabilities.size());
74 assert(backwardTransitions.getRowCount() == numStates);
75 assert(backwardTransitions.getColumnCount() == numStates);
76 auto const& rowGroupIndices = transitionMatrix.getRowGroupIndices();
77
78 // Initialize the 'valid' choices.
79 // A choice is valid iff it goes to processed states with non-zero probability.
80 // Initially, mark all choices as valid that have non-zero probability to go to the target states *or* to a different Scc.
81 auto validChoices = storm::utility::vector::filterGreaterZero(oneStepTargetProbabilities);
82 for (uint64_t state = 0; state < numStates; ++state) {
83 auto const scc = stateToScc(state);
84 for (auto rowIndex = rowGroupIndices[state], rowEnd = rowGroupIndices[state + 1]; rowIndex < rowEnd; ++rowIndex) {
85 auto const row = transitionMatrix.getRow(rowIndex);
86 if (std::any_of(row.begin(), row.end(), [&stateToScc, &scc](auto const& entry) { return scc != stateToScc(entry.getColumn()); })) {
87 validChoices.set(rowIndex, true);
88 }
89 }
90 }
91
92 // Vector that holds the result.
93 std::vector<ValueType> result(numStates, storm::utility::one<ValueType>());
94 // The states that we already have assigned a value for.
95 storm::storage::BitVector processedStates(numStates, false);
96
97 // Auxiliary function that checks if the given row is valid, i.e. has a transition to a different SCC or to an already processed state.
98 // Since a once valid choice can never become invalid, we cache valid choices
99 auto isValidChoice = [&transitionMatrix, &validChoices, &processedStates](uint64_t const& choice) {
100 if (validChoices.get(choice)) {
101 return true;
102 }
103 auto row = transitionMatrix.getRow(choice);
104 // choices that lead to different SCCs already have been marked as valid above.
105 // Hence, we don't have to check for SCCs anymore.
106 if (std::any_of(row.begin(), row.end(), [&processedStates](auto const& entry) { return processedStates.get(entry.getColumn()); })) {
107 validChoices.set(choice, true); // cache for next time
108 return true;
109 } else {
110 return false;
111 }
112 };
113
114 // Auxiliary function that computes the value of a given row
115 auto getChoiceValue = [&transitionMatrix, &oneStepTargetProbabilities, &processedStates, &stateToScc, &result](uint64_t const& rowIndex,
116 uint64_t const& sccIndex) {
117 ValueType rowValue = oneStepTargetProbabilities[rowIndex];
118 for (auto const& entry : transitionMatrix.getRow(rowIndex)) {
119 if (auto successorState = entry.getColumn(); sccIndex != stateToScc(successorState)) {
120 rowValue += entry.getValue(); // * 1
121 } else if (processedStates.get(successorState)) {
122 rowValue += entry.getValue() * result[successorState];
123 }
124 }
125 return rowValue;
126 };
127
128 // For efficiency, we maintain a set of candidateStates that satisfy some necessary conditions for being processed.
129 // A candidateState s is unprocessed, but has a processed successor state s' such that s' was processed *after* the previous time we checked candidate s.
130 storm::storage::BitVector candidateStates(numStates, true);
131
132 // We usually get the best performance by processing states in inverted order.
133 // This is because in the sparse engine the states are explored with a BFS/DFS
134 uint64_t unprocessedEnd = numStates;
135 auto candidateStateIt = candidateStates.rbegin();
136 auto const candidateStateItEnd = candidateStates.rend();
137 while (true) {
138 // Assert invariant: all states with index >= unprocessedEnd are processed and state (unprocessedEnd - 1) is not processed
139 STORM_LOG_ASSERT(processedStates.getNextUnsetIndex(unprocessedEnd) == processedStates.size(), "Invalid index for last unexplored state");
140 STORM_LOG_ASSERT(candidateStates.isSubsetOf(~processedStates), "");
141 uint64_t const state = *candidateStateIt;
142 auto const group = transitionMatrix.getRowGroupIndices(state);
143 if (std::all_of(group.begin(), group.end(), [&isValidChoice](auto const& choice) { return isValidChoice(choice); })) {
144 // Compute the state value
145 storm::utility::Minimum<ValueType> minimalStateValue;
146 auto const scc = stateToScc(state);
147 for (auto choice : group) {
148 minimalStateValue &= getChoiceValue(choice, scc);
149 }
150 result[state] = *minimalStateValue;
151 processedStates.set(state);
152 if (state == unprocessedEnd - 1) {
153 unprocessedEnd = processedStates.getStartOfOneSequenceBefore(unprocessedEnd - 1);
154 if (unprocessedEnd == 0) {
155 STORM_LOG_ASSERT(processedStates.full(), "Expected all states to be processed");
156 break;
157 }
158 }
159 // Iterate through predecessors that might become valid in the next run
160 for (auto const& predEntry : backwardTransitions.getRow(state)) {
161 if (!processedStates.get(predEntry.getColumn())) {
162 candidateStates.set(predEntry.getColumn());
163 }
164 }
165 }
166 // state is no longer a candidate
167 candidateStates.set(state, false);
168 // Get next candidate state
169 ++candidateStateIt;
170 if (candidateStateIt == candidateStateItEnd) {
171 // wrap around
172 candidateStateIt = candidateStates.rbegin(unprocessedEnd);
173 // If there are no new candidates, we likely have mecs consisting of unprocessed states. For those, there is no upper bound on the EVTs.
174 STORM_LOG_THROW(candidateStateIt != candidateStateItEnd, storm::exceptions::InvalidOperationException,
175 "Unable to compute finite upper bounds for visiting times: No more candidates.");
176 }
177 }
178
179 // Transform the d_t to an upper bound for zeta(t) (i.e. the expected number of visits of t
180 storm::utility::vector::applyPointwise(result, result, [](ValueType const& r) -> ValueType { return storm::utility::one<ValueType>() / r; });
181 return result;
182}
183
184template<typename ValueType>
186 STORM_LOG_TRACE("Computing upper reward bounds using variant-2 of Baier et al.");
187
188 storm::storage::SparseMatrix<ValueType> computedBackwardTransitions;
189 if (!backwardTransitions) {
190 computedBackwardTransitions = transitionMatrix.transpose(true);
191 }
192 auto const& backwardTransRef = backwardTransitions ? *backwardTransitions : computedBackwardTransitions;
193
194 auto expVisits = stateToScc ? computeUpperBoundOnExpectedVisitingTimes(transitionMatrix, backwardTransRef, oneStepTargetProbabilities, stateToScc)
195 : computeUpperBoundOnExpectedVisitingTimes(transitionMatrix, backwardTransRef, oneStepTargetProbabilities);
196
197 ValueType upperBound = storm::utility::zero<ValueType>();
198 for (uint64_t state = 0; state < expVisits.size(); ++state) {
199 ValueType maxReward = storm::utility::zero<ValueType>();
200 // By starting the maxReward with zero, negative rewards are essentially ignored which
201 // is necessary to provide a valid upper bound
202 for (auto row = transitionMatrix.getRowGroupIndices()[state], endRow = transitionMatrix.getRowGroupIndices()[state + 1]; row < endRow; ++row) {
203 maxReward = std::max(maxReward, rewards[row]);
204 }
205 upperBound += expVisits[state] * maxReward;
206 }
207
208 STORM_LOG_TRACE("Baier algorithm for reward bound computation (variant 2) computed bound " << upperBound << ".");
209 return upperBound;
210}
211
214} // namespace storm::modelchecker::helper
ValueType computeUpperBound()
Computes an upper bound on the expected rewards.
static std::vector< ValueType > computeUpperBoundOnExpectedVisitingTimes(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, std::vector< ValueType > const &oneStepTargetProbabilities)
Computes for each state an upper bound for the maximal expected times each state is visited.
BaierUpperRewardBoundsComputer(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, std::vector< ValueType > const &rewards, std::vector< ValueType > const &oneStepTargetProbabilities, std::function< uint64_t(uint64_t)> const &stateToScc={})
Creates an object that can compute upper bounds on the maximal expected rewards for the provided MDP.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:18
const_reverse_iterator rbegin() const
Returns a reverse iterator to the indices of the set bits in the bit vector.
bool full() const
Retrieves whether all bits are set in this bit vector.
const_reverse_iterator rend() const
Returns a reverse iterator pointing at the element past the front of the bit vector.
bool isSubsetOf(BitVector const &other) const
Checks whether all bits that are set in the current bit vector are also set in the given bit vector.
void set(uint_fast64_t index, bool value=true)
Sets the given truth value at the given index.
size_t size() const
Retrieves the number of bits this bit vector can store.
uint_fast64_t getNextUnsetIndex(uint_fast64_t startingIndex) const
Retrieves the index of the bit that is the next bit set to false in the bit vector.
bool get(uint_fast64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
uint64_t getStartOfOneSequenceBefore(uint64_t endIndex) const
Retrieves the smallest index i such that all bits in the range [i,endIndex) are 1.
A class that holds a possibly non-square matrix in the compressed row storage format.
const_rows getRow(index_type row) const
Returns an object representing the given row.
index_type getRowGroupCount() const
Returns the number of row groups in the matrix.
index_type getColumnCount() const
Returns the number of columns of the matrix.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
storm::storage::SparseMatrix< value_type > transpose(bool joinGroups=false, bool keepZeros=false) const
Transposes the matrix.
index_type getRowCount() const
Returns the number of rows of the matrix.
This class represents the decomposition of a graph-like structure into its strongly connected compone...
std::vector< uint64_t > computeStateToSccIndexMap(uint64_t numberOfStates) const
Computes a vector that for each state has the index of the scc of that state in it.
Stores and manages an extremal (maximal or minimal) value.
Definition Extremum.h:15
#define STORM_LOG_TRACE(message)
Definition logging.h:17
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
storm::storage::BitVector filterGreaterZero(std::vector< T > const &values)
Retrieves a bit vector containing all the indices for which the value at this position is greater tha...
Definition vector.h:552
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