/* * Dtmc.h * * Created on: 14.11.2012 * Author: Christian Dehnert */ #ifndef STORM_MODELS_DTMC_H_ #define STORM_MODELS_DTMC_H_ #include <ostream> #include <iostream> #include <memory> #include <cstdlib> #include "AbstractDeterministicModel.h" #include "AtomicPropositionsLabeling.h" #include "src/storage/SparseMatrix.h" #include "src/exceptions/InvalidArgumentException.h" #include "src/settings/Settings.h" #include "src/utility/vector.h" #include "src/utility/matrix.h" namespace storm { namespace models { /*! * This class represents a discrete-time Markov chain (DTMC) whose states are * labeled with atomic propositions. */ template <class T> class Dtmc : public storm::models::AbstractDeterministicModel<T> { public: /*! * Constructs a DTMC object from the given transition probability matrix and * the given labeling of the states. * All values are copied. * @param probabilityMatrix The matrix representing the transitions in the model. * @param stateLabeling The labeling that assigns a set of atomic * propositions to each state. * @param optionalStateRewardVector The reward values associated with the states. * @param optionalTransitionRewardMatrix The reward values associated with the transitions of the model. * @param optionalChoiceLabeling A vector that represents the labels associated with the choices of each state. */ Dtmc(storm::storage::SparseMatrix<T> const& probabilityMatrix, storm::models::AtomicPropositionsLabeling const& stateLabeling, boost::optional<std::vector<T>> const& optionalStateRewardVector = {}, boost::optional<storm::storage::SparseMatrix<T>> const& optionalTransitionRewardMatrix = {}, boost::optional<std::vector<boost::container::flat_set<uint_fast64_t>>> const& optionalChoiceLabeling = {}) : AbstractDeterministicModel<T>(probabilityMatrix, stateLabeling, optionalStateRewardVector, optionalTransitionRewardMatrix, optionalChoiceLabeling) { if (!this->checkValidityOfProbabilityMatrix()) { LOG4CPLUS_ERROR(logger, "Probability matrix is invalid."); throw storm::exceptions::InvalidArgumentException() << "Probability matrix is invalid."; } if (this->hasTransitionRewards()) { if (!this->getTransitionRewardMatrix().isSubmatrixOf(this->getTransitionMatrix())) { LOG4CPLUS_ERROR(logger, "Transition reward matrix is not a submatrix of the transition matrix, i.e. there are rewards for transitions that do not exist."); throw storm::exceptions::InvalidArgumentException() << "There are transition rewards for nonexistent transitions."; } } } /*! * Constructs a DTMC object from the given transition probability matrix and * the given labeling of the states. * All values are moved. * @param probabilityMatrix The matrix representing the transitions in the model. * @param stateLabeling The labeling that assigns a set of atomic * propositions to each state. * @param optionalStateRewardVector The reward values associated with the states. * @param optionalTransitionRewardMatrix The reward values associated with the transitions of the model. * @param optionalChoiceLabeling A vector that represents the labels associated with the choices of each state. */ Dtmc(storm::storage::SparseMatrix<T>&& probabilityMatrix, storm::models::AtomicPropositionsLabeling&& stateLabeling, boost::optional<std::vector<T>>&& optionalStateRewardVector, boost::optional<storm::storage::SparseMatrix<T>>&& optionalTransitionRewardMatrix, boost::optional<std::vector<boost::container::flat_set<uint_fast64_t>>>&& optionalChoiceLabeling) // The std::move call must be repeated here because otherwise this calls the copy constructor of the Base Class : AbstractDeterministicModel<T>(std::move(probabilityMatrix), std::move(stateLabeling), std::move(optionalStateRewardVector), std::move(optionalTransitionRewardMatrix), std::move(optionalChoiceLabeling)) { if (!this->checkValidityOfProbabilityMatrix()) { LOG4CPLUS_ERROR(logger, "Probability matrix is invalid."); throw storm::exceptions::InvalidArgumentException() << "Probability matrix is invalid."; } if (this->hasTransitionRewards()) { if (!this->getTransitionRewardMatrix().isSubmatrixOf(this->getTransitionMatrix())) { LOG4CPLUS_ERROR(logger, "Transition reward matrix is not a submatrix of the transition matrix, i.e. there are rewards for transitions that do not exist."); throw storm::exceptions::InvalidArgumentException() << "There are transition rewards for nonexistent transitions."; } } } /*! * Copy Constructor. Performs a deep copy of the given DTMC. * @param dtmc A reference to the DTMC that is to be copied. */ Dtmc(Dtmc<T> const & dtmc) : AbstractDeterministicModel<T>(dtmc) { // Intentionally left empty. } /*! * Move Constructor. Performs a move on the given DTMC. * @param dtmc A reference to the DTMC that is to be moved. */ Dtmc(Dtmc<T>&& dtmc) : AbstractDeterministicModel<T>(std::move(dtmc)) { // Intentionally left empty. } //! Destructor /*! * Destructor. */ ~Dtmc() { // Intentionally left empty. } storm::models::ModelType getType() const { return DTMC; } /*! * Calculates a hash over all values contained in this Model. * @return size_t A Hash Value */ virtual std::size_t getHash() const override { return AbstractDeterministicModel<T>::getHash(); } /*! * Generates a sub-Dtmc of this Dtmc induced by the states specified by the bitvector. * E.g. a Dtmc that is partial isomorph (on the given states) to this one. * @param subSysStates A BitVector where all states that should be kept are indicated * by a set bit of the corresponding index. * Waring: If the vector does not have the correct size, it will be resized. * @return The sub-Dtmc. */ storm::models::Dtmc<T> getSubDtmc(storm::storage::BitVector& subSysStates) { // Is there any state in the subsystem? if(subSysStates.getNumberOfSetBits() == 0) { LOG4CPLUS_ERROR(logger, "No states in subsystem!"); return storm::models::Dtmc<T>(storm::storage::SparseMatrix<T>(), storm::models::AtomicPropositionsLabeling(this->getStateLabeling(), subSysStates), boost::optional<std::vector<T>>(), boost::optional<storm::storage::SparseMatrix<T>>(), boost::optional<std::vector<boost::container::flat_set<uint_fast64_t>>>()); } // Does the vector have the right size? if(subSysStates.size() != this->getNumberOfStates()) { LOG4CPLUS_INFO(logger, "BitVector has wrong size. Resizing it..."); subSysStates.resize(this->getNumberOfStates()); } // Test if it is a proper subsystem of this Dtmc, i.e. if there is at least one state to be left out. if(subSysStates.getNumberOfSetBits() == subSysStates.size()) { LOG4CPLUS_INFO(logger, "All states are kept. This is no proper subsystem."); return storm::models::Dtmc<T>(*this); } // 1. Get all necessary information from the old transition matrix storm::storage::SparseMatrix<T> const& origMat = this->getTransitionMatrix(); // Iterate over all rows. Count the number of all transitions from the old system to be // transfered to the new one. Also build a mapping from the state number of the old system // to the state number of the new system. uint_fast64_t subSysTransitionCount = 0; uint_fast64_t newRow = 0; std::vector<uint_fast64_t> stateMapping; for(uint_fast64_t row = 0; row < origMat.getRowCount(); ++row) { if(subSysStates.get(row)){ for(auto const& entry : origMat.getRow(row)) { if(subSysStates.get(entry.first)) { subSysTransitionCount++; } } stateMapping.push_back(newRow); newRow++; } else { stateMapping.push_back((uint_fast64_t) -1); } } // 2. Construct transition matrix // Take all states indicated by the vector as well as one additional state s_b as target of // all transitions that target a state that is not kept. uint_fast64_t const newStateCount = subSysStates.getNumberOfSetBits() + 1; // The number of transitions of the new Dtmc is the number of transitions transfered // from the old one plus one transition for each state to s_b. storm::storage::SparseMatrixBuilder<T> newMatBuilder(newStateCount,newStateCount,subSysTransitionCount + newStateCount); // Now fill the matrix. newRow = 0; T rest = utility::constantZero<T>(); for(uint_fast64_t row = 0; row < origMat.getRowCount(); ++row) { if(subSysStates.get(row)){ // Transfer transitions for(auto& entry : origMat.getRow(row)) { if(subSysStates.get(entry.first)) { newMatBuilder.addNextValue(newRow, stateMapping[entry.first], entry.second); } else { rest += entry.second; } } // Insert the transition taking care of the remaining outgoing probability. newMatBuilder.addNextValue(newRow, newStateCount - 1, rest); rest = storm::utility::constantZero<T>(); newRow++; } } // Insert last transition: self loop on s_b newMatBuilder.addNextValue(newStateCount - 1, newStateCount - 1, storm::utility::constantOne<T>()); // 3. Take care of the labeling. storm::models::AtomicPropositionsLabeling newLabeling = storm::models::AtomicPropositionsLabeling(this->getStateLabeling(), subSysStates); newLabeling.addState(); if(!newLabeling.containsAtomicProposition("s_b")) { newLabeling.addAtomicProposition("s_b"); } newLabeling.addAtomicPropositionToState("s_b", newStateCount - 1); // 4. Handle the optionals boost::optional<std::vector<T>> newStateRewards; if(this->hasStateRewards()) { // Get the rewards and move the needed values to the front. std::vector<T> newRewards(this->getStateRewardVector()); storm::utility::vector::selectVectorValues(newRewards, subSysStates, newRewards); // Throw away all values after the last state and set the reward for s_b to 0. newRewards.resize(newStateCount); newRewards[newStateCount - 1] = (T) 0; newStateRewards = newRewards; } boost::optional<storm::storage::SparseMatrix<T>> newTransitionRewards; if(this->hasTransitionRewards()) { storm::storage::SparseMatrixBuilder<T> newTransRewardsBuilder(newStateCount, subSysTransitionCount + newStateCount); // Copy the rewards for the kept states newRow = 0; for(uint_fast64_t row = 0; row < this->getTransitionRewardMatrix().getRowCount(); ++row) { if(subSysStates.get(row)){ // Transfer transition rewards for(auto& entry : this->getTransitionRewardMatrix().getRow(row)) { if(subSysStates.get(entry.first)) { newTransRewardsBuilder.addNextValue(newRow, stateMapping[entry.first], entry.second); } } // Insert the reward (e.g. 0) for the transition taking care of the remaining outgoing probability. newTransRewardsBuilder.addNextValue(newRow, newStateCount - 1, storm::utility::constantZero<T>()); newRow++; } } newTransitionRewards = newTransRewardsBuilder.build(); } boost::optional<std::vector<boost::container::flat_set<uint_fast64_t>>> newChoiceLabels; if(this->hasChoiceLabeling()) { // Get the choice label sets and move the needed values to the front. std::vector<boost::container::flat_set<uint_fast64_t>> newChoice(this->getChoiceLabeling()); storm::utility::vector::selectVectorValues(newChoice, subSysStates, newChoice); // Throw away all values after the last state and set the choice label set for s_b as empty. newChoice.resize(newStateCount); newChoice[newStateCount - 1] = boost::container::flat_set<uint_fast64_t>(); newChoiceLabels = newChoice; } // 5. Make Dtmc from its parts and return it return storm::models::Dtmc<T>(newMatBuilder.build(), newLabeling, newStateRewards, std::move(newTransitionRewards), newChoiceLabels); } virtual std::shared_ptr<AbstractModel<T>> applyScheduler(storm::storage::Scheduler const& scheduler) const override { storm::storage::SparseMatrix<T> newTransitionMatrix = storm::utility::matrix::applyScheduler(this->getTransitionMatrix(), scheduler); return std::shared_ptr<AbstractModel<T>>(new Dtmc(newTransitionMatrix, this->getStateLabeling(), this->hasStateRewards() ? this->getStateRewardVector() : boost::optional<std::vector<T>>(), this->hasTransitionRewards() ? this->getTransitionRewardMatrix() : boost::optional<storm::storage::SparseMatrix<T>>(), this->hasChoiceLabeling() ? this->getChoiceLabeling() : boost::optional<std::vector<boost::container::flat_set<uint_fast64_t>>>())); } private: /*! * @brief Perform some sanity checks. * * Checks probability matrix if all rows sum up to one. */ bool checkValidityOfProbabilityMatrix() { // Get the settings object to customize linear solving. if (this->getTransitionMatrix().getRowCount() != this->getTransitionMatrix().getColumnCount()) { // not square LOG4CPLUS_ERROR(logger, "Probability matrix is not square."); return false; } for (uint_fast64_t row = 0; row < this->getTransitionMatrix().getRowCount(); ++row) { T sum = this->getTransitionMatrix().getRowSum(row); if (sum == T(0)) { LOG4CPLUS_ERROR(logger, "Row " << row << " is a deadlock (sum == " << sum << ")."); return false; } if (!storm::utility::isOne(sum)) { LOG4CPLUS_ERROR(logger, "Row " << row << " has sum " << sum << "."); return false; } } return true; } }; } // namespace models } // namespace storm #endif /* STORM_MODELS_DTMC_H_ */