Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
PreprocessingPomdpValueBoundsModelChecker.cpp
Go to the documentation of this file.
2#include <random>
3
6
9
15
16namespace storm {
17namespace pomdp {
18namespace modelchecker {
19template<typename ValueType>
22
23template<typename ValueType>
28
29template<typename ValueType>
35
36template<typename ValueType>
37std::vector<ValueType> PreprocessingPomdpValueBoundsModelChecker<ValueType>::getChoiceValues(std::vector<ValueType> const& stateValues,
38 std::vector<ValueType>* actionBasedRewards) {
39 std::vector<ValueType> choiceValues((pomdp.getNumberOfChoices()));
40 pomdp.getTransitionMatrix().multiplyWithVector(stateValues, choiceValues, actionBasedRewards);
41 return choiceValues;
42}
43
44template<typename ValueType>
45std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>> PreprocessingPomdpValueBoundsModelChecker<ValueType>::computeValuesForGuessedScheduler(
46 storm::Environment const& env, std::vector<ValueType> const& stateValues, std::vector<ValueType>* actionBasedRewards, storm::logic::Formula const& formula,
48 ValueType const& scoreThreshold, bool relativeScore) {
49 // Create some positional scheduler for the POMDP
50 storm::storage::Scheduler<ValueType> pomdpScheduler(pomdp.getNumberOfStates());
51 // For each state, we heuristically find a good distribution over output actions.
52 auto choiceValues = getChoiceValues(stateValues, actionBasedRewards);
53 auto const& choiceIndices = pomdp.getTransitionMatrix().getRowGroupIndices();
54 std::vector<storm::storage::Distribution<ValueType, uint_fast64_t>> choiceDistributions(pomdp.getNrObservations());
55 for (uint64_t state = 0; state < pomdp.getNumberOfStates(); ++state) {
56 auto& choiceDistribution = choiceDistributions[pomdp.getObservation(state)];
57 ValueType const& stateValue = stateValues[state];
58 STORM_LOG_ASSERT(stateValue >= storm::utility::zero<ValueType>(), "State value expected non-negative.");
59 for (auto choice = choiceIndices[state]; choice < choiceIndices[state + 1]; ++choice) {
60 ValueType const& choiceValue = choiceValues[choice];
61 STORM_LOG_ASSERT(choiceValue >= storm::utility::zero<ValueType>(), "Choice value expected non-negative.");
62 // Rate this choice by considering the relative difference between the choice value and the (optimal) state value
63 // A high score shall mean that the choice is "good"
64 if (storm::utility::isInfinity(stateValue)) {
65 // For infinity states, we simply distribute uniformly.
66 // This case could be handled a bit more sensible
67 choiceDistribution.addProbability(choice - choiceIndices[state], scoreThreshold);
68 } else {
69 ValueType choiceScore = info.minimize() ? (choiceValue - stateValue) : (stateValue - choiceValue);
70 if (relativeScore) {
71 ValueType avg = (stateValue + choiceValue) / storm::utility::convertNumber<ValueType, uint64_t>(2);
72 if (!storm::utility::isZero(avg)) {
73 choiceScore /= avg;
74 }
75 }
76 choiceScore = storm::utility::one<ValueType>() - choiceScore;
77 if (choiceScore >= scoreThreshold) {
78 choiceDistribution.addProbability(choice - choiceIndices[state], choiceScore);
79 }
80 }
81 }
82 // If the distribution is empty, i.e. no choice has had a suitable score, distribute uniformly
83 if (choiceDistribution.size() == 0) {
84 for (auto choice = choiceIndices[state]; choice < choiceIndices[state + 1]; ++choice) {
85 choiceDistribution.addProbability(choice - choiceIndices[state], scoreThreshold);
86 }
87 }
88 STORM_LOG_ASSERT(choiceDistribution.size() > 0, "Empty choice distribution.");
89 }
90 // Normalize all distributions
91 for (auto& choiceDistribution : choiceDistributions) {
92 choiceDistribution.normalize();
93 }
94 // Set the scheduler for all states
95 for (uint64_t state = 0; state < pomdp.getNumberOfStates(); ++state) {
96 pomdpScheduler.setChoice(choiceDistributions[pomdp.getObservation(state)], state);
97 }
98 STORM_LOG_ASSERT(!pomdpScheduler.isPartialScheduler(), "Expected a fully defined scheduler.");
99 auto scheduledModel = underlyingMdp->applyScheduler(pomdpScheduler, false);
100
101 auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(env, scheduledModel, storm::api::createTask<ValueType>(formula.asSharedPointer(), false));
102 STORM_LOG_THROW(resultPtr, storm::exceptions::UnexpectedException, "No check result obtained.");
103 STORM_LOG_THROW(resultPtr->isExplicitQuantitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected Check result Type.");
104 std::vector<ValueType> pomdpSchedulerResult = std::move(resultPtr->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector());
105 return std::make_pair(pomdpSchedulerResult, pomdpScheduler);
106}
107
108template<typename ValueType>
109std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>> PreprocessingPomdpValueBoundsModelChecker<ValueType>::computeValuesForRandomFMPolicy(
110 storm::Environment const& env, storm::logic::Formula const& formula, storm::pomdp::analysis::FormulaInformation const& info, uint64_t memoryBound) {
111 // Consider memoryless policy on memory-unfolded POMDP
112 storm::storage::Scheduler<ValueType> pomdpScheduler(pomdp.getNumberOfStates() * memoryBound);
113
114 STORM_LOG_DEBUG("Computing the unfolding for memory bound " << memoryBound);
115 storm::storage::PomdpMemory memory = storm::storage::PomdpMemoryBuilder().build(storm::storage::PomdpMemoryPattern::Full, memoryBound);
116 storm::transformer::PomdpMemoryUnfolder<ValueType> memoryUnfolder(pomdp, memory);
117 // We keep unreachable states to not mess with the state ordering and capture potential better choices
118 auto memPomdp = memoryUnfolder.transform(false);
119
120 // Determine an observation-based policy by choosing any of the enabled actions uniformly at random
121 std::vector<uint64_t> obsChoiceVector(memPomdp->getNrObservations());
122 std::random_device rd;
123 auto engine = std::mt19937(rd());
124 for (uint64_t obs = 0; obs < memPomdp->getNrObservations(); ++obs) {
125 uint64_t nrChoices = memPomdp->getNumberOfChoices(memPomdp->getStatesWithObservation(obs).front());
126 std::uniform_int_distribution<uint64_t> uniform_dist(0, nrChoices - 1);
127 obsChoiceVector[obs] = uniform_dist(engine);
128 }
129 for (uint64_t state = 0; state < memPomdp->getNumberOfStates(); ++state) {
130 pomdpScheduler.setChoice(obsChoiceVector[memPomdp->getObservation(state)], state);
131 }
132
133 // Model check the DTMC resulting from the policy
134 auto underlyingMdp =
135 std::make_shared<storm::models::sparse::Mdp<ValueType>>(memPomdp->getTransitionMatrix(), memPomdp->getStateLabeling(), memPomdp->getRewardModels());
136 auto scheduledModel = underlyingMdp->applyScheduler(pomdpScheduler, false);
137 auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(env, scheduledModel, storm::api::createTask<ValueType>(formula.asSharedPointer(), false));
138 STORM_LOG_THROW(resultPtr, storm::exceptions::UnexpectedException, "No check result obtained.");
139 STORM_LOG_THROW(resultPtr->isExplicitQuantitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected Check result Type.");
140 std::vector<ValueType> pomdpSchedulerResult = std::move(resultPtr->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector());
141
142 // Take the optimal value in ANY of the unfolded states for a POMDP state as the resulting state value
143 std::vector<ValueType> res(pomdp.getNumberOfStates(), storm::utility::zero<ValueType>());
144 storm::storage::BitVector hasValue(pomdp.getNumberOfStates(), false);
145 for (uint64_t memPomdpState = 0; memPomdpState < pomdpSchedulerResult.size(); ++memPomdpState) {
146 uint64_t modelState = memPomdpState / memoryBound;
147 if (!hasValue.get(modelState) || (info.minimize() && pomdpSchedulerResult[memPomdpState] < res[modelState]) ||
148 (!info.minimize() && pomdpSchedulerResult[memPomdpState] > res[modelState])) {
149 res[modelState] = pomdpSchedulerResult[memPomdpState];
150 hasValue.set(modelState);
151 }
152 }
153 return std::make_pair(res, pomdpScheduler);
154}
155
156template<typename ValueType>
157[[maybe_unused]] std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>
158PreprocessingPomdpValueBoundsModelChecker<ValueType>::computeValuesForRandomMemorylessPolicy(
159 storm::Environment const& env, storm::logic::Formula const& formula, storm::pomdp::analysis::FormulaInformation const& info,
160 std::shared_ptr<storm::models::sparse::Mdp<ValueType>> underlyingMdp) {
161 storm::storage::Scheduler<ValueType> pomdpScheduler(pomdp.getNumberOfStates());
162 std::vector<uint64_t> obsChoiceVector(pomdp.getNrObservations());
163
164 std::random_device rd;
165 auto engine = std::mt19937(rd());
166 for (uint64_t obs = 0; obs < pomdp.getNrObservations(); ++obs) {
167 uint64_t nrChoices = pomdp.getNumberOfChoices(pomdp.getStatesWithObservation(obs).front());
168 std::uniform_int_distribution<uint64_t> uniform_dist(0, nrChoices - 1);
169 obsChoiceVector[obs] = uniform_dist(engine);
170 }
171
172 for (uint64_t state = 0; state < pomdp.getNumberOfStates(); ++state) {
173 STORM_LOG_DEBUG("State " << state << " -- Random Choice " << obsChoiceVector[pomdp.getObservation(state)]);
174 pomdpScheduler.setChoice(obsChoiceVector[pomdp.getObservation(state)], state);
175 }
176
177 auto scheduledModel = underlyingMdp->applyScheduler(pomdpScheduler, false);
178
179 auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(env, scheduledModel, storm::api::createTask<ValueType>(formula.asSharedPointer(), false));
180 STORM_LOG_THROW(resultPtr, storm::exceptions::UnexpectedException, "No check result obtained.");
181 STORM_LOG_THROW(resultPtr->isExplicitQuantitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected Check result Type.");
182 std::vector<ValueType> pomdpSchedulerResult = std::move(resultPtr->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector());
183
184 STORM_LOG_DEBUG("Initial Value for guessed Policy: " << pomdpSchedulerResult[pomdp.getInitialStates().getNextSetIndex(0)]);
185
186 return std::make_pair(pomdpSchedulerResult, pomdpScheduler);
187}
188
189template<typename ValueType>
192 STORM_LOG_THROW(info.isNonNestedReachabilityProbability() || info.isNonNestedExpectedRewardFormula(), storm::exceptions::NotSupportedException,
193 "The property type is not supported for this analysis.");
194
195 // Compute the values on the fully observable MDP
196 // We need an actual MDP so that we can apply schedulers below.
197 // Also, the api call in the next line will require a copy anyway.
198 auto underlyingMdp =
199 std::make_shared<storm::models::sparse::Mdp<ValueType>>(pomdp.getTransitionMatrix(), pomdp.getStateLabeling(), pomdp.getRewardModels());
200 auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(env, underlyingMdp, storm::api::createTask<ValueType>(formula.asSharedPointer(), false));
201 STORM_LOG_THROW(resultPtr, storm::exceptions::UnexpectedException, "No check result obtained.");
202 STORM_LOG_THROW(resultPtr->isExplicitQuantitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected Check result Type.");
203 std::vector<ValueType> fullyObservableResult = std::move(resultPtr->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector());
204
205 std::vector<ValueType> actionBasedRewards;
206 std::vector<ValueType>* actionBasedRewardsPtr = nullptr;
208 actionBasedRewards = pomdp.getRewardModel(info.getRewardModelName()).getTotalRewardVector(pomdp.getTransitionMatrix());
209 actionBasedRewardsPtr = &actionBasedRewards;
210 }
211 std::vector<std::vector<ValueType>> guessedSchedulerValues;
212 std::vector<storm::storage::Scheduler<ValueType>> guessedSchedulers;
213 std::shared_ptr<std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>> guessedSchedulerPair;
214 std::vector<std::pair<double, bool>> guessParameters({{0.875, false}, {0.875, true}, {0.75, false}, {0.75, true}});
215 for (auto const& pars : guessParameters) {
216 guessedSchedulerPair = std::make_shared<std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>>(
217 computeValuesForGuessedScheduler(env, fullyObservableResult, actionBasedRewardsPtr, formula, info, underlyingMdp,
219 guessedSchedulerValues.push_back(guessedSchedulerPair->first);
220 guessedSchedulers.push_back(guessedSchedulerPair->second);
221 }
222
223 // compute the 'best' guess and do a few iterations on it
224 uint64_t bestGuess = 0;
225 ValueType bestGuessSum = std::accumulate(guessedSchedulerValues.front().begin(), guessedSchedulerValues.front().end(), storm::utility::zero<ValueType>());
226 for (uint64_t guess = 1; guess < guessedSchedulerValues.size(); ++guess) {
227 ValueType guessSum = std::accumulate(guessedSchedulerValues[guess].begin(), guessedSchedulerValues[guess].end(), storm::utility::zero<ValueType>());
228 if ((info.minimize() && guessSum < bestGuessSum) || (info.maximize() && guessSum > bestGuessSum)) {
229 bestGuess = guess;
230 bestGuessSum = guessSum;
231 }
232 }
233 guessedSchedulerPair = std::make_shared<std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>>(
234 computeValuesForGuessedScheduler(env, guessedSchedulerValues[bestGuess], actionBasedRewardsPtr, formula, info, underlyingMdp,
235 storm::utility::convertNumber<ValueType>(guessParameters[bestGuess].first), guessParameters[bestGuess].second));
236 guessedSchedulerValues.push_back(guessedSchedulerPair->first);
237 guessedSchedulers.push_back(guessedSchedulerPair->second);
238 guessedSchedulerPair = std::make_shared<std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>>(
239 computeValuesForGuessedScheduler(env, guessedSchedulerValues.back(), actionBasedRewardsPtr, formula, info, underlyingMdp,
240 storm::utility::convertNumber<ValueType>(guessParameters[bestGuess].first), guessParameters[bestGuess].second));
241 guessedSchedulerValues.push_back(guessedSchedulerPair->first);
242 guessedSchedulers.push_back(guessedSchedulerPair->second);
243 guessedSchedulerPair = std::make_shared<std::pair<std::vector<ValueType>, storm::storage::Scheduler<ValueType>>>(
244 computeValuesForGuessedScheduler(env, guessedSchedulerValues.back(), actionBasedRewardsPtr, formula, info, underlyingMdp,
245 storm::utility::convertNumber<ValueType>(guessParameters[bestGuess].first), guessParameters[bestGuess].second));
246 guessedSchedulerValues.push_back(guessedSchedulerPair->first);
247 guessedSchedulers.push_back(guessedSchedulerPair->second);
248
249 // Check if one of the guesses is worse than one of the others (and potentially delete it)
250 // Avoid deleting entries during the loop to ensure that indices remain valid
251 storm::storage::BitVector keptGuesses(guessedSchedulerValues.size(), true);
252 for (uint64_t i = 0; i < guessedSchedulerValues.size() - 1; ++i) {
253 if (!keptGuesses.get(i)) {
254 continue;
255 }
256 for (uint64_t j = i + 1; j < guessedSchedulerValues.size(); ++j) {
257 if (!keptGuesses.get(j)) {
258 continue;
259 }
260 if (storm::utility::vector::compareElementWise(guessedSchedulerValues[i], guessedSchedulerValues[j], std::less_equal<ValueType>())) {
261 if (info.minimize()) {
262 // In this case we are guessing upper bounds (and smaller upper bounds are better)
263 keptGuesses.set(j, false);
264 } else {
265 // In this case we are guessing lower bounds (and larger lower bounds are better)
266 keptGuesses.set(i, false);
267 break;
268 }
269 } else if (storm::utility::vector::compareElementWise(guessedSchedulerValues[j], guessedSchedulerValues[i], std::less_equal<ValueType>())) {
270 if (info.minimize()) {
271 keptGuesses.set(i, false);
272 break;
273 } else {
274 keptGuesses.set(j, false);
275 }
276 }
277 }
278 }
279 STORM_LOG_INFO("Keeping scheduler guesses " << keptGuesses);
280 storm::utility::vector::filterVectorInPlace(guessedSchedulerValues, keptGuesses);
281 std::vector<storm::storage::Scheduler<ValueType>> filteredSchedulers;
282 for (uint64_t i = 0; i < guessedSchedulers.size(); ++i) {
283 if (keptGuesses[i]) {
284 filteredSchedulers.push_back(guessedSchedulers[i]);
285 }
286 }
287
288 // Finally prepare the result
289 ValueBounds result;
290 if (info.minimize()) {
291 result.lower.push_back(std::move(fullyObservableResult));
292 result.upper = std::move(guessedSchedulerValues);
293 result.upperSchedulers = filteredSchedulers;
294 } else {
295 result.lower = std::move(guessedSchedulerValues);
296 result.upper.push_back(std::move(fullyObservableResult));
297 result.lowerSchedulers = filteredSchedulers;
298 }
299 STORM_LOG_WARN_COND_DEBUG(storm::utility::vector::compareElementWise(result.lower.front(), result.upper.front(), std::less_equal<ValueType>()),
300 "Lower bound is larger than upper bound");
301 return result;
302}
303
304template<typename ValueType>
310
311template<typename ValueType>
316
317template<typename ValueType>
320 STORM_LOG_THROW(info.isNonNestedExpectedRewardFormula(), storm::exceptions::NotSupportedException, "The property type is not supported for this analysis.");
321
322 // Compute the values for the opposite direction on the fully observable MDP
323 // We need an actual MDP so that we can apply schedulers below.
324 // Also, the api call in the next line will require a copy anyway.
326 if (formula.asOperatorFormula().getOptimalityType() == storm::solver::OptimizationDirection::Maximize) {
327 newFormula.setOptimalityType(storm::solver::OptimizationDirection::Minimize);
328 } else {
329 newFormula.setOptimalityType(storm::solver::OptimizationDirection::Maximize);
330 }
331 auto formulaPtr = std::make_shared<storm::logic::RewardOperatorFormula>(newFormula);
332 auto underlyingMdp =
333 std::make_shared<storm::models::sparse::Mdp<ValueType>>(pomdp.getTransitionMatrix(), pomdp.getStateLabeling(), pomdp.getRewardModels());
334 auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(env, underlyingMdp, storm::api::createTask<ValueType>(formulaPtr, false));
335 STORM_LOG_THROW(resultPtr, storm::exceptions::UnexpectedException, "No check result obtained.");
336 STORM_LOG_THROW(resultPtr->isExplicitQuantitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected Check result Type.");
337 std::vector<ValueType> resultVec = std::move(resultPtr->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector());
339 if (info.minimize()) {
340 res.min = false;
341 } else {
342 res.min = true;
343 }
345 res.values = std::move(resultVec);
346 return res;
347}
348
350
352} // namespace modelchecker
353} // namespace pomdp
354} // namespace storm
RewardOperatorFormula & asRewardOperatorFormula()
Definition Formula.cpp:484
OperatorFormula & asOperatorFormula()
Definition Formula.cpp:492
std::shared_ptr< Formula const > asSharedPointer()
Definition Formula.cpp:571
void setOptimalityType(storm::solver::OptimizationDirection newOptimalityType)
storm::solver::OptimizationDirection const & getOptimalityType() const
This class represents a (discrete-time) Markov decision process.
Definition Mdp.h:13
This class represents a partially observable Markov decision process.
Definition Pomdp.h:13
PreprocessingPomdpValueBoundsModelChecker(storm::models::sparse::Pomdp< ValueType > const &pomdp)
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
This class defines which action is chosen in a particular state of a non-deterministic model.
Definition Scheduler.h:18
#define STORM_LOG_INFO(message)
Definition logging.h:27
#define STORM_LOG_DEBUG(message)
Definition logging.h:21
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:28
#define STORM_LOG_WARN_COND_DEBUG(cond, message)
Definition macros.h:16
storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > createTask(std::shared_ptr< const storm::logic::Formula > const &formula, bool onlyInitialStatesRelevant=false)
std::unique_ptr< storm::modelchecker::CheckResult > verifyWithSparseEngine(storm::Environment const &env, std::shared_ptr< storm::models::sparse::Dtmc< ValueType > > const &dtmc, storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > const &task)
FormulaInformation getFormulaInformation(PomdpType const &pomdp, storm::logic::ProbabilityOperatorFormula const &formula)
bool compareElementWise(std::vector< T > const &left, std::vector< T > const &right, Comparator comp=std::less< T >())
Definition vector.h:172
storm::storage::BitVector filterInfinity(std::vector< T > const &values)
Retrieves a bit vector containing all the indices for which the value at this position is equal to on...
Definition vector.h:541
void filterVectorInPlace(std::vector< Type > &v, storm::storage::BitVector const &filter)
Definition vector.h:1071
bool isZero(ValueType const &a)
Definition constants.cpp:42
ValueType zero()
Definition constants.cpp:24
ValueType one()
Definition constants.cpp:19
bool isInfinity(ValueType const &a)
TargetType convertNumber(SourceType const &number)
std::vector< storm::storage::Scheduler< ValueType > > lowerSchedulers
std::vector< storm::storage::Scheduler< ValueType > > upperSchedulers