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