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