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