#include "storm/solver/TopologicalLinearEquationSolver.h" #include "storm/environment/solver/TopologicalSolverEnvironment.h" #include "storm/utility/constants.h" #include "storm/utility/vector.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/InvalidEnvironmentException.h" #include "storm/exceptions/UnexpectedException.h" namespace storm { namespace solver { template TopologicalLinearEquationSolver::TopologicalLinearEquationSolver() : localA(nullptr), A(nullptr) { // Intentionally left empty. } template TopologicalLinearEquationSolver::TopologicalLinearEquationSolver(storm::storage::SparseMatrix const& A) : localA(nullptr), A(nullptr) { this->setMatrix(A); } template TopologicalLinearEquationSolver::TopologicalLinearEquationSolver(storm::storage::SparseMatrix&& A) : localA(nullptr), A(nullptr) { this->setMatrix(std::move(A)); } template void TopologicalLinearEquationSolver::setMatrix(storm::storage::SparseMatrix const& A) { localA.reset(); this->A = &A; clearCache(); } template void TopologicalLinearEquationSolver::setMatrix(storm::storage::SparseMatrix&& A) { localA = std::make_unique>(std::move(A)); this->A = localA.get(); clearCache(); } template storm::Environment TopologicalLinearEquationSolver::getEnvironmentForUnderlyingSolver(storm::Environment const& env, bool adaptPrecision) const { storm::Environment subEnv(env); subEnv.solver().setLinearEquationSolverType(env.solver().topological().getUnderlyingEquationSolverType(), env.solver().topological().isUnderlyingEquationSolverTypeSetFromDefault()); if (adaptPrecision) { STORM_LOG_ASSERT(this->longestSccChainSize, "Did not compute the longest SCC chain size although it is needed."); auto subEnvPrec = subEnv.solver().getPrecisionOfLinearEquationSolver(subEnv.solver().getLinearEquationSolverType()); subEnv.solver().setLinearEquationSolverPrecision(static_cast(subEnvPrec.first.get() / storm::utility::convertNumber(this->longestSccChainSize.get()))); } return subEnv; } template bool TopologicalLinearEquationSolver::internalSolveEquations(Environment const& env, std::vector& x, std::vector const& b) const { // For sound computations we need to increase the precision in each SCC bool needAdaptPrecision = env.solver().isForceSoundness() && env.solver().getPrecisionOfLinearEquationSolver(env.solver().topological().getUnderlyingEquationSolverType()).first.is_initialized(); if (!this->sortedSccDecomposition || (needAdaptPrecision && !this->longestSccChainSize)) { STORM_LOG_TRACE("Creating SCC decomposition."); createSortedSccDecomposition(needAdaptPrecision); } //std::cout << "Sorted SCC decomposition: " << std::endl; //for (auto const& scc : *this->sortedSccDecomposition) { //std::cout << "SCC: "; // for (auto const& row : scc) { //std::cout << row << " "; // } //std::cout << std::endl; //} // We do not need to adapt the precision if all SCCs are trivial (i.e., the system is acyclic) needAdaptPrecision = needAdaptPrecision && (this->sortedSccDecomposition->size() != this->getMatrixRowCount()); storm::Environment sccSolverEnvironment = getEnvironmentForUnderlyingSolver(env, needAdaptPrecision); std::cout << "Found " << this->sortedSccDecomposition->size() << "SCCs. Average size is " << static_cast(this->getMatrixRowCount()) / static_cast(this->sortedSccDecomposition->size()) << "." << std::endl; if (this->longestSccChainSize) { std::cout << "Longest SCC chain size is " << this->longestSccChainSize.get() << std::endl; } // Handle the case where there is just one large SCC bool returnValue = true; if (this->sortedSccDecomposition->size() == 1) { returnValue = solveFullyConnectedEquationSystem(sccSolverEnvironment, x, b); } else { storm::storage::BitVector sccAsBitVector(x.size(), false); for (auto const& scc : *this->sortedSccDecomposition) { if (scc.isTrivial()) { returnValue = solveTrivialScc(*scc.begin(), x, b) && returnValue; } else { sccAsBitVector.clear(); for (auto const& state : scc) { sccAsBitVector.set(state, true); } returnValue = solveScc(sccSolverEnvironment, sccAsBitVector, x, b) && returnValue; } } } if (!this->isCachingEnabled()) { clearCache(); } return returnValue; } template void TopologicalLinearEquationSolver::createSortedSccDecomposition(bool needLongestChainSize) const { // Obtain the scc decomposition auto sccDecomposition = storm::storage::StronglyConnectedComponentDecomposition(*this->A); // Get a mapping from matrix row to the corresponding scc STORM_LOG_THROW(sccDecomposition.size() < std::numeric_limits::max(), storm::exceptions::UnexpectedException, "The number of SCCs is too large."); std::vector sccIndices(this->A->getRowCount(), std::numeric_limits::max()); uint32_t sccIndex = 0; for (auto const& scc : sccDecomposition) { for (auto const& row : scc) { sccIndices[row] = sccIndex; } ++sccIndex; } // Prepare the resulting set of sorted sccs this->sortedSccDecomposition = std::make_unique>(); std::vector& sortedSCCs = *this->sortedSccDecomposition; sortedSCCs.reserve(sccDecomposition.size()); // Find a topological sort via DFS. storm::storage::BitVector unsortedSCCs(sccDecomposition.size(), true); std::vector sccStack, chainSizes; if (needLongestChainSize) { chainSizes.resize(sccDecomposition.size(), 1u); } uint32_t longestChainSize = 0; uint32_t const token = std::numeric_limits::max(); std::set successorSCCs; for (uint32_t firstUnsortedScc = 0; firstUnsortedScc < unsortedSCCs.size(); firstUnsortedScc = unsortedSCCs.getNextSetIndex(firstUnsortedScc + 1)) { sccStack.push_back(firstUnsortedScc); while (!sccStack.empty()) { uint32_t currentSccIndex = sccStack.back(); if (currentSccIndex != token) { // Check whether the SCC is still unprocessed if (unsortedSCCs.get(currentSccIndex)) { // Explore the successors of the scc. storm::storage::StronglyConnectedComponent const& currentScc = sccDecomposition.getBlock(currentSccIndex); // We first push a token on the stack in order to recognize later when all successors of this SCC have been explored already. sccStack.push_back(token); // Now add all successors that are not already sorted. // Successors should only be added once, so we first prepare a set of them and add them afterwards. successorSCCs.clear(); for (auto const& row : currentScc) { for (auto const& entry : this->A->getRow(row)) { auto const& successorSCC = sccIndices[entry.getColumn()]; if (successorSCC != currentSccIndex && unsortedSCCs.get(successorSCC)) { successorSCCs.insert(successorSCC); } } } sccStack.insert(sccStack.end(), successorSCCs.begin(), successorSCCs.end()); } } else { // all successors of the current scc have already been explored. sccStack.pop_back(); // pop the token currentSccIndex = sccStack.back(); storm::storage::StronglyConnectedComponent& scc = sccDecomposition.getBlock(currentSccIndex); // Compute the longest chain size for this scc if (needLongestChainSize) { uint32_t& currentChainSize = chainSizes[currentSccIndex]; for (auto const& row : scc) { for (auto const& entry : this->A->getRow(row)) { auto const& successorSCC = sccIndices[entry.getColumn()]; if (successorSCC != currentSccIndex) { currentChainSize = std::max(currentChainSize, chainSizes[successorSCC] + 1); } } } longestChainSize = std::max(longestChainSize, currentChainSize); } unsortedSCCs.set(currentSccIndex, false); sccStack.pop_back(); // pop the current scc index sortedSCCs.push_back(std::move(scc)); } } } if (longestChainSize > 0) { this->longestSccChainSize = longestChainSize; } } template bool TopologicalLinearEquationSolver::solveTrivialScc(uint64_t const& sccState, std::vector& globalX, std::vector const& globalB) const { ValueType& xi = globalX[sccState]; xi = globalB[sccState]; bool hasDiagonalEntry = false; ValueType denominator; for (auto const& entry : this->A->getRow(sccState)) { if (entry.getColumn() == sccState) { STORM_LOG_ASSERT(!storm::utility::isOne(entry.getValue()), "Diagonal entry of fix point system has value 1."); hasDiagonalEntry = true; denominator = storm::utility::one() - entry.getValue(); } else { xi += entry.getValue() * globalX[entry.getColumn()]; } } if (hasDiagonalEntry) { xi /= denominator; } //std::cout << "Solved trivial scc " << sccState << " with result " << globalX[sccState] << std::endl; return true; } template bool TopologicalLinearEquationSolver::solveFullyConnectedEquationSystem(storm::Environment const& sccSolverEnvironment, std::vector& x, std::vector const& b) const { if (!this->sccSolver) { this->sccSolver = GeneralLinearEquationSolverFactory().create(sccSolverEnvironment, LinearEquationSolverTask::SolveEquations); this->sccSolver->setCachingEnabled(true); this->sccSolver->setBoundsFromOtherSolver(*this); if (this->sccSolver->getEquationProblemFormat(sccSolverEnvironment) == LinearEquationSolverProblemFormat::EquationSystem) { // Convert the matrix to an equation system. Note that we need to insert diagonal entries. storm::storage::SparseMatrix eqSysA(*this->A, true); eqSysA.convertToEquationSystem(); this->sccSolver->setMatrix(std::move(eqSysA)); } else { this->sccSolver->setMatrix(*this->A); } } return this->sccSolver->solveEquations(sccSolverEnvironment, x, b); } template bool TopologicalLinearEquationSolver::solveScc(storm::Environment const& sccSolverEnvironment, storm::storage::BitVector const& scc, std::vector& globalX, std::vector const& globalB) const { // Set up the SCC solver if (!this->sccSolver) { this->sccSolver = GeneralLinearEquationSolverFactory().create(sccSolverEnvironment, LinearEquationSolverTask::SolveEquations); this->sccSolver->setCachingEnabled(true); } // Matrix bool asEquationSystem = this->sccSolver->getEquationProblemFormat(sccSolverEnvironment) == LinearEquationSolverProblemFormat::EquationSystem; storm::storage::SparseMatrix sccA = this->A->getSubmatrix(true, scc, scc, asEquationSystem); if (asEquationSystem) { sccA.convertToEquationSystem(); } //std::cout << "Solving SCC " << scc << std::endl; //std::cout << "Matrix is " << sccA << std::endl; this->sccSolver->setMatrix(std::move(sccA)); // x Vector auto sccX = storm::utility::vector::filterVector(globalX, scc); // b Vector std::vector sccB; sccB.reserve(scc.getNumberOfSetBits()); for (auto const& row : scc) { ValueType bi = globalB[row]; for (auto const& entry : this->A->getRow(row)) { if (!scc.get(entry.getColumn())) { bi += entry.getValue() * globalX[entry.getColumn()]; } } sccB.push_back(std::move(bi)); } // lower/upper bounds if (this->hasLowerBound(storm::solver::AbstractEquationSolver::BoundType::Global)) { this->sccSolver->setLowerBound(this->getLowerBound()); } else if (this->hasLowerBound(storm::solver::AbstractEquationSolver::BoundType::Local)) { this->sccSolver->setLowerBounds(storm::utility::vector::filterVector(this->getLowerBounds(), scc)); } if (this->hasUpperBound(storm::solver::AbstractEquationSolver::BoundType::Global)) { this->sccSolver->setUpperBound(this->getUpperBound()); } else if (this->hasUpperBound(storm::solver::AbstractEquationSolver::BoundType::Local)) { this->sccSolver->setUpperBounds(storm::utility::vector::filterVector(this->getUpperBounds(), scc)); } //std::cout << "rhs is " << storm::utility::vector::toString(sccB) << std::endl; //std::cout << "x is " << storm::utility::vector::toString(sccX) << std::endl; bool returnvalue = this->sccSolver->solveEquations(sccSolverEnvironment, sccX, sccB); storm::utility::vector::setVectorValues(globalX, scc, sccX); return returnvalue; } template void TopologicalLinearEquationSolver::multiply(std::vector& x, std::vector const* b, std::vector& result) const { if (&x != &result) { multiplier.multAdd(*A, x, b, result); } else { // If the two vectors are aliases, we need to create a temporary. if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } multiplier.multAdd(*A, x, b, *this->cachedRowVector); result.swap(*this->cachedRowVector); if (!this->isCachingEnabled()) { clearCache(); } } } template void TopologicalLinearEquationSolver::multiplyAndReduce(OptimizationDirection const& dir, std::vector const& rowGroupIndices, std::vector& x, std::vector const* b, std::vector& result, std::vector* choices) const { if (&x != &result) { multiplier.multAddReduce(dir, rowGroupIndices, *A, x, b, result, choices); } else { // If the two vectors are aliases, we need to create a temporary. if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } multiplier.multAddReduce(dir, rowGroupIndices, *A, x, b, *this->cachedRowVector, choices); result.swap(*this->cachedRowVector); if (!this->isCachingEnabled()) { clearCache(); } } } template bool TopologicalLinearEquationSolver::supportsGaussSeidelMultiplication() const { return true; } template void TopologicalLinearEquationSolver::multiplyGaussSeidel(std::vector& x, std::vector const* b) const { STORM_LOG_ASSERT(this->A->getRowCount() == this->A->getColumnCount(), "This function is only applicable for square matrices."); multiplier.multAddGaussSeidelBackward(*A, x, b); } template void TopologicalLinearEquationSolver::multiplyAndReduceGaussSeidel(OptimizationDirection const& dir, std::vector const& rowGroupIndices, std::vector& x, std::vector const* b, std::vector* choices) const { multiplier.multAddReduceGaussSeidelBackward(dir, rowGroupIndices, *A, x, b, choices); } template ValueType TopologicalLinearEquationSolver::multiplyRow(uint64_t const& rowIndex, std::vector const& x) const { return multiplier.multiplyRow(*A, rowIndex, x); } template LinearEquationSolverProblemFormat TopologicalLinearEquationSolver::getEquationProblemFormat(Environment const& env) const { return LinearEquationSolverProblemFormat::FixedPointSystem; } template LinearEquationSolverRequirements TopologicalLinearEquationSolver::getRequirements(Environment const& env, LinearEquationSolverTask const& task) const { // Return the requirements of the underlying solver return GeneralLinearEquationSolverFactory().getRequirements(getEnvironmentForUnderlyingSolver(env), task); } template void TopologicalLinearEquationSolver::clearCache() const { sortedSccDecomposition.reset(); longestSccChainSize = boost::none; sccSolver.reset(); LinearEquationSolver::clearCache(); } template uint64_t TopologicalLinearEquationSolver::getMatrixRowCount() const { return this->A->getRowCount(); } template uint64_t TopologicalLinearEquationSolver::getMatrixColumnCount() const { return this->A->getColumnCount(); } template std::unique_ptr> TopologicalLinearEquationSolverFactory::create(Environment const& env, LinearEquationSolverTask const& task) const { return std::make_unique>(); } template std::unique_ptr> TopologicalLinearEquationSolverFactory::clone() const { return std::make_unique>(*this); } // Explicitly instantiate the linear equation solver. template class TopologicalLinearEquationSolver; template class TopologicalLinearEquationSolverFactory; #ifdef STORM_HAVE_CARL template class TopologicalLinearEquationSolver; template class TopologicalLinearEquationSolverFactory; template class TopologicalLinearEquationSolver; template class TopologicalLinearEquationSolverFactory; #endif } }