#include "storm/models/sparse/Model.h" #include #include "storm/adapters/RationalFunctionAdapter.h" #include "storm/exceptions/IllegalArgumentException.h" #include "storm/exceptions/IllegalFunctionCallException.h" #include "storm/models/sparse/Ctmc.h" #include "storm/models/sparse/MarkovAutomaton.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/utility/vector.h" #include "storm/utility/export.h" #include "storm/utility/NumberTraits.h" namespace storm { namespace models { namespace sparse { template Model::Model(ModelType modelType, storm::storage::sparse::ModelComponents const& components) : storm::models::Model(modelType), transitionMatrix(components.transitionMatrix), stateLabeling(components.stateLabeling), rewardModels(components.rewardModels), choiceLabeling(components.choiceLabeling), stateValuations(components.stateValuations), choiceOrigins(components.choiceOrigins) { assertValidityOfComponents(components); } template Model::Model(ModelType modelType, storm::storage::sparse::ModelComponents&& components) : storm::models::Model(modelType), transitionMatrix(std::move(components.transitionMatrix)), stateLabeling(std::move(components.stateLabeling)), rewardModels(std::move(components.rewardModels)), choiceLabeling(std::move(components.choiceLabeling)), stateValuations(std::move(components.stateValuations)), choiceOrigins(std::move(components.choiceOrigins)) { assertValidityOfComponents(components); } template void Model::assertValidityOfComponents(storm::storage::sparse::ModelComponents const& components) const { // More costly checks are only asserted to avoid doing them in release mode. uint_fast64_t stateCount = this->getNumberOfStates(); uint_fast64_t choiceCount = this->getTransitionMatrix().getRowCount(); // general components for all model types. STORM_LOG_THROW(this->getTransitionMatrix().getColumnCount() == stateCount, storm::exceptions::IllegalArgumentException, "Invalid column count of transition matrix."); STORM_LOG_ASSERT(components.rateTransitions || this->hasParameters() || this->getTransitionMatrix().isProbabilistic(), "The matrix is not probabilistic."); STORM_LOG_THROW(this->getStateLabeling().getNumberOfItems() == stateCount, storm::exceptions::IllegalArgumentException, "Invalid item count (" << this->getStateLabeling().getNumberOfItems() << ") of state labeling (states: " << stateCount << ")."); for (auto const& rewardModel : this->getRewardModels()) { STORM_LOG_THROW(!rewardModel.second.hasStateRewards() || rewardModel.second.getStateRewardVector().size() == stateCount, storm::exceptions::IllegalArgumentException, "Invalid size (" << rewardModel.second.getStateRewardVector().size() << ") of state reward vector (states:" << stateCount << ")."); STORM_LOG_THROW(!rewardModel.second.hasStateActionRewards() || rewardModel.second.getStateActionRewardVector().size() == choiceCount, storm::exceptions::IllegalArgumentException, "Invalid size of state reward vector."); STORM_LOG_ASSERT(!rewardModel.second.hasTransitionRewards() || rewardModel.second.getTransitionRewardMatrix().isSubmatrixOf(this->getTransitionMatrix()), "The transition reward matrix is not a submatrix of the transition matrix, i.e. there are rewards for transitions that do not exist."); } STORM_LOG_THROW(!this->hasChoiceLabeling() || this->getChoiceLabeling().getNumberOfItems() == choiceCount, storm::exceptions::IllegalArgumentException, "Invalid item count of choice labeling."); STORM_LOG_THROW(!this->hasStateValuations() || this->getStateValuations().getNumberOfStates() == stateCount, storm::exceptions::IllegalArgumentException, "Invalid choice count for choice origins."); STORM_LOG_THROW(!this->hasChoiceOrigins() || this->getChoiceOrigins()->getNumberOfChoices() == choiceCount, storm::exceptions::IllegalArgumentException, "Invalid choice count for choice origins."); // Branch on type of nondeterminism if (this->isOfType(ModelType::Dtmc) || this->isOfType(ModelType::Ctmc)) { STORM_LOG_THROW(this->getTransitionMatrix().hasTrivialRowGrouping(), storm::exceptions::IllegalArgumentException, "Can not create deterministic model: Transition matrix has non-trivial row grouping."); STORM_LOG_THROW(stateCount == this->getTransitionMatrix().getRowCount(), storm::exceptions::IllegalArgumentException, "Can not create deterministic model: Number of rows of transition matrix does not match state count."); STORM_LOG_THROW(stateCount == this->getTransitionMatrix().getColumnCount(), storm::exceptions::IllegalArgumentException, "Can not create deterministic model: Number of columns of transition matrix does not match state count."); STORM_LOG_ERROR_COND(!components.player1Matrix.is_initialized(), "Player 1 matrix given for a model that is no stochastic game (will be ignored)."); } else if (this->isOfType(ModelType::Mdp) || this->isOfType(ModelType::MarkovAutomaton) || this->isOfType(ModelType::Pomdp)) { STORM_LOG_THROW(stateCount == this->getTransitionMatrix().getRowGroupCount(), storm::exceptions::IllegalArgumentException, "Can not create nondeterministic model: Number of row groups (" << this->getTransitionMatrix().getRowGroupCount() << ") of transition matrix does not match state count (" << stateCount << ")."); STORM_LOG_THROW(stateCount == this->getTransitionMatrix().getColumnCount(), storm::exceptions::IllegalArgumentException, "Can not create nondeterministic model: Number of columns of transition matrix does not match state count."); STORM_LOG_ERROR_COND(!components.player1Matrix.is_initialized(), "Player 1 matrix given for a model that is no stochastic game (will be ignored)."); } else { STORM_LOG_THROW(this->isOfType(ModelType::S2pg), storm::exceptions::IllegalArgumentException, "Invalid model type."); STORM_LOG_THROW(components.player1Matrix.is_initialized(), storm::exceptions::IllegalArgumentException, "No player 1 matrix given for stochastic game."); STORM_LOG_ASSERT(components.player1Matrix->isProbabilistic(), "Can not create stochastic game: There is a row in the p1 matrix with not exactly one entry."); STORM_LOG_THROW(stateCount == components.player1Matrix->getRowGroupCount(), storm::exceptions::IllegalArgumentException, "Can not create stochastic game: Number of row groups of p1 matrix does not match state count."); STORM_LOG_THROW(this->getTransitionMatrix().getRowGroupCount() == components.player1Matrix->getColumnCount(), storm::exceptions::IllegalArgumentException, "Can not create stochastic game: Number of row groups of p2 matrix does not match column count of p1 matrix."); } // Branch on continuous/discrete timing if (this->isOfType(ModelType::Ctmc) || this->isOfType(ModelType::MarkovAutomaton)) { STORM_LOG_THROW(components.rateTransitions || components.exitRates.is_initialized(), storm::exceptions::IllegalArgumentException, "Can not create continuous time model: no rates are given."); STORM_LOG_THROW(!components.exitRates.is_initialized() || components.exitRates->size() == stateCount, storm::exceptions::IllegalArgumentException, "Size of exit rate vector does not match state count."); STORM_LOG_THROW(this->isOfType(ModelType::Ctmc) || components.markovianStates.is_initialized(), storm::exceptions::IllegalArgumentException, "Can not create Markov Automaton: no Markovian states given."); } else { STORM_LOG_WARN_COND(!components.rateTransitions && !components.exitRates.is_initialized(), "Rates specified for discrete-time model. The rates will be ignored."); } STORM_LOG_WARN_COND(this->isOfType(ModelType::MarkovAutomaton) || !components.markovianStates.is_initialized(), "Markovian states given for a model that is not a Markov automaton (will be ignored)."); } template storm::storage::SparseMatrix Model::getBackwardTransitions() const { return this->getTransitionMatrix().transpose(true); } template typename storm::storage::SparseMatrix::const_rows Model::getRows(storm::storage::sparse::state_type state) const { return this->getTransitionMatrix().getRowGroup(state); } template uint_fast64_t Model::getNumberOfStates() const { return this->getTransitionMatrix().getColumnCount(); } template uint_fast64_t Model::getNumberOfTransitions() const { return this->getTransitionMatrix().getNonzeroEntryCount(); } template uint_fast64_t Model::getNumberOfChoices() const { return this->getTransitionMatrix().getRowCount(); } template storm::storage::BitVector const& Model::getInitialStates() const { return this->getStates("init"); } template storm::storage::BitVector const& Model::getStates(std::string const& label) const { return stateLabeling.getStates(label); } template bool Model::hasLabel(std::string const& label) const { return stateLabeling.containsLabel(label); } template storm::storage::SparseMatrix const& Model::getTransitionMatrix() const { return transitionMatrix; } template storm::storage::SparseMatrix& Model::getTransitionMatrix() { return transitionMatrix; } template bool Model::hasRewardModel(std::string const& rewardModelName) const { return this->rewardModels.find(rewardModelName) != this->rewardModels.end(); } template RewardModelType& Model::rewardModel(std::string const& rewardModelName) { STORM_LOG_ASSERT(this->hasRewardModel(rewardModelName), "Model has no reward model."); return this->rewardModels.find(rewardModelName)->second; } template RewardModelType const& Model::getRewardModel(std::string const& rewardModelName) const { auto it = this->rewardModels.find(rewardModelName); if (it == this->rewardModels.end()) { if (rewardModelName.empty()) { if (this->hasUniqueRewardModel()) { return this->getUniqueRewardModel(); } else { STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "Unable to refer to default reward model, because there is no default model or it is not unique."); } } else { STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "The requested reward model '" << rewardModelName << "' does not exist."); } } return it->second; } template void Model::addRewardModel(std::string const& rewardModelName, RewardModelType const& newRewardModel) { if (this->hasRewardModel(rewardModelName)) { STORM_LOG_THROW(!(this->hasRewardModel(rewardModelName)), storm::exceptions::IllegalArgumentException, "A reward model with the given name '" << rewardModelName << "' already exists."); } STORM_LOG_ASSERT(newRewardModel.isCompatible(this->getNumberOfStates(), this->getTransitionMatrix().getRowCount()), "New reward model is not compatible."); this->rewardModels.emplace(rewardModelName, newRewardModel); } template bool Model::removeRewardModel(std::string const& rewardModelName) { auto it = this->rewardModels.find(rewardModelName); bool res = (it != this->rewardModels.end()); if (res) { this->rewardModels.erase(it->first); } return res; } template void Model::restrictRewardModels(std::set const& keptRewardModels) { std::set removedRewardModels; for (auto const& rewModel : this->getRewardModels()) { if (keptRewardModels.find(rewModel.first) == keptRewardModels.end()) { removedRewardModels.insert(rewModel.first); } } for (auto const& rewModelName : removedRewardModels) { this->removeRewardModel(rewModelName); } } template bool Model::hasUniqueRewardModel() const { return this->getNumberOfRewardModels() == 1; } template std::string const& Model::getUniqueRewardModelName() const { STORM_LOG_THROW(this->getNumberOfRewardModels() == 1, storm::exceptions::IllegalFunctionCallException, "The reward model is not unique."); return this->rewardModels.begin()->first; } template bool Model::hasRewardModel() const { return !this->rewardModels.empty(); } template RewardModelType const& Model::getUniqueRewardModel() const { STORM_LOG_THROW(this->getNumberOfRewardModels() == 1, storm::exceptions::IllegalFunctionCallException, "The reward model is not unique."); return this->rewardModels.cbegin()->second; } template RewardModelType& Model::getUniqueRewardModel() { STORM_LOG_THROW(this->getNumberOfRewardModels() == 1, storm::exceptions::IllegalFunctionCallException, "The reward model is not unique."); return this->rewardModels.begin()->second; } template uint_fast64_t Model::getNumberOfRewardModels() const { return this->rewardModels.size(); } template storm::models::sparse::StateLabeling const& Model::getStateLabeling() const { return stateLabeling; } template storm::models::sparse::StateLabeling& Model::getStateLabeling() { return stateLabeling; } template bool Model::hasChoiceLabeling() const { return static_cast(choiceLabeling); } template storm::models::sparse::ChoiceLabeling const& Model::getChoiceLabeling() const { return choiceLabeling.get(); } template boost::optional const& Model::getOptionalChoiceLabeling() const { return choiceLabeling; } template boost::optional& Model::getOptionalChoiceLabeling() { return choiceLabeling; } template bool Model::hasStateValuations() const { return static_cast(stateValuations); } template storm::storage::sparse::StateValuations const& Model::getStateValuations() const { return stateValuations.get(); } template boost::optional const& Model::getOptionalStateValuations() const { return stateValuations; } template boost::optional& Model::getOptionalStateValuations() { return stateValuations; } template bool Model::hasChoiceOrigins() const { return static_cast(choiceOrigins); } template std::shared_ptr const& Model::getChoiceOrigins() const { return choiceOrigins.get(); } template boost::optional> const& Model::getOptionalChoiceOrigins() const { return choiceOrigins; } template boost::optional>& Model::getOptionalChoiceOrigins() { return choiceOrigins; } template void Model::printModelInformationToStream(std::ostream& out) const { this->printModelInformationHeaderToStream(out); this->printModelInformationFooterToStream(out); } template void Model::printModelInformationHeaderToStream(std::ostream& out) const { out << "-------------------------------------------------------------- " << std::endl; out << "Model type: \t" << this->getType() << " (sparse)" << std::endl; out << "States: \t" << this->getNumberOfStates() << std::endl; out << "Transitions: \t" << this->getNumberOfTransitions() << std::endl; } template void Model::printModelInformationFooterToStream(std::ostream& out) const { this->printRewardModelsInformationToStream(out); out << "State Labels: \t"; this->getStateLabeling().printLabelingInformationToStream(out); out << "Choice Labels: \t"; if (this->hasChoiceLabeling()) { this->getChoiceLabeling().printLabelingInformationToStream(out); } else { out << "none" << std::endl; } out << "-------------------------------------------------------------- " << std::endl; } template void Model::printRewardModelsInformationToStream(std::ostream& out) const { if (this->rewardModels.size()) { std::vector rewardModelNames; std::for_each(this->rewardModels.cbegin(), this->rewardModels.cend(), [&rewardModelNames] (typename std::pair const& nameRewardModelPair) { if (nameRewardModelPair.first.empty()) { rewardModelNames.push_back("(default)"); } else { rewardModelNames.push_back(nameRewardModelPair.first); } }); out << "Reward Models: " << boost::join(rewardModelNames, ", ") << std::endl; } else { out << "Reward Models: none" << std::endl; } } template void Model::writeDotToStream(std::ostream& outStream, size_t maxWidthLabel, bool includeLabeling, storm::storage::BitVector const* subsystem, std::vector const* firstValue, std::vector const* secondValue, std::vector const* stateColoring, std::vector const* colors, std::vector*, bool finalizeOutput) const { outStream << "digraph model {" << std::endl; // Write all states to the stream. for (uint_fast64_t state = 0, highestStateIndex = this->getNumberOfStates() - 1; state <= highestStateIndex; ++state) { if (subsystem == nullptr || subsystem->get(state)) { outStream << "\t" << state; if (includeLabeling || firstValue != nullptr || secondValue != nullptr || stateColoring != nullptr || hasStateValuations()) { outStream << " [ "; // If we need to print some extra information, do so now. if (includeLabeling || firstValue != nullptr || secondValue != nullptr || hasStateValuations()) { outStream << "label = \"" << state; if (hasStateValuations()) { std::string stateInfo = getStateValuations().getStateInfo(state); std::vector results; boost::split(results, stateInfo, [](char c) { return c == ',';}); storm::utility::outputFixedWidth(outStream, results, maxWidthLabel); } outStream << ": "; // Now print the state labeling to the stream if requested. if (includeLabeling) { outStream << "{"; storm::utility::outputFixedWidth(outStream, this->getLabelsOfState(state), maxWidthLabel); outStream << "}"; } outStream << this->additionalDotStateInfo(state); // If we are to include some values for the state as well, we do so now. if (firstValue != nullptr || secondValue != nullptr) { outStream << " ["; if (firstValue != nullptr) { outStream << (*firstValue)[state]; if (secondValue != nullptr) { outStream << ", "; } } if (secondValue != nullptr) { outStream << (*secondValue)[state]; } outStream << "]"; } outStream << "\""; // Now, we color the states if there were colors given. if (stateColoring != nullptr && colors != nullptr) { outStream << ", "; outStream << " style = filled, fillcolor = " << (*colors)[(*stateColoring)[state]]; } } outStream << " ]"; } outStream << ";" << std::endl; } } // If this methods has not been called from a derived class, we want to close the digraph here. if (finalizeOutput) { outStream << "}" << std::endl; } } template std::string Model::additionalDotStateInfo(uint64_t state) const { return ""; } template std::set Model::getLabelsOfState(storm::storage::sparse::state_type state) const { return this->stateLabeling.getLabelsOfState(state); } template void Model::setTransitionMatrix(storm::storage::SparseMatrix const& transitionMatrix) { this->transitionMatrix = transitionMatrix; } template void Model::setTransitionMatrix(storm::storage::SparseMatrix&& transitionMatrix) { this->transitionMatrix = std::move(transitionMatrix); } template bool Model::isSinkState(uint64_t state) const { for (auto const& entry : this->getTransitionMatrix().getRowGroup(state)) { if (entry.getColumn() != state) { return false; } if (storm::utility::isOne(entry.getValue())) { return false; } } return true; } template bool Model::isSparseModel() const { return true; } template bool Model::supportsParameters() const { return std::is_same::value; } template bool Model::hasParameters() const { if (!this->supportsParameters()) { return false; } // Check for parameters for (auto const& entry : this->getTransitionMatrix()) { if (!storm::utility::isConstant(entry.getValue())) { return true; } } // Only constant values present return false; } template bool Model::isExact() const { return storm::NumberTraits::IsExact && storm::NumberTraits::IsExact; } template std::unordered_map& Model::getRewardModels() { return this->rewardModels; } template std::unordered_map const& Model::getRewardModels() const { return this->rewardModels; } #ifdef STORM_HAVE_CARL std::set getProbabilityParameters(Model const& model) { return storm::storage::getVariables(model.getTransitionMatrix()); } std::set getRewardParameters(Model const& model) { std::set result; for(auto rewModel : model.getRewardModels()) { std::set tmp = getRewardModelParameters(rewModel.second); result.insert(tmp.begin(), tmp.end()); } return result; } std::set getRateParameters(Model const& model) { if (model.isOfType(storm::models::ModelType::Ctmc)) { auto const& ctmc = model.template as>(); return storm::utility::vector::getVariables(ctmc->getExitRateVector()); } else if (model.isOfType(storm::models::ModelType::MarkovAutomaton)) { auto const& ma = model.template as>(); return storm::utility::vector::getVariables(ma->getExitRates()); } else { return {}; } } std::set getAllParameters(Model const& model) { std::set parameters = getProbabilityParameters(model); std::set rewardParameters = getRewardParameters(model); parameters.insert(rewardParameters.begin(), rewardParameters.end()); std::set rateParameters = getRewardParameters(model); parameters.insert(rateParameters.begin(), rateParameters.end()); return parameters; } #endif template class Model; template class Model; #ifdef STORM_HAVE_CARL template class Model; template class Model>; template class Model; #endif } } }