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