#include "storm/solver/IterativeMinMaxLinearEquationSolver.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/MinMaxEquationSolverSettings.h" #include "storm/utility/vector.h" #include "storm/utility/macros.h" #include "storm/exceptions/InvalidSettingsException.h" #include "storm/exceptions/InvalidStateException.h" namespace storm { namespace solver { template IterativeMinMaxLinearEquationSolverSettings::IterativeMinMaxLinearEquationSolverSettings() { // Get the settings object to customize linear solving. storm::settings::modules::MinMaxEquationSolverSettings const& settings = storm::settings::getModule(); maximalNumberOfIterations = settings.getMaximalIterationCount(); precision = storm::utility::convertNumber(settings.getPrecision()); relative = settings.getConvergenceCriterion() == storm::settings::modules::MinMaxEquationSolverSettings::ConvergenceCriterion::Relative; setSolutionMethod(settings.getMinMaxEquationSolvingMethod()); } template void IterativeMinMaxLinearEquationSolverSettings::setSolutionMethod(SolutionMethod const& solutionMethod) { this->solutionMethod = solutionMethod; } template void IterativeMinMaxLinearEquationSolverSettings::setSolutionMethod(MinMaxMethod const& solutionMethod) { switch (solutionMethod) { case MinMaxMethod::ValueIteration: this->solutionMethod = SolutionMethod::ValueIteration; break; case MinMaxMethod::PolicyIteration: this->solutionMethod = SolutionMethod::PolicyIteration; break; case MinMaxMethod::Acyclic: this->solutionMethod = SolutionMethod::Acyclic; break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unsupported technique for iterative MinMax linear equation solver."); } } template void IterativeMinMaxLinearEquationSolverSettings::setMaximalNumberOfIterations(uint64_t maximalNumberOfIterations) { this->maximalNumberOfIterations = maximalNumberOfIterations; } template void IterativeMinMaxLinearEquationSolverSettings::setRelativeTerminationCriterion(bool value) { this->relative = value; } template void IterativeMinMaxLinearEquationSolverSettings::setPrecision(ValueType precision) { this->precision = precision; } template typename IterativeMinMaxLinearEquationSolverSettings::SolutionMethod const& IterativeMinMaxLinearEquationSolverSettings::getSolutionMethod() const { return solutionMethod; } template uint64_t IterativeMinMaxLinearEquationSolverSettings::getMaximalNumberOfIterations() const { return maximalNumberOfIterations; } template ValueType IterativeMinMaxLinearEquationSolverSettings::getPrecision() const { return precision; } template bool IterativeMinMaxLinearEquationSolverSettings::getRelativeTerminationCriterion() const { return relative; } template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(std::unique_ptr>&& linearEquationSolverFactory, IterativeMinMaxLinearEquationSolverSettings const& settings) : StandardMinMaxLinearEquationSolver(std::move(linearEquationSolverFactory)), settings(settings) { // Intentionally left empty } template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(storm::storage::SparseMatrix const& A, std::unique_ptr>&& linearEquationSolverFactory, IterativeMinMaxLinearEquationSolverSettings const& settings) : StandardMinMaxLinearEquationSolver(A, std::move(linearEquationSolverFactory)), settings(settings) { // Intentionally left empty. } template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(storm::storage::SparseMatrix&& A, std::unique_ptr>&& linearEquationSolverFactory, IterativeMinMaxLinearEquationSolverSettings const& settings) : StandardMinMaxLinearEquationSolver(std::move(A), std::move(linearEquationSolverFactory)), settings(settings) { // Intentionally left empty. } template bool IterativeMinMaxLinearEquationSolver::internalSolveEquations(OptimizationDirection dir, std::vector& x, std::vector const& b) const { switch (this->getSettings().getSolutionMethod()) { case IterativeMinMaxLinearEquationSolverSettings::SolutionMethod::ValueIteration: return solveEquationsValueIteration(dir, x, b); case IterativeMinMaxLinearEquationSolverSettings::SolutionMethod::PolicyIteration: return solveEquationsPolicyIteration(dir, x, b); case IterativeMinMaxLinearEquationSolverSettings::SolutionMethod::Acyclic: return solveEquationsAcyclic(dir, x, b); default: STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "This solver does not implement the selected solution method"); } return false; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsPolicyIteration(OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Create the initial scheduler. std::vector scheduler = this->hasInitialScheduler() ? this->getInitialScheduler() : std::vector(this->A->getRowGroupCount()); // Get a vector for storing the right-hand side of the inner equation system. if(!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } std::vector& subB = *auxiliaryRowGroupVector; // Resolve the nondeterminism according to the current scheduler. storm::storage::SparseMatrix submatrix = this->A->selectRowsFromRowGroups(scheduler, true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(subB, scheduler, this->A->getRowGroupIndices(), b); // Create a solver that we will use throughout the procedure. We will modify the matrix in each iteration. auto solver = this->linearEquationSolverFactory->create(std::move(submatrix)); if (this->lowerBound) { solver->setLowerBound(this->lowerBound.get()); } if (this->upperBound) { solver->setUpperBound(this->upperBound.get()); } solver->setCachingEnabled(true); Status status = Status::InProgress; uint64_t iterations = 0; do { // Solve the equation system for the 'DTMC'. solver->solveEquations(x, subB); // Go through the multiplication result and see whether we can improve any of the choices. bool schedulerImproved = false; for (uint_fast64_t group = 0; group < this->A->getRowGroupCount(); ++group) { uint_fast64_t currentChoice = scheduler[group]; for (uint_fast64_t choice = this->A->getRowGroupIndices()[group]; choice < this->A->getRowGroupIndices()[group + 1]; ++choice) { // If the choice is the currently selected one, we can skip it. if (choice - this->A->getRowGroupIndices()[group] == currentChoice) { continue; } // Create the value of the choice. ValueType choiceValue = storm::utility::zero(); for (auto const& entry : this->A->getRow(choice)) { choiceValue += entry.getValue() * x[entry.getColumn()]; } choiceValue += b[choice]; // If the value is strictly better than the solution of the inner system, we need to improve the scheduler. // TODO: If the underlying solver is not precise, this might run forever (i.e. when a state has two choices where the (exact) values are equal). // only changing the scheduler if the values are not equal (modulo precision) would make this unsound. if (valueImproved(dir, x[group], choiceValue)) { schedulerImproved = true; scheduler[group] = choice - this->A->getRowGroupIndices()[group]; x[group] = std::move(choiceValue); } } } // If the scheduler did not improve, we are done. if (!schedulerImproved) { status = Status::Converged; } else { // Update the scheduler and the solver. submatrix = this->A->selectRowsFromRowGroups(scheduler, true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(subB, scheduler, this->A->getRowGroupIndices(), b); solver->setMatrix(std::move(submatrix)); } // Update environment variables. ++iterations; status = updateStatusIfNotConverged(status, x, iterations); } while (status == Status::InProgress); reportStatus(status, iterations); // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::move(scheduler); } if(!this->isCachingEnabled()) { clearCache(); } return status == Status::Converged || status == Status::TerminatedEarly; } template bool IterativeMinMaxLinearEquationSolver::valueImproved(OptimizationDirection dir, ValueType const& value1, ValueType const& value2) const { if (dir == OptimizationDirection::Minimize) { return value2 < value1; } else { return value2 > value1; } } template ValueType IterativeMinMaxLinearEquationSolver::getPrecision() const { return this->getSettings().getPrecision(); } template bool IterativeMinMaxLinearEquationSolver::getRelative() const { return this->getSettings().getRelativeTerminationCriterion(); } template MinMaxLinearEquationSolverRequirements IterativeMinMaxLinearEquationSolver::getRequirements(MinMaxLinearEquationSolverSystemType const& equationSystemType, boost::optional const& direction) const { MinMaxLinearEquationSolverRequirements requirements; if (equationSystemType == MinMaxLinearEquationSolverSystemType::UntilProbabilities) { if (this->getSettings().getSolutionMethod() == IterativeMinMaxLinearEquationSolverSettings::SolutionMethod::PolicyIteration) { if (!direction || direction.get() == OptimizationDirection::Maximize) { requirements.set(MinMaxLinearEquationSolverRequirements::Element::ValidInitialScheduler); } } } else if (equationSystemType == MinMaxLinearEquationSolverSystemType::ReachabilityRewards) { if (this->getSettings().getSolutionMethod() == IterativeMinMaxLinearEquationSolverSettings::SolutionMethod::PolicyIteration) { if (!direction || direction.get() == OptimizationDirection::Minimize) { requirements.set(MinMaxLinearEquationSolverRequirements::Element::ValidInitialScheduler); } } } return requirements; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsValueIteration(OptimizationDirection dir, std::vector& x, std::vector const& b) const { if(!this->linEqSolverA) { this->linEqSolverA = this->linearEquationSolverFactory->create(*this->A); this->linEqSolverA->setCachingEnabled(true); } if (!this->auxiliaryRowVector) { this->auxiliaryRowVector = std::make_unique>(this->A->getRowCount()); } std::vector& multiplyResult = *this->auxiliaryRowVector; if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } if (this->hasInitialScheduler()) { // Resolve the nondeterminism according to the initial scheduler. storm::storage::SparseMatrix submatrix = this->A->selectRowsFromRowGroups(this->getInitialScheduler(), true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(*auxiliaryRowGroupVector, this->getInitialScheduler(), this->A->getRowGroupIndices(), b); // Solve the resulting equation system. // Note that the linEqSolver might consider a slightly different interpretation of "equalModuloPrecision". Hence, we iteratively increase its precision. auto submatrixSolver = this->linearEquationSolverFactory->create(std::move(submatrix)); submatrixSolver->setCachingEnabled(true); if (this->lowerBound) { submatrixSolver->setLowerBound(this->lowerBound.get()); } if (this->upperBound) { submatrixSolver->setUpperBound(this->upperBound.get()); } submatrixSolver->solveEquations(x, *auxiliaryRowGroupVector); } std::vector* newX = auxiliaryRowGroupVector.get(); std::vector* currentX = &x; // Proceed with the iterations as long as the method did not converge or reach the maximum number of iterations. uint64_t iterations = 0; Status status = Status::InProgress; while (status == Status::InProgress) { // Compute x' = A*x + b. this->linEqSolverA->multiply(*currentX, &b, multiplyResult); // Reduce the vector x' by applying min/max for all non-deterministic choices. storm::utility::vector::reduceVectorMinOrMax(dir, multiplyResult, *newX, this->A->getRowGroupIndices()); // Determine whether the method converged. if (storm::utility::vector::equalModuloPrecision(*currentX, *newX, this->getSettings().getPrecision(), this->getSettings().getRelativeTerminationCriterion())) { status = Status::Converged; } // Update environment variables. std::swap(currentX, newX); ++iterations; status = updateStatusIfNotConverged(status, *currentX, iterations); } reportStatus(status, iterations); // If we performed an odd number of iterations, we need to swap the x and currentX, because the newest result // is currently stored in currentX, but x is the output vector. if (currentX == auxiliaryRowGroupVector.get()) { std::swap(x, *currentX); } // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { // Due to a custom termination condition, it may be the case that no iterations are performed. In this // case we need to compute x'= A*x+b once. if (iterations==0) { this->linEqSolverA->multiply(x, &b, multiplyResult); } this->schedulerChoices = std::vector(this->A->getRowGroupCount()); // Reduce the multiplyResult and keep track of the choices made storm::utility::vector::reduceVectorMinOrMax(dir, multiplyResult, x, this->A->getRowGroupIndices(), &this->schedulerChoices.get()); } if (!this->isCachingEnabled()) { clearCache(); } return status == Status::Converged || status == Status::TerminatedEarly; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsAcyclic(OptimizationDirection dir, std::vector& x, std::vector const& b) const { uint64_t numGroups = this->A->getRowGroupCount(); // Allocate memory for the scheduler (if required) if (this->isTrackSchedulerSet()) { if (this->schedulerChoices) { this->schedulerChoices->resize(numGroups); } else { this->schedulerChoices = std::vector(numGroups); } } // We now compute a topological sort of the row groups. // If caching is enabled, it might be possible to obtain this sort from the cache. if (this->isCachingEnabled()) { if (rowGroupOrdering) { for (auto const& group : *rowGroupOrdering) { computeOptimalValueForRowGroup(group, dir, x, b, this->isTrackSchedulerSet() ? &this->schedulerChoices.get()[group] : nullptr); } return true; } else { rowGroupOrdering = std::make_unique>(); rowGroupOrdering->reserve(numGroups); } } auto transposedMatrix = this->A->transpose(true); // We store the groups that have already been processed, i.e., the groups for which x[group] was already set to the correct value. storm::storage::BitVector processedGroups(numGroups, false); // Furthermore, we keep track of all candidate groups for which we still need to check whether this group can be processed now. // A group can be processed if all successors have already been processed. // Notice that the BitVector candidates is considered in a reversed way, i.e., group i is a candidate iff candidates.get(numGroups - i - 1) is true. // This is due to the observation that groups with higher indices usually need to be processed earlier. storm::storage::BitVector candidates(numGroups, true); uint64_t candidate = numGroups - 1; for (uint64_t numCandidates = candidates.size(); numCandidates > 0; --numCandidates) { candidates.set(numGroups - candidate - 1, false); // Check if the candidate row group has an unprocessed successor bool hasUnprocessedSuccessor = false; for (auto const& entry : this->A->getRowGroup(candidate)) { if (!processedGroups.get(entry.getColumn())) { hasUnprocessedSuccessor = true; break; } } uint64_t nextCandidate = numGroups - candidates.getNextSetIndex(numGroups - candidate - 1 + 1) - 1; if (!hasUnprocessedSuccessor) { // This candidate can be processed. processedGroups.set(candidate); computeOptimalValueForRowGroup(candidate, dir, x, b, this->isTrackSchedulerSet() ? &this->schedulerChoices.get()[candidate] : nullptr); if (this->isCachingEnabled()) { rowGroupOrdering->push_back(candidate); } // Add new candidates for (auto const& predecessorEntry : transposedMatrix.getRow(candidate)) { uint64_t predecessor = predecessorEntry.getColumn(); if (!candidates.get(numGroups - predecessor - 1)) { candidates.set(numGroups - predecessor - 1, true); nextCandidate = std::max(nextCandidate, predecessor); ++numCandidates; } } } candidate = nextCandidate; } assert(candidates.empty()); STORM_LOG_THROW(processedGroups.full(), storm::exceptions::InvalidOperationException, "The MinMax equation system is not acyclic."); return true; } template void IterativeMinMaxLinearEquationSolver::computeOptimalValueForRowGroup(uint_fast64_t group, OptimizationDirection dir, std::vector& x, std::vector const& b, uint_fast64_t* choice) const { uint64_t row = this->A->getRowGroupIndices()[group]; uint64_t groupEnd = this->A->getRowGroupIndices()[group + 1]; assert(row != groupEnd); auto bIt = b.begin() + row; ValueType& xi = x[group]; xi = this->A->multiplyRowWithVector(row, x) + *bIt; uint64_t optimalRow = row; for (++row, ++bIt; row < groupEnd; ++row, ++bIt) { ValueType choiceVal = this->A->multiplyRowWithVector(row, x) + *bIt; if (minimize(dir)) { if (choiceVal < xi) { xi = choiceVal; optimalRow = row; } } else { if (choiceVal > xi) { xi = choiceVal; optimalRow = row; } } } if (choice != nullptr) { *choice = optimalRow - this->A->getRowGroupIndices()[group]; } } template typename IterativeMinMaxLinearEquationSolver::Status IterativeMinMaxLinearEquationSolver::updateStatusIfNotConverged(Status status, std::vector const& x, uint64_t iterations) const { if (status != Status::Converged) { if (this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(x)) { status = Status::TerminatedEarly; } else if (iterations >= this->getSettings().getMaximalNumberOfIterations()) { status = Status::MaximalIterationsExceeded; } } return status; } template void IterativeMinMaxLinearEquationSolver::reportStatus(Status status, uint64_t iterations) const { switch (status) { case Status::Converged: STORM_LOG_INFO("Iterative solver converged after " << iterations << " iterations."); break; case Status::TerminatedEarly: STORM_LOG_INFO("Iterative solver terminated early after " << iterations << " iterations."); break; case Status::MaximalIterationsExceeded: STORM_LOG_WARN("Iterative solver did not converge after " << iterations << " iterations."); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Iterative solver terminated unexpectedly."); } } template IterativeMinMaxLinearEquationSolverSettings const& IterativeMinMaxLinearEquationSolver::getSettings() const { return settings; } template void IterativeMinMaxLinearEquationSolver::setSettings(IterativeMinMaxLinearEquationSolverSettings const& newSettings) { settings = newSettings; } template void IterativeMinMaxLinearEquationSolver::clearCache() const { auxiliaryRowGroupVector.reset(); rowGroupOrdering.reset(); StandardMinMaxLinearEquationSolver::clearCache(); } template IterativeMinMaxLinearEquationSolverFactory::IterativeMinMaxLinearEquationSolverFactory(MinMaxMethodSelection const& method, bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(method, trackScheduler) { settings.setSolutionMethod(this->getMinMaxMethod()); } template IterativeMinMaxLinearEquationSolverFactory::IterativeMinMaxLinearEquationSolverFactory(std::unique_ptr>&& linearEquationSolverFactory, MinMaxMethodSelection const& method, bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(std::move(linearEquationSolverFactory), method, trackScheduler) { settings.setSolutionMethod(this->getMinMaxMethod()); } template IterativeMinMaxLinearEquationSolverFactory::IterativeMinMaxLinearEquationSolverFactory(EquationSolverType const& solverType, MinMaxMethodSelection const& method, bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(solverType, method, trackScheduler) { settings.setSolutionMethod(this->getMinMaxMethod()); } template std::unique_ptr> IterativeMinMaxLinearEquationSolverFactory::create() const { STORM_LOG_ASSERT(this->linearEquationSolverFactory, "Linear equation solver factory not initialized."); std::unique_ptr> result = std::make_unique>(this->linearEquationSolverFactory->clone(), settings); result->setTrackScheduler(this->isTrackSchedulerSet()); result->setRequirementsChecked(this->isRequirementsCheckedSet()); return result; } template IterativeMinMaxLinearEquationSolverSettings& IterativeMinMaxLinearEquationSolverFactory::getSettings() { return settings; } template IterativeMinMaxLinearEquationSolverSettings const& IterativeMinMaxLinearEquationSolverFactory::getSettings() const { return settings; } template void IterativeMinMaxLinearEquationSolverFactory::setMinMaxMethod(MinMaxMethodSelection const& newMethod) { MinMaxLinearEquationSolverFactory::setMinMaxMethod(newMethod); settings.setSolutionMethod(this->getMinMaxMethod()); } template void IterativeMinMaxLinearEquationSolverFactory::setMinMaxMethod(MinMaxMethod const& newMethod) { MinMaxLinearEquationSolverFactory::setMinMaxMethod(newMethod); settings.setSolutionMethod(this->getMinMaxMethod()); } template class IterativeMinMaxLinearEquationSolverSettings; template class IterativeMinMaxLinearEquationSolver; template class IterativeMinMaxLinearEquationSolverFactory; #ifdef STORM_HAVE_CARL template class IterativeMinMaxLinearEquationSolverSettings; template class IterativeMinMaxLinearEquationSolver; template class IterativeMinMaxLinearEquationSolverFactory; #endif } }