/* * GmmxxDtmcPrctlModelChecker.h * * Created on: 06.12.2012 * Author: Christian Dehnert */ #ifndef STORM_MODELCHECKER_GMMXXDTMCPRCTLMODELCHECKER_H_ #define STORM_MODELCHECKER_GMMXXDTMCPRCTLMODELCHECKER_H_ #include #include "src/models/Dtmc.h" #include "src/modelchecker/DtmcPrctlModelChecker.h" #include "src/utility/GraphAnalyzer.h" #include "src/utility/Vector.h" #include "src/utility/ConstTemplates.h" #include "src/utility/Settings.h" #include "src/adapters/GmmxxAdapter.h" #include "src/exceptions/InvalidPropertyException.h" #include "src/storage/JacobiDecomposition.h" #include "gmm/gmm_matrix.h" #include "gmm/gmm_iter_solvers.h" #include "log4cplus/logger.h" #include "log4cplus/loggingmacros.h" extern log4cplus::Logger logger; namespace storm { namespace modelChecker { /* * A model checking engine that makes use of the gmm++ backend. */ template class GmmxxDtmcPrctlModelChecker : public DtmcPrctlModelChecker { public: explicit GmmxxDtmcPrctlModelChecker(storm::models::Dtmc& dtmc) : DtmcPrctlModelChecker(dtmc) { // Intentionally left empty. } virtual ~GmmxxDtmcPrctlModelChecker() { } virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const { // First, we need to compute the states that satisfy the sub-formulas of the until-formula. storm::storage::BitVector* leftStates = formula.getLeft().check(*this); storm::storage::BitVector* rightStates = formula.getRight().check(*this); // Copy the matrix before we make any changes. storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionMatrix()); // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. tmpMatrix.makeRowsAbsorbing(~(*leftStates | *rightStates) | *rightStates); // Transform the transition probability matrix to the gmm++ format to use its arithmetic. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(tmpMatrix); // Create the vector with which to multiply. std::vector* result = new std::vector(this->getModel().getNumberOfStates()); storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); // Now perform matrix-vector multiplication as long as we meet the bound of the formula. std::vector* swap = nullptr; std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { gmm::mult(*gmmxxMatrix, *result, *tmpResult); swap = tmpResult; tmpResult = result; result = swap; } delete tmpResult; // Delete intermediate results and return result. delete gmmxxMatrix; delete leftStates; delete rightStates; return result; } virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const { // First, we need to compute the states that satisfy the sub-formula of the next-formula. storm::storage::BitVector* nextStates = formula.getChild().check(*this); // Transform the transition probability matrix to the gmm++ format to use its arithmetic. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); // Create the vector with which to multiply and initialize it correctly. std::vector x(this->getModel().getNumberOfStates()); storm::utility::setVectorValues(&x, *nextStates, storm::utility::constGetOne()); // Delete obsolete sub-result. delete nextStates; // Create resulting vector. std::vector* result = new std::vector(this->getModel().getNumberOfStates()); // Perform the actual computation, namely matrix-vector multiplication. gmm::mult(*gmmxxMatrix, x, *result); // Delete temporary matrix and return result. delete gmmxxMatrix; return result; } virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const { // First, we need to compute the states that satisfy the sub-formulas of the until-formula. storm::storage::BitVector* leftStates = formula.getLeft().check(*this); storm::storage::BitVector* rightStates = formula.getRight().check(*this); // Then, we need to identify the states which have to be taken out of the matrix, i.e. // all states that have probability 0 and 1 of satisfying the until-formula. storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); storm::utility::GraphAnalyzer::performProb01(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); // Delete sub-results that are obsolete now. delete leftStates; delete rightStates; LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); // Create resulting vector. std::vector* result = new std::vector(this->getModel().getNumberOfStates()); // Only try to solve system if there are states for which the probability is unknown. uint_fast64_t mayBeStatesSetBitCount = maybeStates.getNumberOfSetBits(); if (mayBeStatesSetBitCount > 0 && !qualitative) { // Now we can eliminate the rows and columns from the original transition probability matrix. storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); // Converting the matrix from the fixpoint notation to the form needed for the equation // system. That is, we go from x = A*x + b to (I-A)x = b. submatrix->convertToEquationSystem(); // Transform the submatrix to the gmm++ format to use its solvers. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); delete submatrix; // Initialize the x vector with 0.5 for each element. This is the initial guess for // the iterative solvers. It should be safe as for all 'maybe' states we know that the // probability is strictly larger than 0. std::vector x(mayBeStatesSetBitCount, Type(0.5)); // Prepare the right-hand side of the equation system. For entry i this corresponds to // the accumulated probability of going from state i to some 'yes' state. std::vector b(mayBeStatesSetBitCount); this->getModel().getTransitionMatrix()->getConstrainedRowSumVector(maybeStates, statesWithProbability1, &b); // Solve the corresponding system of linear equations. this->solveLinearEquationSystem(*gmmxxMatrix, x, b); // Set values of resulting vector according to result. storm::utility::setVectorValues(result, maybeStates, x); // Delete temporary matrix. delete gmmxxMatrix; } else if (qualitative) { // If we only need a qualitative result, we can safely assume that the results will only be compared to // bounds which are either 0 or 1. Setting the value to 0.5 is thus safe. storm::utility::setVectorValues(result, maybeStates, Type(0.5)); } // Set values of resulting vector that are known exactly. storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); return result; } virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const { // Only compute the result if the model has a state-based reward model. if (!this->getModel().hasStateRewards()) { LOG4CPLUS_ERROR(logger, "Missing (state-based) reward model for formula."); throw storm::exceptions::InvalidPropertyException() << "Missing (state-based) reward model for formula."; } // Transform the transition probability matrix to the gmm++ format to use its arithmetic. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); // Initialize result to state rewards of the model. std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); // Now perform matrix-vector multiplication as long as we meet the bound of the formula. std::vector* swap = nullptr; std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { gmm::mult(*gmmxxMatrix, *result, *tmpResult); swap = tmpResult; tmpResult = result; result = swap; } // Delete temporary variables and return result. delete tmpResult; delete gmmxxMatrix; return result; } virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const { // Only compute the result if the model has at least one reward model. if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { LOG4CPLUS_ERROR(logger, "Missing reward model for formula."); throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; } // Transform the transition probability matrix to the gmm++ format to use its arithmetic. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); // Compute the reward vector to add in each step based on the available reward models. std::vector* totalRewardVector = nullptr; if (this->getModel().hasTransitionRewards()) { totalRewardVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); if (this->getModel().hasStateRewards()) { gmm::add(*this->getModel().getStateRewardVector(), *totalRewardVector); } } else { totalRewardVector = new std::vector(*this->getModel().getStateRewardVector()); } std::vector* result = new std::vector(this->getModel().getNumberOfStates()); // Now perform matrix-vector multiplication as long as we meet the bound of the formula. std::vector* swap = nullptr; std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { gmm::mult(*gmmxxMatrix, *result, *tmpResult); swap = tmpResult; tmpResult = result; result = swap; // Add the reward vector to the result. gmm::add(*totalRewardVector, *result); } // Delete temporary variables and return result. delete tmpResult; delete gmmxxMatrix; delete totalRewardVector; return result; } virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const { // Only compute the result if the model has at least one reward model. if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { LOG4CPLUS_ERROR(logger, "Missing reward model for formula. Skipping formula"); throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; } // Determine the states for which the target predicate holds. storm::storage::BitVector* targetStates = formula.getChild().check(*this); // Determine which states have a reward of infinity by definition. storm::storage::BitVector infinityStates(this->getModel().getNumberOfStates()); storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true); storm::utility::GraphAnalyzer::performProb1(this->getModel(), trueStates, *targetStates, &infinityStates); infinityStates.complement(); // Create resulting vector. std::vector* result = new std::vector(this->getModel().getNumberOfStates()); // Check whether there are states for which we have to compute the result. storm::storage::BitVector maybeStates = ~(*targetStates) & ~infinityStates; const int maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); if (maybeStatesSetBitCount > 0) { // Now we can eliminate the rows and columns from the original transition probability matrix. storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); // Converting the matrix from the fixpoint notation to the form needed for the equation // system. That is, we go from x = A*x + b to (I-A)x = b. submatrix->convertToEquationSystem(); // Transform the submatrix to the gmm++ format to use its solvers. gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); delete submatrix; // Initialize the x vector with 1 for each element. This is the initial guess for // the iterative solvers. std::vector x(maybeStatesSetBitCount, storm::utility::constGetOne()); // Prepare the right-hand side of the equation system. std::vector* b = new std::vector(maybeStatesSetBitCount); if (this->getModel().hasTransitionRewards()) { // If a transition-based reward model is available, we initialize the right-hand // side to the vector resulting from summing the rows of the pointwise product // of the transition probability matrix and the transition reward matrix. std::vector* pointwiseProductRowSumVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); storm::utility::selectVectorValues(b, maybeStates, *pointwiseProductRowSumVector); delete pointwiseProductRowSumVector; if (this->getModel().hasStateRewards()) { // If a state-based reward model is also available, we need to add this vector // as well. As the state reward vector contains entries not just for the states // that we still consider (i.e. maybeStates), we need to extract these values // first. std::vector* subStateRewards = new std::vector(maybeStatesSetBitCount); storm::utility::setVectorValues(subStateRewards, maybeStates, *this->getModel().getStateRewardVector()); gmm::add(*subStateRewards, *b); delete subStateRewards; } } else { // If only a state-based reward model is available, we take this vector as the // right-hand side. As the state reward vector contains entries not just for the // states that we still consider (i.e. maybeStates), we need to extract these values // first. storm::utility::setVectorValues(b, maybeStates, *this->getModel().getStateRewardVector()); } // Solve the corresponding system of linear equations. this->solveLinearEquationSystem(*gmmxxMatrix, x, *b); // Set values of resulting vector according to result. storm::utility::setVectorValues(result, maybeStates, x); // Delete temporary matrix and right-hand side. delete gmmxxMatrix; delete b; } // Set values of resulting vector that are known exactly. storm::utility::setVectorValues(result, *targetStates, storm::utility::constGetZero()); storm::utility::setVectorValues(result, infinityStates, storm::utility::constGetInfinity()); // Delete temporary storages and return result. delete targetStates; return result; } /*! * Returns the name of this module. * @return The name of this module. */ static std::string getModuleName() { return "gmm++det"; } /*! * Returns a trigger such that if the option "matrixlib" is set to "gmm++", this model checker * is to be used. * @return An option trigger for this module. */ static std::pair getOptionTrigger() { return std::pair("matrixlib", "gmm++"); } /*! * Registers all options associated with the gmm++ matrix library. */ static void putOptions(boost::program_options::options_description* desc) { desc->add_options()("lemethod", boost::program_options::value()->default_value("bicgstab")->notifier(&validateLeMethod), "Sets the method used for linear equation solving. Must be in {bicgstab, qmr}."); desc->add_options()("maxiter", boost::program_options::value()->default_value(10000), "Sets the maximal number of iterations for iterative equation solving."); desc->add_options()("precision", boost::program_options::value()->default_value(1e-6), "Sets the precision for iterative equation solving."); desc->add_options()("precond", boost::program_options::value()->default_value("ilu")->notifier(&validatePreconditioner), "Sets the preconditioning technique for linear equation solving. Must be in {ilu, diagonal, ildlt, none}."); desc->add_options()("relative", boost::program_options::value()->default_value(true), "Sets whether the relative or absolute error is considered for detecting convergence."); } /*! * Validates whether the given lemethod matches one of the available ones. * Throws an exception of type InvalidSettings in case the selected method is illegal. */ static void validateLeMethod(const std::string& lemethod) { if ((lemethod != "bicgstab") && (lemethod != "qmr")) { throw exceptions::InvalidSettingsException() << "Argument " << lemethod << " for option 'lemethod' is invalid."; } } /*! * Validates whether the given preconditioner matches one of the available ones. * Throws an exception of type InvalidSettings in case the selected preconditioner is illegal. */ static void validatePreconditioner(const std::string& preconditioner) { if ((preconditioner != "ilu") && (preconditioner != "diagonal") && (preconditioner != "ildlt") && (preconditioner != "none")) { throw exceptions::InvalidSettingsException() << "Argument " << preconditioner << " for option 'precond' is invalid."; } } private: /*! * Solves the linear equation system Ax=b with the given parameters. * * @param A The matrix A specifying the coefficients of the linear equations. * @param x The vector x for which to solve the equations. The initial value of the elements of * this vector are used as the initial guess and might thus influence performance and convergence. * @param b The vector b specifying the values on the right-hand-sides of the equations. * @return The solution of the system of linear equations in form of the elements of the vector * x. */ void solveLinearEquationSystem(gmm::csr_matrix const& A, std::vector& x, std::vector const& b) const { // Get the settings object to customize linear solving. storm::settings::Settings* s = storm::settings::instance(); // Prepare an iteration object that determines the accuracy, maximum number of iterations // and the like. gmm::iteration iter(s->get("precision"), 0, s->get("maxiter")); // Now do the actual solving. LOG4CPLUS_INFO(logger, "Starting iterative solver."); const std::string& precond = s->getString("precond"); if (precond == "ilu") { LOG4CPLUS_INFO(logger, "Using ILU preconditioner."); } else if (precond == "diagonal") { LOG4CPLUS_INFO(logger, "Using diagonal preconditioner."); } else if (precond == "ildlt") { LOG4CPLUS_INFO(logger, "Using ILDLT preconditioner."); } else if (precond == "none") { LOG4CPLUS_INFO(logger, "Using no preconditioner."); } if (s->getString("lemethod") == "bicgstab") { LOG4CPLUS_INFO(logger, "Using BiCGStab method."); if (precond == "ilu") { gmm::bicgstab(A, x, b, gmm::ilu_precond>(A), iter); } else if (precond == "diagonal") { gmm::bicgstab(A, x, b, gmm::diagonal_precond>(A), iter); } else if (precond == "ildlt") { gmm::bicgstab(A, x, b, gmm::ildlt_precond>(A), iter); } else if (precond == "none") { gmm::bicgstab(A, x, b, gmm::identity_matrix(), iter); } // FIXME: gmres has been disabled, because it triggers gmm++ compilation errors /* } else if (s->getString("lemethod").compare("gmres") == 0) { LOG4CPLUS_INFO(logger, "Using GMRES method."); if (precond.compare("ilu")) { gmm::gmres(A, x, b, gmm::ilu_precond>(A), s->get("restart"), iter); } else if (precond == "diagonal") { gmm::gmres(A, x, b, gmm::diagonal_precond>(A), s->get("restart"), iter); } else if (precond == "ildlt") { gmm::gmres(A, x, b, gmm::ildlt_precond>(A), s->get("restart"), iter); } else if (precond == "none") { gmm::gmres(A, x, b, gmm::identity_matrix(), s->get("restart"), iter); } */ } else if (s->getString("lemethod") == "qmr") { LOG4CPLUS_INFO(logger, "Using QMR method."); if (precond == "ilu") { gmm::qmr(A, x, b, gmm::ilu_precond>(A), iter); } else if (precond == "diagonal") { gmm::qmr(A, x, b, gmm::diagonal_precond>(A), iter); } else if (precond == "ildlt") { gmm::qmr(A, x, b, gmm::ildlt_precond>(A), iter); } else if (precond == "none") { gmm::qmr(A, x, b, gmm::identity_matrix(), iter); } } // Check if the solver converged and issue a warning otherwise. if (iter.converged()) { LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iter.get_iteration() << " iterations."); } else { LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); } } /*! * Solves the linear equation system Ax=b with the given parameters * using the Jacobi Method and therefor the Jacobi Decomposition of A. * * @param A The matrix A specifying the coefficients of the linear equations. * @param x The vector x for which to solve the equations. The initial value of the elements of * this vector are used as the initial guess and might thus influence performance and convergence. * @param b The vector b specifying the values on the right-hand-sides of the equations. * @return The solution of the system of linear equations in form of the elements of the vector * x. */ void solveLinearEquationSystemWithJacobi(storm::storage::SparseMatrix const& A, std::vector& x, std::vector const& b) const { // Get the settings object to customize linear solving. storm::settings::Settings* s = storm::settings::instance(); double precision = s->get("precision"); if (precision <= 0) { LOG4CPLUS_ERROR(logger, "Selected precision for linear equation solving must be strictly greater than zero for Jacobi method."); } // Get a Jacobi Decomposition of the Input Matrix A storm::storage::JacobiDecomposition* jacobiDecomposition = A.getJacobiDecomposition(); // Convert the Diagonal matrix to GMM format gmm::csr_matrix* gmmxxDiagonalInverted = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiDInv()); // Convert the LU Matrix to GMM format gmm::csr_matrix* gmmxxLU = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiLU()); delete jacobiDecomposition; LOG4CPLUS_INFO(logger, "Starting iterative Jacobi Solver."); // x_(k + 1) = D^-1 * (b - R * x_k) std::vector* xNext = new std::vector(x.size()); const std::vector* xCopy = xNext; std::vector* xCurrent = &x; uint_fast64_t iterationCount = 0; do { // R * x_k -> xCurrent gmm::mult(*gmmxxLU, *xCurrent, *xNext); // b - R * x_k gmm::add(b, gmm::scaled(*xNext, -1.0), *xNext); // D^-1 * (b - R * x_k) gmm::mult(*gmmxxDiagonalInverted, *xNext, *xNext); std::vector* swap = xNext; xNext = xCurrent; xCurrent = swap; ++iterationCount; } while (gmm::vect_norminf(*xCurrent) > precision); delete xCopy; LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iterationCount << " iterations."); } }; } //namespace modelChecker } //namespace storm #endif /* STORM_MODELCHECKER_GMMXXDTMCPRCTLMODELCHECKER_H_ */