Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparseDeterministicInfiniteHorizonHelper.cpp
Go to the documentation of this file.
2
3#include <numeric>
4
21
22namespace storm {
23namespace modelchecker {
24namespace helper {
25
26template<typename ValueType>
31
32template<typename ValueType>
34 std::vector<ValueType> const& exitRates)
35 : SparseInfiniteHorizonHelper<ValueType, false>(transitionMatrix, exitRates) {}
36
37template<typename ValueType>
39 if (this->_longRunComponentDecomposition == nullptr) {
40 // The decomposition has not been provided or computed, yet.
41 this->_computedLongRunComponentDecomposition = std::make_unique<storm::storage::StronglyConnectedComponentDecomposition<ValueType>>(
43 this->_longRunComponentDecomposition = this->_computedLongRunComponentDecomposition.get();
44 }
45}
46
47template<typename ValueType>
49 ValueGetter const& actionValueGetter,
51 // For deterministic models, we compute the LRA for a BSCC
52
53 STORM_LOG_ASSERT(!this->isProduceSchedulerSet(), "Scheduler production enabled for deterministic model.");
54
55 auto trivialResult = computeLraForTrivialBscc(env, stateValueGetter, actionValueGetter, component);
56 if (trivialResult.first) {
57 return trivialResult.second;
58 }
59
60 // Solve nontrivial BSCC with the method specified in the settings
61 storm::solver::LraMethod method = env.solver().lra().getDetLraMethod();
63 method == storm::solver::LraMethod::ValueIteration) {
64 method = storm::solver::LraMethod::GainBiasEquations;
65 STORM_LOG_INFO("Selecting " << storm::solver::toString(method)
66 << " as the solution technique for long-run properties to guarantee exact results. If you want to override this, please "
67 "explicitly specify a different LRA method.");
68 } else if (env.solver().isForceSoundness() && env.solver().lra().isDetLraMethodSetFromDefault() && method != storm::solver::LraMethod::ValueIteration) {
69 method = storm::solver::LraMethod::ValueIteration;
70 STORM_LOG_INFO("Selecting " << storm::solver::toString(method)
71 << " as the solution technique for long-run properties to guarantee sound results. If you want to override this, please "
72 "explicitly specify a different LRA method.");
73 }
74 STORM_LOG_TRACE("Computing LRA for BSCC of size " << component.size() << " using '" << storm::solver::toString(method) << "'.");
75 if (method == storm::solver::LraMethod::ValueIteration) {
76 return computeLraForBsccVi(env, stateValueGetter, actionValueGetter, component);
77 } else if (method == storm::solver::LraMethod::LraDistributionEquations) {
78 // We only need the first element of the pair as the lra distribution is not relevant at this point.
79 return computeLraForBsccSteadyStateDistr(env, stateValueGetter, actionValueGetter, component).first;
80 }
81 STORM_LOG_WARN_COND(method == storm::solver::LraMethod::GainBiasEquations,
82 "Unsupported lra method selected. Defaulting to " << storm::solver::toString(storm::solver::LraMethod::GainBiasEquations) << ".");
83 // We don't need the bias values
84 return computeLraForBsccGainBias(env, stateValueGetter, actionValueGetter, component).first;
85}
86
87template<typename ValueType>
89 Environment const& env, ValueGetter const& stateValueGetter, ValueGetter const& actionValueGetter,
91 // For deterministic models, we can catch the case where all values are the same. This includes the special case where the BSCC consist only of just one
92 // state.
93 bool first = true;
94 ValueType val = storm::utility::zero<ValueType>();
95 for (auto const& element : component) {
96 auto state = internal::getComponentElementState(element);
98 "Unexpected choice index at state " << state << " of deterministic model.");
99 ValueType curr =
100 stateValueGetter(state) + (this->isContinuousTime() ? (*this->_exitRates)[state] * actionValueGetter(state) : actionValueGetter(state));
101 if (first) {
102 val = curr;
103 first = false;
104 } else if (val != curr) {
105 return {false, storm::utility::zero<ValueType>()};
106 }
107 }
108 // All values are the same
109 return {true, val};
110}
111
112template<>
114 Environment const& /*env*/, ValueGetter const& /*stateValueGetter*/, ValueGetter const& /*actionValueGetter*/,
116 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The requested Method for LRA computation is not supported for parametric models.");
117}
118template<typename ValueType>
120 ValueGetter const& actionValueGetter,
122 // Collect parameters of the computation
123 ValueType aperiodicFactor = storm::utility::convertNumber<ValueType>(env.solver().lra().getAperiodicFactor());
124
125 // Now create a helper and perform the algorithm
126 if (this->isContinuousTime()) {
127 // We assume a CTMC (with deterministic timed states and no instant states)
130 viHelper(bscc, this->_transitionMatrix, aperiodicFactor, this->_markovianStates, this->_exitRates);
131 return viHelper.performValueIteration(env, stateValueGetter, actionValueGetter, this->_exitRates);
132 } else {
133 // We assume a DTMC (with deterministic timed states and no instant states)
136 viHelper(bscc, this->_transitionMatrix, aperiodicFactor);
137 return viHelper.performValueIteration(env, stateValueGetter, actionValueGetter);
138 }
139}
140
141template<typename ValueType>
143 Environment const& env, ValueGetter const& stateValuesGetter, ValueGetter const& actionValuesGetter,
145 // we want that the returned vector is sorted as the bscc. So let's assert that the bscc is sorted ascendingly.
146 STORM_LOG_ASSERT(std::is_sorted(bscc.begin(), bscc.end()), "Expected that bsccs are sorted.");
147
148 // We build the equation system as in Line 3 of Algorithm 3 from
149 // Kretinsky, Meggendorfer: Efficient Strategy Iteration for Mean Payoff in Markov Decision Processes (ATVA 2017)
150 // https://doi.org/10.1007/978-3-319-68167-2_25
151 // The first variable corresponds to the gain of the bscc whereas the subsequent variables yield the bias for each state s_1, s_2, ....
152 // No bias variable for s_0 is needed since it is always set to zero, yielding an nxn equation system matrix
153 // To make this work for CTMC, we could uniformize the model. This preserves LRA and ensures that we can compute the
154 // LRA as for a DTMC (the soujourn time in each state is the same). If we then multiply the equations with the uniformization rate,
155 // the uniformization rate cancels out. Hence, we obtain the equation system below.
156
157 // Get a mapping from global state indices to local ones.
158 std::unordered_map<uint64_t, uint64_t> toLocalIndexMap;
159 uint64_t localIndex = 0;
160 for (auto const& globalIndex : bscc) {
161 toLocalIndexMap[globalIndex] = localIndex;
162 ++localIndex;
163 }
164
165 // Prepare an environment for the underlying equation solver
166 auto subEnv = env;
167 if (subEnv.solver().getLinearEquationSolverType() == storm::solver::EquationSolverType::Topological) {
168 // Topological solver does not make any sense since the BSCC is connected.
169 subEnv.solver().setLinearEquationSolverType(subEnv.solver().topological().getUnderlyingEquationSolverType(),
170 subEnv.solver().topological().isUnderlyingEquationSolverTypeSetFromDefault());
171 }
172 subEnv.solver().setLinearEquationSolverPrecision(env.solver().lra().getPrecision(), env.solver().lra().getRelativeTerminationCriterion());
173
174 // Build the equation system matrix and vector.
176 bool isEquationSystemFormat =
179 std::vector<ValueType> eqSysVector;
180 eqSysVector.reserve(bscc.size());
181 // The first row asserts that the weighted bias variables and the reward at s_0 sum up to the gain
182 uint64_t row = 0;
183 ValueType entryValue;
184 for (auto const& globalState : bscc) {
185 ValueType rateAtState = this->_exitRates ? (*this->_exitRates)[globalState] : storm::utility::one<ValueType>();
186 // Coefficient for the gain variable
187 if (isEquationSystemFormat) {
188 // '1-0' in row 0 and -(-1) in other rows
189 builder.addNextValue(row, 0, storm::utility::one<ValueType>());
190 } else if (row > 0) {
191 // No coeficient in row 0, othwerise substract the gain
192 builder.addNextValue(row, 0, -storm::utility::one<ValueType>());
193 }
194 // Compute weighted sum over successor state. As this is a BSCC, each successor state will again be in the BSCC.
195 if (row > 0) {
196 if (isEquationSystemFormat) {
197 builder.addDiagonalEntry(row, rateAtState);
198 } else if (!storm::utility::isOne(rateAtState)) {
199 builder.addDiagonalEntry(row, storm::utility::one<ValueType>() - rateAtState);
200 }
201 }
202 for (auto const& entry : this->_transitionMatrix.getRow(globalState)) {
203 uint64_t col = toLocalIndexMap[entry.getColumn()];
204 if (col == 0) {
205 // Skip transition to state_0. This corresponds to setting the bias of state_0 to zero
206 continue;
207 }
208 entryValue = entry.getValue() * rateAtState;
209 if (isEquationSystemFormat) {
210 entryValue = -entryValue;
211 }
212 builder.addNextValue(row, col, entryValue);
213 }
214 eqSysVector.push_back(stateValuesGetter(globalState) + rateAtState * actionValuesGetter(globalState));
215 ++row;
216 }
217
218 // Create a linear equation solver
219 auto solver = linearEquationSolverFactory.create(subEnv, builder.build());
220 // Check solver requirements.
221 auto requirements = solver->getRequirements(subEnv);
222 STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
223 "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked.");
224 // Todo: Find bounds on the bias variables. Just inserting the maximal value from the vector probably does not work.
225
226 std::vector<ValueType> eqSysSol(bscc.size(), storm::utility::zero<ValueType>());
227 // Take the mean of the rewards as an initial guess for the gain
228 // eqSysSol.front() = std::accumulate(eqSysVector.begin(), eqSysVector.end(), storm::utility::zero<ValueType>()) / storm::utility::convertNumber<ValueType,
229 // uint64_t>(bscc.size());
230 solver->solveEquations(subEnv, eqSysSol, eqSysVector);
231
232 ValueType gain = eqSysSol.front();
233 // insert bias value for state 0
234 eqSysSol.front() = storm::utility::zero<ValueType>();
235 // Return the gain and the bias values
236 return std::pair<ValueType, std::vector<ValueType>>(std::move(gain), std::move(eqSysSol));
237}
238
239template<typename ValueType>
242 // We catch the (easy) case where the BSCC is a singleton.
243 if (bscc.size() == 1) {
244 return {storm::utility::one<ValueType>()};
245 }
248 if (env.solver().isForceSoundness()) {
250 } else {
252 }
253 }
256 }
257
259 return computeSteadyStateDistrForBsccEqSys(env, bscc);
260 } else {
262 "Unexpected algorithm for steady state distribution computation.");
263 return computeSteadyStateDistrForBsccEVTs(env, bscc);
264 }
265}
266
267template<typename ValueType>
270 // Computes steady state distributions by computing EVTs on a slightly modified system. See https://arxiv.org/abs/2401.10638 for more information.
271 storm::storage::BitVector bsccAsBitVector(this->_transitionMatrix.getColumnCount(), false);
272 bsccAsBitVector.set(bscc.begin(), bscc.end(), true);
273
274 // Remove one state from the BSCC (so it becomes an SCC) and compute visiting times on that SCC.
275 uint64_t const proxyState = *bsccAsBitVector.rbegin();
276 bsccAsBitVector.set(proxyState, false);
277 auto visittimesHelper = this->isContinuousTime() ? SparseDeterministicVisitingTimesHelper<ValueType>(this->_transitionMatrix, *this->_exitRates)
278 : SparseDeterministicVisitingTimesHelper<ValueType>(this->_transitionMatrix);
279 this->createBackwardTransitions();
280 visittimesHelper.provideBackwardTransitions(*this->_backwardTransitions);
281 std::vector<ValueType> initialValues;
282 initialValues.reserve(bscc.size() - 1);
283 auto row = this->_transitionMatrix.getRow(proxyState);
284 auto entryIt = row.begin();
285 auto const entryItEnd = row.end();
286 for (auto state : bsccAsBitVector) {
287 if (entryIt != entryItEnd && state == entryIt->getColumn()) {
288 initialValues.push_back(entryIt->getValue());
289 ++entryIt;
290 } else {
291 initialValues.push_back(storm::utility::zero<ValueType>());
292 }
293 }
294 STORM_LOG_ASSERT(entryIt == entryItEnd || entryIt->getColumn() == proxyState, "Unexpected matrix row.");
295 auto evtEnv = env;
296 if (evtEnv.solver().isForceSoundness()) {
297 auto prec = evtEnv.solver().getPrecisionOfLinearEquationSolver(evtEnv.solver().getLinearEquationSolverType());
298 if (prec.first.is_initialized()) {
299 auto requiredPrecision = *prec.first;
300 // We need to adapt the precision. We focus on propagation of relative errors. Absolut precision is then also fine as we're computing with values in
301 // [0,1].
302 //
303 // We are going to normalize a vector of EVTs. Assuming that the EVT vector has been computed with relative precision eps, the sum of all
304 // EVTs still is eps-precise w.r.t. the exact (unknown) sum of the EVTs. Let x and y be the computed EVT of a given state and the sum of all
305 // computed EVTs, respectively. We have relative errors of delta_x = (x-x')/x' and delta_y = (y-y')/y' respectively, where x' and y' are the exact
306 // values. Note that x = x' * (1+delta_x) and |delta_x| <= eps.
307 //
308 // The relative error in the normalized value x/y = (x'/y')*((1+delta_x)/(1+delta_y)) = (x'/y')*(1+((delta_x-delta_y)/(1+\delta_y))) can be upper
309 // bounded by 2*eps/(1-eps). We set eps so that this term is equal to requiredPrecision
310 storm::RationalNumber eps = requiredPrecision / (storm::utility::convertNumber<storm::RationalNumber, uint64_t>(2) + requiredPrecision);
311 evtEnv.solver().setLinearEquationSolverPrecision(eps, prec.second);
312 }
313 }
314 auto visitingTimes = visittimesHelper.computeExpectedVisitingTimes(evtEnv, bsccAsBitVector, initialValues);
315 visitingTimes.push_back(storm::utility::one<ValueType>()); // Add the value for the proxy state
316 bsccAsBitVector.set(proxyState, true);
317
318 ValueType sumOfVisitingTimes = storm::utility::zero<ValueType>();
319 if (this->isContinuousTime()) {
320 auto resultIt = visitingTimes.begin();
321 for (auto state : bsccAsBitVector) {
322 *resultIt /= (*this->_exitRates)[state];
323 sumOfVisitingTimes += *resultIt;
324 ++resultIt;
325 }
326 } else {
327 sumOfVisitingTimes = std::accumulate(visitingTimes.begin(), visitingTimes.end(), storm::utility::zero<ValueType>());
328 }
329 storm::utility::vector::scaleVectorInPlace(visitingTimes, storm::utility::one<ValueType>() / sumOfVisitingTimes);
330 return visitingTimes;
331}
332
333template<typename ValueType>
335 Environment const& inputEnv, storm::storage::StronglyConnectedComponent const& bscc) {
336 // We want that the returned values are sorted properly. Let's assert that strongly connected components use a sorted container type
337 STORM_LOG_ASSERT(std::is_sorted(bscc.begin(), bscc.end()), "Expected that bsccs are sorted.");
338
339 // Prepare an environment for the underlying linear equation solver
340 auto env = inputEnv;
341 if (env.solver().getLinearEquationSolverType() == storm::solver::EquationSolverType::Topological) {
342 // Topological solver does not make any sense since the BSCC is connected.
343 env.solver().setLinearEquationSolverType(env.solver().topological().getUnderlyingEquationSolverType(),
344 env.solver().topological().isUnderlyingEquationSolverTypeSetFromDefault());
345 }
346
347 STORM_LOG_WARN_COND(!env.solver().isForceSoundness(),
348 "Sound computations are not properly implemented for this computation. You might get incorrect results.");
349
350 // Let A be ab auxiliary Matrix with A[s,s] = R(s,s) - r(s) & A[s,s'] = R(s,s') for s,s' in BSCC and s!=s'.
351 // We build and solve the equation system for
352 // x*A=0 & x_0+...+x_n=1 <=> A^t*x=0=x-x & x_0+...+x_n=1 <=> (1+A^t)*x = x & 1-x_0-...-x_n-1=x_n
353 // Then, x[i] will be the fraction of the time we are in state i.
354
355 // Get a mapping from global state indices to local ones as well as a bitvector containing states within the BSCC.
356 std::unordered_map<uint64_t, uint64_t> toLocalIndexMap;
357 storm::storage::BitVector bsccStates(this->_transitionMatrix.getRowCount(), false);
358 uint64_t localIndex = 0;
359 for (auto const& globalIndex : bscc) {
360 bsccStates.set(globalIndex, true);
361 toLocalIndexMap[globalIndex] = localIndex;
362 ++localIndex;
363 }
364
365 // Build the auxiliary Matrix A.
366 auto auxMatrix = this->_transitionMatrix.getSubmatrix(false, bsccStates, bsccStates, true); // add diagonal entries!
367 uint64_t row = 0;
368 for (auto const& globalIndex : bscc) {
369 ValueType rateAtState = this->_exitRates ? (*this->_exitRates)[globalIndex] : storm::utility::one<ValueType>();
370 for (auto& entry : auxMatrix.getRow(row)) {
371 if (entry.getColumn() == row) {
372 // This value is non-zero since we have a BSCC with more than one state
373 entry.setValue(rateAtState * (entry.getValue() - storm::utility::one<ValueType>()));
374 } else if (this->isContinuousTime()) {
375 entry.setValue(entry.getValue() * rateAtState);
376 }
377 }
378 ++row;
379 }
380 assert(row == auxMatrix.getRowCount());
381
382 // We need to consider A^t. This will not delete diagonal entries since they are non-zero.
383 auxMatrix = auxMatrix.transpose();
384
385 // Check whether we need the fixpoint characterization
387 bool isFixpointFormat = linearEquationSolverFactory.getEquationProblemFormat(env) == storm::solver::LinearEquationSolverProblemFormat::FixedPointSystem;
388 if (isFixpointFormat) {
389 // Add a 1 on the diagonal
390 for (row = 0; row < auxMatrix.getRowCount(); ++row) {
391 for (auto& entry : auxMatrix.getRow(row)) {
392 if (entry.getColumn() == row) {
393 entry.setValue(storm::utility::one<ValueType>() + entry.getValue());
394 }
395 }
396 }
397 }
398
399 // We now build the equation system matrix.
400 // We can drop the last row of A and add ones in this row instead to assert that the variables sum up to one
401 // Phase 1: replace the existing entries of the last row with ones
402 uint64_t col = 0;
403 uint64_t lastRow = auxMatrix.getRowCount() - 1;
404 for (auto& entry : auxMatrix.getRow(lastRow)) {
405 entry.setColumn(col);
406 if (isFixpointFormat) {
407 if (col == lastRow) {
408 entry.setValue(storm::utility::zero<ValueType>());
409 } else {
410 entry.setValue(-storm::utility::one<ValueType>());
411 }
412 } else {
413 entry.setValue(storm::utility::one<ValueType>());
414 }
415 ++col;
416 }
417 storm::storage::SparseMatrixBuilder<ValueType> builder(std::move(auxMatrix));
418 for (; col <= lastRow; ++col) {
419 if (isFixpointFormat) {
420 if (col != lastRow) {
421 builder.addNextValue(lastRow, col, -storm::utility::one<ValueType>());
422 }
423 } else {
424 builder.addNextValue(lastRow, col, storm::utility::one<ValueType>());
425 }
426 }
427
428 std::vector<ValueType> bsccEquationSystemRightSide(bscc.size(), storm::utility::zero<ValueType>());
429 bsccEquationSystemRightSide.back() = storm::utility::one<ValueType>();
430
431 // Create a linear equation solver
432 auto solver = linearEquationSolverFactory.create(env, builder.build());
433 solver->setBounds(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>());
434 // Check solver requirements.
435 auto requirements = solver->getRequirements(env);
436 requirements.clearLowerBounds();
437 requirements.clearUpperBounds();
438 STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
439 "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked.");
440
441 std::vector<ValueType> steadyStateDistr(bscc.size(), storm::utility::one<ValueType>() / storm::utility::convertNumber<ValueType, uint64_t>(bscc.size()));
442 solver->solveEquations(env, steadyStateDistr, bsccEquationSystemRightSide);
443
444 // As a last step, we normalize these values to counter numerical inaccuracies a bit.
445 // This is only reasonable in non-exact mode.
446 if (!env.solver().isForceExact()) {
447 ValueType sum = std::accumulate(steadyStateDistr.begin(), steadyStateDistr.end(), storm::utility::zero<ValueType>());
448 storm::utility::vector::scaleVectorInPlace<ValueType, ValueType>(steadyStateDistr, storm::utility::one<ValueType>() / sum);
449 }
450
451 return steadyStateDistr;
452}
453
454template<typename ValueType>
456 Environment const& env, ValueGetter const& stateValuesGetter, ValueGetter const& actionValuesGetter,
458 // Get the LRA distribution for the provided bscc.
459 auto steadyStateDistr = computeSteadyStateDistrForBscc(env, bscc);
460
461 // Calculate final LRA Value
462 ValueType result = storm::utility::zero<ValueType>();
463 auto solIt = steadyStateDistr.begin();
464 for (auto const& globalState : bscc) {
465 if (this->isContinuousTime()) {
466 result += (*solIt) * (stateValuesGetter(globalState) + (*this->_exitRates)[globalState] * actionValuesGetter(globalState));
467 } else {
468 result += (*solIt) * (stateValuesGetter(globalState) + actionValuesGetter(globalState));
469 }
470 ++solIt;
471 }
472 assert(solIt == steadyStateDistr.end());
473
474 return std::pair<ValueType, std::vector<ValueType>>(std::move(result), std::move(steadyStateDistr));
475}
476
477template<typename ValueType>
478std::pair<storm::storage::SparseMatrix<ValueType>, std::vector<ValueType>> SparseDeterministicInfiniteHorizonHelper<ValueType>::buildSspMatrixVector(
479 std::vector<ValueType> const& bsccLraValues, std::vector<uint64_t> const& inputStateToBsccIndexMap, storm::storage::BitVector const& statesNotInComponent,
480 bool asEquationSystem) {
481 // Create SSP Matrix.
482 // In contrast to the version for nondeterministic models, we eliminate the auxiliary states representing each BSCC on the fly
483
484 // Probability mass that would lead to a BSCC will be considered in the rhs of the equation system
485 auto sspMatrix = this->_transitionMatrix.getSubmatrix(false, statesNotInComponent, statesNotInComponent, asEquationSystem);
486 if (asEquationSystem) {
487 sspMatrix.convertToEquationSystem();
488 }
489
490 // Create the SSP right-hand-side
491 std::vector<ValueType> rhs;
492 rhs.reserve(sspMatrix.getRowCount());
493 for (auto state : statesNotInComponent) {
494 ValueType stateValue = storm::utility::zero<ValueType>();
495 for (auto const& transition : this->_transitionMatrix.getRow(state)) {
496 if (!statesNotInComponent.get(transition.getColumn())) {
497 // This transition leads to a BSCC!
498 stateValue += transition.getValue() * bsccLraValues[inputStateToBsccIndexMap[transition.getColumn()]];
499 }
500 }
501 rhs.push_back(std::move(stateValue));
502 }
503
504 return std::make_pair(std::move(sspMatrix), std::move(rhs));
505}
506
507template<typename ValueType>
509 std::vector<ValueType> const& componentLraValues) {
510 STORM_LOG_ASSERT(this->_longRunComponentDecomposition != nullptr, "Decomposition not computed, yet.");
511
512 // For fast transition rewriting, we build a mapping from the input state indices to the state indices of a new transition matrix
513 // which redirects all transitions leading to a former BSCC state to a new (imaginary) auxiliary state.
514 // Each auxiliary state gets assigned the value of that BSCC and we compute expected rewards (aka stochastic shortest path, SSP) on that new system.
515 // For efficiency reasons, we actually build the system where the auxiliary states are already eliminated.
516
517 // First gather the states that are part of a component
518 // and create a mapping from states that lie in a component to the corresponding component index.
519 storm::storage::BitVector statesInComponents(this->_transitionMatrix.getRowGroupCount());
520 std::vector<uint64_t> stateIndexMap(this->_transitionMatrix.getRowGroupCount(), std::numeric_limits<uint64_t>::max());
521 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
522 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
523 uint64_t state = internal::getComponentElementState(element);
524 statesInComponents.set(state);
525 stateIndexMap[state] = currentComponentIndex;
526 }
527 }
528 // Map the non-component states to their index in the SSP. Note that the order of these states will be preserved.
529 uint64_t numberOfNonComponentStates = 0;
530 storm::storage::BitVector statesNotInComponent = ~statesInComponents;
531 for (auto nonComponentState : statesNotInComponent) {
532 stateIndexMap[nonComponentState] = numberOfNonComponentStates;
533 ++numberOfNonComponentStates;
534 }
535
536 // The next step is to create the equation system solving the SSP (unless the whole system consists of BSCCs)
537 std::vector<ValueType> sspValues;
538 if (numberOfNonComponentStates > 0) {
540 bool isEqSysFormat = linearEquationSolverFactory.getEquationProblemFormat(env) == storm::solver::LinearEquationSolverProblemFormat::EquationSystem;
541 auto sspMatrixVector = buildSspMatrixVector(componentLraValues, stateIndexMap, statesNotInComponent, isEqSysFormat);
542 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver = linearEquationSolverFactory.create(env, sspMatrixVector.first);
543 auto lowerUpperBounds = std::minmax_element(componentLraValues.begin(), componentLraValues.end());
544 solver->setBounds(*lowerUpperBounds.first, *lowerUpperBounds.second);
545 // Check solver requirements
546 auto requirements = solver->getRequirements(env);
547 requirements.clearUpperBounds();
548 requirements.clearLowerBounds();
549 STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
550 "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked.");
551 sspValues.assign(sspMatrixVector.first.getRowCount(),
552 (*lowerUpperBounds.first + *lowerUpperBounds.second) / storm::utility::convertNumber<ValueType, uint64_t>(2));
553 solver->solveEquations(env, sspValues, sspMatrixVector.second);
554 }
555
556 // Prepare result vector.
557 std::vector<ValueType> result(this->_transitionMatrix.getRowGroupCount());
558 for (uint64_t state = 0; state < stateIndexMap.size(); ++state) {
559 if (statesNotInComponent.get(state)) {
560 result[state] = sspValues[stateIndexMap[state]];
561 } else {
562 result[state] = componentLraValues[stateIndexMap[state]];
563 }
564 }
565 return result;
566}
567
568template<typename ValueType>
570 createDecomposition();
571 STORM_LOG_THROW(this->_longRunComponentDecomposition->size() <= 1, storm::exceptions::InvalidOperationException, "");
572 return computeLongRunAverageStateDistribution(env, [](uint64_t) { return storm::utility::zero<ValueType>(); });
573}
574
575template<typename ValueType>
577 uint64_t const& initialState) {
578 STORM_LOG_ASSERT(initialState < this->_transitionMatrix.getRowGroupCount(),
579 "Invlid initial state index: " << initialState << ". Have only " << this->_transitionMatrix.getRowGroupCount() << " states.");
580 return computeLongRunAverageStateDistribution(env, [&initialState](uint64_t stateIndex) {
581 return initialState == stateIndex ? storm::utility::one<ValueType>() : storm::utility::zero<ValueType>();
582 });
583}
584
585template<typename ValueType>
587 Environment const& env, ValueGetter const& initialDistributionGetter) {
588 createDecomposition();
589
590 Environment subEnv = env;
591 if (subEnv.solver().isForceSoundness()) {
592 // We need to adapt the precision. We focus on propagation of relative errors. Absolut precision is then also fine as we're dealing with values in
593 // [0,1]
595 if (prec.first.is_initialized()) {
596 auto requiredPrecision = *prec.first;
597 // We are going to multiply two numbers x and y that each have relative errors of delta_x = (x-x')/x' and delta_y = (y-y')/y' respectively.
598 // Here, x' and y' are the exact values. Note that x = x' * (1+delta_x) and |delta_x| <= eps
599 // The result x*y= x' * y' * (1+delta_x) * (1+delta_y) will have a relative error of delta_x + delta_y + delta_x*\delta_y <= (2eps * eps^2)
600 // We set eps such that (2eps * eps^2) <= requiredPrecision
601 storm::RationalNumber eps = storm::utility::sqrt<RationalNumber>(storm::utility::one<storm::RationalNumber>() + requiredPrecision) -
602 storm::utility::one<storm::RationalNumber>();
603 subEnv.solver().setLinearEquationSolverPrecision(eps, prec.second);
604 STORM_LOG_INFO("Precision for BSCC reachability and BSCC steady state distribution analysis set to " << storm::utility::convertNumber<double>(eps)
605 << ".");
606 }
607 }
608 // Compute for each BSCC get the probability with which we reach that BSCC
609 auto bsccReachProbs = computeBsccReachabilityProbabilities(subEnv, initialDistributionGetter);
610 // We are now ready to compute the resulting lra distribution
611 std::vector<ValueType> steadyStateDistr(this->_transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
612 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
613 auto const& component = (*this->_longRunComponentDecomposition)[currentComponentIndex];
614 // Compute distribution for current bscc
615 auto bsccDistr = this->computeSteadyStateDistrForBscc(subEnv, component);
616 // Scale with probability to reach that bscc
617 auto const& scalingFactor = bsccReachProbs[currentComponentIndex];
618 if (!storm::utility::isOne(scalingFactor)) {
619 storm::utility::vector::scaleVectorInPlace(bsccDistr, scalingFactor);
620 }
621 // Set the values in the result vector
622 auto bsccDistrIt = bsccDistr.begin();
623 for (auto const& element : component) {
624 uint64_t state = internal::getComponentElementState(element);
625 steadyStateDistr[state] = *bsccDistrIt;
626 ++bsccDistrIt;
627 }
628 STORM_LOG_ASSERT(bsccDistrIt == bsccDistr.end(), "Unexpected number of entries in bscc distribution");
629 }
630 return steadyStateDistr;
631}
632
633template<typename ValueType>
635 std::vector<ValueType> const& toBsccProbabilities) {
637}
638
639template<>
641 std::vector<storm::RationalFunction> const& /*toBsccProbabilities*/) {
642 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
643 "Computing upper bounds for expected visiting times over rational functions is not supported.");
644}
645
646template<typename ValueType>
648 ValueGetter const& initialDistributionGetter) {
649 STORM_LOG_ASSERT(this->_longRunComponentDecomposition != nullptr, "Decomposition not computed, yet.");
650
651 // Compute for each BSCC get the probability with which we reach that BSCC
652 std::vector<ValueType> bsccReachProbs;
653 if (auto numBSCCs = this->_longRunComponentDecomposition->size(); numBSCCs <= 1) {
654 STORM_LOG_ASSERT(numBSCCs == 1, "Found 0 BSCCs in a Markov chain. This should not be possible.");
655 bsccReachProbs = std::vector<ValueType>({storm::utility::one<ValueType>()});
656 } else {
658 bsccReachProbs = computeBsccReachabilityProbabilitiesClassic(env, initialDistributionGetter);
659 } else {
660 bsccReachProbs = computeBsccReachabilityProbabilitiesEVTs(env, initialDistributionGetter);
661 }
662 }
663
664 // As a last step, we normalize these values to counter inaccuracies a bit.
665 // This is only reasonable in non-exact mode and can invalidate accuracy guarantees in sound mode.
666 if (!env.solver().isForceExact() && !env.solver().isForceSoundness()) {
667 ValueType sum = std::accumulate(bsccReachProbs.begin(), bsccReachProbs.end(), storm::utility::zero<ValueType>());
668 storm::utility::vector::scaleVectorInPlace<ValueType, ValueType>(bsccReachProbs, storm::utility::one<ValueType>() / sum);
669 }
670 return bsccReachProbs;
671}
672
673template<typename ValueType>
675 Environment const& env, ValueGetter const& initialDistributionGetter) {
676 // Solve a linear equation system for each BSCC
677
678 // Get the states that do not lie on any BSCC
679 storm::storage::BitVector nonBsccStates(this->_transitionMatrix.getRowCount(), true);
680 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
681 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
682 nonBsccStates.set(internal::getComponentElementState(element), false);
683 }
684 }
685 bool const hasNonBsccStates = !nonBsccStates.empty();
686
687 // Set-up a linear equation solver (unless all states are on a BSCC)
688 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver;
689 if (hasNonBsccStates) {
691 bool isEquationSystemFormat =
693 auto subMatrix = this->_transitionMatrix.getSubmatrix(false, nonBsccStates, nonBsccStates, isEquationSystemFormat);
694 if (isEquationSystemFormat) {
695 subMatrix.convertToEquationSystem();
696 }
697 solver = linearEquationSolverFactory.create(env, std::move(subMatrix));
698 solver->setBounds(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>());
699 // Check solver requirements.
700 auto requirements = solver->getRequirements(env);
701 requirements.clearLowerBounds();
702 requirements.clearUpperBounds();
703 STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
704 "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked.");
705 solver->setCachingEnabled(true);
706 }
707
708 // Run over all BSCCs
709 std::vector<ValueType> bsccReachProbs(this->_longRunComponentDecomposition->size(), storm::utility::zero<ValueType>());
710 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
711 auto const& bscc = (*this->_longRunComponentDecomposition)[currentComponentIndex];
712 auto& bsccVal = bsccReachProbs[currentComponentIndex];
713 // Deal with initial states within the BSCC
714 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
715 uint64_t state = internal::getComponentElementState(element);
716 bsccVal += initialDistributionGetter(state);
717 }
718 // Add reachability probabilities for initial states outside of this BSCC
719 if (hasNonBsccStates) {
720 storm::storage::BitVector bsccAsBitVector(this->_transitionMatrix.getColumnCount(), false);
721 bsccAsBitVector.set(bscc.begin(), bscc.end(), true);
722 // set up and solve the equation system for this BSCC
723 std::vector<ValueType> eqSysRhs;
724 eqSysRhs.reserve(nonBsccStates.getNumberOfSetBits());
725 for (auto state : nonBsccStates) {
726 eqSysRhs.push_back(this->_transitionMatrix.getConstrainedRowSum(state, bsccAsBitVector));
727 }
728 std::vector<ValueType> eqSysSolution(eqSysRhs.size());
729 solver->solveEquations(env, eqSysSolution, eqSysRhs);
730 // Sum up reachability probabilities over initial states
731 uint64_t subsysState = 0;
732 for (auto globalState : nonBsccStates) {
733 bsccVal += initialDistributionGetter(globalState) * eqSysSolution[subsysState];
734 ++subsysState;
735 }
736 }
737 }
738 return bsccReachProbs;
739}
740
741template<typename ValueType>
743 Environment const& env, ValueGetter const& initialDistributionGetter) {
744 // Get the expected number of times we visit each non-BSCC state
745 // See https://arxiv.org/abs/2401.10638 for more information.
746 // We deliberately exclude the exit rates here as we want to make this computation on the induced DTMC to get the expected number of times
747 // that a successor state is chosen probabilistically.
748 auto visittimesHelper = SparseDeterministicVisitingTimesHelper<ValueType>(this->_transitionMatrix);
749 this->createBackwardTransitions();
750 visittimesHelper.provideBackwardTransitions(*this->_backwardTransitions);
751 auto expVisitTimes = visittimesHelper.computeExpectedVisitingTimes(env, initialDistributionGetter);
752 // Then use the expected visiting times to compute BSCC reachability probabilities
753 storm::storage::BitVector nonBsccStates(this->_transitionMatrix.getRowCount(), true);
754 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
755 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
756 nonBsccStates.set(internal::getComponentElementState(element), false);
757 }
758 }
759 std::vector<ValueType> bsccReachProbs(this->_longRunComponentDecomposition->size(), storm::utility::zero<ValueType>());
760 for (uint64_t currentComponentIndex = 0; currentComponentIndex < this->_longRunComponentDecomposition->size(); ++currentComponentIndex) {
761 auto& bsccVal = bsccReachProbs[currentComponentIndex];
762 for (auto const& element : (*this->_longRunComponentDecomposition)[currentComponentIndex]) {
763 uint64_t state = internal::getComponentElementState(element);
764 bsccVal += initialDistributionGetter(state);
765 for (auto const& pred : this->_backwardTransitions->getRow(state)) {
766 if (nonBsccStates.get(pred.getColumn())) {
767 bsccVal += pred.getValue() * expVisitTimes[pred.getColumn()];
768 }
769 }
770 }
771 }
772 return bsccReachProbs;
773}
774
778
779} // namespace helper
780} // namespace modelchecker
781} // namespace storm
SolverEnvironment & solver()
ModelCheckerEnvironment & modelchecker()
storm::RationalNumber const & getAperiodicFactor() const
storm::RationalNumber const & getPrecision() const
storm::solver::LraMethod const & getDetLraMethod() const
SteadyStateDistributionAlgorithm getSteadyStateDistributionAlgorithm() const
void setLinearEquationSolverType(storm::solver::EquationSolverType const &value, bool isSetFromDefault=false)
storm::solver::EquationSolverType const & getLinearEquationSolverType() const
void setLinearEquationSolverPrecision(boost::optional< storm::RationalNumber > const &newPrecision, boost::optional< bool > const &relativePrecision=boost::none)
LongRunAverageSolverEnvironment & lra()
std::pair< boost::optional< storm::RationalNumber >, boost::optional< bool > > getPrecisionOfLinearEquationSolver(storm::solver::EquationSolverType const &solverType) const
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.
Helper class for model checking queries that depend on the long run behavior of the (nondeterministic...
SparseDeterministicInfiniteHorizonHelper(storm::storage::SparseMatrix< ValueType > const &transitionMatrix)
Initializes the helper for a discrete time model (i.e.
ValueType computeLraForBsccVi(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::StronglyConnectedComponent const &bscc)
As computeLraForComponent but uses value iteration as a solution method (independent of what is set i...
std::pair< ValueType, std::vector< ValueType > > computeLraForBsccGainBias(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::StronglyConnectedComponent const &bscc)
As computeLraForComponent but solves a linear equation system encoding gain and bias (independent of ...
virtual ValueType computeLraForComponent(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::StronglyConnectedComponent const &component) override
std::pair< ValueType, std::vector< ValueType > > computeLraForBsccSteadyStateDistr(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::StronglyConnectedComponent const &bscc)
As computeLraForComponent but does the computation by computing the long run average (steady state) d...
std::pair< storm::storage::SparseMatrix< ValueType >, std::vector< ValueType > > buildSspMatrixVector(std::vector< ValueType > const &bsccLraValues, std::vector< uint64_t > const &inputStateToBsccIndexMap, storm::storage::BitVector const &statesNotInComponent, bool asEquationSystem)
std::pair< bool, ValueType > computeLraForTrivialBscc(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::StronglyConnectedComponent const &bscc)
SparseInfiniteHorizonHelper< ValueType, true >::ValueGetter ValueGetter
Function mapping from indices to values.
virtual std::vector< ValueType > buildAndSolveSsp(Environment const &env, std::vector< ValueType > const &mecLraValues) override
std::vector< ValueType > computeLongRunAverageStateDistribution(Environment const &env)
Computes the long run average state distribution, i.e., a vector that assigns for each state s the av...
std::vector< ValueType > computeSteadyStateDistrForBsccEVTs(Environment const &env, storm::storage::StronglyConnectedComponent const &bscc)
std::vector< ValueType > computeSteadyStateDistrForBscc(Environment const &env, storm::storage::StronglyConnectedComponent const &bscc)
Computes the long run average (steady state) distribution for the given BSCC.
std::vector< ValueType > computeBsccReachabilityProbabilitiesClassic(Environment const &env, ValueGetter const &initialDistributionGetter)
std::vector< ValueType > computeBsccReachabilityProbabilitiesEVTs(Environment const &env, ValueGetter const &initialDistributionGetter)
std::vector< ValueType > computeBsccReachabilityProbabilities(Environment const &env, ValueGetter const &initialDistributionGetter)
Computes for each BSCC the probability to reach that SCC assuming the given distribution over initial...
std::vector< ValueType > computeSteadyStateDistrForBsccEqSys(Environment const &env, storm::storage::StronglyConnectedComponent const &bscc)
Helper class for computing for each state the expected number of times to visit that state assuming a...
Helper class for model checking queries that depend on the long run behavior of the (nondeterministic...
Helper class that performs iterations of the value iteration method.
Definition LraViHelper.h:42
virtual std::unique_ptr< LinearEquationSolver< ValueType > > create(Environment const &env) const override
Creates an equation solver with the current settings, but without a matrix.
virtual LinearEquationSolverProblemFormat getEquationProblemFormat(Environment const &env) const
Retrieves the problem format that the solver expects if it was created with the current settings.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
const_reverse_iterator rbegin() const
Returns a reverse iterator to the indices of the set bits in the bit vector.
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
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 addDiagonalEntry(index_type row, ValueType const &value)
Makes sure that a diagonal entry will be inserted at the given row.
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.
std::size_t size() const
Retrieves the number of states in this SCC.
iterator begin()
Returns an iterator to the states in this SCC.
Definition StateBlock.cpp:5
iterator end()
Returns an iterator that points one past the end of the states in this SCC.
This class represents a strongly connected component, i.e., a set of states such that every state can...
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
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 * getComponentElementChoicesBegin(typename storm::storage::StronglyConnectedComponent::value_type const &element)
std::vector< ValueType > computeUpperBoundsForExpectedVisitingTimes(storm::storage::SparseMatrix< ValueType > const &nonBsccMatrix, std::vector< ValueType > const &toBsccProbabilities)
std::string toString(GurobiSolverMethod const &method)
Yields a string representation of the GurobiSolverMethod.
void scaleVectorInPlace(std::vector< ValueType1 > &target, ValueType2 const &factor)
Multiplies each element of the given vector with the given factor and writes the result into the vect...
Definition vector.h:447
bool isOne(ValueType const &a)
Definition constants.cpp:34
StronglyConnectedComponentDecompositionOptions & onlyBottomSccs(bool value=true)
Sets if only bottom SCCs, i.e. SCCs in which all states have no way of leaving the SCC),...