#include "storm/builder/ExplicitModelBuilder.h" #include #include "storm/builder/RewardModelBuilder.h" #include "storm/builder/ChoiceInformationBuilder.h" #include "storm/exceptions/AbortException.h" #include "storm/exceptions/WrongFormatException.h" #include "storm/generator/PrismNextStateGenerator.h" #include "storm/generator/JaniNextStateGenerator.h" #include "storm/models/sparse/Dtmc.h" #include "storm/models/sparse/Ctmc.h" #include "storm/models/sparse/Mdp.h" #include "storm/models/sparse/MarkovAutomaton.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/settings/modules/BuildSettings.h" #include "storm/storage/expressions/ExpressionManager.h" #include "storm/storage/jani/Model.h" #include "storm/storage/jani/Automaton.h" #include "storm/storage/jani/AutomatonComposition.h" #include "storm/storage/jani/ParallelComposition.h" #include "storm/utility/builder.h" #include "storm/utility/constants.h" #include "storm/utility/prism.h" #include "storm/utility/macros.h" #include "storm/utility/ConstantsComparator.h" #include "storm/utility/SignalHandler.h" namespace storm { namespace builder { template ExplicitModelBuilder::Options::Options() : explorationOrder(storm::settings::getModule().getExplorationOrder()) { // Intentionally left empty. } template ExplicitModelBuilder::ExplicitModelBuilder(std::shared_ptr> const& generator, Options const& options) : generator(generator), options(options), stateStorage(generator->getStateSize()) { // Intentionally left empty. } template ExplicitModelBuilder::ExplicitModelBuilder(storm::prism::Program const& program, storm::generator::NextStateGeneratorOptions const& generatorOptions, Options const& builderOptions) : ExplicitModelBuilder(std::make_shared>(program, generatorOptions), builderOptions) { // Intentionally left empty. } template ExplicitModelBuilder::ExplicitModelBuilder(storm::jani::Model const& model, storm::generator::NextStateGeneratorOptions const& generatorOptions, Options const& builderOptions) : ExplicitModelBuilder(std::make_shared>(model, generatorOptions), builderOptions) { // Intentionally left empty. } template std::shared_ptr> ExplicitModelBuilder::build() { STORM_LOG_DEBUG("Exploration order is: " << options.explorationOrder); switch (generator->getModelType()) { case storm::generator::ModelType::DTMC: return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::Dtmc, buildModelComponents()); case storm::generator::ModelType::CTMC: return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::Ctmc, buildModelComponents()); case storm::generator::ModelType::MDP: return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::Mdp, buildModelComponents()); case storm::generator::ModelType::POMDP: return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::Pomdp, buildModelComponents()); case storm::generator::ModelType::MA: return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::MarkovAutomaton, buildModelComponents()); default: STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Error while creating model: cannot handle this model type."); } return nullptr; } template StateType ExplicitModelBuilder::getOrAddStateIndex(CompressedState const& state) { StateType newIndex = static_cast(stateStorage.getNumberOfStates()); // Check, if the state was already registered. std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); StateType actualIndex = actualIndexBucketPair.first; if (actualIndex == newIndex) { if (options.explorationOrder == ExplorationOrder::Dfs) { statesToExplore.emplace_front(state, actualIndex); // Reserve one slot for the new state in the remapping. stateRemapping.get().push_back(storm::utility::zero()); } else if (options.explorationOrder == ExplorationOrder::Bfs) { statesToExplore.emplace_back(state, actualIndex); } else { STORM_LOG_ASSERT(false, "Invalid exploration order."); } } return actualIndex; } template void ExplicitModelBuilder::buildMatrices(storm::storage::SparseMatrixBuilder& transitionMatrixBuilder, std::vector>& rewardModelBuilders, ChoiceInformationBuilder& choiceInformationBuilder, boost::optional& markovianStates) { // Create markovian states bit vector, if required. if (generator->getModelType() == storm::generator::ModelType::MA) { // The bit vector will be resized when the correct size is known. markovianStates = storm::storage::BitVector(1000); } // Create a callback for the next-state generator to enable it to request the index of states. std::function stateToIdCallback = std::bind(&ExplicitModelBuilder::getOrAddStateIndex, this, std::placeholders::_1); // If the exploration order is something different from breadth-first, we need to keep track of the remapping // from state ids to row groups. For this, we actually store the reversed mapping of row groups to state-ids // and later reverse it. if (options.explorationOrder != ExplorationOrder::Bfs) { stateRemapping = std::vector(); } // Let the generator create all initial states. this->stateStorage.initialStateIndices = generator->getInitialStates(stateToIdCallback); STORM_LOG_THROW(!this->stateStorage.initialStateIndices.empty(), storm::exceptions::WrongFormatException, "The model does not have a single initial state."); // Now explore the current state until there is no more reachable state. uint_fast64_t currentRowGroup = 0; uint_fast64_t currentRow = 0; auto timeOfStart = std::chrono::high_resolution_clock::now(); auto timeOfLastMessage = std::chrono::high_resolution_clock::now(); uint64_t numberOfExploredStates = 0; uint64_t numberOfExploredStatesSinceLastMessage = 0; // Perform a search through the model. while (!statesToExplore.empty()) { // Get the first state in the queue. CompressedState currentState = statesToExplore.front().first; StateType currentIndex = statesToExplore.front().second; statesToExplore.pop_front(); // If the exploration order differs from breadth-first, we remember that this row group was actually // filled with the transitions of a different state. if (options.explorationOrder != ExplorationOrder::Bfs) { stateRemapping.get()[currentIndex] = currentRowGroup; } if (currentIndex % 100000 == 0) { STORM_LOG_TRACE("Exploring state with id " << currentIndex << "."); } generator->load(currentState); storm::generator::StateBehavior behavior = generator->expand(stateToIdCallback); // If there is no behavior, we might have to introduce a self-loop. if (behavior.empty()) { if (!storm::settings::getModule().isDontFixDeadlocksSet() || !behavior.wasExpanded()) { // If the behavior was actually expanded and yet there are no transitions, then we have a deadlock state. if (behavior.wasExpanded()) { this->stateStorage.deadlockStateIndices.push_back(currentIndex); } if (markovianStates) { markovianStates.get().grow(currentRowGroup + 1, false); markovianStates.get().set(currentRowGroup); } if (!generator->isDeterministicModel()) { transitionMatrixBuilder.newRowGroup(currentRow); } transitionMatrixBuilder.addNextValue(currentRow, currentIndex, storm::utility::one()); for (auto& rewardModelBuilder : rewardModelBuilders) { if (rewardModelBuilder.hasStateRewards()) { rewardModelBuilder.addStateReward(storm::utility::zero()); } if (rewardModelBuilder.hasStateActionRewards()) { rewardModelBuilder.addStateActionReward(storm::utility::zero()); } } ++currentRow; ++currentRowGroup; } else { STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Error while creating sparse matrix from probabilistic program: found deadlock state (" << generator->toValuation(currentState).toString(true) << "). For fixing these, please provide the appropriate option."); } } else { // Add the state rewards to the corresponding reward models. auto stateRewardIt = behavior.getStateRewards().begin(); for (auto& rewardModelBuilder : rewardModelBuilders) { if (rewardModelBuilder.hasStateRewards()) { rewardModelBuilder.addStateReward(*stateRewardIt); } ++stateRewardIt; } // If the model is nondeterministic, we need to open a row group. if (!generator->isDeterministicModel()) { transitionMatrixBuilder.newRowGroup(currentRow); } // Now add all choices. for (auto const& choice : behavior) { // add the generated choice information if (choice.hasLabels()) { for (auto const& label : choice.getLabels()) { choiceInformationBuilder.addLabel(label, currentRow); } } if (choice.hasOriginData()) { choiceInformationBuilder.addOriginData(choice.getOriginData(), currentRow); } // If we keep track of the Markovian choices, store whether the current one is Markovian. if (markovianStates && choice.isMarkovian()) { markovianStates.get().grow(currentRowGroup + 1, false); markovianStates.get().set(currentRowGroup); } // Add the probabilistic behavior to the matrix. for (auto const& stateProbabilityPair : choice) { transitionMatrixBuilder.addNextValue(currentRow, stateProbabilityPair.first, stateProbabilityPair.second); } // Add the rewards to the reward models. auto choiceRewardIt = choice.getRewards().begin(); for (auto& rewardModelBuilder : rewardModelBuilders) { if (rewardModelBuilder.hasStateActionRewards()) { rewardModelBuilder.addStateActionReward(*choiceRewardIt); } ++choiceRewardIt; } ++currentRow; } ++currentRowGroup; } ++numberOfExploredStates; if (generator->getOptions().isShowProgressSet()) { ++numberOfExploredStatesSinceLastMessage; auto now = std::chrono::high_resolution_clock::now(); auto durationSinceLastMessage = std::chrono::duration_cast(now - timeOfLastMessage).count(); if (static_cast(durationSinceLastMessage) >= generator->getOptions().getShowProgressDelay()) { auto statesPerSecond = numberOfExploredStatesSinceLastMessage / durationSinceLastMessage; auto durationSinceStart = std::chrono::duration_cast(now - timeOfStart).count(); std::cout << "Explored " << numberOfExploredStates << " states in " << durationSinceStart << " seconds (currently " << statesPerSecond << " states per second)." << std::endl; timeOfLastMessage = std::chrono::high_resolution_clock::now(); numberOfExploredStatesSinceLastMessage = 0; } } if (storm::utility::resources::isTerminate()) { auto durationSinceStart = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - timeOfStart).count(); std::cout << "Explored " << numberOfExploredStates << " states in " << durationSinceStart << " seconds before abort." << std::endl; STORM_LOG_THROW(false, storm::exceptions::AbortException, "Aborted in state space exploration."); break; } } if (markovianStates) { // Since we now know the correct size, cut the bit vector to the correct length. markovianStates->resize(currentRowGroup, false); } // If the exploration order was not breadth-first, we need to fix the entries in the matrix according to // (reversed) mapping of row groups to indices. if (options.explorationOrder != ExplorationOrder::Bfs) { STORM_LOG_ASSERT(stateRemapping, "Unable to fix columns without mapping."); std::vector const& remapping = stateRemapping.get(); // We need to fix the following entities: // (a) the transition matrix // (b) the initial states // (c) the hash map storing the mapping states -> ids // (d) fix remapping for state-generation labels // Fix (a). transitionMatrixBuilder.replaceColumns(remapping, 0); // Fix (b). std::vector newInitialStateIndices(this->stateStorage.initialStateIndices.size()); std::transform(this->stateStorage.initialStateIndices.begin(), this->stateStorage.initialStateIndices.end(), newInitialStateIndices.begin(), [&remapping] (StateType const& state) { return remapping[state]; } ); std::sort(newInitialStateIndices.begin(), newInitialStateIndices.end()); this->stateStorage.initialStateIndices = std::move(newInitialStateIndices); // Fix (c). this->stateStorage.stateToId.remap([&remapping] (StateType const& state) { return remapping[state]; } ); this->generator->remapStateIds([&remapping] (StateType const& state) { return remapping[state]; }); } } template storm::storage::sparse::ModelComponents ExplicitModelBuilder::buildModelComponents() { // Determine whether we have to combine different choices to one or whether this model can have more than // one choice per state. bool deterministicModel = generator->isDeterministicModel(); // Prepare the component builders storm::storage::SparseMatrixBuilder transitionMatrixBuilder(0, 0, 0, false, !deterministicModel, 0); std::vector> rewardModelBuilders; for (uint64_t i = 0; i < generator->getNumberOfRewardModels(); ++i) { rewardModelBuilders.emplace_back(generator->getRewardModelInformation(i)); } ChoiceInformationBuilder choiceInformationBuilder; boost::optional markovianStates; buildMatrices(transitionMatrixBuilder, rewardModelBuilders, choiceInformationBuilder, markovianStates); // Initialize the model components with the obtained information. storm::storage::sparse::ModelComponents modelComponents(transitionMatrixBuilder.build(0, transitionMatrixBuilder.getCurrentRowGroupCount()), buildStateLabeling(), std::unordered_map(), !generator->isDiscreteTimeModel(), std::move(markovianStates)); // Now finalize all reward models. for (auto& rewardModelBuilder : rewardModelBuilders) { modelComponents.rewardModels.emplace(rewardModelBuilder.getName(), rewardModelBuilder.build(modelComponents.transitionMatrix.getRowCount(), modelComponents.transitionMatrix.getColumnCount(), modelComponents.transitionMatrix.getRowGroupCount())); } // Build the choice labeling modelComponents.choiceLabeling = choiceInformationBuilder.buildChoiceLabeling(modelComponents.transitionMatrix.getRowCount()); // If requested, build the state valuations and choice origins if (generator->getOptions().isBuildStateValuationsSet()) { std::vector valuations(modelComponents.transitionMatrix.getRowGroupCount()); for (auto const& bitVectorIndexPair : stateStorage.stateToId) { valuations[bitVectorIndexPair.second] = generator->toValuation(bitVectorIndexPair.first); } modelComponents.stateValuations = storm::storage::sparse::StateValuations(std::move(valuations)); } if (generator->getOptions().isBuildChoiceOriginsSet()) { auto originData = choiceInformationBuilder.buildDataOfChoiceOrigins(modelComponents.transitionMatrix.getRowCount()); modelComponents.choiceOrigins = generator->generateChoiceOrigins(originData); } if (generator->isPartiallyObservable()) { std::vector classes; uint32_t newObservation = 0; classes.resize(stateStorage.getNumberOfStates()); std::unordered_map, uint32_t>>> observationActions; for (auto const& bitVectorIndexPair : stateStorage.stateToId) { uint32_t varObservation = generator->observabilityClass(bitVectorIndexPair.first); uint32_t observation = -1; // Is replaced later on. bool checkActionNames = false; if (checkActionNames) { bool foundActionSet = false; std::vector actionNames; bool addedAnonymousAction = false; for (uint64_t choice = modelComponents.transitionMatrix.getRowGroupIndices()[bitVectorIndexPair.second]; choice < modelComponents.transitionMatrix.getRowGroupIndices()[bitVectorIndexPair.second + 1]; ++choice) { if (modelComponents.choiceLabeling.get().getLabelsOfChoice(choice).empty()) { STORM_LOG_THROW(!addedAnonymousAction, storm::exceptions::WrongFormatException, "Cannot have multiple anonymous actions, as these cannot be mapped correctly."); actionNames.push_back(""); addedAnonymousAction = true; } else { STORM_LOG_ASSERT( modelComponents.choiceLabeling.get().getLabelsOfChoice(choice).size() == 1, "Expect choice labelling to contain exactly one label at this point, but found " << modelComponents.choiceLabeling.get().getLabelsOfChoice( choice).size()); actionNames.push_back( *modelComponents.choiceLabeling.get().getLabelsOfChoice(choice).begin()); } } STORM_LOG_TRACE("VarObservation: " << varObservation << " Action Names: " << storm::utility::vector::toString(actionNames)); auto it = observationActions.find(varObservation); if (it == observationActions.end()) { observationActions.emplace(varObservation, std::vector, uint32_t>>()); } else { for (auto const &entries : it->second) { STORM_LOG_TRACE(storm::utility::vector::toString(entries.first)); if (entries.first == actionNames) { observation = entries.second; foundActionSet = true; break; } } STORM_LOG_THROW( generator->getOptions().isInferObservationsFromActionsSet() || foundActionSet, storm::exceptions::WrongFormatException, "Two states with the same observation have a different set of enabled actions, this is only allowed with a special option."); } if (!foundActionSet) { observation = newObservation; observationActions.find(varObservation)->second.emplace_back(actionNames, newObservation); ++newObservation; } classes[bitVectorIndexPair.second] = observation; } else { classes[bitVectorIndexPair.second] = varObservation; } } modelComponents.observabilityClasses = classes; } return modelComponents; } template storm::models::sparse::StateLabeling ExplicitModelBuilder::buildStateLabeling() { return generator->label(stateStorage, stateStorage.initialStateIndices, stateStorage.deadlockStateIndices); } // Explicitly instantiate the class. template class ExplicitModelBuilder, uint32_t>; #ifdef STORM_HAVE_CARL template class ExplicitModelBuilder, uint32_t>; template class ExplicitModelBuilder, uint32_t>; template class ExplicitModelBuilder, uint32_t>; #endif } }