Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
MakePOMDPCanonic.cpp
Go to the documentation of this file.
2
8
9namespace storm {
10namespace transformer {
11
12namespace detail {
14 uint64_t choiceLabelId;
16
17 bool compatibleWith(ActionIdentifier const& other) const {
18 if (choiceLabelId != other.choiceLabelId) {
19 return false; // different labels.
20 }
21 if (choiceLabelId > 0) {
22 // Notice that we assume that we already have ensured that names
23 // are not used more than once.
24 return true; // actions have a name, name coincides.
25 } else {
26 // action is unnamed.
27 // We only call this method (at least we only should call this method)
28 // if there are multiple actions. Then two tau actions are only compatible
29 // if they are described by the same choice origin.
30 return choiceOriginId == other.choiceOriginId;
31 }
32 }
33
34 friend bool operator<(ActionIdentifier const& lhs, ActionIdentifier const& rhs);
35};
36
37template<typename iterator1, typename iterator2>
38bool compatibleWith(iterator1 start1, iterator1 end1, iterator2 start2, iterator2 end2) {
39 iterator1 it1 = start1;
40 iterator2 it2 = start2;
41 while (it1 != end1 && it2 != end2) {
42 if (!it1->compatibleWith(it2->first)) {
43 return false;
44 }
45 ++it1;
46 ++it2;
47 }
48 return it1 == end1 && it2 == end2;
49}
50
51bool operator<(ActionIdentifier const& lhs, ActionIdentifier const& rhs) {
52 if (lhs.choiceLabelId == rhs.choiceLabelId) {
53 return lhs.choiceOriginId < rhs.choiceOriginId;
54 }
55 return lhs.choiceLabelId < rhs.choiceLabelId;
56}
57
59 public:
60 uint64_t registerLabel(std::string const& label) {
61 auto it = std::find(storage.begin(), storage.end(), label);
62 if (it == storage.end()) {
63 storage.push_back(label);
64 return storage.size() - 1;
65 } else {
66 return it - storage.begin();
67 }
68 }
69
70 std::string const& getLabel(uint64_t id) const {
71 STORM_LOG_ASSERT(id < storage.size(), "Id must be in storage");
72 return storage[id];
73 }
74
75 friend std::ostream& operator<<(std::ostream& os, ChoiceLabelIdStorage const& labelStorage);
76
77 private:
78 std::vector<std::string> storage = {""};
79};
80
81std::ostream& operator<<(std::ostream& os, ChoiceLabelIdStorage const& labelStorage) {
82 os << "LabelStorage: {";
83 uint64_t i = 0;
84 for (auto const& entry : labelStorage.storage) {
85 os << i << " -> " << entry << " ;";
86 ++i;
87 }
88 os << "}";
89 return os;
90}
91
92void actionIdentifiersToStream(std::ostream& stream, std::vector<ActionIdentifier> const& actionIdentifiers, ChoiceLabelIdStorage const& labelStorage) {
93 stream << "actions: {";
94 bool first = true;
95 for (auto ai : actionIdentifiers) {
96 if (first) {
97 first = false;
98 } else {
99 stream << " ";
100 }
101 stream << "[" << ai.choiceLabelId << " (" << labelStorage.getLabel(ai.choiceLabelId) << ")";
102 stream << ", " << ai.choiceOriginId << "]";
103 }
104 stream << "}";
105}
106
107template<typename IrrelevantType>
108void actionIdentifiersToStream(std::ostream& stream, std::map<ActionIdentifier, IrrelevantType> const& actionIdentifiers,
109 ChoiceLabelIdStorage const& labelStorage) {
110 stream << "actions: {";
111 bool first = true;
112 for (auto ai : actionIdentifiers) {
113 if (first) {
114 first = false;
115 } else {
116 stream << " ";
117 }
118 stream << "[" << ai.first.choiceLabelId << " (" << labelStorage.getLabel(ai.first.choiceLabelId) << ")";
119 stream << ", " << ai.first.choiceOriginId << "]";
120 }
121 stream << "}";
122}
123
124} // namespace detail
125
126template<typename ValueType>
127std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> MakePOMDPCanonic<ValueType>::transform() const {
128 STORM_LOG_THROW(pomdp.hasChoiceLabeling(), storm::exceptions::InvalidArgumentException,
129 "Model must have been built with choice labels (--buildchoicelab for command line users)");
130 std::vector<uint64_t> permutation = computeCanonicalPermutation();
131 return applyPermutationOnPomdp(permutation);
132}
133
134template<typename ValueType>
135std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> MakePOMDPCanonic<ValueType>::applyPermutationOnPomdp(std::vector<uint64_t> permutation) const {
136 auto rewardModels = pomdp.getRewardModels();
137 std::unordered_map<std::string, storm::models::sparse::StandardRewardModel<ValueType>> newRewardModels;
138 for (auto& rewardModelNameAndModel : rewardModels) {
139 newRewardModels.emplace(rewardModelNameAndModel.first, rewardModelNameAndModel.second.permuteActions(permutation));
140 }
141 storm::storage::sparse::ModelComponents<ValueType> modelcomponents(pomdp.getTransitionMatrix().permuteRows(permutation), pomdp.getStateLabeling(),
142 newRewardModels, false, boost::none);
143 modelcomponents.observabilityClasses = pomdp.getObservations();
144 modelcomponents.stateValuations = pomdp.getOptionalStateValuations();
145 modelcomponents.choiceLabeling = pomdp.getChoiceLabeling();
146 modelcomponents.choiceLabeling->permuteItems(permutation);
147 modelcomponents.observationValuations = pomdp.getOptionalObservationValuations();
148 return std::make_shared<storm::models::sparse::Pomdp<ValueType>>(modelcomponents, true);
149}
150
151template<typename ValueType>
152std::string MakePOMDPCanonic<ValueType>::getStateInformation(uint64_t state) const {
153 if (pomdp.hasStateValuations()) {
154 return std::to_string(state) + " " + pomdp.getStateValuations().getStateInfo(state);
155 } else {
156 return std::to_string(state);
157 }
158}
159
160template<typename ValueType>
162 if (pomdp.hasObservationValuations()) {
163 return std::to_string(obs) + " " + pomdp.getObservationValuations().getStateInfo(obs);
164 } else {
165 return std::to_string(obs);
166 }
167}
168
169template<typename ValueType>
171 std::map<uint32_t, std::vector<detail::ActionIdentifier>> observationActionIdentifiers;
172 std::map<uint32_t, uint64_t> actionIdentifierDefinition;
173 auto const& choiceLabeling = pomdp.getChoiceLabeling();
174 detail::ChoiceLabelIdStorage labelStorage;
175
176 std::vector<uint64_t> permutation;
177 uint64_t nrObservations = pomdp.getNrObservations();
178 storm::storage::BitVector oneActionObservations(nrObservations);
179 storm::storage::BitVector moreActionObservations(nrObservations);
180 uint64_t freshChoiceOriginId = 0; // Only used if no choice origins are available.
181
182 for (uint64_t state = 0; state < pomdp.getNumberOfStates(); ++state) {
183 uint64_t rowIndexFrom = pomdp.getTransitionMatrix().getRowGroupIndices()[state];
184 uint64_t rowIndexTo = pomdp.getTransitionMatrix().getRowGroupIndices()[state + 1];
185
186 uint64_t observation = pomdp.getObservation(state);
187 if (rowIndexFrom + 1 == rowIndexTo) {
188 permutation.push_back(rowIndexFrom);
189 if (moreActionObservations.get(observation)) {
190 // We have seen this observation previously with multiple actions. Error!
191 // TODO provide more diagnostic information
192 std::string actionval = "";
193 if (pomdp.hasChoiceLabeling()) {
194 auto labelsOfChoice = pomdp.getChoiceLabeling().getLabelsOfChoice(rowIndexFrom);
195 if (labelsOfChoice.empty()) {
196 actionval = "[[no-label]]";
197 } else {
198 actionval = *pomdp.getChoiceLabeling().getLabelsOfChoice(rowIndexFrom).begin();
199 }
200 }
201 STORM_LOG_THROW(false, storm::exceptions::AmbiguousModelException,
202 "Observation " << getObservationInformation(observation) << " sometimes provides multiple actions, but in state "
203 << getStateInformation(state) << " provides only one action " << actionval << ".");
204 }
205 oneActionObservations.set(observation);
206
207 // One action is ALWAYS fine.
208 continue;
209 } else {
210 if (oneActionObservations.get(observation)) {
211 // We have seen this observation previously with one action. Error!
212 // std::string actionval= "";
213 // if (pomdp.hasChoiceLabeling()) {
214 // actionval = *pomdp.getChoiceLabeling().getLabelsOfChoice(rowIndexFrom).begin();
215 // }
216 STORM_LOG_THROW(false, storm::exceptions::AmbiguousModelException,
217 "Observation " << getObservationInformation(observation) << " sometimes provides one action, but in state "
218 << getStateInformation(state) << " provides " << rowIndexTo - rowIndexFrom << " actions.");
219 }
220 moreActionObservations.set(observation);
221 }
222
223 std::map<detail::ActionIdentifier, uint64_t> actionIdentifiers;
224 std::set<uint64_t> actionLabels;
225 for (uint64_t actionIndex = rowIndexFrom; actionIndex < rowIndexTo; ++actionIndex) {
226 // While this is in full generality a set of labels,
227 // for models modelled with prism, we actually know that these are singleton sets.
228 std::set<std::string> labels = choiceLabeling.getLabelsOfChoice(actionIndex);
229 STORM_LOG_ASSERT(labels.size() <= 1, "We expect choice labels to be single sets");
230 // Generate action identifier
231 uint64_t labelId = -1;
232 if (labels.size() == 1) {
233 labelId = labelStorage.registerLabel(*labels.begin());
234 STORM_LOG_THROW(actionLabels.count(labelId) == 0, storm::exceptions::AmbiguousModelException,
235 "Multiple actions with label '" << *labels.begin() << "' exist in state id " << state << ".");
236 actionLabels.emplace(labelId);
237 } else {
238 labelId = labelStorage.registerLabel("");
239 }
240
242 ai.choiceLabelId = labelId;
243 if (pomdp.hasChoiceOrigins()) {
244 ai.choiceOriginId = pomdp.getChoiceOrigins()->getIdentifier(actionIndex);
245 } else {
246 ai.choiceOriginId = freshChoiceOriginId++;
247 }
248 STORM_LOG_ASSERT(actionIdentifiers.count(ai) == 0, "Action with this identifier already exists for this state");
249 actionIdentifiers.emplace(ai, actionIndex);
250 }
251 STORM_LOG_ASSERT(actionIdentifiers.size() == rowIndexTo - rowIndexFrom, "Number of action identifiers should match number of actions");
252
253 if (observationActionIdentifiers.count(observation) == 0) {
254 // First state with this observation
255 // store the corresponding vector.
256 std::vector<detail::ActionIdentifier> ais;
257 for (auto const& als : actionIdentifiers) {
258 ais.push_back(als.first);
259 }
260
261 observationActionIdentifiers.emplace(observation, ais);
262 actionIdentifierDefinition.emplace(observation, state);
263 } else {
264 auto referenceStart = observationActionIdentifiers[observation].begin();
265 auto referenceEnd = observationActionIdentifiers[observation].end();
266 STORM_LOG_ASSERT(observationActionIdentifiers[observation].size() == pomdp.getNumberOfChoices(actionIdentifierDefinition[observation]),
267 "Number of actions recorded for state does not coincide with number of actions.");
268 if (observationActionIdentifiers[observation].size() != actionIdentifiers.size()) {
269 STORM_LOG_THROW(false, storm::exceptions::AmbiguousModelException,
270 "Number of actions in state '" << getStateInformation(state) << "' (nr actions:" << actionIdentifiers.size() << ") and state '"
271 << getStateInformation(actionIdentifierDefinition[observation])
272 << "' (actions: " << observationActionIdentifiers[observation].size()
273 << " ), both having observation " << getObservationInformation(observation) << " do not match.");
274 }
275 if (!detail::compatibleWith(referenceStart, referenceEnd, actionIdentifiers.begin(), actionIdentifiers.end())) {
276 std::cout << "Observation " << getObservationInformation(observation) << ": \n";
277 detail::actionIdentifiersToStream(std::cout, observationActionIdentifiers[observation], labelStorage);
278 std::cout << " according to state " << actionIdentifierDefinition[observation] << ".\n";
279 detail::actionIdentifiersToStream(std::cout, actionIdentifiers, labelStorage);
280 std::cout << " according to state " << state << ".\n";
281 std::cout << "(Actions are given as [id (label), originId])\n";
282
283 for (auto const& ai : actionIdentifiers) {
284 if (labelStorage.getLabel(ai.first.choiceLabelId) == "") {
285 for (auto const& ai2 : observationActionIdentifiers[observation]) {
286 STORM_LOG_WARN_COND(ai2.choiceLabelId != ai.first.choiceLabelId,
287 "Actions with empty label are only considered the same action if they originate from the same command.");
288 }
289 }
290 }
291
292 STORM_LOG_THROW(false, storm::exceptions::AmbiguousModelException,
293 "Actions identifiers do not align between states \n\t"
294 << getStateInformation(state) << "\nand\n\t" << getStateInformation(actionIdentifierDefinition[observation])
295 << "\nboth having observation " << getObservationInformation(observation) << ". See output above for more information.");
296 }
297 }
298
299 for (auto const& al : actionIdentifiers) {
300 permutation.push_back(al.second);
301 }
302 }
303 return permutation;
304}
305
306template class MakePOMDPCanonic<double>;
311} // namespace transformer
312} // namespace storm
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.
std::shared_ptr< storm::models::sparse::Pomdp< ValueType > > transform() const
std::shared_ptr< storm::models::sparse::Pomdp< ValueType > > applyPermutationOnPomdp(std::vector< uint64_t > permutation) const
std::vector< uint64_t > computeCanonicalPermutation() const
storm::models::sparse::Pomdp< ValueType > const & pomdp
std::string getObservationInformation(uint32_t obs) const
std::string getStateInformation(uint64_t state) const
uint64_t registerLabel(std::string const &label)
std::string const & getLabel(uint64_t id) const
friend std::ostream & operator<<(std::ostream &os, ChoiceLabelIdStorage const &labelStorage)
#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
bool compatibleWith(iterator1 start1, iterator1 end1, iterator2 start2, iterator2 end2)
void actionIdentifiersToStream(std::ostream &stream, std::vector< ActionIdentifier > const &actionIdentifiers, ChoiceLabelIdStorage const &labelStorage)
std::ostream & operator<<(std::ostream &os, ChoiceLabelIdStorage const &labelStorage)
bool operator<(ActionIdentifier const &lhs, ActionIdentifier const &rhs)
std::optional< storm::storage::sparse::StateValuations > stateValuations
std::optional< storm::storage::sparse::StateValuations > observationValuations
std::optional< storm::models::sparse::ChoiceLabeling > choiceLabeling
std::optional< std::vector< uint32_t > > observabilityClasses
friend bool operator<(ActionIdentifier const &lhs, ActionIdentifier const &rhs)
bool compatibleWith(ActionIdentifier const &other) const