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