Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparseNondeterministicInfiniteHorizonHelper.cpp
Go to the documentation of this file.
2
15#include "storm/utility/graph.h"
18
19namespace storm {
20namespace modelchecker {
21namespace helper {
22
23template<typename ValueType>
29
30template<typename ValueType>
32 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& markovianStates, std::vector<ValueType> const& exitRates)
33 : SparseInfiniteHorizonHelper<ValueType, true>(transitionMatrix, markovianStates, exitRates) {
34 // Intentionally left empty.
35}
36
37template<typename ValueType>
39 STORM_LOG_ASSERT(this->isProduceSchedulerSet(), "Trying to get the produced optimal choices although no scheduler was requested.");
40 STORM_LOG_ASSERT(this->_producedOptimalChoices.is_initialized(),
41 "Trying to get the produced optimal choices but none were available. Was there a computation call before?");
42 return this->_producedOptimalChoices.get();
43}
44
45template<typename ValueType>
47 STORM_LOG_ASSERT(this->isProduceSchedulerSet(), "Trying to get the produced optimal choices although no scheduler was requested.");
48 STORM_LOG_ASSERT(this->_producedOptimalChoices.is_initialized(),
49 "Trying to get the produced optimal choices but none were available. Was there a computation call before?");
50 return this->_producedOptimalChoices.get();
51}
52
53template<typename ValueType>
55 auto const& optimalChoices = getProducedOptimalChoices();
56 storm::storage::Scheduler<ValueType> scheduler(optimalChoices.size());
57 for (uint64_t state = 0; state < optimalChoices.size(); ++state) {
58 scheduler.setChoice(optimalChoices[state], state);
59 }
60 return scheduler;
61}
62
63template<typename ValueType>
65 if (this->_longRunComponentDecomposition == nullptr) {
66 // The decomposition has not been provided or computed, yet.
69 std::make_unique<storm::storage::MaximalEndComponentDecomposition<ValueType>>(this->_transitionMatrix, *this->_backwardTransitions);
71 }
72}
73
74template<typename ValueType>
76 ValueGetter const& actionRewardsGetter,
77 storm::storage::MaximalEndComponent const& component) {
78 // For models with potential nondeterminisim, we compute the LRA for a maximal end component (MEC)
79
80 // Allocate memory for the nondeterministic choices.
81 if (this->isProduceSchedulerSet()) {
82 if (!this->_producedOptimalChoices.is_initialized()) {
83 this->_producedOptimalChoices.emplace();
84 }
85 this->_producedOptimalChoices->resize(this->_transitionMatrix.getRowGroupCount());
86 }
87
88 // Catch cases where the component is a single state
89 if (auto const trivialResult = this->computeLraForTrivialMec(env, stateRewardsGetter, actionRewardsGetter, component); trivialResult.has_value()) {
90 return trivialResult.value();
91 }
92 // Catch cases where the component contains another component as a subcomponent with zero reward
93 if (auto const zeroResult = this->computeLraForZeroMec(env, stateRewardsGetter, actionRewardsGetter, component); zeroResult.has_value()) {
94 return zeroResult.value();
95 }
96
97 // Solve nontrivial MEC with the method specified in the settings
98 storm::solver::LraMethod method = env.solver().lra().getNondetLraMethod();
100 method != storm::solver::LraMethod::LinearProgramming) {
102 "Selecting 'LP' as the solution technique for long-run properties to guarantee exact results. If you want to override this, please explicitly "
103 "specify a different LRA method.");
104 method = storm::solver::LraMethod::LinearProgramming;
105 } else if (env.solver().isForceSoundness() && env.solver().lra().isNondetLraMethodSetFromDefault() && method != storm::solver::LraMethod::ValueIteration) {
107 "Selecting 'VI' as the solution technique for long-run properties to guarantee sound results. If you want to override this, please explicitly "
108 "specify a different LRA method.");
109 method = storm::solver::LraMethod::ValueIteration;
110 }
111 STORM_LOG_ERROR_COND(!this->isProduceSchedulerSet() || method == storm::solver::LraMethod::ValueIteration,
112 "Scheduler generation not supported for the chosen LRA method. Try value-iteration.");
113 if (method == storm::solver::LraMethod::LinearProgramming) {
114 return computeLraForMecLp(env, stateRewardsGetter, actionRewardsGetter, component);
115 } else if (method == storm::solver::LraMethod::ValueIteration) {
116 return computeLraForMecVi(env, stateRewardsGetter, actionRewardsGetter, component);
117 } else {
118 STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unsupported technique.");
119 }
120}
121
122template<typename ValueType>
124 ValueGetter const& stateRewardsGetter,
125 ValueGetter const& actionRewardsGetter,
126 storm::storage::MaximalEndComponent const& component) {
127 if (component.size() != 1) {
128 return std::nullopt; // MEC is not trivial
129 }
130 // If the component only consists of a single state, we compute the LRA value directly
131 auto const& element = *component.begin();
132 uint64_t const state = internal::getComponentElementState(element);
133 auto choiceIt = internal::getComponentElementChoicesBegin(element);
134 if (!this->isContinuousTime()) {
135 // This is an MDP.
136 // Find the choice with the highest/lowest reward
137 ValueType bestValue = actionRewardsGetter(*choiceIt);
138 uint64_t bestChoice = *choiceIt;
139 for (++choiceIt; choiceIt != internal::getComponentElementChoicesEnd(element); ++choiceIt) {
140 ValueType currentValue = actionRewardsGetter(*choiceIt);
141 if ((this->minimize() && currentValue < bestValue) || (this->maximize() && currentValue > bestValue)) {
142 bestValue = std::move(currentValue);
143 bestChoice = *choiceIt;
144 }
145 }
146 if (this->isProduceSchedulerSet()) {
147 this->_producedOptimalChoices.get()[state] = bestChoice - this->_transitionMatrix.getRowGroupIndices()[state];
148 }
149 bestValue += stateRewardsGetter(state);
150 return bestValue;
151 } else {
152 // In a Markov Automaton, singleton components have to consist of a Markovian state because of the non-Zenoness assumption. Then, there is just one
153 // possible choice.
154 STORM_LOG_ASSERT(this->_markovianStates != nullptr,
155 "Nondeterministic continuous time model without Markovian states... Is this a not a Markov Automaton?");
156 STORM_LOG_THROW(this->_markovianStates->get(state), storm::exceptions::InvalidOperationException,
157 "Markov Automaton has Zeno behavior. Computation of Long Run Average values not supported.");
158 STORM_LOG_ASSERT(internal::getComponentElementChoiceCount(element) == 1, "Markovian state has Nondeterministic behavior.");
159 if (this->isProduceSchedulerSet()) {
160 this->_producedOptimalChoices.get()[state] = 0;
161 }
162 ValueType result = stateRewardsGetter(state) +
163 (this->isContinuousTime() ? (*this->_exitRates)[state] * actionRewardsGetter(*choiceIt) : actionRewardsGetter(*choiceIt));
164 return result;
165 }
166}
167
168template<typename ValueType>
170 ValueGetter const& stateRewardsGetter,
171 ValueGetter const& actionRewardsGetter,
172 storm::storage::MaximalEndComponent const& component) {
173 // We first get those component states and choices that do not immediately collect non-zero reward
174 bool hasNonZeroReward = false;
175 std::map<uint64_t, std::set<uint64_t>> zeroLraStatesChoices;
176 for (auto const& [state, choices] : component) {
177 auto const stateReward = stateRewardsGetter(state);
178 if (!storm::utility::isZero(stateReward)) {
179 // If we minimize and see a negative reward, we cannot infer zero long-run average reward for the entire MEC, even if a 0 reward sub-EC exists.
180 // Maximize and seeing a positive reward is similar.
181 if ((stateReward < storm::utility::zero<ValueType>()) == this->minimize()) {
182 // Component has non-zero reward, but in the wrong direction
183 return std::nullopt;
184 }
185 hasNonZeroReward = true;
186 continue;
187 }
188 std::set<uint64_t> zeroLraChoices;
189 for (auto const choice : choices) {
190 auto const actionReward = actionRewardsGetter(choice);
191 if (!storm::utility::isZero(actionReward)) {
192 // Catch non-zero reward in the wrong direction, similar to state rewards above.
193 if ((actionReward < storm::utility::zero<ValueType>()) == this->minimize()) {
194 return std::nullopt;
195 }
196 hasNonZeroReward = true;
197 } else {
198 zeroLraChoices.insert(choice);
199 }
200 }
201 if (!zeroLraChoices.empty()) {
202 zeroLraStatesChoices.emplace(state, std::move(zeroLraChoices));
203 }
204 }
205 // If there are non-zero rewards in this MEC, we have to check whether there is a sub-EC with zero rewards.
206 if (hasNonZeroReward) {
208 std::set<uint64_t> candidates;
209 for (auto const& [state, _] : zeroLraStatesChoices) {
210 candidates.insert(state);
211 }
212 while (!candidates.empty()) {
213 uint64_t const state = *candidates.begin();
214 candidates.erase(candidates.begin());
215 auto& choices = zeroLraStatesChoices.at(state);
216 // We erase some choices while iterating over them, but this is fine for std::set as long as we do it like that
217 for (auto choiceIt = choices.begin(); choiceIt != choices.end();) {
218 auto const row = this->_transitionMatrix.getRow(*choiceIt);
219 if (std::all_of(row.begin(), row.end(),
220 [&zeroLraStatesChoices](auto const& transition) { return zeroLraStatesChoices.contains(transition.getColumn()); })) {
221 ++choiceIt; // keep the choice
222 } else {
223 // This choice has a transition to a non-zero LRA state, so we erase it
224 choiceIt = choices.erase(choiceIt); // Note: erase returns the iterator to the next element
225 }
226 }
227 if (choices.empty()) {
228 // No zero LRA choice for this state, so we erase it and add its predecessors to the stack
229 zeroLraStatesChoices.erase(state);
230 for (auto const& backwardTransition : this->_backwardTransitions->getRow(state)) {
231 auto const predecessor = backwardTransition.getColumn();
232 if (zeroLraStatesChoices.contains(predecessor)) {
233 candidates.insert(predecessor);
234 }
235 }
236 }
237 }
238 }
239 if (zeroLraStatesChoices.empty()) {
240 // No zero LRA sub-EC, so we cannot infer zero LRA for the entire MEC
241 return std::nullopt;
242 }
243 // We have found a zero LRA sub-EC. We can infer zero LRA
244 if (this->isProduceSchedulerSet()) {
245 // Ensure that all other states get some choice that leads to the zero LRA sub-EC
246 if (zeroLraStatesChoices.size() < component.size()) {
248 std::set<uint64_t> candidates; // Always contains states with one transition to zeroLraStatesChoices
249 // helper function to add predecessors of a state to the candidate set
250 auto insertPredecessors = [&](uint64_t const state) {
251 for (auto const& backwardTransition : this->_backwardTransitions->getRow(state)) {
252 auto const predecessor = backwardTransition.getColumn();
253 if (!zeroLraStatesChoices.contains(predecessor) && component.containsState(predecessor)) {
254 candidates.insert(predecessor);
255 }
256 }
257 };
258 for (auto const& [state, _] : zeroLraStatesChoices) {
259 insertPredecessors(state);
260 }
261 while (!candidates.empty()) {
262 uint64_t const state = *candidates.begin();
263 candidates.erase(candidates.begin());
264 for (auto const choice : this->_transitionMatrix.getRowGroupIndices(state)) {
265 auto const row = this->_transitionMatrix.getRow(choice);
266 if (std::any_of(row.begin(), row.end(),
267 [&zeroLraStatesChoices](auto const& transition) { return zeroLraStatesChoices.contains(transition.getColumn()); })) {
268 zeroLraStatesChoices.emplace(state, std::set<uint64_t>{choice});
269 }
270 }
271 STORM_LOG_ASSERT(zeroLraStatesChoices.contains(state), "No suitable choice found for state " << state);
272 insertPredecessors(state);
273 }
274 }
275 // Now all states should have a choice
276 STORM_LOG_ASSERT(component.size() == zeroLraStatesChoices.size(),
277 "Did not find a choice for all states: " << zeroLraStatesChoices.size() << " / " << component.size() << ".");
278 // Finally, set some choice as optimal
279 for (auto const& [state, choices] : zeroLraStatesChoices) {
280 this->_producedOptimalChoices.get()[state] = *choices.begin() - this->_transitionMatrix.getRowGroupIndices()[state];
281 }
282 }
283
285}
286
287template<typename ValueType>
289 ValueGetter const& actionRewardsGetter,
291 // Collect some parameters of the computation
293 std::vector<uint64_t>* optimalChoices = nullptr;
294 if (this->isProduceSchedulerSet()) {
295 optimalChoices = &this->_producedOptimalChoices.get();
296 }
297
298 // Now create a helper and perform the algorithm
299 if (this->isContinuousTime()) {
300 // We assume a Markov Automaton (with deterministic timed states and nondeterministic instant states)
303 viHelper(mec, this->_transitionMatrix, aperiodicFactor, this->_markovianStates, this->_exitRates);
304 return viHelper.performValueIteration(env, stateRewardsGetter, actionRewardsGetter, this->_exitRates, &this->getOptimizationDirection(),
305 optimalChoices);
306 } else {
307 // We assume an MDP (with nondeterministic timed states and no instant states)
310 viHelper(mec, this->_transitionMatrix, aperiodicFactor);
311 return viHelper.performValueIteration(env, stateRewardsGetter, actionRewardsGetter, nullptr, &this->getOptimizationDirection(), optimalChoices);
312 }
313}
314
315template<typename ValueType>
317 ValueGetter const& actionRewardsGetter,
319 // Create an LP solver
321
322 // Now build the LP formulation as described in:
323 // Guck et al.: Modelling and Analysis of Markov Reward Automata (ATVA'14), https://doi.org/10.1007/978-3-319-11936-6_13
324 solver->setOptimizationDirection(invert(this->getOptimizationDirection()));
325
326 // Create variables
327 // TODO: Investigate whether we can easily make the variables bounded
328 std::map<uint_fast64_t, storm::expressions::Variable> stateToVariableMap;
329 for (auto const& stateChoicesPair : mec) {
330 std::string variableName = "x" + std::to_string(stateChoicesPair.first);
331 stateToVariableMap[stateChoicesPair.first] = solver->addUnboundedContinuousVariable(variableName);
332 }
333 storm::expressions::Variable k = solver->addUnboundedContinuousVariable("k", storm::utility::one<ValueType>());
334 solver->update();
335
336 // Add constraints.
337 for (auto const& stateChoicesPair : mec) {
338 uint_fast64_t state = stateChoicesPair.first;
339 bool stateIsMarkovian = this->_markovianStates && this->_markovianStates->get(state);
340
341 // Now create a suitable constraint for each choice
342 // x_s {≤, ≥} -k/rate(s) + sum_s' P(s,act,s') * x_s' + (value(s)/rate(s) + value(s,act))
343 for (auto choice : stateChoicesPair.second) {
344 std::vector<storm::expressions::Expression> summands;
345 auto matrixRow = this->_transitionMatrix.getRow(choice);
346 summands.reserve(matrixRow.getNumberOfEntries() + 2);
347 // add -k/rate(s) (only if s is either a Markovian state or we have an MDP)
348 if (stateIsMarkovian) {
349 summands.push_back(-(k / solver->getManager().rational((*this->_exitRates)[state])));
350 } else if (!this->isContinuousTime()) {
351 summands.push_back(-k);
352 }
353 // add sum_s' P(s,act,s') * x_s'
354 for (auto const& element : matrixRow) {
355 summands.push_back(stateToVariableMap.at(element.getColumn()) * solver->getConstant(element.getValue()));
356 }
357 // add value for state and choice
358 ValueType value;
359 if (stateIsMarkovian) {
360 // divide state reward with exit rate
361 value = stateRewardsGetter(state) / (*this->_exitRates)[state] + actionRewardsGetter(choice);
362 } else if (!this->isContinuousTime()) {
363 // in discrete time models no scaling is needed
364 value = stateRewardsGetter(state) + actionRewardsGetter(choice);
365 } else {
366 // state is a probabilistic state of a Markov automaton. The state reward is not collected
367 value = actionRewardsGetter(choice);
368 }
369 summands.push_back(solver->getConstant(value));
371 if (this->minimize()) {
372 constraint = stateToVariableMap.at(state) <= storm::expressions::sum(summands);
373 } else {
374 constraint = stateToVariableMap.at(state) >= storm::expressions::sum(summands);
375 }
376 solver->addConstraint("s" + std::to_string(state) + "," + std::to_string(choice), constraint);
377 }
378 }
379
380 solver->optimize();
381 STORM_LOG_THROW(!this->isProduceSchedulerSet(), storm::exceptions::NotImplementedException,
382 "Scheduler extraction is not yet implemented for LP based LRA method.");
383 return solver->getContinuousValue(k);
384}
385
391template<typename ValueType>
392void addSspMatrixChoice(uint64_t const& inputMatrixChoice, storm::storage::SparseMatrix<ValueType> const& inputTransitionMatrix,
393 std::vector<uint64_t> const& inputToSspStateMap, uint64_t const& numberOfNonComponentStates, uint64_t const& currentSspChoice,
395 // As there could be multiple transitions to the same MEC, we accumulate them in this map before adding them to the matrix builder.
396 std::map<uint64_t, ValueType> auxiliaryStateToProbabilityMap;
397
398 for (auto const& transition : inputTransitionMatrix.getRow(inputMatrixChoice)) {
399 if (!storm::utility::isZero(transition.getValue())) {
400 auto const& sspTransitionTarget = inputToSspStateMap[transition.getColumn()];
401 // Since the auxiliary Component states are appended at the end of the matrix, we can use this check to
402 // decide whether the transition leads to a component state or not
403 if (sspTransitionTarget < numberOfNonComponentStates) {
404 // If the target state is not contained in a component, we can copy over the entry.
405 sspMatrixBuilder.addNextValue(currentSspChoice, sspTransitionTarget, transition.getValue());
406 } else {
407 // If the target state is contained in component i, we need to add the probability to the corresponding field in the vector
408 // so that we are able to write the cumulative probability to the component into the matrix later.
409 auto insertionRes = auxiliaryStateToProbabilityMap.emplace(sspTransitionTarget, transition.getValue());
410 if (!insertionRes.second) {
411 // sspTransitionTarget already existed in the map, i.e., there already was a transition to that component.
412 // Hence, we add up the probabilities.
413 insertionRes.first->second += transition.getValue();
414 }
415 }
416 }
417 }
418
419 // Now insert all (cumulative) probability values that target a component.
420 for (auto const& componentToProbEntry : auxiliaryStateToProbabilityMap) {
421 sspMatrixBuilder.addNextValue(currentSspChoice, componentToProbEntry.first, componentToProbEntry.second);
422 }
423}
424
425template<typename ValueType>
426std::pair<storm::storage::SparseMatrix<ValueType>, std::vector<ValueType>> SparseNondeterministicInfiniteHorizonHelper<ValueType>::buildSspMatrixVector(
427 std::vector<ValueType> const& mecLraValues, std::vector<uint64_t> const& inputToSspStateMap, storm::storage::BitVector const& statesNotInComponent,
428 uint64_t numberOfNonComponentStates, std::vector<std::pair<uint64_t, uint64_t>>* sspComponentExitChoicesToOriginalMap) {
429 auto const& choiceIndices = this->_transitionMatrix.getRowGroupIndices();
430
431 std::vector<ValueType> rhs;
432 uint64_t numberOfSspStates = numberOfNonComponentStates + this->_longRunComponentDecomposition->size();
433 storm::storage::SparseMatrixBuilder<ValueType> sspMatrixBuilder(0, numberOfSspStates, 0, true, true, numberOfSspStates);
434 // If the source state of a transition is not contained in any component, we copy its choices (and perform the necessary modifications).
435 uint64_t currentSspChoice = 0;
436 for (auto nonComponentState : statesNotInComponent) {
437 sspMatrixBuilder.newRowGroup(currentSspChoice);
438 for (uint64_t choice = choiceIndices[nonComponentState]; choice < choiceIndices[nonComponentState + 1]; ++choice, ++currentSspChoice) {
439 rhs.push_back(storm::utility::zero<ValueType>());
440 addSspMatrixChoice(choice, this->_transitionMatrix, inputToSspStateMap, numberOfNonComponentStates, currentSspChoice, sspMatrixBuilder);
441 }
442 }
443 // Now we construct the choices for the auxiliary states which reflect former Component states.
444 for (uint64_t componentIndex = 0; componentIndex < this->_longRunComponentDecomposition->size(); ++componentIndex) {
445 auto const& component = (*this->_longRunComponentDecomposition)[componentIndex];
446 sspMatrixBuilder.newRowGroup(currentSspChoice);
447 // For nondeterministic models it might still be that we leave the component again. This needs to be reflected in the SSP
448 // by adding the "exiting" choices of the MEC to the axiliary states
449 for (auto const& element : component) {
450 uint64_t componentState = internal::getComponentElementState(element);
451 for (uint64_t choice = choiceIndices[componentState]; choice < choiceIndices[componentState + 1]; ++choice) {
452 // If the choice is not contained in the component itself, we have to add a similar distribution to the auxiliary state.
453 if (!internal::componentElementChoicesContains(element, choice)) {
454 rhs.push_back(storm::utility::zero<ValueType>());
455 addSspMatrixChoice(choice, this->_transitionMatrix, inputToSspStateMap, numberOfNonComponentStates, currentSspChoice, sspMatrixBuilder);
456 if (sspComponentExitChoicesToOriginalMap) {
457 // Later we need to be able to map this choice back to the original input model
458 sspComponentExitChoicesToOriginalMap->emplace_back(componentState, choice - choiceIndices[componentState]);
459 }
460 ++currentSspChoice;
461 }
462 }
463 }
464 // For each auxiliary state, there is the option to achieve the reward value of the LRA associated with the component.
465 rhs.push_back(mecLraValues[componentIndex]);
466 if (sspComponentExitChoicesToOriginalMap) {
467 // Insert some invalid values so we can later detect that this choice is not an exit choice
468 sspComponentExitChoicesToOriginalMap->emplace_back(std::numeric_limits<uint_fast64_t>::max(), std::numeric_limits<uint_fast64_t>::max());
469 }
470 ++currentSspChoice;
471 }
472 return std::make_pair(sspMatrixBuilder.build(currentSspChoice, numberOfSspStates, numberOfSspStates), std::move(rhs));
473}
474
475template<typename ValueType>
477 std::vector<uint64_t> const& sspChoices, storm::storage::SparseMatrix<ValueType> const& sspMatrix, std::vector<uint64_t> const& inputToSspStateMap,
478 storm::storage::BitVector const& statesNotInComponent, uint64_t numberOfNonComponentStates,
479 std::vector<std::pair<uint64_t, uint64_t>> const& sspComponentExitChoicesToOriginalMap) {
480 // We first take care of non-mec states
481 storm::utility::vector::setVectorValues(this->_producedOptimalChoices.get(), statesNotInComponent, sspChoices);
482 // Secondly, we consider MEC states. There are 3 cases for each MEC state:
483 // 1. The SSP choices encode that we want to stay in the MEC
484 // 2. The SSP choices encode that we want to leave the MEC and
485 // a) we take an exit (non-MEC) choice at the given state
486 // b) we have to take a MEC choice at the given state in a way that eventually an exit state of the MEC is reached
487 uint64_t exitChoiceOffset = sspMatrix.getRowGroupIndices()[numberOfNonComponentStates];
488 for (auto const& mec : *this->_longRunComponentDecomposition) {
489 // Get the sspState of this MEC (using one representative mec state)
490 auto const& sspState = inputToSspStateMap[mec.begin()->first];
491 uint64_t sspChoiceIndex = sspMatrix.getRowGroupIndices()[sspState] + sspChoices[sspState];
492 // Obtain the state and choice of the original model to which the selected choice corresponds.
493 auto const& originalStateChoice = sspComponentExitChoicesToOriginalMap[sspChoiceIndex - exitChoiceOffset];
494 // Check if we are in Case 1 or 2
495 if (originalStateChoice.first == std::numeric_limits<uint_fast64_t>::max()) {
496 // The optimal choice is to stay in this mec (Case 1)
497 // In this case, no further operations are necessary. The scheduler has already been set to the optimal choices during the call of
498 // computeLraForMec.
499 STORM_LOG_ASSERT(sspMatrix.getRow(sspState, sspChoices[sspState]).getNumberOfEntries() == 0, "Expected empty row at choice that stays in MEC.");
500 } else {
501 // The best choice is to leave this MEC via the selected state and choice. (Case 2)
502 // Set the exit choice (Case 2.a)
503 this->_producedOptimalChoices.get()[originalStateChoice.first] = originalStateChoice.second;
504 // The remaining states in this MEC need to reach the state with the exit choice with probability 1. (Case 2.b)
505 // Perform a backwards search from the exit state, only using MEC choices
506 // We start by setting an invalid choice to all remaining mec states (so that we can easily detect them as unprocessed)
507 for (auto const& stateActions : mec) {
508 if (stateActions.first != originalStateChoice.first) {
509 this->_producedOptimalChoices.get()[stateActions.first] = std::numeric_limits<uint64_t>::max();
510 }
511 }
512 // Ensure that backwards transitions are available
514 // Now start a backwards DFS
515 std::vector<uint64_t> stack = {originalStateChoice.first};
516 while (!stack.empty()) {
517 uint64_t currentState = stack.back();
518 stack.pop_back();
519 for (auto const& backwardsTransition : this->_backwardTransitions->getRowGroup(currentState)) {
520 uint64_t predecessorState = backwardsTransition.getColumn();
521 if (mec.containsState(predecessorState)) {
522 auto& selectedPredChoice = this->_producedOptimalChoices.get()[predecessorState];
523 if (selectedPredChoice == std::numeric_limits<uint64_t>::max()) {
524 // We don't already have a choice for this predecessor.
525 // We now need to check whether there is a *MEC* choice leading to currentState
526 for (auto const& predChoice : mec.getChoicesForState(predecessorState)) {
527 for (auto const& forwardTransition : this->_transitionMatrix.getRow(predChoice)) {
528 if (forwardTransition.getColumn() == currentState && !storm::utility::isZero(forwardTransition.getValue())) {
529 // Playing this choice (infinitely often) will lead to current state (infinitely often)!
530 selectedPredChoice = predChoice - this->_transitionMatrix.getRowGroupIndices()[predecessorState];
531 stack.push_back(predecessorState);
532 break;
533 }
534 }
535 if (selectedPredChoice != std::numeric_limits<uint64_t>::max()) {
536 break;
537 }
538 }
539 }
540 }
541 }
542 }
543 }
544 }
545}
546
547template<typename ValueType>
549 std::vector<ValueType> const& componentLraValues) {
550 STORM_LOG_ASSERT(this->_longRunComponentDecomposition != nullptr, "Decomposition not computed, yet.");
551
552 // For fast transition rewriting, we build a mapping from the input state indices to the state indices of a new transition matrix
553 // which redirects all transitions leading to a former component state to a new auxiliary state.
554 // There will be one auxiliary state for each component. These states will be appended to the end of the matrix.
555
556 // First gather the states that are part of a component
557 // and create a mapping from states that lie in a component to the corresponding component index.
558 storm::storage::BitVector statesInComponents(this->_transitionMatrix.getRowGroupCount());
559 std::vector<uint64_t> inputToSspStateMap(this->_transitionMatrix.getRowGroupCount(), std::numeric_limits<uint64_t>::max());
560 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
561 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
562 uint64_t state = internal::getComponentElementState(element);
563 statesInComponents.set(state);
564 inputToSspStateMap[state] = currentComponentIndex;
565 }
566 }
567 // Now take care of the non-component states. Note that the order of these states will be preserved.
568 uint64_t numberOfNonComponentStates = 0;
569 storm::storage::BitVector statesNotInComponent = ~statesInComponents;
570 for (auto nonComponentState : statesNotInComponent) {
571 inputToSspStateMap[nonComponentState] = numberOfNonComponentStates;
572 ++numberOfNonComponentStates;
573 }
574 // Finalize the mapping for the component states which now still assigns component states to to their component index.
575 // To make sure that they point to the auxiliary states (located at the end of the SspMatrix), we need to shift them by the
576 // number of states that are not in a component.
577 for (auto mecState : statesInComponents) {
578 inputToSspStateMap[mecState] += numberOfNonComponentStates;
579 }
580
581 // For scheduler extraction, we will need to create a mapping between choices at the auxiliary states and the
582 // corresponding choices in the original model.
583 std::vector<std::pair<uint_fast64_t, uint_fast64_t>> sspComponentExitChoicesToOriginalMap;
584
585 // The next step is to create the SSP matrix and the right-hand side of the SSP.
586 auto sspMatrixVector = buildSspMatrixVector(componentLraValues, inputToSspStateMap, statesNotInComponent, numberOfNonComponentStates,
587 this->isProduceSchedulerSet() ? &sspComponentExitChoicesToOriginalMap : nullptr);
588
589 // Set-up a solver
592 minMaxLinearEquationSolverFactory.getRequirements(env, true, true, this->getOptimizationDirection(), false, this->isProduceSchedulerSet());
593 requirements.clearBounds();
594 STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
595 "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked.");
596 std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> solver = minMaxLinearEquationSolverFactory.create(env, sspMatrixVector.first);
597 solver->setHasUniqueSolution();
598 solver->setHasNoEndComponents();
599 solver->setTrackScheduler(this->isProduceSchedulerSet());
600 auto lowerUpperBounds = std::minmax_element(componentLraValues.begin(), componentLraValues.end());
601 solver->setLowerBound(*lowerUpperBounds.first);
602 solver->setUpperBound(*lowerUpperBounds.second);
603 solver->setRequirementsChecked();
604
605 // Solve the equation system
606 std::vector<ValueType> x(sspMatrixVector.first.getRowGroupCount());
607 solver->solveEquations(env, this->getOptimizationDirection(), x, sspMatrixVector.second);
608
609 // Prepare scheduler (if requested)
610 if (this->isProduceSchedulerSet() && solver->hasScheduler()) {
611 // Translate result for ssp matrix to original model
612 constructOptimalChoices(solver->getSchedulerChoices(), sspMatrixVector.first, inputToSspStateMap, statesNotInComponent, numberOfNonComponentStates,
613 sspComponentExitChoicesToOriginalMap);
614 } else {
615 STORM_LOG_ERROR_COND(!this->isProduceSchedulerSet(), "Requested to produce a scheduler, but no scheduler was generated.");
616 }
617
618 // Prepare result vector.
619 // For efficiency reasons, we re-use the memory of our rhs for this!
620 std::vector<ValueType> result = std::move(sspMatrixVector.second);
621 result.resize(this->_transitionMatrix.getRowGroupCount());
622 result.shrink_to_fit();
623 storm::utility::vector::selectVectorValues(result, inputToSspStateMap, x);
624 return result;
625}
626
629
630} // namespace helper
631} // namespace modelchecker
632} // namespace storm
SolverEnvironment & solver()
storm::RationalNumber const & getAperiodicFactor() const
storm::solver::LraMethod const & getNondetLraMethod() const
LongRunAverageSolverEnvironment & lra()
SparseInfiniteHorizonHelper(storm::storage::SparseMatrix< ValueType > const &transitionMatrix)
storm::storage::Decomposition< LongRunComponentType > const * _longRunComponentDecomposition
std::unique_ptr< storm::storage::Decomposition< LongRunComponentType > > _computedLongRunComponentDecomposition
Helper class for model checking queries that depend on the long run behavior of the (nondeterministic...
std::optional< ValueType > computeLraForTrivialMec(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &mec)
Checks if the component consists of a single state and if so, returns the value of that state.
virtual std::vector< ValueType > buildAndSolveSsp(Environment const &env, std::vector< ValueType > const &mecLraValues) override
std::pair< storm::storage::SparseMatrix< ValueType >, std::vector< ValueType > > buildSspMatrixVector(std::vector< ValueType > const &mecLraValues, std::vector< uint64_t > const &inputToSspStateMap, storm::storage::BitVector const &statesNotInComponent, uint64_t numberOfNonComponentStates, std::vector< std::pair< uint64_t, uint64_t > > *sspComponentExitChoicesToOriginalMap)
SparseInfiniteHorizonHelper< ValueType, true >::ValueGetter ValueGetter
Function mapping from indices to values.
virtual ValueType computeLraForComponent(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &component) override
void constructOptimalChoices(std::vector< uint64_t > const &sspChoices, storm::storage::SparseMatrix< ValueType > const &sspMatrix, std::vector< uint64_t > const &inputToSspStateMap, storm::storage::BitVector const &statesNotInComponent, uint64_t numberOfNonComponentStates, std::vector< std::pair< uint64_t, uint64_t > > const &sspComponentExitChoicesToOriginalMap)
SparseNondeterministicInfiniteHorizonHelper(storm::storage::SparseMatrix< ValueType > const &transitionMatrix)
Initializes the helper for a discrete time model (i.e.
std::optional< ValueType > computeLraForZeroMec(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &mec)
Checks if we can determine via graph analysis, that the LRA value of the component is exactly zero.
ValueType computeLraForMecLp(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &mec)
As computeLraForMec but uses linear programming as a solution method (independent of what is set in e...
ValueType computeLraForMecVi(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &mec)
As computeLraForMec but uses value iteration as a solution method (independent of what is set in env)...
Helper class that performs iterations of the value iteration method.
Definition LraViHelper.h:42
virtual std::unique_ptr< MinMaxLinearEquationSolver< ValueType, SolutionType > > create(Environment const &env) const override
MinMaxLinearEquationSolverRequirements getRequirements(Environment const &env, bool hasUniqueSolution=false, bool hasNoEndComponents=false, boost::optional< storm::solver::OptimizationDirection > const &direction=boost::none, bool hasInitialScheduler=false, bool trackScheduler=false) const
Retrieves the requirements of the solver that would be created when calling create() right now.
std::string getEnabledRequirementsAsString() const
Returns a string that enumerates the enabled requirements.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
This class represents a maximal end-component of a nondeterministic model.
bool containsState(uint_fast64_t state) const
Retrieves whether the given state is contained in this MEC.
iterator begin()
Retrieves an iterator that points to the first state and its choices in the MEC.
This class defines which action is chosen in a particular state of a non-deterministic model.
Definition Scheduler.h:18
void setChoice(SchedulerChoice< ValueType > const &choice, uint_fast64_t modelState, uint_fast64_t memoryState=0)
Sets the choice defined by the scheduler for the given state.
Definition Scheduler.cpp:38
index_type getNumberOfEntries() const
Retrieves the number of entries in the rows.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
void newRowGroup(index_type startingRow)
Starts a new row group in the matrix.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
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.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_ERROR_COND(cond, message)
Definition macros.h:52
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
Expression sum(std::vector< storm::expressions::Expression > const &expressions)
bool componentElementChoicesContains(typename storm::storage::StronglyConnectedComponent::value_type const &element, uint64_t choice)
uint64_t getComponentElementState(typename storm::storage::StronglyConnectedComponent::value_type const &element)
Auxiliary functions that deal with the different kinds of components (MECs on potentially nondetermin...
uint64_t const * getComponentElementChoicesEnd(typename storm::storage::StronglyConnectedComponent::value_type const &element)
constexpr uint64_t getComponentElementChoiceCount(typename storm::storage::StronglyConnectedComponent::value_type const &)
@ NondetTsNoIs
deterministic choice at timed states, deterministic choice at instant states (as in Markov Automata w...
Definition LraViHelper.h:26
@ DetTsNondetIs
deterministic choice at timed states, no instant states (as in DTMCs and CTMCs)
Definition LraViHelper.h:24
uint64_t const * getComponentElementChoicesBegin(typename storm::storage::StronglyConnectedComponent::value_type const &element)
void addSspMatrixChoice(uint64_t const &inputMatrixChoice, storm::storage::SparseMatrix< ValueType > const &inputTransitionMatrix, std::vector< uint64_t > const &inputToSspStateMap, uint64_t const &numberOfNonComponentStates, uint64_t const &currentSspChoice, storm::storage::SparseMatrixBuilder< ValueType > &sspMatrixBuilder)
Auxiliary function that adds the entries of the Ssp Matrix for a single choice (i....
std::unique_ptr< storm::solver::LpSolver< ValueType > > getLpSolver(std::string const &name, storm::solver::LpSolverTypeSelection solvType)
Definition solver.cpp:113
void setVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Sets the provided values at the provided positions in the given vector.
Definition vector.h:78
void selectVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Selects the elements from a vector at the specified positions and writes them consecutively into anot...
Definition vector.h:184
bool isZero(ValueType const &a)
Definition constants.cpp:39
ValueType zero()
Definition constants.cpp:24
ValueType one()
Definition constants.cpp:19
TargetType convertNumber(SourceType const &number)
static const bool IsExact