Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
StronglyConnectedComponentDecomposition.cpp
Go to the documentation of this file.
2
8
10
11namespace storm::storage {
12
17
22
27
32
37
42
44 preorderNumbers.assign(numStates, std::numeric_limits<uint64_t>::max());
45 recursionStateStack.clear();
46 s.clear();
47 p.clear();
48}
49
50bool SccDecompositionMemoryCache::hasPreorderNumber(uint64_t stateIndex) const {
51 return preorderNumbers[stateIndex] != std::numeric_limits<uint64_t>::max();
52}
53
54void SccDecompositionResult::initialize(uint64_t numStates, bool computeSccDepths) {
55 sccCount = 0;
56 stateToSccMapping.assign(numStates, std::numeric_limits<uint64_t>::max());
58 nonTrivialStates.resize(numStates, false);
59 if (computeSccDepths) {
60 if (sccDepths) {
61 sccDepths->clear();
62 } else {
63 sccDepths.emplace();
64 }
65 } else {
66 sccDepths = std::nullopt;
67 }
68}
69
70bool SccDecompositionResult::stateHasScc(uint64_t stateIndex) const {
71 return stateToSccMapping[stateIndex] != std::numeric_limits<uint64_t>::max();
72}
73
74template<typename ValueType>
78
79template<typename ValueType>
84
85template<typename ValueType>
90
91template<typename ValueType>
97
98template<typename ValueType>
103
104template<typename ValueType>
110
134template<typename ValueType>
136 storm::OptionalRef<storm::storage::BitVector const> choices, bool /*forceTopologicalSort*/, uint64_t startState,
137 uint64_t& currentIndex, SccDecompositionResult& result, SccDecompositionMemoryCache& cache) {
138 // The forceTopologicalSort flag can be ignored as this method always generates a topological sort.
139
140 // Prepare the stack used for turning the recursive procedure into an iterative one.
141 STORM_LOG_ASSERT(cache.recursionStateStack.empty(), "Expected an empty recursion stack.");
142 cache.recursionStateStack.push_back(startState);
143
144 while (!cache.recursionStateStack.empty()) {
145 // Peek at the topmost state in the stack, but leave it on there for now.
146 uint64_t currentState = cache.recursionStateStack.back();
147 assert(!subsystem || subsystem->get(currentState));
148
149 // If the state has not yet been seen, we need to assign it a preorder number and iterate over its successors.
150 if (!cache.hasPreorderNumber(currentState)) {
151 cache.preorderNumbers[currentState] = currentIndex++;
152
153 cache.s.push_back(currentState);
154 cache.p.push_back(currentState);
155
156 for (uint64_t row = transitionMatrix.getRowGroupIndices()[currentState], rowEnd = transitionMatrix.getRowGroupIndices()[currentState + 1];
157 row != rowEnd; ++row) {
158 if (choices && !choices->get(row)) {
159 continue;
160 }
161
162 for (auto const& successor : transitionMatrix.getRow(row)) {
163 if ((!subsystem || subsystem->get(successor.getColumn())) && successor.getValue() != storm::utility::zero<ValueType>()) {
164 if (currentState == successor.getColumn()) {
165 result.nonTrivialStates.set(currentState, true);
166 }
167
168 if (!cache.hasPreorderNumber(successor.getColumn())) {
169 // In this case, we must recursively visit the successor. We therefore push the state
170 // onto the recursion stack.
171 cache.recursionStateStack.push_back(successor.getColumn());
172 } else {
173 if (!result.stateHasScc(successor.getColumn())) {
174 while (cache.preorderNumbers[cache.p.back()] > cache.preorderNumbers[successor.getColumn()]) {
175 cache.p.pop_back();
176 }
177 }
178 }
179 }
180 }
181 }
182 } else {
183 // In this case, we have searched all successors of the current state and can exit the "recursion"
184 // on the current state.
185 if (currentState == cache.p.back()) {
186 cache.p.pop_back();
187 if (result.sccDepths) {
188 uint64_t sccDepth = 0;
189 // Find the largest depth over successor SCCs.
190 auto stateIt = cache.s.end();
191 do {
192 --stateIt;
193 for (uint64_t row = transitionMatrix.getRowGroupIndices()[*stateIt], rowEnd = transitionMatrix.getRowGroupIndices()[*stateIt + 1];
194 row != rowEnd; ++row) {
195 if (choices && !choices->get(row)) {
196 continue;
197 }
198 for (auto const& successor : transitionMatrix.getRow(row)) {
199 if ((!subsystem || subsystem->get(successor.getColumn())) && successor.getValue() != storm::utility::zero<ValueType>() &&
200 result.stateHasScc(successor.getColumn())) {
201 sccDepth = std::max(sccDepth, (*result.sccDepths)[result.stateToSccMapping[successor.getColumn()]] + 1);
202 }
203 }
204 }
205 } while (*stateIt != currentState);
206 result.sccDepths->push_back(sccDepth);
207 }
208 bool nonSingletonScc = cache.s.back() != currentState;
209 uint64_t poppedState = 0;
210 do {
211 poppedState = cache.s.back();
212 cache.s.pop_back();
213 result.stateToSccMapping[poppedState] = result.sccCount;
214 if (nonSingletonScc) {
215 result.nonTrivialStates.set(poppedState, true);
216 }
217 } while (poppedState != currentState);
218 ++result.sccCount;
219 }
220
221 cache.recursionStateStack.pop_back();
222 }
223 }
224}
225
226template<typename ValueType>
227void StronglyConnectedComponentDecomposition<ValueType>::performSccDecomposition(storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
228 StronglyConnectedComponentDecompositionOptions const& options) {
229 SccDecompositionResult result;
230 storm::storage::performSccDecomposition(transitionMatrix, options, result);
231
232 STORM_LOG_ASSERT(!options.areOnlyBottomSccsConsidered || result.sccDepths.has_value(), "Scc depths not computed but needed.");
233 if (result.sccDepths) {
234 this->sccDepths = std::move(*result.sccDepths);
235 }
236
237 // After we obtained the state-to-SCC mapping, we build the actual blocks.
238 this->blocks.resize(result.sccCount);
239 for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) {
240 // Check if this state (and is SCC) should be considered in this decomposition.
241 if ((!options.optSubsystem || options.optSubsystem->get(state))) {
242 if (!options.areNaiveSccsDropped || result.nonTrivialStates.get(state)) {
243 uint64_t sccIndex = result.stateToSccMapping[state];
244 if (!options.areOnlyBottomSccsConsidered || sccDepths.value()[sccIndex] == 0) {
245 this->blocks[sccIndex].insert(state);
246 if (!result.nonTrivialStates.get(state)) {
247 this->blocks[sccIndex].setIsTrivial(true);
248 STORM_LOG_ASSERT(this->blocks[sccIndex].size() == 1, "Unexpected number of states in a trivial SCC.");
249 }
250 }
251 }
252 }
253 }
254
255 // If requested, we need to delete all naive SCCs.
256 if (options.areOnlyBottomSccsConsidered || options.areNaiveSccsDropped) {
257 storm::storage::BitVector blocksToDrop(result.sccCount);
258 for (uint64_t sccIndex = 0; sccIndex < result.sccCount; ++sccIndex) {
259 if (this->blocks[sccIndex].empty()) {
260 blocksToDrop.set(sccIndex, true);
261 }
262 }
263 // Create the new set of blocks by moving all the blocks we need to keep into it.
264 storm::utility::vector::filterVectorInPlace(this->blocks, ~blocksToDrop);
265 if (this->sccDepths) {
266 storm::utility::vector::filterVectorInPlace(this->sccDepths.value(), ~blocksToDrop);
267 }
268 }
269}
270
271template<typename ValueType>
273 SccDecompositionResult& result) {
275 performSccDecomposition(transitionMatrix, options, result, cache);
276}
277
278template<typename ValueType>
281 STORM_LOG_ASSERT(!options.optChoices || options.optSubsystem, "Expecting subsystem if choices are given.");
282
283 uint64_t numberOfStates = transitionMatrix.getRowGroupCount();
284 result.initialize(numberOfStates, options.isComputeSccDepthsSet || options.areOnlyBottomSccsConsidered);
285 cache.initialize(numberOfStates);
286
287 // Start the search for SCCs from every state in the block.
288 uint64_t currentIndex = 0;
289 auto performSccDecompFromState = [&](uint64_t startState) {
290 if (!cache.hasPreorderNumber(startState)) {
291 performSccDecompositionGCM(transitionMatrix, options.optSubsystem, options.optChoices, options.isTopologicalSortForced, startState, currentIndex,
292 result, cache);
293 }
294 };
295
296 if (options.optSubsystem) {
297 for (auto state : *options.optSubsystem) {
298 performSccDecompFromState(state);
299 }
300 } else {
301 for (uint64_t state = 0; state < numberOfStates; ++state) {
302 performSccDecompFromState(state);
303 }
304 }
305}
306
307template<typename ValueType>
309 return sccDepths.has_value();
310}
311
312template<typename ValueType>
314 STORM_LOG_THROW(sccDepths.has_value(), storm::exceptions::InvalidOperationException,
315 "Tried to get the SCC depth but SCC depths were not computed upon construction.");
316 STORM_LOG_ASSERT(sccIndex < sccDepths->size(), "SCC index " << sccIndex << " is out of range (" << sccDepths->size() << ")");
317 return sccDepths.value()[sccIndex];
318}
319
320template<typename ValueType>
322 STORM_LOG_THROW(sccDepths.has_value(), storm::exceptions::InvalidOperationException,
323 "Tried to get the maximum SCC depth but SCC depths were not computed upon construction.");
324 STORM_LOG_THROW(!sccDepths->empty(), storm::exceptions::InvalidOperationException,
325 "Tried to get the maximum SCC depth but this SCC decomposition seems to be empty.");
326 return *std::max_element(sccDepths->begin(), sccDepths->end());
327}
328
329template<typename ValueType>
330std::vector<uint64_t> StronglyConnectedComponentDecomposition<ValueType>::computeStateToSccIndexMap(uint64_t numberOfStates) const {
331 std::vector<uint64_t> result(numberOfStates, std::numeric_limits<uint64_t>::max());
332 uint64_t sccIndex = 0;
333 for (auto const& scc : *this) {
334 for (auto const& state : scc) {
335 result[state] = sccIndex;
336 }
337 ++sccIndex;
338 }
339 return result;
340}
341
346
347} // namespace storm::storage
Helper class that optionally holds a reference to an object of type T.
Definition OptionalRef.h:48
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:18
void clear()
Removes all set bits from the bit vector.
void resize(uint_fast64_t newLength, bool init=false)
Resizes the bit vector to hold the given new number of bits.
void set(uint_fast64_t index, bool value=true)
Sets the given truth value at the given index.
This class represents the decomposition of a model into blocks which are of the template type.
std::vector< block_type > blocks
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.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
This class represents the decomposition of a graph-like structure into its strongly connected compone...
StronglyConnectedComponentDecomposition & operator=(StronglyConnectedComponentDecomposition const &other)
Assigns the contents of the given SCC decomposition to the current one by copying its contents.
uint_fast64_t getSccDepth(uint_fast64_t const &sccIndex) const
Gets the depth of the SCC with the given index.
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.
bool hasSccDepth() const
Retrieves whether SCCDepths have been computed during construction of this.
uint_fast64_t getMaxSccDepth() const
Gets the maximum depth of an SCC.
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
void performSccDecompositionGCM(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::OptionalRef< storm::storage::BitVector const > subsystem, storm::OptionalRef< storm::storage::BitVector const > choices, bool, uint64_t startState, uint64_t &currentIndex, SccDecompositionResult &result, SccDecompositionMemoryCache &cache)
Uses the algorithm by Gabow/Cheriyan/Mehlhorn ("Path-based strongly connected component algorithm") t...
void performSccDecomposition(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, StronglyConnectedComponentDecompositionOptions const &options, SccDecompositionResult &result)
Computes an SCC decomposition for the given matrix and options.
void filterVectorInPlace(std::vector< Type > &v, storm::storage::BitVector const &filter)
Definition vector.h:1224
Holds temporary computation data used during the SCC decomposition algorithm.
Holds the result data of the implemented SCC decomposition algorithm.
std::vector< uint64_t > stateToSccMapping
Number of found SCCs.
void initialize(uint64_t numStates, bool computeSccDepths)
storm::storage::BitVector nonTrivialStates
Mapping from states to the SCC it belongs to.
uint64_t sccCount
True if an SCC is associated to the given state.
std::optional< std::vector< uint64_t > > sccDepths
Keep track of trivial states (singleton SCCs without selfloop).
StronglyConnectedComponentDecompositionOptions & computeSccDepths(bool value=true)
Sets if scc depths can be retrieved.
StronglyConnectedComponentDecompositionOptions & dropNaiveSccs(bool value=true)
Sets if trivial SCCs (i.e. SCCs consisting of just one state without a self-loop) are to be kept in t...
StronglyConnectedComponentDecompositionOptions & forceTopologicalSort(bool value=true)
Enforces that the returned SCCs are sorted in a topological order.
StronglyConnectedComponentDecompositionOptions & subsystem(storm::storage::BitVector const &subsystem)
Sets a bit vector indicating which subsystem to consider for the decomposition into SCCs.
StronglyConnectedComponentDecompositionOptions & choices(storm::storage::BitVector const &choices)
Sets a bit vector indicating which choices of the states are contained in the subsystem.
StronglyConnectedComponentDecompositionOptions & onlyBottomSccs(bool value=true)
Sets if only bottom SCCs, i.e. SCCs in which all states have no way of leaving the SCC),...