Storm 1.11.1.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>
37 : PcaaWeightVectorChecker<SparseModelType>(preprocessorResult.objectives) {
38 // Intantionally left empty
39}
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(
88 storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(), transitionMatrix.transpose(true),
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),
116 actionsWithoutRewardInUnboundedPhase);
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>());
124 offsetToWeightedSum = storm::utility::zero<ValueType>();
125 optimalChoices.resize(transitionMatrix.getRowGroupCount(), 0);
126
127 // Print some statistics (if requested)
128 if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
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");
132 if (lraMecDecomposition) {
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"
148 << "\t" << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVector)));
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));
155 storm::utility::vector::scaleVectorInPlace<ValueType, ValueType>(weightVector, storm::utility::one<ValueType>() / (inputWeightVectorLength));
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: "
197 << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(getAchievablePoint())));
198 // Validate that the results are sufficiently precise
199 ValueType weightedSum = storm::utility::zero<ValueType>();
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()))
210 << ". Weight vector is" << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVector))
211 << ".");
212 if (!storm::utility::isOne(inputWeightVectorLength)) {
213 // reverse the normalization of the weight vector for the returned optimal weighted sum.
214 storm::utility::vector::scaleVectorInPlace<ValueType, ValueType>(weightedResult, inputWeightVectorLength);
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
378 ValueType epsilon = this->getWeightedPrecisionUnboundedPhase() * storm::utility::sqrt(storm::utility::vector::dotProduct(weightVector, weightVector)) /
379 storm::utility::convertNumber<ValueType>(2.0);
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.");
387 storm::modelchecker::helper::SparseNondeterministicInfiniteHorizonHelper<ValueType> helper = createNondetInfiniteHorizonHelper(this->transitionMatrix);
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();
414 ValueType const two = storm::utility::convertNumber<ValueType>(2.0);
415 // see epsilon in https://doi.org/10.18154/RWTH-2023-09669 Algorithm 4.2
416 ValueType adjustedPrecision =
417 this->getWeightedPrecisionUnboundedPhase() * storm::utility::sqrt(storm::utility::vector::dotProduct(weightVector, weightVector)) / two;
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
446 computeSchedulerFinitelyOften(transitionMatrix, transitionMatrix.transpose(true), ~actionsWithoutRewardInUnboundedPhase, statesInLraMec,
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 offsetToWeightedSum = storm::utility::zero<ValueType>();
520 if (requireSoundApproximation) {
521 // Add offset to ensure that we have an upper bound on the true optimal value
522 offsetToWeightedSum += adjustedPrecision;
523 }
524}
525
526template<class SparseModelType>
527void StandardPcaaWeightVectorChecker<SparseModelType>::unboundedIndividualPhase(Environment const& inputEnv, std::vector<ValueType> const& weightVector) {
528 auto solverEnv = inputEnv;
529 storm::storage::SparseMatrix<ValueType> deterministicMatrix = transitionMatrix.selectRowsFromRowGroups(this->optimalChoices, false);
530 storm::storage::SparseMatrix<ValueType> deterministicBackwardTransitions = deterministicMatrix.transpose();
531 std::vector<ValueType> deterministicStateRewards(deterministicMatrix.getRowCount()); // allocate here
533 bool const requireSoundApproximation = !solverEnv.solver().isForceExact() && solverEnv.solver().isForceSoundness();
534 // see epsilon and epsilon_j in https://doi.org/10.18154/RWTH-2023-09669 Algorithm 4.2
535 ValueType const two = storm::utility::convertNumber<ValueType>(2.0);
536 ValueType epsilon = this->getWeightedPrecisionUnboundedPhase() * storm::utility::sqrt(storm::utility::vector::dotProduct(weightVector, weightVector)) / two;
537 if (solverEnv.solver().isForceExact()) {
538 // If we are already using an exact solver, we consider the precision to be zero
539 epsilon = storm::utility::zero<ValueType>();
540 } else if (requireSoundApproximation) {
541 epsilon /= two; // need to be more precise to get a correct and sufficiently tight achievable value
542 }
543
544 auto infiniteHorizonHelper = createDetInfiniteHorizonHelper(deterministicMatrix);
545 infiniteHorizonHelper.provideBackwardTransitions(deterministicBackwardTransitions);
546
547 // We compute an estimate for the results of the individual objectives which is obtained from the weighted result and the result of the objectives
548 // computed so far. Note that weightedResult = Sum_{i=1}^{n} w_i * objectiveResult_i.
549 std::vector<ValueType> weightedSumOfUncheckedObjectives = weightedResult;
550 ValueType sumOfWeightsOfUncheckedObjectives = storm::utility::vector::sum_if(weightVector, objectivesWithNoUpperTimeBound);
551
552 for (uint_fast64_t const& objIndex : storm::utility::vector::getSortedIndices(weightVector)) {
553 auto const& obj = this->objectives[objIndex];
554 if (objectivesWithNoUpperTimeBound.get(objIndex)) {
555 ValueType epsilon_j = epsilon / storm::utility::convertNumber<ValueType, uint64_t>(objectivesWithNoUpperTimeBound.getNumberOfSetBits());
556 if (!storm::utility::isZero(weightVector[objIndex])) {
557 epsilon_j /= storm::utility::abs(weightVector[objIndex]);
558 }
559 solverEnv.solver().setLinearEquationSolverPrecision(storm::utility::convertNumber<RationalNumber>(epsilon_j), false);
560 solverEnv.solver().lra().setPrecision(storm::utility::convertNumber<RationalNumber>(epsilon_j));
561 solverEnv.solver().lra().setRelativeTerminationCriterion(false);
562
563 if (lraObjectives.get(objIndex)) {
564 auto actionValueGetter = [&](uint64_t const& a) {
565 return actionRewards[objIndex][transitionMatrix.getRowGroupIndices()[a] + this->optimalChoices[a]];
566 };
568 if (stateRewards.empty() || stateRewards[objIndex].empty()) {
569 stateValueGetter = [](uint64_t const&) { return storm::utility::zero<ValueType>(); };
570 } else {
571 stateValueGetter = [&](uint64_t const& s) { return stateRewards[objIndex][s]; };
572 }
573 objectiveResults[objIndex] = infiniteHorizonHelper.computeLongRunAverageValues(solverEnv, stateValueGetter, actionValueGetter);
574 } else { // i.e. a total reward objective
575 storm::utility::vector::selectVectorValues(deterministicStateRewards, this->optimalChoices, transitionMatrix.getRowGroupIndices(),
576 actionRewards[objIndex]);
577 storm::storage::BitVector statesWithRewards = ~storm::utility::vector::filterZero(deterministicStateRewards);
578 // As maybestates we pick the states from which a state with reward is reachable
580 deterministicBackwardTransitions, storm::storage::BitVector(deterministicMatrix.getRowCount(), true), statesWithRewards);
581
582 // Compute the estimate for this objective
583 if (!storm::utility::isZero(weightVector[objIndex]) && !storm::utility::isZero(sumOfWeightsOfUncheckedObjectives)) {
584 objectiveResults[objIndex] = weightedSumOfUncheckedObjectives;
585 ValueType scalingFactor = storm::utility::one<ValueType>() / sumOfWeightsOfUncheckedObjectives;
586 if (storm::solver::minimize(obj.formula->getOptimalityType())) {
587 scalingFactor *= -storm::utility::one<ValueType>();
588 }
589 storm::utility::vector::scaleVectorInPlace(objectiveResults[objIndex], scalingFactor);
590 storm::utility::vector::clip(objectiveResults[objIndex], obj.lowerResultBound, obj.upperResultBound);
591 }
592 // Make sure that the objectiveResult is initialized correctly
593 objectiveResults[objIndex].resize(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
594
595 if (!maybeStates.empty()) {
596 bool needEquationSystem =
598 storm::storage::SparseMatrix<ValueType> submatrix = deterministicMatrix.getSubmatrix(true, maybeStates, maybeStates, needEquationSystem);
599 if (needEquationSystem) {
600 // Converting the matrix from the fixpoint notation to the form needed for the equation
601 // system. That is, we go from x = A*x + b to (I-A)x = b.
602 submatrix.convertToEquationSystem();
603 }
604
605 // Prepare solution vector and rhs of the equation system.
606 std::vector<ValueType> x = storm::utility::vector::filterVector(objectiveResults[objIndex], maybeStates);
607 std::vector<ValueType> b = storm::utility::vector::filterVector(deterministicStateRewards, maybeStates);
608
609 // Now solve the resulting equation system.
610 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver = linearEquationSolverFactory.create(solverEnv, submatrix);
611 auto req = solver->getRequirements(solverEnv);
612 solver->clearBounds();
613 storm::storage::BitVector submatrixRowsWithSumLessOne = deterministicMatrix.getRowFilter(maybeStates, maybeStates) % maybeStates;
614 submatrixRowsWithSumLessOne.complement();
615 this->setBoundsToSolver(*solver, req.lowerBounds(), req.upperBounds(), objIndex, submatrix, submatrixRowsWithSumLessOne, b);
616 if (solver->hasLowerBound()) {
617 req.clearLowerBounds();
618 }
619 if (solver->hasUpperBound()) {
620 req.clearUpperBounds();
621 }
622 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
623 "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked.");
624 solver->solveEquations(solverEnv, x, b);
625 if (requireSoundApproximation) {
626 // add offsets to ensure that we have an upper/lower bound on the true optimal value
627 offsetsToAchievablePoint[objIndex] =
628 storm::solver::maximize(this->objectives[objIndex].formula->getOptimalityType()) ? -epsilon_j : epsilon_j;
629 }
630 // Set the result for this objective accordingly
631 storm::utility::vector::setVectorValues<ValueType>(objectiveResults[objIndex], maybeStates, x);
632 }
633 storm::utility::vector::setVectorValues<ValueType>(objectiveResults[objIndex], ~maybeStates, storm::utility::zero<ValueType>());
634 }
635 // Update the estimate for the next objectives.
636 if (!storm::utility::isZero(weightVector[objIndex])) {
637 storm::utility::vector::addScaledVector(weightedSumOfUncheckedObjectives, objectiveResults[objIndex], -weightVector[objIndex]);
638 sumOfWeightsOfUncheckedObjectives -= weightVector[objIndex];
639 }
640 } else {
641 // Other objectives will be computed in bounded phase.
642 objectiveResults[objIndex] = std::vector<ValueType>(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
643 }
644 }
645}
646
647template<class SparseModelType>
648void StandardPcaaWeightVectorChecker<SparseModelType>::updateEcQuotient(std::vector<ValueType> const& weightedRewardVector) {
649 // Check whether we need to update the currently cached ecElimResult
650 storm::storage::BitVector newTotalReward0Choices = storm::utility::vector::filterZero(weightedRewardVector);
651 storm::storage::BitVector zeroLraRewardChoices(weightedRewardVector.size(), true);
652 if (lraMecDecomposition) {
653 for (uint64_t mecIndex = 0; mecIndex < lraMecDecomposition->mecs.size(); ++mecIndex) {
654 if (!storm::utility::isZero(lraMecDecomposition->auxMecValues[mecIndex])) {
655 // The mec has a non-zero value, so flag all its choices as non-zero
656 auto const& mec = lraMecDecomposition->mecs[mecIndex];
657 for (auto const& stateChoices : mec) {
658 for (auto const& choice : stateChoices.second) {
659 zeroLraRewardChoices.set(choice, false);
660 }
661 }
662 }
663 }
664 }
665 storm::storage::BitVector newReward0Choices = newTotalReward0Choices & zeroLraRewardChoices;
666 if (!ecQuotient || ecQuotient->origReward0Choices != newReward0Choices) {
667 // It is sufficient to consider the states from which a transition with non-zero reward is reachable. (The remaining states always have reward zero).
668 auto nonZeroRewardStates = transitionMatrix.getRowGroupFilter(newReward0Choices, true);
669 nonZeroRewardStates.complement();
671 transitionMatrix.transpose(true), storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true), nonZeroRewardStates);
672
673 // Remove neutral end components, i.e., ECs in which no total reward is earned.
674 // Note that such ECs contain one (or maybe more) LRA ECs.
675 auto ecElimResult = storm::transformer::EndComponentEliminator<ValueType>::transform(transitionMatrix, subsystemStates,
676 ecChoicesHint & newTotalReward0Choices, totalReward0EStates);
677
678 storm::storage::BitVector rowsWithSumLessOne(ecElimResult.matrix.getRowCount(), false);
679 for (uint64_t row = 0; row < rowsWithSumLessOne.size(); ++row) {
680 if (ecElimResult.matrix.getRow(row).getNumberOfEntries() == 0) {
681 rowsWithSumLessOne.set(row, true);
682 } else {
683 for (auto const& entry : transitionMatrix.getRow(ecElimResult.newToOldRowMapping[row])) {
684 if (!subsystemStates.get(entry.getColumn())) {
685 rowsWithSumLessOne.set(row, true);
686 break;
687 }
688 }
689 }
690 }
691
692 ecQuotient = EcQuotient();
693 ecQuotient->matrix = std::move(ecElimResult.matrix);
694 ecQuotient->ecqToOriginalChoiceMapping = std::move(ecElimResult.newToOldRowMapping);
695 ecQuotient->originalToEcqStateMapping = std::move(ecElimResult.oldToNewStateMapping);
696 ecQuotient->ecqToOriginalStateMapping.resize(ecQuotient->matrix.getRowGroupCount());
697 for (uint64_t state = 0; state < ecQuotient->originalToEcqStateMapping.size(); ++state) {
698 uint64_t ecqState = ecQuotient->originalToEcqStateMapping[state];
699 if (ecqState < ecQuotient->matrix.getRowGroupCount()) {
700 ecQuotient->ecqToOriginalStateMapping[ecqState].insert(state);
701 }
702 }
703 ecQuotient->ecqStayInEcChoices = std::move(ecElimResult.sinkRows);
704 ecQuotient->origReward0Choices = std::move(newReward0Choices);
705 ecQuotient->origTotalReward0Choices = std::move(newTotalReward0Choices);
706 ecQuotient->rowsWithSumLessOne = std::move(rowsWithSumLessOne);
707 ecQuotient->auxStateValues.resize(ecQuotient->matrix.getRowGroupCount());
708 ecQuotient->auxChoiceValues.resize(ecQuotient->matrix.getRowCount());
709 }
710}
711
712template<class SparseModelType>
714 bool requiresUpper, uint64_t objIndex,
716 storm::storage::BitVector const& rowsWithSumLessOne,
717 std::vector<ValueType> const& rewards) const {
718 // Check whether bounds are already available
719 if (this->objectives[objIndex].lowerResultBound) {
720 solver.setLowerBound(this->objectives[objIndex].lowerResultBound.get());
721 }
722 if (this->objectives[objIndex].upperResultBound) {
723 solver.setUpperBound(this->objectives[objIndex].upperResultBound.get());
724 }
725
726 if ((requiresLower && !solver.hasLowerBound()) || (requiresUpper && !solver.hasUpperBound())) {
727 computeAndSetBoundsToSolver(solver, requiresLower, requiresUpper, transitions, rowsWithSumLessOne, rewards);
728 }
729}
730
731template<class SparseModelType>
733 bool requiresUpper, std::vector<ValueType> const& weightVector,
734 storm::storage::BitVector const& objectiveFilter,
736 storm::storage::BitVector const& rowsWithSumLessOne,
737 std::vector<ValueType> const& rewards) const {
738 // Check whether bounds are already available
739 boost::optional<ValueType> lowerBound = this->computeWeightedResultBound(true, weightVector, objectiveFilter & ~lraObjectives);
740 if (lowerBound) {
741 if (!lraObjectives.empty()) {
742 auto min = std::min_element(lraMecDecomposition->auxMecValues.begin(), lraMecDecomposition->auxMecValues.end());
743 if (min != lraMecDecomposition->auxMecValues.end()) {
744 lowerBound.get() += *min;
745 }
746 }
747 solver.setLowerBound(lowerBound.get());
748 }
749 boost::optional<ValueType> upperBound = this->computeWeightedResultBound(false, weightVector, objectiveFilter);
750 if (upperBound) {
751 if (!lraObjectives.empty()) {
752 auto max = std::max_element(lraMecDecomposition->auxMecValues.begin(), lraMecDecomposition->auxMecValues.end());
753 if (max != lraMecDecomposition->auxMecValues.end()) {
754 upperBound.get() += *max;
755 }
756 }
757 solver.setUpperBound(upperBound.get());
758 }
759
760 if ((requiresLower && !solver.hasLowerBound()) || (requiresUpper && !solver.hasUpperBound())) {
761 computeAndSetBoundsToSolver(solver, requiresLower, requiresUpper, transitions, rowsWithSumLessOne, rewards);
762 }
763}
764
765template<class SparseModelType>
767 bool requiresUpper,
769 storm::storage::BitVector const& rowsWithSumLessOne,
770 std::vector<ValueType> const& rewards) const {
771 // Compute the one step target probs
772 std::vector<ValueType> oneStepTargetProbs(transitions.getRowCount(), storm::utility::zero<ValueType>());
773 for (auto row : rowsWithSumLessOne) {
774 oneStepTargetProbs[row] = storm::utility::one<ValueType>() - transitions.getRowSum(row);
775 }
776
777 if (requiresLower && !solver.hasLowerBound()) {
778 // Compute lower bounds
779 std::vector<ValueType> negativeRewards;
780 negativeRewards.reserve(transitions.getRowCount());
781 uint64_t row = 0;
782 for (auto const& rew : rewards) {
783 if (rew < storm::utility::zero<ValueType>()) {
784 negativeRewards.resize(row, storm::utility::zero<ValueType>());
785 negativeRewards.push_back(-rew);
786 }
787 ++row;
788 }
789 if (!negativeRewards.empty()) {
790 negativeRewards.resize(row, storm::utility::zero<ValueType>());
791 std::vector<ValueType> lowerBounds =
792 storm::modelchecker::helper::DsMpiMdpUpperRewardBoundsComputer<ValueType>(transitions, negativeRewards, oneStepTargetProbs)
794 storm::utility::vector::scaleVectorInPlace(lowerBounds, -storm::utility::one<ValueType>());
795 solver.setLowerBounds(std::move(lowerBounds));
796 } else {
797 solver.setLowerBound(storm::utility::zero<ValueType>());
798 }
799 }
800
801 // Compute upper bounds
802 if (requiresUpper && !solver.hasUpperBound()) {
803 std::vector<ValueType> positiveRewards;
804 positiveRewards.reserve(transitions.getRowCount());
805 uint64_t row = 0;
806 for (auto const& rew : rewards) {
807 if (rew > storm::utility::zero<ValueType>()) {
808 positiveRewards.resize(row, storm::utility::zero<ValueType>());
809 positiveRewards.push_back(rew);
810 }
811 ++row;
812 }
813 if (!positiveRewards.empty()) {
814 positiveRewards.resize(row, storm::utility::zero<ValueType>());
815 solver.setUpperBound(
816 storm::modelchecker::helper::BaierUpperRewardBoundsComputer<ValueType>(transitions, positiveRewards, oneStepTargetProbs).computeUpperBound());
817 } else {
818 solver.setUpperBound(storm::utility::zero<ValueType>());
819 }
820 }
821}
822
823template<class SparseModelType>
825 std::vector<uint_fast64_t> const& ecqOptimalChoices,
826 std::map<uint64_t, uint64_t> const& ecqStateToOptimalMecMap,
827 std::vector<ValueType>& originalSolution,
828 std::vector<uint_fast64_t>& originalOptimalChoices) const {
829 auto backwardsTransitions = transitionMatrix.transpose(true);
830
831 // Keep track of states for which no choice has been set yet.
832 storm::storage::BitVector unprocessedStates(transitionMatrix.getRowGroupCount(), true);
833
834 // 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
835 // (Declared already at this point to avoid expensive allocations in each loop iteration)
836 storm::storage::BitVector ecStatesToReach(transitionMatrix.getRowGroupCount(), false);
837 storm::storage::BitVector ecStatesToProcess(transitionMatrix.getRowGroupCount(), false);
838
839 // Run through each state of the ec quotient as well as the associated state(s) of the original model
840 for (uint64_t ecqState = 0; ecqState < ecqSolution.size(); ++ecqState) {
841 uint64_t ecqChoice = ecQuotient->matrix.getRowGroupIndices()[ecqState] + ecqOptimalChoices[ecqState];
842 uint_fast64_t origChoice = ecQuotient->ecqToOriginalChoiceMapping[ecqChoice];
843 auto const& origStates = ecQuotient->ecqToOriginalStateMapping[ecqState];
844 STORM_LOG_ASSERT(!origStates.empty(), "Unexpected empty set of original states.");
845 if (ecQuotient->ecqStayInEcChoices.get(ecqChoice)) {
846 // We stay in the current state(s) forever (End component)
847 // 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.
848 if (!ecqStateToOptimalMecMap.empty()) {
849 // 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
850 // performed within this EC.
851 STORM_LOG_ASSERT(ecqStateToOptimalMecMap.count(ecqState) > 0, "No Lra Mec associated to given eliminated EC");
852 auto const& lraMec = lraMecDecomposition->mecs[ecqStateToOptimalMecMap.at(ecqState)];
853 if (lraMec.size() == origStates.size()) {
854 // LRA mec and eliminated EC coincide
855 for (auto const& state : origStates) {
856 STORM_LOG_ASSERT(lraMec.containsState(state), "Expected state to be contained in the lra mec.");
857 // Note that the optimal choice for this state has already been set in the infinite horizon phase.
858 unprocessedStates.set(state, false);
859 originalSolution[state] = ecqSolution[ecqState];
860 }
861 } else {
862 // 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.
863 STORM_LOG_ASSERT(lraMec.size() < origStates.size(), "Lra Mec (" << lraMec.size()
864 << " states) should be a proper subset of the eliminated ec ("
865 << origStates.size() << " states).");
866 for (auto const& state : origStates) {
867 if (lraMec.containsState(state)) {
868 ecStatesToReach.set(state, true);
869 // Note that the optimal choice for this state has already been set in the infinite horizon phase.
870 } else {
871 ecStatesToProcess.set(state, true);
872 }
873 unprocessedStates.set(state, false);
874 originalSolution[state] = ecqSolution[ecqState];
875 }
876 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess, ecStatesToReach, originalOptimalChoices,
877 &ecQuotient->origTotalReward0Choices);
878 // Clear bitvectors for next ecqState.
879 ecStatesToProcess.clear();
880 ecStatesToReach.clear();
881 }
882 } else {
883 // If there is no LRA Mec to reach, we just need to make sure that finite total reward is collected for all objectives
884 // In this branch our BitVectors have a slightly different meaning, so we create more readable aliases
885 storm::storage::BitVector& ecStatesToAvoid = ecStatesToReach;
886 bool needSchedulerComputation = false;
887 STORM_LOG_ASSERT(storm::utility::isZero(ecqSolution[ecqState]),
888 "Solution for state that stays inside EC must be zero. Got " << ecqSolution[ecqState] << " instead.");
889 for (auto const& state : origStates) {
890 originalSolution[state] = storm::utility::zero<ValueType>(); // i.e. ecqSolution[ecqState];
891 ecStatesToProcess.set(state, true);
892 }
893 auto validChoices = transitionMatrix.getRowFilter(ecStatesToProcess, ecStatesToProcess);
894 auto valid0RewardChoices = validChoices & actionsWithoutRewardInUnboundedPhase;
895 for (auto const& state : origStates) {
896 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
897 auto groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
898 auto nextValidChoice = valid0RewardChoices.getNextSetIndex(groupStart);
899 if (nextValidChoice < groupEnd) {
900 originalOptimalChoices[state] = nextValidChoice - groupStart;
901 } else {
902 // this state should not be reached infinitely often
903 ecStatesToAvoid.set(state, true);
904 needSchedulerComputation = true;
905 }
906 }
907 if (needSchedulerComputation) {
908 // There are ec states which we should not visit infinitely often
909 auto ecStatesThatCanAvoid =
910 storm::utility::graph::performProbGreater0A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardsTransitions,
911 ecStatesToProcess, ecStatesToAvoid, false, 0, valid0RewardChoices);
912 ecStatesThatCanAvoid.complement();
913 // Set the choice for all states that can achieve value 0
914 computeSchedulerProb0(transitionMatrix, backwardsTransitions, ecStatesThatCanAvoid, ecStatesToAvoid, valid0RewardChoices,
915 originalOptimalChoices);
916 // Set the choice for all remaining states
917 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess & ~ecStatesToAvoid, ecStatesToAvoid, originalOptimalChoices,
918 &validChoices);
919 }
920 ecStatesToAvoid.clear();
921 ecStatesToProcess.clear();
922 }
923 } else {
924 // We eventually leave the current state(s)
925 // In this case, we can safely take the origChoice at the corresponding state (say 's').
926 // For all other origStates associated with ecqState (if there are any), we make sure that the state 's' is reached almost surely.
927 if (origStates.size() > 1) {
928 for (auto const& state : origStates) {
929 // Check if the orig choice originates from this state
930 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
931 auto groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
932 if (origChoice >= groupStart && origChoice < groupEnd) {
933 originalOptimalChoices[state] = origChoice - groupStart;
934 ecStatesToReach.set(state, true);
935 } else {
936 STORM_LOG_ASSERT(origStates.size() > 1, "Multiple original states expected.");
937 ecStatesToProcess.set(state, true);
938 }
939 unprocessedStates.set(state, false);
940 originalSolution[state] = ecqSolution[ecqState];
941 }
942 auto validChoices = transitionMatrix.getRowFilter(ecStatesToProcess, ecStatesToProcess | ecStatesToReach);
943 computeSchedulerProb1(transitionMatrix, backwardsTransitions, ecStatesToProcess, ecStatesToReach, originalOptimalChoices, &validChoices);
944 // Clear bitvectors for next ecqState.
945 ecStatesToProcess.clear();
946 ecStatesToReach.clear();
947 } else {
948 // There is just one state so we take the associated choice.
949 auto state = *origStates.begin();
950 auto groupStart = transitionMatrix.getRowGroupIndices()[state];
952 origChoice >= groupStart && origChoice < transitionMatrix.getRowGroupIndices()[state + 1],
953 "Invalid choice: " << originalOptimalChoices[state] << " at a state with " << transitionMatrix.getRowGroupSize(state) << " choices.");
954 originalOptimalChoices[state] = origChoice - groupStart;
955 originalSolution[state] = ecqSolution[ecqState];
956 unprocessedStates.set(state, false);
957 }
958 }
959 }
960
961 // The states that still not have been processed, there is no associated state of the ec quotient.
962 // This is because the value for these states will be 0 under all (lra optimal-) schedulers.
963 storm::utility::vector::setVectorValues(originalSolution, unprocessedStates, storm::utility::zero<ValueType>());
964 // Get a set of states for which we know that no reward (for all objectives) will be collected
965 if (this->lraMecDecomposition) {
966 // In this case, all unprocessed non-lra mec states should reach an (unprocessed) lra mec
967 for (auto const& mec : this->lraMecDecomposition->mecs) {
968 for (auto const& sc : mec) {
969 if (unprocessedStates.get(sc.first)) {
970 ecStatesToReach.set(sc.first, true);
971 }
972 }
973 }
974 } else {
975 ecStatesToReach = unprocessedStates & totalReward0EStates;
976 // Set a scheduler for the ecStates that we want to reach
977 computeSchedulerProb0(transitionMatrix, backwardsTransitions, ecStatesToReach, ~unprocessedStates | ~totalReward0EStates,
978 actionsWithoutRewardInUnboundedPhase, originalOptimalChoices);
979 }
980 unprocessedStates &= ~ecStatesToReach;
981 // Set a scheduler for the remaining states
982 computeSchedulerProb1(transitionMatrix, backwardsTransitions, unprocessedStates, ecStatesToReach, originalOptimalChoices);
983}
984
987
990
991} // namespace multiobjective
992} // namespace modelchecker
993} // namespace storm
SolverEnvironment & solver()
MinMaxSolverEnvironment & minMax()
ValueType computeUpperBound()
Computes an upper bound on the expected rewards.
std::vector< ValueType > computeUpperBounds()
Computes upper bounds on the expected rewards.
void setProduceScheduler(bool value)
Sets whether an optimal scheduler shall be constructed during the computation.
void setOptimizationDirection(storm::solver::OptimizationDirection const &direction)
Sets the optimization direction, i.e., whether we want to minimize or maximize the value for each sta...
void provideLongRunComponentDecomposition(storm::storage::Decomposition< LongRunComponentType > const &decomposition)
Provides the decomposition into long run components (BSCCs/MECs) that can be used during the computat...
std::vector< ValueType > computeLongRunAverageValues(Environment const &env, std::vector< ValueType > const *stateValues=nullptr, std::vector< ValueType > const *actionValues=nullptr)
Computes the long run average value given the provided state and action-based 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.
virtual ValueType computeLraForComponent(Environment const &env, ValueGetter const &stateValuesGetter, ValueGetter const &actionValuesGetter, storm::storage::MaximalEndComponent const &component) override
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.
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 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.
void setUpperBound(ValueType const &value)
Sets an upper bound for the solution that can potentially be used by the solver.
bool hasUpperBound(BoundType const &type=BoundType::Any) const
Retrieves whether this solver has an upper bound.
bool hasLowerBound(BoundType const &type=BoundType::Any) const
Retrieves whether this solver has a lower bound.
void setLowerBound(ValueType const &value)
Sets a lower bound for the solution that can potentially be used by the solver.
void setLowerBounds(std::vector< ValueType > const &values)
Sets lower bounds for the solution that can potentially be used by the solver.
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.
void resize(uint64_t newLength, bool init=false)
Resizes the bit vector to hold the given new number of bits.
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 selectRowsFromRowGroups(std::vector< index_type > const &rowGroupToRowIndexMapping, bool insertDiagonalEntries=true) const
Selects exactly one row from each row group of this matrix and returns the resulting matrix.
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)
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
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:1145
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
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:1099
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 sqrt(ValueType const &number)