Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ExplicitModelBuilder.cpp
Go to the documentation of this file.
2
3#include <map>
4
23
24namespace storm {
25namespace builder {
26
27template<typename StateType>
28StateType ExplicitStateLookup<StateType>::lookup(std::map<storm::expressions::Variable, storm::expressions::Expression> const& stateDescription) const {
29 auto cs = storm::generator::createCompressedState(this->varInfo, stateDescription, true);
30 // TODO search once
31 if (!stateToId.contains(cs)) {
32 return static_cast<StateType>(this->size());
33 }
34 return this->stateToId.getValue(cs);
35}
36
37template<typename StateType>
39 return this->stateToId.size();
40}
41
42template<typename ValueType, typename RewardModelType, typename StateType>
44 auto const& buildSettings = storm::settings::getModule<storm::settings::modules::BuildSettings>();
45 explorationOrder = buildSettings.getExplorationOrder();
46 fixDeadlocks = !buildSettings.isDontFixDeadlocksSet();
47 if (buildSettings.isExplorationStateLimitSet()) {
48 explorationStateLimit = buildSettings.getExplorationStateLimit();
49 }
50}
51
52template<typename ValueType, typename RewardModelType, typename StateType>
54 std::shared_ptr<storm::generator::NextStateGenerator<ValueType, StateType>> const& generator, Options const& options)
55 : generator(generator), options(options), stateStorage(generator->getStateSize()) {
56 // Intentionally left empty.
57}
58
59template<typename ValueType, typename RewardModelType, typename StateType>
61 storm::generator::NextStateGeneratorOptions const& generatorOptions,
62 Options const& builderOptions)
63 : ExplicitModelBuilder(std::make_shared<storm::generator::PrismNextStateGenerator<ValueType, StateType>>(program, generatorOptions), builderOptions) {
64 // Intentionally left empty.
65}
66
67template<typename ValueType, typename RewardModelType, typename StateType>
69 storm::generator::NextStateGeneratorOptions const& generatorOptions,
70 Options const& builderOptions)
71 requires(!storm::IsIntervalType<ValueType>)
72 : ExplicitModelBuilder(std::make_shared<storm::generator::JaniNextStateGenerator<ValueType, StateType>>(model, generatorOptions), builderOptions) {
73 // Intentionally left empty.
74}
75
76template<typename ValueType, typename RewardModelType, typename StateType>
77std::shared_ptr<storm::models::sparse::Model<ValueType, RewardModelType>> ExplicitModelBuilder<ValueType, RewardModelType, StateType>::build() {
78 STORM_LOG_DEBUG("Exploration order is: " << options.explorationOrder);
79
80 switch (generator->getModelType()) {
93 default:
94 STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Error while creating model: cannot handle this model type.");
95 }
96
97 return nullptr;
98}
99
100template<typename ValueType, typename RewardModelType, typename StateType>
102 StateType newIndex = static_cast<StateType>(stateStorage.getNumberOfStates());
103
104 // Check, if the state was already registered.
105 std::pair<StateType, std::size_t> actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex);
106
107 StateType actualIndex = actualIndexBucketPair.first;
108
109 if (actualIndex == newIndex) {
110 if (options.explorationOrder == ExplorationOrder::Dfs) {
111 statesToExplore.emplace_front(state, actualIndex);
112
113 // Reserve one slot for the new state in the remapping.
114 stateRemapping.get().push_back(storm::utility::zero<StateType>());
115 } else if (options.explorationOrder == ExplorationOrder::Bfs) {
116 statesToExplore.emplace_back(state, actualIndex);
117 } else {
118 STORM_LOG_ASSERT(false, "Invalid exploration order.");
119 }
120 }
121
122 return actualIndex;
123}
124
125template<typename ValueType, typename RewardModelType, typename StateType>
127 return ExplicitStateLookup<StateType>(this->generator->getVariableInformation(), this->stateStorage.stateToId);
128}
129
130template<typename ValueType, typename RewardModelType, typename StateType>
132 storm::storage::SparseMatrixBuilder<ValueType>& transitionMatrixBuilder,
133 std::vector<RewardModelBuilder<typename RewardModelType::ValueType>>& rewardModelBuilders,
134 StateAndChoiceInformationBuilder& stateAndChoiceInformationBuilder) {
135 // Initialize building state valuations (if necessary)
136 if (stateAndChoiceInformationBuilder.isBuildStateValuations()) {
137 stateAndChoiceInformationBuilder.stateValuationsBuilder() = generator->initializeStateValuationsBuilder();
138 }
139
140 // Create a callback for the next-state generator to enable it to request the index of states.
141 std::function<StateType(CompressedState const&)> stateToIdCallback =
142 std::bind(&ExplicitModelBuilder<ValueType, RewardModelType, StateType>::getOrAddStateIndex, this, std::placeholders::_1);
143
144 // If the exploration order is something different from breadth-first, we need to keep track of the remapping
145 // from state ids to row groups. For this, we actually store the reversed mapping of row groups to state-ids
146 // and later reverse it.
147 if (options.explorationOrder != ExplorationOrder::Bfs) {
148 stateRemapping = std::vector<uint_fast64_t>();
149 }
150
151 // Let the generator create all initial states.
152 this->stateStorage.initialStateIndices = generator->getInitialStates(stateToIdCallback);
153 STORM_LOG_THROW(!this->stateStorage.initialStateIndices.empty(), storm::exceptions::WrongFormatException,
154 "The model does not have a single initial state.");
155
156 // Now explore the current state until there is no more reachable state.
157 uint_fast64_t currentRowGroup = 0;
158 uint_fast64_t currentRow = 0;
159
160 auto timeOfStart = std::chrono::high_resolution_clock::now();
161 auto timeOfLastMessage = std::chrono::high_resolution_clock::now();
162 uint64_t numberOfExploredStates = 0;
163 uint64_t numberOfExploredStatesSinceLastMessage = 0;
164
165 // Perform a search through the model.
166 while (!statesToExplore.empty()) {
167 // Get the first state in the queue.
168 CompressedState currentState = statesToExplore.front().first;
169 StateType currentIndex = statesToExplore.front().second;
170 statesToExplore.pop_front();
171
172 // If the exploration order differs from breadth-first, we remember that this row group was actually
173 // filled with the transitions of a different state.
174 if (options.explorationOrder != ExplorationOrder::Bfs) {
175 stateRemapping.get()[currentIndex] = currentRowGroup;
176 }
177
178 if (currentIndex % 100000 == 0) {
179 STORM_LOG_TRACE("Exploring state with id " << currentIndex << ".");
180 }
181
182 generator->load(currentState);
183 if (stateAndChoiceInformationBuilder.isBuildStateValuations()) {
184 generator->addStateValuation(currentIndex, stateAndChoiceInformationBuilder.stateValuationsBuilder());
185 }
186
188 // If the exploration state limit is set and the limit is reached, we stop the exploration.
189 bool const stateLimitExceeded = options.explorationStateLimit.has_value() && stateStorage.getNumberOfStates() >= options.explorationStateLimit.value();
190 if (!stateLimitExceeded) {
191 behavior = generator->expand(stateToIdCallback);
192 }
193
194 if (behavior.empty()) {
195 // There are three possible cases for missing behavior:
196 if (behavior.wasExpanded()) {
197 // (a) The state is a deadlock state, i.e. there is no behavior even though the state was expanded
198 STORM_LOG_THROW(options.fixDeadlocks, storm::exceptions::WrongFormatException,
199 "Error while creating sparse matrix from probabilistic program: found deadlock state ("
200 << generator->stateToString(currentState) << "). For fixing these, please provide the appropriate option.");
201 this->stateStorage.deadlockStateIndices.push_back(currentIndex);
202 } else {
203 if (stateLimitExceeded) {
204 // (b) The state was not expanded because the state limit is reached
205 this->stateStorage.unexploredStateIndices.push_back(currentIndex);
206 }
207 // (c) the state was not expanded because it is terminal, i.e., exploration from that state is not required for the given property/ies
208 }
209
210 // In all cases, we need to add a self-loop to the transition matrix.
211
212 if (!generator->isDeterministicModel()) {
213 transitionMatrixBuilder.newRowGroup(currentRow);
214 }
215
216 transitionMatrixBuilder.addNextValue(currentRow, currentIndex, storm::utility::one<ValueType>());
217
218 for (auto& rewardModelBuilder : rewardModelBuilders) {
219 if (rewardModelBuilder.hasStateRewards()) {
220 rewardModelBuilder.addStateReward(storm::utility::zero<ValueType>());
221 }
222
223 if (rewardModelBuilder.hasStateActionRewards()) {
224 rewardModelBuilder.addStateActionReward(storm::utility::zero<ValueType>());
225 }
226 }
227
228 // This state shall be Markovian (to not introduce Zeno behavior)
229 if (stateAndChoiceInformationBuilder.isBuildMarkovianStates()) {
230 stateAndChoiceInformationBuilder.addMarkovianState(currentRowGroup);
231 }
232 // Other state-based information does not need to be treated, in particular:
233 // * StateValuations have already been set above
234 // * The associated player shall be the "default" player, i.e. INVALID_PLAYER_INDEX
235
236 ++currentRow;
237 ++currentRowGroup;
238 } else {
239 // Add the state rewards to the corresponding reward models.
240 auto stateRewardIt = behavior.getStateRewards().begin();
241 for (auto& rewardModelBuilder : rewardModelBuilders) {
242 if (rewardModelBuilder.hasStateRewards()) {
243 rewardModelBuilder.addStateReward(*stateRewardIt);
244 }
245 ++stateRewardIt;
246 }
247
248 // If the model is nondeterministic, we need to open a row group.
249 if (!generator->isDeterministicModel()) {
250 transitionMatrixBuilder.newRowGroup(currentRow);
251 }
252
253 // Now add all choices.
254 bool firstChoiceOfState = true;
255 for (auto const& choice : behavior) {
256 // add the generated choice information
257 if (stateAndChoiceInformationBuilder.isBuildChoiceLabels() && choice.hasLabels()) {
258 for (auto const& label : choice.getLabels()) {
259 stateAndChoiceInformationBuilder.addChoiceLabel(label, currentRow);
260 }
261 }
262 if (stateAndChoiceInformationBuilder.isBuildChoiceOrigins() && choice.hasOriginData()) {
263 stateAndChoiceInformationBuilder.addChoiceOriginData(choice.getOriginData(), currentRow);
264 }
265 if (stateAndChoiceInformationBuilder.isBuildStatePlayerIndications() && choice.hasPlayerIndex()) {
267 firstChoiceOfState || stateAndChoiceInformationBuilder.hasStatePlayerIndicationBeenSet(choice.getPlayerIndex(), currentRowGroup),
268 "There is a state where different players have an enabled choice."); // Should have been detected in generator, already
269 if (firstChoiceOfState) {
270 stateAndChoiceInformationBuilder.addStatePlayerIndication(choice.getPlayerIndex(), currentRowGroup);
271 }
272 }
273 if (stateAndChoiceInformationBuilder.isBuildMarkovianStates() && choice.isMarkovian()) {
274 stateAndChoiceInformationBuilder.addMarkovianState(currentRowGroup);
275 }
276
277 // Add the probabilistic behavior to the matrix.
278 for (auto const& stateProbabilityPair : choice) {
279 transitionMatrixBuilder.addNextValue(currentRow, stateProbabilityPair.first, stateProbabilityPair.second);
280 }
281
282 // Add the rewards to the reward models.
283 auto choiceRewardIt = choice.getRewards().begin();
284 for (auto& rewardModelBuilder : rewardModelBuilders) {
285 if (rewardModelBuilder.hasStateActionRewards()) {
286 rewardModelBuilder.addStateActionReward(*choiceRewardIt);
287 }
288 ++choiceRewardIt;
289 }
290 ++currentRow;
291 firstChoiceOfState = false;
292 }
293
294 ++currentRowGroup;
295 }
296
297 ++numberOfExploredStates;
298 if (generator->getOptions().isShowProgressSet()) {
299 ++numberOfExploredStatesSinceLastMessage;
300
301 auto now = std::chrono::high_resolution_clock::now();
302 auto durationSinceLastMessage = std::chrono::duration_cast<std::chrono::seconds>(now - timeOfLastMessage).count();
303 if (static_cast<uint64_t>(durationSinceLastMessage) >= generator->getOptions().getShowProgressDelay()) {
304 auto statesPerSecond = numberOfExploredStatesSinceLastMessage / durationSinceLastMessage;
305 auto durationSinceStart = std::chrono::duration_cast<std::chrono::seconds>(now - timeOfStart).count();
306 std::cout << "Explored " << numberOfExploredStates << " states in " << durationSinceStart << " seconds (currently " << statesPerSecond
307 << " states per second).\n";
308 timeOfLastMessage = std::chrono::high_resolution_clock::now();
309 numberOfExploredStatesSinceLastMessage = 0;
310 }
311 }
312
314 auto durationSinceStart = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - timeOfStart).count();
315 std::cout << "Explored " << numberOfExploredStates << " states in " << durationSinceStart << " seconds before abort.\n";
316 STORM_LOG_THROW(false, storm::exceptions::AbortException, "Aborted in state space exploration.");
317 break;
318 }
319 }
320
321 // If the exploration order was not breadth-first, we need to fix the entries in the matrix according to
322 // (reversed) mapping of row groups to indices.
323 if (options.explorationOrder != ExplorationOrder::Bfs) {
324 STORM_LOG_ASSERT(stateRemapping, "Unable to fix columns without mapping.");
325 std::vector<uint_fast64_t> const& remapping = stateRemapping.get();
326
327 // We need to fix the following entities:
328 // (a) the transition matrix
329 // (b) the initial states
330 // (c) the hash map storing the mapping states -> ids
331 // (d) fix remapping for state-generation labels
332
333 // Fix (a).
334 transitionMatrixBuilder.replaceColumns(remapping, 0);
335
336 // Fix (b).
337 std::vector<StateType> newInitialStateIndices(this->stateStorage.initialStateIndices.size());
338 std::transform(this->stateStorage.initialStateIndices.begin(), this->stateStorage.initialStateIndices.end(), newInitialStateIndices.begin(),
339 [&remapping](StateType const& state) { return remapping[state]; });
340 std::sort(newInitialStateIndices.begin(), newInitialStateIndices.end());
341 this->stateStorage.initialStateIndices = std::move(newInitialStateIndices);
342
343 // Fix (c).
344 this->stateStorage.stateToId.remap([&remapping](StateType const& state) { return remapping[state]; });
345
346 this->generator->remapStateIds([&remapping](StateType const& state) { return remapping[state]; });
347 }
348}
349
350template<typename ValueType, typename RewardModelType, typename StateType>
351storm::storage::sparse::ModelComponents<ValueType, RewardModelType> ExplicitModelBuilder<ValueType, RewardModelType, StateType>::buildModelComponents() {
352 // Determine whether we have to combine different choices to one or whether this model can have more than
353 // one choice per state.
354 bool deterministicModel = generator->isDeterministicModel();
355
356 // Prepare the component builders
357 storm::storage::SparseMatrixBuilder<ValueType> transitionMatrixBuilder(0, 0, 0, false, !deterministicModel, 0);
358 std::vector<RewardModelBuilder<typename RewardModelType::ValueType>> rewardModelBuilders;
359 for (uint64_t i = 0; i < generator->getNumberOfRewardModels(); ++i) {
360 rewardModelBuilders.emplace_back(generator->getRewardModelInformation(i));
361 }
362 StateAndChoiceInformationBuilder stateAndChoiceInformationBuilder;
363 stateAndChoiceInformationBuilder.setBuildChoiceLabels(generator->getOptions().isBuildChoiceLabelsSet());
364 stateAndChoiceInformationBuilder.setBuildChoiceOrigins(generator->getOptions().isBuildChoiceOriginsSet());
365 stateAndChoiceInformationBuilder.setBuildStatePlayerIndications(generator->getModelType() == storm::generator::ModelType::SMG);
366 stateAndChoiceInformationBuilder.setBuildMarkovianStates(generator->getModelType() == storm::generator::ModelType::MA);
367 stateAndChoiceInformationBuilder.setBuildStateValuations(generator->getOptions().isBuildStateValuationsSet());
368
369 buildMatrices(transitionMatrixBuilder, rewardModelBuilders, stateAndChoiceInformationBuilder);
370
371 // Initialize the model components with the obtained information.
373 transitionMatrixBuilder.build(0, transitionMatrixBuilder.getCurrentRowGroupCount()), buildStateLabeling(),
374 std::unordered_map<std::string, RewardModelType>(), !generator->isDiscreteTimeModel());
375
376 uint_fast64_t numStates = modelComponents.transitionMatrix.getColumnCount();
377 uint_fast64_t numChoices = modelComponents.transitionMatrix.getRowCount();
378
379 // Now finalize all reward models.
380 for (auto& rewardModelBuilder : rewardModelBuilders) {
381 modelComponents.rewardModels.emplace(rewardModelBuilder.getName(),
382 rewardModelBuilder.build(numChoices, modelComponents.transitionMatrix.getColumnCount(), numStates));
383 }
384 // Build the player assignment
385 if (stateAndChoiceInformationBuilder.isBuildStatePlayerIndications()) {
386 modelComponents.statePlayerIndications = stateAndChoiceInformationBuilder.buildStatePlayerIndications(numStates);
387 modelComponents.playerNameToIndexMap = generator->getPlayerNameToIndexMap();
388 }
389 // Build Markovian states
390 if (stateAndChoiceInformationBuilder.isBuildMarkovianStates()) {
391 modelComponents.markovianStates = stateAndChoiceInformationBuilder.buildMarkovianStates(numStates);
392 }
393 // Build the choice labeling
394 if (stateAndChoiceInformationBuilder.isBuildChoiceLabels()) {
395 modelComponents.choiceLabeling = stateAndChoiceInformationBuilder.buildChoiceLabeling(numChoices);
396 }
397 // If requested, build the state valuations and choice origins
398 if (stateAndChoiceInformationBuilder.isBuildStateValuations()) {
399 modelComponents.stateValuations = stateAndChoiceInformationBuilder.stateValuationsBuilder().build();
400 }
401 if (stateAndChoiceInformationBuilder.isBuildChoiceOrigins()) {
402 auto originData = stateAndChoiceInformationBuilder.buildDataOfChoiceOrigins(numChoices);
403 modelComponents.choiceOrigins = generator->generateChoiceOrigins(originData);
404 }
405 if (generator->isPartiallyObservable()) {
406 std::vector<uint32_t> classes(stateStorage.getNumberOfStates());
407 std::unordered_map<uint32_t, std::vector<std::pair<std::vector<std::string>, uint32_t>>> observationActions;
408 for (auto const& bitVectorIndexPair : stateStorage.stateToId) {
409 uint32_t varObservation = generator->observabilityClass(bitVectorIndexPair.first);
410 classes[bitVectorIndexPair.second] = varObservation;
411 }
412
413 modelComponents.observabilityClasses = classes;
414 if (generator->getOptions().isBuildObservationValuationsSet()) {
415 modelComponents.observationValuations = generator->makeObservationValuation();
416 }
417 }
418 return modelComponents;
419}
420
421template<typename ValueType, typename RewardModelType, typename StateType>
422storm::models::sparse::StateLabeling ExplicitModelBuilder<ValueType, RewardModelType, StateType>::buildStateLabeling() {
423 return generator->label(stateStorage, stateStorage.initialStateIndices, stateStorage.deadlockStateIndices, stateStorage.unexploredStateIndices);
424}
425
426// Explicitly instantiate the class.
427template class ExplicitModelBuilder<double, storm::models::sparse::StandardRewardModel<double>, uint32_t>;
428template class ExplicitStateLookup<uint32_t>;
429
430template class ExplicitModelBuilder<RationalNumber, storm::models::sparse::StandardRewardModel<RationalNumber>, uint32_t>;
431template class ExplicitModelBuilder<RationalFunction, storm::models::sparse::StandardRewardModel<RationalFunction>, uint32_t>;
432template class ExplicitModelBuilder<double, storm::models::sparse::StandardRewardModel<storm::Interval>, uint32_t>; // TODO: where is this used?
433template class ExplicitModelBuilder<storm::Interval, storm::models::sparse::StandardRewardModel<storm::Interval>, uint32_t>;
434
435} // namespace builder
436} // namespace storm
ExplicitStateLookup< StateType > exportExplicitStateLookup() const
Export a wrapper that contains (a copy of) the internal information that maps states to ids.
ExplicitModelBuilder(std::shared_ptr< storm::generator::NextStateGenerator< ValueType, StateType > > const &generator, Options const &options=Options())
Creates an explicit model builder that uses the provided generator.
std::shared_ptr< storm::models::sparse::Model< ValueType, RewardModelType > > build()
Convert the program given at construction time to an abstract model.
StateType lookup(std::map< storm::expressions::Variable, storm::expressions::Expression > const &stateDescription) const
Lookup state.
uint64_t size() const
How many states have been stored?
A structure that is used to keep track of a reward model currently being built.
This class collects information regarding the states and choices during model building.
storm::storage::sparse::StateValuationsBuilder & stateValuationsBuilder()
bool hasStatePlayerIndicationBeenSet(storm::storage::PlayerIndex expectedPlayer, uint_fast64_t stateIndex) const
void addStatePlayerIndication(storm::storage::PlayerIndex player, uint_fast64_t stateIndex)
void addChoiceLabel(std::string const &label, uint_fast64_t choiceIndex)
void addChoiceOriginData(boost::any const &originData, uint_fast64_t choiceIndex)
bool empty() const
Retrieves whether the behavior is empty in the sense that there are no available choices.
bool wasExpanded() const
Retrieves whether the state was expanded.
std::vector< ValueType > const & getStateRewards() const
Retrieves the list of state rewards under selected reward models.
This class manages the labeling of the state space with a number of (atomic) labels.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
A class that can be used to build a sparse matrix by adding value by value.
index_type getCurrentRowGroupCount() const
Retrieves the current row group count.
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 replaceColumns(std::vector< index_type > const &replacements, index_type offset)
Replaces all columns with id > offset according to replacements.
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_DEBUG(message)
Definition logging.h:18
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
CompressedState createCompressedState(VariableInformation const &varInfo, std::map< storm::expressions::Variable, storm::expressions::Expression > const &stateDescription, bool checkOutOfBounds)
storm::storage::BitVector CompressedState
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
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
Options()
Creates an object representing the default building options.