Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ToStateBasedObservationTransformer.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <optional>
6#include <vector>
7
12#include "storm/utility/graph.h"
14
16
17template<typename ValueType>
18std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> ToStateBasedObservationTransformer<ValueType>::transform(
19 storm::models::sparse::Mdp<ValueType> const& mdp, TransitionObservationFunction const& transitionObservationFunction, ObservationType initialObservation,
20 bool dropUnreachableStates) {
21 STORM_LOG_WARN_COND(!mdp.hasStateValuations(), "State valuations are not preserved in transformation to state-based observations.");
22 STORM_LOG_WARN_COND(!mdp.hasChoiceOrigins(), "Choice origins are not preserved in transformation to state-based observations.");
23 auto const& transitionMatrix = mdp.getTransitionMatrix();
24 storm::storage::BitVector retainedStates(mdp.getNumberOfStates(), true);
25 if (dropUnreachableStates) {
26 retainedStates = storm::utility::graph::getReachableStates(transitionMatrix, mdp.getInitialStates(), retainedStates,
28 }
29
30 // Create a vector that for each state contains the set of observations with which we may enter that state.
31 std::vector<std::vector<ObservationType>> stateObservations(mdp.getNumberOfStates());
32 // Start with the initial observations
33 for (auto const initState : mdp.getInitialStates()) {
34 stateObservations[initState].push_back(initialObservation);
35 }
36 // Now run over the retained transitions, keeping the original state and local action indices.
37 // Also gather all transition observations so that we do not have to query them twice
38 std::vector<ObservationType> transitionTargetObservations;
39 transitionTargetObservations.reserve(transitionMatrix.getEntryCount());
40 for (auto state : retainedStates) {
41 auto const firstChoice = transitionMatrix.getRowGroupIndices()[state];
42 for (auto choice : transitionMatrix.getRowGroupIndices(state)) {
43 for (auto const& entry : transitionMatrix.getRow(choice)) {
44 if (dropUnreachableStates && storm::utility::isZero(entry.getValue())) {
45 continue;
46 }
47 auto const obs = transitionObservationFunction(state, choice - firstChoice, entry.getColumn());
48 transitionTargetObservations.push_back(obs);
49 auto& obsSet = stateObservations[entry.getColumn()];
50 if (std::find(obsSet.begin(), obsSet.end(), obs) == obsSet.end()) {
51 obsSet.push_back(obs);
52 }
53 }
54 }
55 }
56
57 // Create state offsets. The entry for a given input model state is the id of the first copy of that state.
58 std::vector<uint64_t> stateOffsets;
59 stateOffsets.reserve(mdp.getNumberOfStates() + 1);
60 stateOffsets.push_back(0);
61 for (uint64_t offset = 0; auto& obsSet : stateObservations) {
62 // When retaining unreachable states, give states without incoming transitions one copy.
63 if (!dropUnreachableStates && obsSet.empty()) {
64 obsSet.push_back(initialObservation);
65 }
66 offset += obsSet.size();
67 stateOffsets.push_back(offset);
68 }
69
70 // Create the transition matrix of the resulting model.
71 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(0, stateOffsets.back(), 0, true, true, stateOffsets.back());
72 auto transTargetObsIt = transitionTargetObservations.begin();
73 uint64_t rowInResultMatrix = 0;
74 for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) {
75 auto const transTargetObsBegin = transTargetObsIt;
76 for (auto _ [[maybe_unused]] : stateObservations[state]) {
77 transTargetObsIt = transTargetObsBegin;
78 matrixBuilder.newRowGroup(rowInResultMatrix);
79 for (auto choice : transitionMatrix.getRowGroupIndices(state)) {
80 for (auto const& entry : transitionMatrix.getRow(choice)) {
81 if (dropUnreachableStates && storm::utility::isZero(entry.getValue())) {
82 continue;
83 }
84 auto const targetObs = *transTargetObsIt;
85 auto const& targetObsSet = stateObservations[entry.getColumn()];
86 auto const findIt = std::find(targetObsSet.begin(), targetObsSet.end(), targetObs);
87 STORM_LOG_ASSERT(findIt != targetObsSet.end(), "Transition target observation not found in target state.");
88 auto transitionTarget = stateOffsets[entry.getColumn()] + std::distance(targetObsSet.begin(), findIt);
89 matrixBuilder.addNextValue(rowInResultMatrix, transitionTarget, entry.getValue());
90 ++transTargetObsIt;
91 }
92 ++rowInResultMatrix;
93 }
94 }
95 }
96
97 uint64_t const numResultStates = stateOffsets.back();
98 uint64_t const numResultChoices = rowInResultMatrix;
99
100 storm::storage::sparse::ModelComponents<ValueType> components(matrixBuilder.build(numResultChoices, numResultStates, numResultStates),
101 storm::models::sparse::StateLabeling(numResultStates));
102
103 // Helper functions to iterate over all states/choices. Unfolding states/choices are called in ascending order of their ids.
104 auto forEachState = [&stateOffsets](auto const& f) {
105 for (uint64_t ogState = 0; ogState < stateOffsets.size() - 1; ++ogState) {
106 for (uint64_t resultState = stateOffsets[ogState]; resultState < stateOffsets[ogState + 1]; ++resultState) {
107 f(resultState, ogState);
108 }
109 }
110 };
111 auto forEachChoice = [&stateOffsets, &transitionMatrix, &components](auto const& f) {
112 for (uint64_t ogState = 0; ogState < stateOffsets.size() - 1; ++ogState) {
113 for (uint64_t resultState = stateOffsets[ogState]; resultState < stateOffsets[ogState + 1]; ++resultState) {
114 STORM_LOG_ASSERT(components.transitionMatrix.getRowGroupSize(resultState) == transitionMatrix.getRowGroupSize(ogState),
115 "Number of choices in unfolding and original model differ.");
116 auto ogChoice = transitionMatrix.getRowGroupIndices()[ogState];
117 for (auto resultChoice : components.transitionMatrix.getRowGroupIndices(resultState)) {
118 f(resultChoice, ogChoice);
119 ++ogChoice;
120 }
121 }
122 }
123 };
124
125 // Create the state labeling
126 storm::storage::BitVector initialStates(numResultStates, false);
127 for (auto ogInitState : mdp.getInitialStates()) {
128 auto const& obsSet = stateObservations[ogInitState];
129 auto const findIt = std::find(obsSet.begin(), obsSet.end(), initialObservation);
130 STORM_LOG_ASSERT(findIt != obsSet.end(), "Initial observation not found in initial state.");
131 auto const resultInitState = stateOffsets[ogInitState] + std::distance(obsSet.begin(), findIt);
132 initialStates.set(resultInitState);
133 }
134 components.stateLabeling.addLabel("init", std::move(initialStates));
135 for (auto label : mdp.getStateLabeling().getLabels()) {
136 if (label == "init") {
137 continue;
138 }
139 auto const& originalLabel = mdp.getStateLabeling().getStates(label);
140 storm::storage::BitVector newLabel(numResultStates, false);
141 forEachState([&newLabel, &originalLabel](uint64_t resultState, StateIdType ogState) {
142 if (originalLabel.get(ogState)) {
143 newLabel.set(resultState);
144 }
145 });
146 components.stateLabeling.addLabel(label, std::move(newLabel));
147 }
148
149 // Create the (optional) choice labeling
150 if (mdp.hasChoiceLabeling()) {
151 components.choiceLabeling.emplace(numResultChoices);
152 for (auto label : mdp.getChoiceLabeling().getLabels()) {
153 auto const& originalLabel = mdp.getChoiceLabeling().getChoices(label);
154 storm::storage::BitVector newLabel(numResultChoices, false);
155 forEachChoice([&newLabel, &originalLabel](uint64_t unfoldingChoice, uint64_t originalChoice) {
156 if (originalLabel.get(originalChoice)) {
157 newLabel.set(unfoldingChoice);
158 }
159 });
160 components.choiceLabeling->addLabel(label, std::move(newLabel));
161 }
162 }
163
164 // Create the reward models
165 for (auto const& [name, rewmodel] : mdp.getRewardModels()) {
166 STORM_LOG_THROW(!rewmodel.hasTransitionRewards(), storm::exceptions::NotSupportedException,
167 "Transition rewards are currently not supported in this context.");
168 std::optional<std::vector<ValueType>> stateRewards, stateActionRewards;
169 if (rewmodel.hasStateRewards()) {
170 stateRewards.emplace();
171 stateRewards->reserve(numResultStates);
172 forEachState(
173 [&stateRewards, &rewmodel](uint64_t _, StateIdType originalState) { stateRewards->push_back(rewmodel.getStateReward(originalState)); });
174 }
175 if (rewmodel.hasStateActionRewards()) {
176 stateActionRewards.emplace();
177 stateActionRewards->reserve(numResultChoices);
178 forEachChoice([&stateActionRewards, &rewmodel](uint64_t _, StateIdType originalChoice) {
179 stateActionRewards->push_back(rewmodel.getStateActionReward(originalChoice));
180 });
181 }
182 components.rewardModels.emplace(name, storm::models::sparse::StandardRewardModel<ValueType>{std::move(stateRewards), std::move(stateActionRewards)});
183 }
184
185 // Create POMDP-specific components
186 std::vector<ObservationType> flatStateObservations;
187 flatStateObservations.reserve(numResultStates);
188 for (auto const& obsSet : stateObservations) {
189 flatStateObservations.insert(flatStateObservations.end(), obsSet.begin(), obsSet.end());
190 }
191 components.observabilityClasses = std::move(flatStateObservations);
192
194 ->template as<storm::models::sparse::Pomdp<ValueType>>();
195}
196
197template<typename ValueType>
198std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> ToStateBasedObservationTransformer<ValueType>::transformRewardAware(
199 storm::models::sparse::Pomdp<ValueType> const& pomdp, std::set<std::string> const& observableRewardModels, bool dropUnreachableStates) {
200 STORM_LOG_THROW(pomdp.getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException,
201 "The model must have exactly one initial state.");
202 auto const initialObservation = pomdp.getObservation(pomdp.getInitialStates().getNextSetIndex(0));
203
204 struct TransitionObservation {
205 ObservationType targetStateObservation;
206 std::vector<ValueType> rewards;
207 bool operator<(TransitionObservation const& other) const {
208 if (targetStateObservation != other.targetStateObservation) {
209 return targetStateObservation < other.targetStateObservation;
210 }
211 return rewards < other.rewards;
212 }
213 };
214 std::map<TransitionObservation, ObservationType> observationIndexStorage;
215 // Generated observations must not collide with the original observations retained by initial-state copies.
216 ObservationType freshObservation = pomdp.getNrObservations();
217 auto getOrAddObservationIndex = [&observationIndexStorage, &freshObservation](TransitionObservation const& obs) {
218 auto [it, inserted] = observationIndexStorage.try_emplace(obs, freshObservation);
219 if (inserted) {
220 ++freshObservation;
221 }
222 return it->second;
223 };
224 auto result = transform(
225 pomdp,
226 [&pomdp, &getOrAddObservationIndex, &observableRewardModels](StateIdType srcState, ActionIdType action, StateIdType targetState) {
227 auto const globalChoice = pomdp.getTransitionMatrix().getRowGroupIndices()[srcState] + action;
228 TransitionObservation obs{pomdp.getObservation(targetState), {}};
229 for (auto const& rewName : observableRewardModels) {
230 auto const& rewModel = pomdp.getRewardModel(rewName);
231 obs.rewards.push_back(storm::utility::zero<ValueType>());
232 if (rewModel.hasStateRewards()) {
233 obs.rewards.back() += rewModel.getStateReward(srcState);
234 }
235 if (rewModel.hasStateActionRewards()) {
236 obs.rewards.back() += rewModel.getStateActionReward(globalChoice);
237 }
238 STORM_LOG_THROW(!rewModel.hasTransitionRewards(), storm::exceptions::NotSupportedException,
239 "Transition rewards are currently not supported in this context.");
240 }
241 return getOrAddObservationIndex(obs);
242 },
243 initialObservation, dropUnreachableStates);
244 result->setIsCanonic(pomdp.isCanonic());
245 return result;
246}
247
250} // namespace storm::pomdp::transformer
storm::storage::BitVector const & getChoices(std::string const &label) const
Returns the labeling of choices associated with the given label.
std::set< std::string > getLabels() const
Retrieves the set of labels contained in this labeling.
void addLabel(std::string const &label)
Adds a new label to the labelings.
This class represents a (discrete-time) Markov decision process.
Definition Mdp.h:16
storm::models::sparse::ChoiceLabeling const & getChoiceLabeling() const
Retrieves the labels for the choices of the model.
Definition Model.cpp:363
storm::storage::SparseMatrix< ValueType > const & getTransitionMatrix() const
Retrieves the matrix representing the transitions of the model.
Definition Model.cpp:226
std::unordered_map< std::string, RewardModelType > const & getRewardModels() const
Retrieves the reward models.
Definition Model.cpp:728
bool hasStateValuations() const
Retrieves whether this model was build with state valuations.
Definition Model.cpp:378
bool hasChoiceLabeling() const
Retrieves whether this model has a labeling of the choices.
Definition Model.cpp:358
storm::models::sparse::StateLabeling const & getStateLabeling() const
Returns the state labeling associated with this model.
Definition Model.cpp:348
bool hasChoiceOrigins() const
Retrieves whether this model was build with choice origins.
Definition Model.cpp:398
virtual uint_fast64_t getNumberOfStates() const override
Returns the number of states of the model.
Definition Model.cpp:191
storm::storage::BitVector const & getInitialStates() const
Retrieves the initial states of the model.
Definition Model.cpp:206
This class represents a partially observable Markov decision process.
Definition Pomdp.h:13
This class manages the labeling of the state space with a number of (atomic) labels.
storm::storage::BitVector const & getStates(std::string const &label) const
Returns the labeling of states associated with the given label.
static std::shared_ptr< storm::models::sparse::Pomdp< ValueType > > transformRewardAware(storm::models::sparse::Pomdp< ValueType > const &pomdp, std::set< std::string > const &observableRewardModels, bool dropUnreachableStates=true)
Makes rewards observable by extending observations with the state and state-action reward values of t...
std::function< ObservationType(StateIdType, ActionIdType, StateIdType)> TransitionObservationFunction
static std::shared_ptr< storm::models::sparse::Pomdp< ValueType > > transform(storm::models::sparse::Mdp< ValueType > const &mdp, TransitionObservationFunction const &transitionObservationFunction, ObservationType initialObservation, bool dropUnreachableStates=true)
Creates a POMDP (with state-based observations) out of an MDP and a transition-based observation func...
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.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
void newRowGroup(index_type startingRow)
Starts a new row group in the matrix.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:54
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
std::shared_ptr< storm::models::sparse::Model< ValueType, RewardModelType > > buildModelFromComponents(storm::models::ModelType modelType, storm::storage::sparse::ModelComponents< ValueType, RewardModelType > &&components)
Definition builder.cpp:20
storm::storage::BitVector getReachableStates(storm::storage::SparseMatrix< T > const &transitionMatrix, storm::storage::BitVector const &initialStates, storm::storage::BitVector const &constraintStates, storm::storage::BitVector const &targetStates, bool useStepBound, uint_fast64_t maximalSteps, boost::optional< storm::storage::BitVector > const &choiceFilter)
Performs a forward depth-first search through the underlying graph structure to identify the states t...
Definition graph.cpp:42
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType zero()
Definition constants.cpp:26
std::unordered_map< std::string, RewardModelType > rewardModels
storm::storage::SparseMatrix< ValueType > transitionMatrix
std::optional< storm::models::sparse::ChoiceLabeling > choiceLabeling
storm::models::sparse::StateLabeling stateLabeling
std::optional< std::vector< uint32_t > > observabilityClasses