Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
StandardPcaaWeightVectorChecker.cpp
Go to the documentation of this file.
2
3#include <map>
4#include <set>
5
24#include "storm/utility/graph.h"
27
28namespace storm {
29namespace modelchecker {
30namespace multiobjective {
31
32template<class SparseModelType>
38
39template<class SparseModelType>
43 STORM_LOG_THROW(rewardAnalysis.rewardFinitenessType != preprocessing::RewardFinitenessType::Infinite, storm::exceptions::NotSupportedException,
44 "There is no Pareto optimal scheduler that yields finite reward for all objectives. This is not supported.");
46 "There might be infinite reward for some scheduler. Multi-objective model checking restricts to schedulers that yield finite reward "
47 "for all objectives. Be aware that solutions yielding infinite reward are discarded.");
48 STORM_LOG_THROW(rewardAnalysis.totalRewardLessInfinityEStates, storm::exceptions::UnexpectedException,
49 "The set of states with reward < infinity for some scheduler has not been computed during preprocessing.");
50 STORM_LOG_THROW(!preprocessorResult.containsRewardBoundedObjective(), storm::exceptions::NotSupportedException,
51 "At least one objective was not reduced to an expected (long run, total or cumulative) reward objective during preprocessing. This is not "
52 "supported by the considered weight vector checker.");
53 STORM_LOG_THROW(preprocessorResult.preprocessedModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException,
54 "The model has multiple initial states.");
56 // Build a subsystem of the preprocessor result model that discards states that yield infinite reward for all schedulers.
57 // We can also merge the states that will have reward zero anyway.
58 storm::storage::BitVector maybeStates = rewardAnalysis.totalRewardLessInfinityEStates.get() & ~rewardAnalysis.reward0AStates;
59 storm::storage::BitVector finiteTotalRewardChoices = preprocessorResult.preprocessedModel->getTransitionMatrix().getRowFilter(
60 rewardAnalysis.totalRewardLessInfinityEStates.get(), rewardAnalysis.totalRewardLessInfinityEStates.get());
61 std::set<std::string> relevantRewardModels;
62 for (auto const& obj : this->objectives) {
63 obj.formula->gatherReferencedRewardModels(relevantRewardModels);
64 }
66 auto mergerResult =
67 merger.mergeTargetAndSinkStates(maybeStates, rewardAnalysis.reward0AStates, storm::storage::BitVector(maybeStates.size(), false),
68 std::vector<std::string>(relevantRewardModels.begin(), relevantRewardModels.end()), finiteTotalRewardChoices);
69 goalStateMergerInputToReducedStateIndexMapping = std::move(mergerResult.oldToNewStateIndexMapping);
70 goalStateMergerReducedToInputChoiceMapping = mergerResult.keptChoices.getNumberOfSetBitsBeforeIndices();
71 // Initialize data specific for the considered model type
72 initializeModelTypeSpecificData(*mergerResult.model);
73
74 // Initilize general data of the model
75 transitionMatrix = std::move(mergerResult.model->getTransitionMatrix());
76 initialState = *mergerResult.model->getInitialStates().begin();
77 totalReward0EStates = rewardAnalysis.totalReward0EStates % maybeStates;
78 if (mergerResult.targetState) {
79 // There is an additional state in the result
80 totalReward0EStates.resize(totalReward0EStates.size() + 1, true);
81
82 // The overapproximation for the possible ec choices consists of the states that can reach the target states with prob. 0 and the target state itself.
83 storm::storage::BitVector targetStateAsVector(transitionMatrix.getRowGroupCount(), false);
84 targetStateAsVector.set(*mergerResult.targetState, true);
87 storm::storage::BitVector(targetStateAsVector.size(), true), targetStateAsVector));
88 ecChoicesHint.set(transitionMatrix.getRowGroupIndices()[*mergerResult.targetState], true);
89 } else {
90 ecChoicesHint = storm::storage::BitVector(transitionMatrix.getRowCount(), true);
91 }
92
93 // set data for unbounded objectives
94 lraObjectives = storm::storage::BitVector(this->objectives.size(), false);
95 objectivesWithNoUpperTimeBound = storm::storage::BitVector(this->objectives.size(), false);
96 actionsWithoutRewardInUnboundedPhase = storm::storage::BitVector(transitionMatrix.getRowCount(), true);
97 for (uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
98 auto const& formula = *this->objectives[objIndex].formula;
99 if (formula.getSubformula().isTotalRewardFormula()) {
100 objectivesWithNoUpperTimeBound.set(objIndex, true);
101 actionsWithoutRewardInUnboundedPhase &= storm::utility::vector::filterZero(actionRewards[objIndex]);
102 }
103 if (formula.getSubformula().isLongRunAverageRewardFormula()) {
104 lraObjectives.set(objIndex, true);
105 objectivesWithNoUpperTimeBound.set(objIndex, true);
106 }
107 }
109 // Set data for LRA objectives (if available)
110 if (!lraObjectives.empty()) {
111 lraMecDecomposition = LraMecDecomposition();
113 transitionMatrix, transitionMatrix.transpose(true), storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true),
115 lraMecDecomposition->auxMecValues.resize(lraMecDecomposition->mecs.size());
116 }
118 // initialize data for the results
119 checkHasBeenCalled = false;
120 objectiveResults.resize(this->objectives.size());
121 offsetsToAchievablePoint.resize(this->objectives.size(), storm::utility::zero<ValueType>());
123 optimalChoices.resize(transitionMatrix.getRowGroupCount(), 0);
125 STORM_LOG_STATISTICS("Weight Vector Checker Statistics:\n");
126 STORM_LOG_STATISTICS("Final preprocessed model has " << transitionMatrix.getRowGroupCount() << " states.\n");
127 STORM_LOG_STATISTICS("Final preprocessed model has " << transitionMatrix.getRowCount() << " actions.\n");
129 STORM_LOG_STATISTICS("Found " << lraMecDecomposition->mecs.size() << " end components that are relevant for LRA-analysis.\n");
130 uint64_t numLraMecStates = 0;
131 for (auto const& mec : this->lraMecDecomposition->mecs) {
132 numLraMecStates += mec.size();
133 }
134 STORM_LOG_STATISTICS(numLraMecStates << " states lie on such an end component.\n");
135 }
137}
138
139template<class SparseModelType>
140void StandardPcaaWeightVectorChecker<SparseModelType>::check(Environment const& env, std::vector<ValueType> weightVector) {
141 // See https://doi.org/10.18154/RWTH-2023-09669 Algorithm 4.2
142 STORM_LOG_INFO("Invoked WeightVectorChecker with weights \n"
144 STORM_LOG_THROW(std::any_of(weightVector.begin(), weightVector.end(), [](auto const& w_i) { return !storm::utility::isZero(w_i); }),
145 storm::exceptions::InvalidOperationException, "Weight vector must not be the zero vector.");
146 checkHasBeenCalled = true;
147 // Normalize weights so the vector has length 1
148 // This is necessary for ensuring the required accuracy, i.e. distance between halfspace induced by weightedSum and weightvector and achievable point.
149 ValueType const inputWeightVectorLength = storm::utility::sqrt(storm::utility::vector::dotProduct(weightVector, weightVector));
151
152 // Prepare and invoke weighted infinite horizon (long run average) phase
153 std::vector<ValueType> weightedRewardVector(transitionMatrix.getRowCount(), storm::utility::zero<ValueType>());
154 if (!lraObjectives.empty()) {
155 boost::optional<std::vector<ValueType>> weightedStateRewardVector;
156 for (uint64_t objIndex : lraObjectives) {
157 ValueType weight =
158 storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType()) ? -weightVector[objIndex] : weightVector[objIndex];
159 storm::utility::vector::addScaledVector(weightedRewardVector, actionRewards[objIndex], weight);
160 if (!stateRewards.empty() && !stateRewards[objIndex].empty()) {
161 if (!weightedStateRewardVector) {
162 weightedStateRewardVector = std::vector<ValueType>(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
163 }
164 storm::utility::vector::addScaledVector(weightedStateRewardVector.get(), stateRewards[objIndex], weight);
165 }
166 }
167 infiniteHorizonWeightedPhase(env, weightedRewardVector, weightedStateRewardVector, weightVector);
168 // Clear all values of the weighted reward vector
169 weightedRewardVector.assign(weightedRewardVector.size(), storm::utility::zero<ValueType>());
170 }
171
172 // Prepare and invoke weighted indefinite horizon (unbounded total reward) phase
173 auto totalRewardObjectives = objectivesWithNoUpperTimeBound & ~lraObjectives;
174 for (uint64_t objIndex : totalRewardObjectives) {
175 if (storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType())) {
176 storm::utility::vector::addScaledVector(weightedRewardVector, actionRewards[objIndex], -weightVector[objIndex]);
177 } else {
178 storm::utility::vector::addScaledVector(weightedRewardVector, actionRewards[objIndex], weightVector[objIndex]);
179 }
180 }
181 unboundedWeightedPhase(env, weightedRewardVector, weightVector);
182
183 unboundedIndividualPhase(env, weightVector);
184 // Only invoke boundedPhase if necessarry, i.e., if there is at least one objective with a time bound
185 for (auto const& obj : this->objectives) {
186 if (!obj.formula->getSubformula().isTotalRewardFormula() && !obj.formula->getSubformula().isLongRunAverageRewardFormula()) {
187 boundedPhase(env, weightVector, weightedRewardVector);
188 break;
189 }
190 }
191 STORM_LOG_INFO("Weight vector check done. Lower bounds for results in initial state: "
193 // Validate that the results are sufficiently precise
195 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
196 weightedSum += (storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType()) ? -weightVector[objIndex] : weightVector[objIndex]) *
197 getAchievablePoint()[objIndex];
198 }
199 ValueType resultingWeightedPrecision = storm::utility::abs<ValueType>(getOptimalWeightedSum() - weightedSum);
200 // Since the weight vector is normalized (has length 1), the resultingWeightedPrecision coincides with the distance between over- and under-approximaiton
201 STORM_LOG_WARN_COND(resultingWeightedPrecision <= this->getWeightedPrecision() + storm::utility::convertNumber<ValueType>(1e-10),
202 "The desired precision was not reached: resulting precision "
203 << resultingWeightedPrecision << " exceeds specified value " << this->getWeightedPrecision() << " by approx. "
204 << (storm::utility::convertNumber<double, ValueType>(resultingWeightedPrecision - this->getWeightedPrecision()))
206 << ".");
207 if (!storm::utility::isOne(inputWeightVectorLength)) {
208 // reverse the normalization of the weight vector for the returned optimal weighted sum.
210 offsetToWeightedSum *= inputWeightVectorLength;
211 }
212}
213
214template<class SparseModelType>
215std::vector<typename StandardPcaaWeightVectorChecker<SparseModelType>::ValueType> StandardPcaaWeightVectorChecker<SparseModelType>::getAchievablePoint() const {
216 STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::InvalidOperationException, "Tried to retrieve results but check(..) has not been called before.");
217 std::vector<ValueType> res;
218 res.reserve(this->objectives.size());
219 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
220 res.push_back(this->objectives[objIndex].clipResult(this->objectiveResults[objIndex][initialState] + this->offsetsToAchievablePoint[objIndex]));
221 }
222 return res;
223}
224
225template<class SparseModelType>
227 STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::InvalidOperationException, "Tried to retrieve results but check(..) has not been called before.");
228 return this->weightedResult[initialState] + this->offsetToWeightedSum;
229}
230
231template<class SparseModelType>
234 STORM_LOG_THROW(this->checkHasBeenCalled, storm::exceptions::InvalidOperationException,
235 "Tried to retrieve results but check(..) has not been called before.");
236 for (auto const& obj : this->objectives) {
237 STORM_LOG_THROW(obj.formula->getSubformula().isTotalRewardFormula() || obj.formula->getSubformula().isLongRunAverageRewardFormula(),
238 storm::exceptions::NotImplementedException, "Scheduler retrival is only implemented for objectives without time-bound.");
239 }
240 auto const numStatesOfInputModel = goalStateMergerInputToReducedStateIndexMapping.size();
241 storm::storage::Scheduler<ValueType> result(numStatesOfInputModel);
242 for (uint64_t inputModelState = 0; inputModelState < numStatesOfInputModel; ++inputModelState) {
243 auto const reducedModelState = goalStateMergerInputToReducedStateIndexMapping[inputModelState];
244 if (reducedModelState >= optimalChoices.size()) {
245 // This state is a "reward0AState", i.e., it has no reward for any scheduler. We can set an arbitrary choice here.
246 result.setChoice(0, inputModelState);
247 } else {
248 auto const reducedModelChoice = optimalChoices[reducedModelState];
249 auto const inputModelChoice = goalStateMergerReducedToInputChoiceMapping[reducedModelChoice];
250 result.setChoice(inputModelChoice, inputModelState);
251 }
252 }
253 return result;
254}
255
256template<typename ValueType>
258 storm::storage::BitVector const& consideredStates, storm::storage::BitVector const& statesToReach, std::vector<uint64_t>& choices,
259 storm::storage::BitVector const* allowedChoices = nullptr) {
260 std::vector<uint64_t> stack;
261 storm::storage::BitVector processedStates = statesToReach;
262 stack.insert(stack.end(), processedStates.begin(), processedStates.end());
263 uint64_t currentState = 0;
264
265 while (!stack.empty()) {
266 currentState = stack.back();
267 stack.pop_back();
268
269 for (auto const& predecessorEntry : backwardTransitions.getRow(currentState)) {
270 auto predecessor = predecessorEntry.getColumn();
271 if (consideredStates.get(predecessor) && !processedStates.get(predecessor)) {
272 // Find a choice leading to an already processed state (such a choice has to exist since this is a predecessor of the currentState)
273 auto const& groupStart = transitionMatrix.getRowGroupIndices()[predecessor];
274 auto const& groupEnd = transitionMatrix.getRowGroupIndices()[predecessor + 1];
275 uint64_t row = allowedChoices ? allowedChoices->getNextSetIndex(groupStart) : groupStart;
276 for (; row < groupEnd; row = allowedChoices ? allowedChoices->getNextSetIndex(row + 1) : row + 1) {
277 bool hasSuccessorInProcessedStates = false;
278 for (auto const& successorOfPredecessor : transitionMatrix.getRow(row)) {
279 if (processedStates.get(successorOfPredecessor.getColumn())) {
280 hasSuccessorInProcessedStates = true;
281 break;
282 }
283 }
284 if (hasSuccessorInProcessedStates) {
285 choices[predecessor] = row - groupStart;
286 processedStates.set(predecessor, true);
287 stack.push_back(predecessor);
288 break;
289 }
290 }
291 STORM_LOG_ASSERT(allowedChoices || row < groupEnd,
292 "Unable to find choice at a predecessor of a processed state that leads to a processed state.");
293 }
294 }
295 }
296 STORM_LOG_ASSERT(consideredStates.isSubsetOf(processedStates), "Not all states have been processed.");
297}
298
299template<typename ValueType>
301 storm::storage::BitVector const& consideredStates, storm::storage::BitVector const& statesToAvoid,
302 storm::storage::BitVector const& allowedChoices, std::vector<uint64_t>& choices) {
303 for (uint64_t state : consideredStates) {
304 auto const& groupStart = transitionMatrix.getRowGroupIndices()[state];
305 auto const& groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
306 bool choiceFound = false;
307 for (uint64_t row = allowedChoices.getNextSetIndex(groupStart); row < groupEnd; row = allowedChoices.getNextSetIndex(row + 1)) {
308 choiceFound = true;
309 for (auto const& element : transitionMatrix.getRow(row)) {
310 if (statesToAvoid.get(element.getColumn())) {
311 choiceFound = false;
312 break;
313 }
314 }
315 if (choiceFound) {
316 choices[state] = row - groupStart;
317 break;
318 }
319 }
320 STORM_LOG_ASSERT(choiceFound, "Unable to find choice for a state.");
321 }
322}
323
324template<typename ValueType>
325std::vector<uint64_t> computeValidInitialScheduler(storm::storage::SparseMatrix<ValueType> const& matrix, storm::storage::BitVector const& rowsWithSumLessOne) {
326 std::vector<uint64_t> result(matrix.getRowGroupCount());
327 auto const& groups = matrix.getRowGroupIndices();
328 auto backwardsTransitions = matrix.transpose(true);
329 storm::storage::BitVector processedStates(result.size(), false);
330 for (uint64_t state = 0; state < result.size(); ++state) {
331 if (rowsWithSumLessOne.getNextSetIndex(groups[state]) < groups[state + 1]) {
332 result[state] = rowsWithSumLessOne.getNextSetIndex(groups[state]) - groups[state];
333 processedStates.set(state, true);
334 }
335 }
336
337 computeSchedulerProb1(matrix, backwardsTransitions, ~processedStates, processedStates, result);
338 return result;
339}
340
346template<typename ValueType>
348 storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& finitelyOftenChoices,
349 storm::storage::BitVector safeStates, std::vector<uint64_t>& choices) {
350 auto badStates = transitionMatrix.getRowGroupFilter(finitelyOftenChoices, true) & ~safeStates;
351 // badStates shall only be reached finitely often
352
353 auto reachBadWithProbGreater0AStates = storm::utility::graph::performProbGreater0A(
354 transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, ~safeStates, badStates, false, 0, ~finitelyOftenChoices);
355 // States in ~reachBadWithProbGreater0AStates can avoid bad states forever by only taking ~finitelyOftenChoices.
356 // We compute a scheduler for these states achieving exactly this (but we exclude the safe states)
357 auto avoidBadStates = ~reachBadWithProbGreater0AStates & ~safeStates;
358 computeSchedulerProb0(transitionMatrix, backwardTransitions, avoidBadStates, reachBadWithProbGreater0AStates, ~finitelyOftenChoices, choices);
359
360 // We need to take care of states that will reach a bad state with prob greater 0 (including the bad states themselves).
361 // due to the precondition, we know that it has to be possible to eventually avoid the bad states for ever.
362 // Perform a backwards search from the avoid states and store choices with prob. 1
363 computeSchedulerProb1(transitionMatrix, backwardTransitions, reachBadWithProbGreater0AStates, avoidBadStates | safeStates, choices);
364}
365
366template<class SparseModelType>
368 std::vector<ValueType> const& weightedActionRewardVector,
369 boost::optional<std::vector<ValueType>> const& weightedStateRewardVector,
370 std::vector<ValueType> const& weightVector) {
371 auto solverEnv = inputEnv;
372 // see epsilon in https://doi.org/10.18154/RWTH-2023-09669 Algorithm 5.2
375 // We want to compute a value v_C for each MEC C that upper bounds the true MEC value and is also epsilon/2 close to it.
376 // We therefore compute a value that is epsilon/4 close to it and then add epsilon/4 as offset below.
377 ValueType const offset = epsilon / storm::utility::convertNumber<ValueType>(4.0);
378 solverEnv.solver().lra().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(offset));
379 solverEnv.solver().lra().setRelativeTerminationCriterion(false);
380 // Compute the optimal (weighted) lra value for each mec, keeping track of the optimal choices
381 STORM_LOG_ASSERT(lraMecDecomposition, "Mec decomposition for lra computations not initialized.");
383 helper.provideLongRunComponentDecomposition(lraMecDecomposition->mecs);
384 helper.setOptimizationDirection(storm::solver::OptimizationDirection::Maximize);
385 helper.setProduceScheduler(true);
386 for (uint64_t mecIndex = 0; mecIndex < lraMecDecomposition->mecs.size(); ++mecIndex) {
387 auto const& mec = lraMecDecomposition->mecs[mecIndex];
388 auto actionValueGetter = [&weightedActionRewardVector](uint64_t const& a) { return weightedActionRewardVector[a]; };
390 if (weightedStateRewardVector) {
391 stateValueGetter = [&weightedStateRewardVector](uint64_t const& s) { return weightedStateRewardVector.get()[s]; };
392 } else {
393 stateValueGetter = [](uint64_t const&) { return storm::utility::zero<ValueType>(); };
394 }
395 lraMecDecomposition->auxMecValues[mecIndex] = helper.computeLraForComponent(solverEnv, stateValueGetter, actionValueGetter, mec) + offset;
396 }
397 // Extract the produced optimal choices for the MECs
398 this->optimalChoices = std::move(helper.getProducedOptimalChoices());
399}
400
401template<class SparseModelType>
402void StandardPcaaWeightVectorChecker<SparseModelType>::unboundedWeightedPhase(Environment const& inputEnv, std::vector<ValueType> const& weightedRewardVector,
403 std::vector<ValueType> const& weightVector) {
405 auto solverEnv = inputEnv;
406 solverEnv.solver().minMax().setRelativeTerminationCriterion(false);
407 solverEnv.solver().lra().setRelativeTerminationCriterion(false);
408 bool const requireSoundApproximation = !solverEnv.solver().isForceExact() && solverEnv.solver().isForceSoundness();
410 // see epsilon in https://doi.org/10.18154/RWTH-2023-09669 Algorithm 4.2
411 ValueType adjustedPrecision =
413 if (solverEnv.solver().isForceExact()) {
414 // If we are already using an exact solver, we consider the precision to be zero
415 adjustedPrecision = storm::utility::zero<ValueType>();
416 } else if (requireSoundApproximation) {
417 adjustedPrecision /= two; // need to be more precise to get a correct and sufficiently tight upper bound on the weighted sum
418 }
419 if (lraObjectives.empty()) {
420 solverEnv.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(adjustedPrecision));
421 } else {
422 // need to be more precise to distribute the approximation error between lra and total reward phase
423 solverEnv.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber, ValueType>(adjustedPrecision / two));
424 solverEnv.solver().lra().setPrecision(storm::utility::convertNumber<storm::RationalNumber, ValueType>(adjustedPrecision / two));
425 }
426
427 // Catch the case where all values on the RHS of the MinMax equation system are zero.
428 if (this->objectivesWithNoUpperTimeBound.empty() ||
429 ((this->lraObjectives.empty() || !storm::utility::vector::hasNonZeroEntry(lraMecDecomposition->auxMecValues)) &&
430 !storm::utility::vector::hasNonZeroEntry(weightedRewardVector))) {
431 this->weightedResult.assign(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
432 storm::storage::BitVector statesInLraMec(transitionMatrix.getRowGroupCount(), false);
433 if (this->lraMecDecomposition) {
434 for (auto const& mec : this->lraMecDecomposition->mecs) {
435 for (auto const& sc : mec) {
436 statesInLraMec.set(sc.first, true);
437 }
438 }
439 }
440 // Get an arbitrary scheduler that yields finite reward for all objectives
442 this->optimalChoices);
443 return;
444 }
445
446 updateEcQuotient(weightedRewardVector);
447
448 // Set up the choice values
449 storm::utility::vector::selectVectorValues(ecQuotient->auxChoiceValues, ecQuotient->ecqToOriginalChoiceMapping, weightedRewardVector);
450 std::map<uint64_t, uint64_t> ecqStateToOptimalMecMap;
451 if (!lraObjectives.empty()) {
452 // We also need to assign a value for each ecQuotientChoice that corresponds to "staying" in the eliminated EC. (at this point these choices should all
453 // have a value of zero). Since each of the eliminated ECs has to contain *at least* one LRA EC, we need to find the largest value among the contained
454 // LRA ECs
455 storm::storage::BitVector foundEcqChoices(ecQuotient->matrix.getRowCount(), false); // keeps track of choices we have already seen before
456 for (uint64_t mecIndex = 0; mecIndex < lraMecDecomposition->mecs.size(); ++mecIndex) {
457 auto const& mec = lraMecDecomposition->mecs[mecIndex];
458 auto const& mecValue = lraMecDecomposition->auxMecValues[mecIndex];
459 uint64_t ecqState = ecQuotient->originalToEcqStateMapping[mec.begin()->first];
460 if (ecqState >= ecQuotient->matrix.getRowGroupCount()) {
461 // The mec was not part of the ecquotient. This means that it must have value 0.
462 // No further processing is needed.
463 continue;
464 }
465 uint64_t ecqChoice = ecQuotient->ecqStayInEcChoices.getNextSetIndex(ecQuotient->matrix.getRowGroupIndices()[ecqState]);
466 STORM_LOG_ASSERT(ecqChoice < ecQuotient->matrix.getRowGroupIndices()[ecqState + 1],
467 "Unable to find choice that represents staying inside the (eliminated) ec.");
468 auto& ecqChoiceValue = ecQuotient->auxChoiceValues[ecqChoice];
469 auto insertionRes = ecqStateToOptimalMecMap.emplace(ecqState, mecIndex);
470 if (insertionRes.second) {
471 // We have seen this ecqState for the first time.
473 "Expected a total reward of zero for choices that represent staying in an EC for ever.");
474 ecqChoiceValue = mecValue;
475 } else {
476 if (mecValue > ecqChoiceValue) { // found a larger value
477 ecqChoiceValue = mecValue;
478 insertionRes.first->second = mecIndex;
479 }
480 }
481 }
482 }
483
484 std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> solver = solverFactory.create(solverEnv, ecQuotient->matrix);
485 solver->setTrackScheduler(true);
486 solver->setHasUniqueSolution(true);
487 solver->setOptimizationDirection(storm::solver::OptimizationDirection::Maximize);
488 auto req = solver->getRequirements(solverEnv, storm::solver::OptimizationDirection::Maximize);
489 setBoundsToSolver(*solver, req.lowerBounds(), req.upperBounds(), weightVector, objectivesWithNoUpperTimeBound, ecQuotient->matrix,
490 ecQuotient->rowsWithSumLessOne, ecQuotient->auxChoiceValues);
491 if (solver->hasLowerBound()) {
492 req.clearLowerBounds();
493 }
494 if (solver->hasUpperBound()) {
495 req.clearUpperBounds();
496 }
497 if (req.validInitialScheduler()) {
498 solver->setInitialScheduler(computeValidInitialScheduler(ecQuotient->matrix, ecQuotient->rowsWithSumLessOne));
499 req.clearValidInitialScheduler();
500 }
501 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
502 "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked.");
503 solver->setRequirementsChecked(true);
504
505 // Use the (0...0) vector as initial guess for the solution.
506 std::fill(ecQuotient->auxStateValues.begin(), ecQuotient->auxStateValues.end(), storm::utility::zero<ValueType>());
507
508 solver->solveEquations(solverEnv, ecQuotient->auxStateValues, ecQuotient->auxChoiceValues);
509 this->weightedResult = std::vector<ValueType>(transitionMatrix.getRowGroupCount());
510
511 transformEcqSolutionToOriginalModel(ecQuotient->auxStateValues, solver->getSchedulerChoices(), ecqStateToOptimalMecMap, this->weightedResult,
512 this->optimalChoices);
513
514 // Add offset to ensure that we have an upper bound on the true optimal value
515 offsetToWeightedSum = requireSoundApproximation ? adjustedPrecision : storm::utility::zero<ValueType>();
516}
517
518template<class SparseModelType>
519void StandardPcaaWeightVectorChecker<SparseModelType>::unboundedIndividualPhase(Environment const& inputEnv, std::vector<ValueType> const& weightVector) {
520 auto solverEnv = inputEnv;
521 storm::storage::SparseMatrix<ValueType> deterministicMatrix = transitionMatrix.selectRowsFromRowGroups(this->optimalChoices, false);
522 storm::storage::SparseMatrix<ValueType> deterministicBackwardTransitions = deterministicMatrix.transpose();
523 std::vector<ValueType> deterministicStateRewards(deterministicMatrix.getRowCount()); // allocate here
525 bool const requireSoundApproximation = !solverEnv.solver().isForceExact() && solverEnv.solver().isForceSoundness();
526 // see epsilon and epsilon_j in https://doi.org/10.18154/RWTH-2023-09669 Algorithm 4.2
529 if (solverEnv.solver().isForceExact()) {
530 // If we are already using an exact solver, we consider the precision to be zero
532 } else if (requireSoundApproximation) {
533 epsilon /= two; // need to be more precise to get a correct and sufficiently tight achievable value
534 }
535
536 auto infiniteHorizonHelper = createDetInfiniteHorizonHelper(deterministicMatrix);
537 infiniteHorizonHelper.provideBackwardTransitions(deterministicBackwardTransitions);
538
539 // We compute an estimate for the results of the individual objectives which is obtained from the weighted result and the result of the objectives
540 // computed so far. Note that weightedResult = Sum_{i=1}^{n} w_i * objectiveResult_i.
541 std::vector<ValueType> weightedSumOfUncheckedObjectives = weightedResult;
542 ValueType sumOfWeightsOfUncheckedObjectives = storm::utility::vector::sum_if(weightVector, objectivesWithNoUpperTimeBound);
543
544 for (uint_fast64_t const& objIndex : storm::utility::vector::getSortedIndices(weightVector)) {
545 auto const& obj = this->objectives[objIndex];
546 if (objectivesWithNoUpperTimeBound.get(objIndex)) {
548 if (!storm::utility::isZero(weightVector[objIndex])) {
549 epsilon_j /= storm::utility::abs(weightVector[objIndex]);
550 }
551 solverEnv.solver().setLinearEquationSolverPrecision(storm::utility::convertNumber<RationalNumber>(epsilon_j), false);
552 solverEnv.solver().lra().setPrecision(storm::utility::convertNumber<RationalNumber>(epsilon_j));
553 solverEnv.solver().lra().setRelativeTerminationCriterion(false);
554
555 if (lraObjectives.get(objIndex)) {
556 auto actionValueGetter = [&](uint64_t const& a) {
557 return actionRewards[objIndex][transitionMatrix.getRowGroupIndices()[a] + this->optimalChoices[a]];
558 };
560 if (stateRewards.empty() || stateRewards[objIndex].empty()) {
561 stateValueGetter = [](uint64_t const&) { return storm::utility::zero<ValueType>(); };
562 } else {
563 stateValueGetter = [&](uint64_t const& s) { return stateRewards[objIndex][s]; };
564 }
565 objectiveResults[objIndex] = infiniteHorizonHelper.computeLongRunAverageValues(solverEnv, stateValueGetter, actionValueGetter);
566 } else { // i.e. a total reward objective
567 storm::utility::vector::selectVectorValues(deterministicStateRewards, this->optimalChoices, transitionMatrix.getRowGroupIndices(),
568 actionRewards[objIndex]);
569 storm::storage::BitVector statesWithRewards = ~storm::utility::vector::filterZero(deterministicStateRewards);
570 // As maybestates we pick the states from which a state with reward is reachable
572 deterministicBackwardTransitions, storm::storage::BitVector(deterministicMatrix.getRowCount(), true), statesWithRewards);
573
574 // Compute the estimate for this objective
575 if (!storm::utility::isZero(weightVector[objIndex]) && !storm::utility::isZero(sumOfWeightsOfUncheckedObjectives)) {
576 objectiveResults[objIndex] = weightedSumOfUncheckedObjectives;
577 ValueType scalingFactor = storm::utility::one<ValueType>() / sumOfWeightsOfUncheckedObjectives;
578 if (storm::solver::minimize(obj.formula->getOptimalityType())) {
579 scalingFactor *= -storm::utility::one<ValueType>();
580 }
582 storm::utility::vector::clip(objectiveResults[objIndex], obj.lowerResultBound, obj.upperResultBound);
583 }
584 // Make sure that the objectiveResult is initialized correctly
585 objectiveResults[objIndex].resize(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
586
587 if (!maybeStates.empty()) {
588 bool needEquationSystem =
590 storm::storage::SparseMatrix<ValueType> submatrix = deterministicMatrix.getSubmatrix(true, maybeStates, maybeStates, needEquationSystem);
591 if (needEquationSystem) {
592 // Converting the matrix from the fixpoint notation to the form needed for the equation
593 // system. That is, we go from x = A*x + b to (I-A)x = b.
594 submatrix.convertToEquationSystem();
595 }
596
597 // Prepare solution vector and rhs of the equation system.
598 std::vector<ValueType> x = storm::utility::vector::filterVector(objectiveResults[objIndex], maybeStates);
599 std::vector<ValueType> b = storm::utility::vector::filterVector(deterministicStateRewards, maybeStates);
600
601 // Now solve the resulting equation system.
602 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver = linearEquationSolverFactory.create(solverEnv, submatrix);
603 auto req = solver->getRequirements(solverEnv);
604 solver->clearBounds();
605 storm::storage::BitVector submatrixRowsWithSumLessOne = deterministicMatrix.getRowFilter(maybeStates, maybeStates) % maybeStates;
606 submatrixRowsWithSumLessOne.complement();
607 this->setBoundsToSolver(*solver, req.lowerBounds(), req.upperBounds(), objIndex, submatrix, submatrixRowsWithSumLessOne, b);
608 if (solver->hasLowerBound()) {
609 req.clearLowerBounds();
610 }
611 if (solver->hasUpperBound()) {
612 req.clearUpperBounds();
613 }
614 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
615 "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked.");
616 solver->solveEquations(solverEnv, x, b);
617 if (requireSoundApproximation) {
618 // add offsets to ensure that we have an upper/lower bound on the true optimal value
619 offsetsToAchievablePoint[objIndex] =
620 storm::solver::maximize(this->objectives[objIndex].formula->getOptimalityType()) ? -epsilon_j : epsilon_j;
621 }
622 // Set the result for this objective accordingly
624 }
626 }
627 // Update the estimate for the next objectives.
628 if (!storm::utility::isZero(weightVector[objIndex])) {
629 storm::utility::vector::addScaledVector(weightedSumOfUncheckedObjectives, objectiveResults[objIndex], -weightVector[objIndex]);
630 sumOfWeightsOfUncheckedObjectives -= weightVector[objIndex];
631 }
632 } else {
633 // Other objectives will be computed in bounded phase.
634 objectiveResults[objIndex] = std::vector<ValueType>(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
635 }
636 }
637}
638
639template<class SparseModelType>
640void StandardPcaaWeightVectorChecker<SparseModelType>::updateEcQuotient(std::vector<ValueType> const& weightedRewardVector) {
641 // Check whether we need to update the currently cached ecElimResult
642 storm::storage::BitVector newTotalReward0Choices = storm::utility::vector::filterZero(weightedRewardVector);
643 storm::storage::BitVector zeroLraRewardChoices(weightedRewardVector.size(), true);
645 for (uint64_t mecIndex = 0; mecIndex < lraMecDecomposition->mecs.size(); ++mecIndex) {
646 if (!storm::utility::isZero(lraMecDecomposition->auxMecValues[mecIndex])) {
647 // The mec has a non-zero value, so flag all its choices as non-zero
648 auto const& mec = lraMecDecomposition->mecs[mecIndex];
649 for (auto const& stateChoices : mec) {
650 for (auto const& choice : stateChoices.second) {
651 zeroLraRewardChoices.set(choice, false);
652 }
653 }
654 }
655 }
656 }
657 storm::storage::BitVector newReward0Choices = newTotalReward0Choices & zeroLraRewardChoices;
658 if (!ecQuotient || ecQuotient->origReward0Choices != newReward0Choices) {
659 // It is sufficient to consider the states from which a transition with non-zero reward is reachable. (The remaining states always have reward zero).
660 auto nonZeroRewardStates = transitionMatrix.getRowGroupFilter(newReward0Choices, true);
661 nonZeroRewardStates.complement();
663 transitionMatrix.transpose(true), storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true), nonZeroRewardStates);
664
665 // Remove neutral end components, i.e., ECs in which no total reward is earned.
666 // Note that such ECs contain one (or maybe more) LRA ECs.
668 ecChoicesHint & newTotalReward0Choices, totalReward0EStates);
669
670 storm::storage::BitVector rowsWithSumLessOne(ecElimResult.matrix.getRowCount(), false);
671 for (uint64_t row = 0; row < rowsWithSumLessOne.size(); ++row) {
672 if (ecElimResult.matrix.getRow(row).getNumberOfEntries() == 0) {
673 rowsWithSumLessOne.set(row, true);
674 } else {
675 for (auto const& entry : transitionMatrix.getRow(ecElimResult.newToOldRowMapping[row])) {
676 if (!subsystemStates.get(entry.getColumn())) {
677 rowsWithSumLessOne.set(row, true);
678 break;
679 }
680 }
681 }
682 }
683
685 ecQuotient->matrix = std::move(ecElimResult.matrix);
686 ecQuotient->ecqToOriginalChoiceMapping = std::move(ecElimResult.newToOldRowMapping);
687 ecQuotient->originalToEcqStateMapping = std::move(ecElimResult.oldToNewStateMapping);
688 ecQuotient->ecqToOriginalStateMapping.resize(ecQuotient->matrix.getRowGroupCount());
689 for (uint64_t state = 0; state < ecQuotient->originalToEcqStateMapping.size(); ++state) {
690 uint64_t ecqState = ecQuotient->originalToEcqStateMapping[state];
691 if (ecqState < ecQuotient->matrix.getRowGroupCount()) {
692 ecQuotient->ecqToOriginalStateMapping[ecqState].insert(state);
693 }
694 }
695 ecQuotient->ecqStayInEcChoices = std::move(ecElimResult.sinkRows);
696 ecQuotient->origReward0Choices = std::move(newReward0Choices);
697 ecQuotient->origTotalReward0Choices = std::move(newTotalReward0Choices);
698 ecQuotient->rowsWithSumLessOne = std::move(rowsWithSumLessOne);
699 ecQuotient->auxStateValues.resize(ecQuotient->matrix.getRowGroupCount());
700 ecQuotient->auxChoiceValues.resize(ecQuotient->matrix.getRowCount());
701 }
702}
703
704template<class SparseModelType>
706 bool requiresUpper, uint64_t objIndex,
708 storm::storage::BitVector const& rowsWithSumLessOne,
709 std::vector<ValueType> const& rewards) const {
710 // Check whether bounds are already available
711 if (this->objectives[objIndex].lowerResultBound) {
712 solver.setLowerBound(this->objectives[objIndex].lowerResultBound.get());
713 }
714 if (this->objectives[objIndex].upperResultBound) {
715 solver.setUpperBound(this->objectives[objIndex].upperResultBound.get());
716 }
717
718 if ((requiresLower && !solver.hasLowerBound()) || (requiresUpper && !solver.hasUpperBound())) {
719 computeAndSetBoundsToSolver(solver, requiresLower, requiresUpper, transitions, rowsWithSumLessOne, rewards);
720 }
721}
722
723template<class SparseModelType>
725 bool requiresUpper, std::vector<ValueType> const& weightVector,
726 storm::storage::BitVector const& objectiveFilter,
728 storm::storage::BitVector const& rowsWithSumLessOne,
729 std::vector<ValueType> const& rewards) const {
730 // Check whether bounds are already available
731 boost::optional<ValueType> lowerBound = this->computeWeightedResultBound(true, weightVector, objectiveFilter & ~lraObjectives);
732 if (lowerBound) {
733 if (!lraObjectives.empty()) {
734 auto min = std::min_element(lraMecDecomposition->auxMecValues.begin(), lraMecDecomposition->auxMecValues.end());
735 if (min != lraMecDecomposition->auxMecValues.end()) {
736 lowerBound.get() += *min;
737 }
738 }
739 solver.setLowerBound(lowerBound.get());
740 }
741 boost::optional<ValueType> upperBound = this->computeWeightedResultBound(false, weightVector, objectiveFilter);
742 if (upperBound) {
743 if (!lraObjectives.empty()) {
744 auto max = std::max_element(lraMecDecomposition->auxMecValues.begin(), lraMecDecomposition->auxMecValues.end());
745 if (max != lraMecDecomposition->auxMecValues.end()) {
746 upperBound.get() += *max;
747 }
748 }
749 solver.setUpperBound(upperBound.get());
750 }
751
752 if ((requiresLower && !solver.hasLowerBound()) || (requiresUpper && !solver.hasUpperBound())) {
753 computeAndSetBoundsToSolver(solver, requiresLower, requiresUpper, transitions, rowsWithSumLessOne, rewards);
754 }
755}
756
757template<class SparseModelType>
759 bool requiresUpper,
761 storm::storage::BitVector const& rowsWithSumLessOne,
762 std::vector<ValueType> const& rewards) const {
763 // Compute the one step target probs
764 std::vector<ValueType> oneStepTargetProbs(transitions.getRowCount(), storm::utility::zero<ValueType>());
765 for (uint64_t row : rowsWithSumLessOne) {
766 oneStepTargetProbs[row] = storm::utility::one<ValueType>() - transitions.getRowSum(row);
767 }
768
769 bool hasNegativeReward = false;
770 bool hasPositiveReward = false;
771 for (auto const& rew : rewards) {
773 hasNegativeReward = true;
774 } else if (rew > storm::utility::zero<ValueType>()) {
775 hasPositiveReward = true;
776 }
777 if (hasNegativeReward && hasPositiveReward) {
778 break;
779 }
780 }
781 if (requiresLower && !solver.hasLowerBound()) {
782 // Compute lower bounds
783 if (hasNegativeReward) {
784 // For lower bounds we actually compute upper bounds for the negated rewards because DsMpi is not implemented for negative rewards.
785 std::vector<ValueType> tmpRewards(rewards.size());
786 storm::utility::vector::applyPointwise(rewards, tmpRewards,
787 [](ValueType const& v) { return std::max<ValueType>(storm::utility::zero<ValueType>(), -v); });
788 std::vector<ValueType> lowerBounds =
791 solver.setLowerBounds(std::move(lowerBounds));
792 } else {
794 }
795 }
796
797 // Compute upper bounds
798 if (requiresUpper && !solver.hasUpperBound()) {
799 if (hasPositiveReward) {
800 solver.setUpperBound(storm::modelchecker::helper::BaierUpperRewardBoundsComputer<ValueType>(transitions, oneStepTargetProbs)
801 .computeTotalRewardBounds(rewards)
802 .upper);
803 } else {
805 }
806 }
807}
808
809template<class SparseModelType>
811 std::vector<uint_fast64_t> const& ecqOptimalChoices,
812 std::map<uint64_t, uint64_t> const& ecqStateToOptimalMecMap,
813 std::vector<ValueType>& originalSolution,
814 std::vector<uint_fast64_t>& originalOptimalChoices) const {
815 auto backwardsTransitions = transitionMatrix.transpose(true);
816
817 // Keep track of states for which no choice has been set yet.
818 storm::storage::BitVector unprocessedStates(transitionMatrix.getRowGroupCount(), true);
819
820 // For each eliminated ec, keep track of the states (within the ec) that we want to reach and the states for which a choice needs to be set
821 // (Declared already at this point to avoid expensive allocations in each loop iteration)
822 storm::storage::BitVector ecStatesToReach(transitionMatrix.getRowGroupCount(), false);
823 storm::storage::BitVector ecStatesToProcess(transitionMatrix.getRowGroupCount(), false);
824
825 // Run through each state of the ec quotient as well as the associated state(s) of the original model
826 for (uint64_t ecqState = 0; ecqState < ecqSolution.size(); ++ecqState) {
827 uint64_t ecqChoice = ecQuotient->matrix.getRowGroupIndices()[ecqState] + ecqOptimalChoices[ecqState];
828 uint_fast64_t origChoice = ecQuotient->ecqToOriginalChoiceMapping[ecqChoice];
829 auto const& origStates = ecQuotient->ecqToOriginalStateMapping[ecqState];
830 STORM_LOG_ASSERT(!origStates.empty(), "Unexpected empty set of original states.");
831 if (ecQuotient->ecqStayInEcChoices.get(ecqChoice)) {
832 // We stay in the current state(s) forever (End component)
833 // We need to set choices in a way that (i) the optimal LRA Mec is reached (if there is any) and (ii) 0 total reward is collected.
834 if (!ecqStateToOptimalMecMap.empty()) {
835 // The current ecqState represents an elimnated EC and we need to stay in this EC and we need to make sure that optimal MEC decisions are
836 // performed within this EC.
837 STORM_LOG_ASSERT(ecqStateToOptimalMecMap.count(ecqState) > 0, "No Lra Mec associated to given eliminated EC.");
838 auto const& lraMec = lraMecDecomposition->mecs[ecqStateToOptimalMecMap.at(ecqState)];
839 if (lraMec.size() == origStates.size()) {
840 // LRA mec and eliminated EC coincide
841 for (auto const& state : origStates) {
842 STORM_LOG_ASSERT(lraMec.containsState(state), "Expected state to be contained in the lra mec.");
843 // Note that the optimal choice for this state has already been set in the infinite horizon phase.
844 unprocessedStates.set(state, false);
845 originalSolution[state] = ecqSolution[ecqState];
846 }
847 } else {
848 // LRA mec is proper subset of eliminated ec. There are also other states for which we have to set choices leading to the LRA MEC inside.
849 STORM_LOG_ASSERT(lraMec.size() < origStates.size(), "Lra Mec (" << lraMec.size()
850 << " states) should be a proper subset of the eliminated ec ("
851 << origStates.size() << " states).");
852 for (auto const& state : origStates) {
853 if (lraMec.containsState(state)) {
854 ecStatesToReach.set(state, true);
855 // Note that the optimal choice for this state has already been set in the infinite horizon phase.
856 } else {
857 ecStatesToProcess.set(state, true);
858 }
859 unprocessedStates.set(state, false);
860 originalSolution[state] = ecqSolution[ecqState];
861 }
862 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess, ecStatesToReach, originalOptimalChoices,
863 &ecQuotient->origTotalReward0Choices);
864 // Clear bitvectors for next ecqState.
865 ecStatesToProcess.clear();
866 ecStatesToReach.clear();
867 }
868 } else {
869 // If there is no LRA Mec to reach, we just need to make sure that finite total reward is collected for all objectives
870 // In this branch our BitVectors have a slightly different meaning, so we create more readable aliases
871 storm::storage::BitVector& ecStatesToAvoid = ecStatesToReach;
872 bool needSchedulerComputation = false;
873 STORM_LOG_ASSERT(storm::utility::isZero(ecqSolution[ecqState]),
874 "Solution for state that stays inside EC must be zero. Got " << ecqSolution[ecqState] << " instead.");
875 for (auto const& state : origStates) {
876 originalSolution[state] = storm::utility::zero<ValueType>(); // i.e. ecqSolution[ecqState];
877 ecStatesToProcess.set(state, true);
878 }
879 auto validChoices = transitionMatrix.getRowFilter(ecStatesToProcess, ecStatesToProcess);
880 auto valid0RewardChoices = validChoices & actionsWithoutRewardInUnboundedPhase;
881 for (auto const& state : origStates) {
882 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
883 auto groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
884 auto nextValidChoice = valid0RewardChoices.getNextSetIndex(groupStart);
885 if (nextValidChoice < groupEnd) {
886 originalOptimalChoices[state] = nextValidChoice - groupStart;
887 } else {
888 // this state should not be reached infinitely often
889 ecStatesToAvoid.set(state, true);
890 needSchedulerComputation = true;
891 }
892 }
893 if (needSchedulerComputation) {
894 // There are ec states which we should not visit infinitely often
895 auto ecStatesThatCanAvoid =
896 storm::utility::graph::performProbGreater0A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardsTransitions,
897 ecStatesToProcess, ecStatesToAvoid, false, 0, valid0RewardChoices);
898 ecStatesThatCanAvoid.complement();
899 // Set the choice for all states that can achieve value 0
900 computeSchedulerProb0(transitionMatrix, backwardsTransitions, ecStatesThatCanAvoid, ecStatesToAvoid, valid0RewardChoices,
901 originalOptimalChoices);
902 // Set the choice for all remaining states
903 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess & ~ecStatesToAvoid, ecStatesToAvoid, originalOptimalChoices,
904 &validChoices);
905 }
906 ecStatesToAvoid.clear();
907 ecStatesToProcess.clear();
908 }
909 } else {
910 // We eventually leave the current state(s)
911 // In this case, we can safely take the origChoice at the corresponding state (say 's').
912 // For all other origStates associated with ecqState (if there are any), we make sure that the state 's' is reached almost surely.
913 if (origStates.size() > 1) {
914 for (auto const& state : origStates) {
915 // Check if the orig choice originates from this state
916 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
917 auto groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
918 if (origChoice >= groupStart && origChoice < groupEnd) {
919 originalOptimalChoices[state] = origChoice - groupStart;
920 ecStatesToReach.set(state, true);
921 } else {
922 STORM_LOG_ASSERT(origStates.size() > 1, "Multiple original states expected.");
923 ecStatesToProcess.set(state, true);
924 }
925 unprocessedStates.set(state, false);
926 originalSolution[state] = ecqSolution[ecqState];
927 }
928 auto validChoices = transitionMatrix.getRowFilter(ecStatesToProcess, ecStatesToProcess | ecStatesToReach);
929 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess, ecStatesToReach, originalOptimalChoices, &validChoices);
930 // Clear bitvectors for next ecqState.
931 ecStatesToProcess.clear();
932 ecStatesToReach.clear();
933 } else {
934 // There is just one state so we take the associated choice.
935 auto state = *origStates.begin();
936 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
938 origChoice >= groupStart && origChoice < transitionMatrix.getRowGroupIndices()[state + 1],
939 "Invalid choice: " << originalOptimalChoices[state] << " at a state with " << transitionMatrix.getRowGroupSize(state) << " choices.");
940 originalOptimalChoices[state] = origChoice - groupStart;
941 originalSolution[state] = ecqSolution[ecqState];
942 unprocessedStates.set(state, false);
943 }
944 }
945 }
946
947 // The states that still not have been processed, there is no associated state of the ec quotient.
948 // This is because the value for these states will be 0 under all (lra optimal-) schedulers.
949 storm::utility::vector::setVectorValues(originalSolution, unprocessedStates, storm::utility::zero<ValueType>());
950 // Get a set of states for which we know that no reward (for all objectives) will be collected
951 if (this->lraMecDecomposition) {
952 // In this case, all unprocessed non-lra mec states should reach an (unprocessed) lra mec
953 for (auto const& mec : this->lraMecDecomposition->mecs) {
954 for (auto const& sc : mec) {
955 if (unprocessedStates.get(sc.first)) {
956 ecStatesToReach.set(sc.first, true);
957 }
958 }
959 }
960 } else {
961 ecStatesToReach = unprocessedStates & totalReward0EStates;
962 // Set a scheduler for the ecStates that we want to reach
963 computeSchedulerProb0(transitionMatrix, backwardsTransitions, ecStatesToReach, ~unprocessedStates | ~totalReward0EStates,
964 actionsWithoutRewardInUnboundedPhase, originalOptimalChoices);
965 }
966 unprocessedStates &= ~ecStatesToReach;
967 // Set a scheduler for the remaining states
968 computeSchedulerProb1(transitionMatrix, backwardsTransitions, unprocessedStates, ecStatesToReach, originalOptimalChoices);
969}
970
973
976
977} // namespace multiobjective
978} // namespace modelchecker
979} // namespace storm
SolverEnvironment & solver()
MinMaxSolverEnvironment & minMax()
std::vector< ValueType > computeUpperBounds()
Computes upper bounds on the expected rewards.
Helper class for model checking queries that depend on the long run behavior of the (nondeterministic...
SparseInfiniteHorizonHelper< ValueType, true >::ValueGetter ValueGetter
Function mapping from indices to values.
PcaaWeightVectorChecker(std::vector< Objective< ValueType > > const &objectives)
boost::optional< ValueType > computeWeightedResultBound(bool lower, std::vector< ValueType > const &weightVector, storm::storage::BitVector const &objectiveFilter) const
Helper Class that takes preprocessed Pcaa data and a weight vector and ...
ValueType getOptimalWeightedSum() const override
Retrieves the optimal weighted sum of the objective values (or an upper bound thereof).
void unboundedWeightedPhase(Environment const &env, std::vector< ValueType > const &weightedRewardVector, std::vector< ValueType > const &weightVector)
Determines the scheduler that optimizes the weighted reward vector of the unbounded objectives.
virtual void boundedPhase(Environment const &env, std::vector< ValueType > const &weightVector, std::vector< ValueType > &weightedRewardVector)=0
For each time epoch (starting with the maximal stepBound occurring in the objectives),...
virtual storm::modelchecker::helper::SparseNondeterministicInfiniteHorizonHelper< ValueType > createNondetInfiniteHorizonHelper(storm::storage::SparseMatrix< ValueType > const &transitions) const =0
void computeAndSetBoundsToSolver(storm::solver::AbstractEquationSolver< ValueType > &solver, bool requiresLower, bool requiresUpper, storm::storage::SparseMatrix< ValueType > const &transitions, storm::storage::BitVector const &rowsWithSumLessOne, std::vector< ValueType > const &rewards) const
virtual DeterministicInfiniteHorizonHelperType createDetInfiniteHorizonHelper(storm::storage::SparseMatrix< ValueType > const &transitions) const =0
virtual std::vector< ValueType > getAchievablePoint() const override
Retrieves the result of the individual objectives at the initial state of the given model.
void infiniteHorizonWeightedPhase(Environment const &env, std::vector< ValueType > const &weightedActionRewardVector, boost::optional< std::vector< ValueType > > const &weightedStateRewardVector, std::vector< ValueType > const &weightVector)
StandardPcaaWeightVectorChecker(preprocessing::SparseMultiObjectivePreprocessorResult< SparseModelType > const &preprocessorResult)
virtual void check(Environment const &env, std::vector< ValueType > weightVector) override
void transformEcqSolutionToOriginalModel(std::vector< ValueType > const &ecqSolution, std::vector< uint_fast64_t > const &ecqOptimalChoices, std::map< uint64_t, uint64_t > const &ecqStateToOptimalMecMap, std::vector< ValueType > &originalSolution, std::vector< uint_fast64_t > &originalOptimalChoices) const
Transforms the results of a min-max-solver that considers a reduced model (without end components) to...
virtual storm::storage::Scheduler< ValueType > computeScheduler() const override
Retrieves a scheduler that induces the current values Note that check(..) has to be called before ret...
void updateEcQuotient(std::vector< ValueType > const &weightedRewardVector)
void initialize(preprocessing::SparseMultiObjectivePreprocessorResult< SparseModelType > const &preprocessorResult)
void unboundedIndividualPhase(Environment const &env, std::vector< ValueType > const &weightVector)
Computes the values of the objectives that do not have a stepBound w.r.t.
void setBoundsToSolver(storm::solver::AbstractEquationSolver< ValueType > &solver, bool requiresLower, bool requiresUpper, uint64_t objIndex, storm::storage::SparseMatrix< ValueType > const &transitions, storm::storage::BitVector const &rowsWithSumLessOne, std::vector< ValueType > const &rewards) const
static ReturnType analyze(storm::modelchecker::multiobjective::preprocessing::SparseMultiObjectivePreprocessorResult< SparseModelType > const &preprocessorResult)
Analyzes the reward objectives of the multi objective query.
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 std::unique_ptr< MinMaxLinearEquationSolver< ValueType, SolutionType > > create(Environment const &env) const override
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
void complement()
Negates all bits in the bit vector.
uint64_t getNextSetIndex(uint64_t startingIndex) const
Retrieves the index of the bit that is the next bit set to true in the bit vector.
const_iterator end() const
Returns an iterator pointing at the element past the back of the bit vector.
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
void clear()
Removes all set bits from the bit vector.
bool isSubsetOf(BitVector const &other) const
Checks whether all bits that are set in the current bit vector are also set in the given bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
const_iterator begin() const
Returns an iterator to the indices of the set bits in the bit vector.
size_t size() const
Retrieves the number of bits this bit vector can store.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
This class represents the decomposition of a nondeterministic model into its maximal end components.
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
A class that holds a possibly non-square matrix in the compressed row storage format.
void convertToEquationSystem()
Transforms the matrix into an equation system.
const_rows getRow(index_type row) const
Returns an object representing the given row.
SparseMatrix getSubmatrix(bool useGroups, storm::storage::BitVector const &rowConstraint, storm::storage::BitVector const &columnConstraint, bool insertDiagonalEntries=false, storm::storage::BitVector const &makeZeroColumns=storm::storage::BitVector()) const
Creates a submatrix of the current matrix by dropping all rows and columns whose bits are not set to ...
value_type getRowSum(index_type row) const
Computes the sum of the entries in a given row.
index_type getRowGroupCount() const
Returns the number of row groups in the matrix.
storm::storage::BitVector getRowGroupFilter(storm::storage::BitVector const &rowConstraint, bool setIfForAllRowsInGroup) const
Returns the indices of all row groups selected by the row constraints.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
storm::storage::SparseMatrix< value_type > transpose(bool joinGroups=false, bool keepZeros=false) const
Transposes the matrix.
index_type getRowCount() const
Returns the number of rows of the matrix.
storm::storage::BitVector getRowFilter(storm::storage::BitVector const &groupConstraint) const
Returns a bitvector representing the set of rows, with all indices set that correspond to one of the ...
static EndComponentEliminatorReturnType transform(storm::storage::SparseMatrix< ValueType > const &originalMatrix, storm::storage::MaximalEndComponentDecomposition< ValueType > ecs, storm::storage::BitVector const &subsystemStates, storm::storage::BitVector const &addSinkRowStates, bool addSelfLoopAtSinkStates=false)
#define STORM_LOG_INFO(message)
Definition logging.h:27
#define STORM_LOG_STATISTICS(message)
Definition logging.h:41
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:36
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:28
std::vector< uint64_t > computeValidInitialScheduler(storm::storage::SparseMatrix< ValueType > const &matrix, storm::storage::BitVector const &rowsWithSumLessOne)
void computeSchedulerFinitelyOften(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &finitelyOftenChoices, storm::storage::BitVector safeStates, std::vector< uint64_t > &choices)
Computes a scheduler taking the choices from the given set only finitely often.
void computeSchedulerProb1(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &consideredStates, storm::storage::BitVector const &statesToReach, std::vector< uint64_t > &choices, storm::storage::BitVector const *allowedChoices=nullptr)
void computeSchedulerProb0(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &consideredStates, storm::storage::BitVector const &statesToAvoid, storm::storage::BitVector const &allowedChoices, std::vector< uint64_t > &choices)
bool constexpr maximize(OptimizationDirection d)
bool constexpr minimize(OptimizationDirection d)
storm::storage::BitVector performProbGreater0(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool useStepBound, uint_fast64_t maximalSteps)
Performs a backward depth-first search trough the underlying graph structure of the given model to de...
Definition graph.cpp:315
storm::storage::BitVector performProbGreater0A(storm::storage::SparseMatrix< T > const &transitionMatrix, std::vector< uint_fast64_t > const &nondeterministicChoiceIndices, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool useStepBound, uint_fast64_t maximalSteps, boost::optional< storm::storage::BitVector > const &choiceConstraint)
Computes the sets of states that have probability greater 0 of satisfying phi until psi under any pos...
Definition graph.cpp:841
storm::storage::BitVector performProbGreater0E(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool useStepBound, uint_fast64_t maximalSteps)
Computes the sets of states that have probability greater 0 of satisfying phi until psi under at leas...
Definition graph.cpp:673
storm::storage::BitVector performProb0E(storm::models::sparse::NondeterministicModel< T, RM > const &model, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Computes the sets of states that have probability 0 of satisfying phi until psi under at least one po...
Definition graph.cpp:960
std::vector< TargetType > convertNumericVector(std::vector< SourceType > const &oldVector)
Converts the given vector to the given ValueType Assumes that both, TargetType and SourceType are num...
Definition vector.h:966
T dotProduct(std::vector< T > const &firstOperand, std::vector< T > const &secondOperand)
Computes the dot product (aka scalar product) and returns the result.
Definition vector.h:473
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
void addScaledVector(std::vector< InValueType1 > &firstOperand, std::vector< InValueType2 > const &secondOperand, InValueType3 const &factor)
Computes x:= x + a*y, i.e., adds each element of the first vector and (the corresponding element of t...
Definition vector.h:460
std::string toString(std::vector< ValueType > const &vector)
Output vector as string.
Definition vector.h:1179
void clip(std::vector< ValueType > &x, boost::optional< ValueType > const &lowerBound, boost::optional< ValueType > const &upperBound)
Takes the input vector and ensures that all entries conform to the bounds.
Definition vector.h:888
void applyPointwise(std::vector< InValueType1 > const &firstOperand, std::vector< InValueType2 > const &secondOperand, std::vector< OutValueType > &target, Operation f=Operation())
Applies the given operation pointwise on the two given vectors and writes the result to the third vec...
Definition vector.h:374
VT sum_if(std::vector< VT > const &values, storm::storage::BitVector const &filter)
Sum the entries from values that are set to one in the filter vector.
Definition vector.h:552
std::vector< uint_fast64_t > getSortedIndices(std::vector< T > const &v)
Returns a list of indices such that the first index refers to the highest entry of the given vector,...
Definition vector.h:144
storm::storage::BitVector filterZero(std::vector< T > const &values)
Retrieves a bit vector containing all the indices for which the value at this position is equal to ze...
Definition vector.h:519
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 hasNonZeroEntry(std::vector< T > const &v)
Definition vector.h:1133
std::vector< Type > filterVector(std::vector< Type > const &in, storm::storage::BitVector const &filter)
Definition vector.h:1060
bool isOne(ValueType const &a)
Definition constants.cpp:37
bool isZero(ValueType const &a)
Definition constants.cpp:42
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:24
ValueType one()
Definition constants.cpp:19
ValueType sqrt(ValueType const &number)
TargetType convertNumber(SourceType const &number)