Browse Source

improved code for sound power iteration

main
TimQu 7 years ago
parent
commit
e76a77abc9
  1. 356
      src/storm/solver/NativeLinearEquationSolver.cpp

356
src/storm/solver/NativeLinearEquationSolver.cpp

@ -557,187 +557,239 @@ namespace storm {
} }
template<typename ValueType> template<typename ValueType>
bool NativeLinearEquationSolver<ValueType>::solveEquationsSoundPower(Environment const& env, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { class SoundPowerHelper {
STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (SoundPower)"); public:
bool useGaussSeidelMultiplication = env.solver().native().getPowerMethodMultiplicationStyle() == storm::solver::MultiplicationStyle::GaussSeidel; SoundPowerHelper(std::vector<ValueType>& x, std::vector<ValueType>& y, bool relative, ValueType const& precision) : x(x), y(y), hasLowerBound(false), hasUpperBound(false), minIndex(0), maxIndex(0), relative(relative), precision(precision) {
x.assign(x.size(), storm::utility::zero<ValueType>());
// Prepare the solution vectors. y.assign(x.size(), storm::utility::one<ValueType>());
assert(x.size() == getMatrixRowCount()); convergencePhase1 = true;
std::vector<ValueType> *stepBoundedX, *stepBoundedStayProbs, *tmp; firstIndexViolatingConvergence = 0;
if (useGaussSeidelMultiplication) {
stepBoundedX = &x;
stepBoundedX->assign(getMatrixRowCount(), storm::utility::zero<ValueType>());
if (!this->cachedRowVector) {
this->cachedRowVector = std::make_unique<std::vector<ValueType>>(getMatrixRowCount(), storm::utility::one<ValueType>());
} else {
this->cachedRowVector->assign(getMatrixRowCount(), storm::utility::one<ValueType>());
}
stepBoundedStayProbs = this->cachedRowVector.get();
tmp = nullptr;
} else {
if (!this->cachedRowVector) {
this->cachedRowVector = std::make_unique<std::vector<ValueType>>(getMatrixRowCount(), storm::utility::zero<ValueType>());
} else {
this->cachedRowVector->assign(getMatrixRowCount(), storm::utility::zero<ValueType>());
}
stepBoundedX = this->cachedRowVector.get();
if (!this->cachedRowVector2) {
this->cachedRowVector2 = std::make_unique<std::vector<ValueType>>(getMatrixRowCount(), storm::utility::one<ValueType>());
} else {
this->cachedRowVector2->assign(getMatrixRowCount(), storm::utility::one<ValueType>());
}
stepBoundedStayProbs = this->cachedRowVector2.get();
tmp = &x;
} }
inline void setLowerBound(ValueType const& value) {
ValueType precision = storm::utility::convertNumber<ValueType>(env.solver().native().getPrecision()); hasLowerBound = true;
bool relative = env.solver().native().getRelativeTerminationCriterion(); lowerBound = value;
if (!relative) {
precision *= storm::utility::convertNumber<ValueType>(2.0);
} }
uint64_t maxIter = env.solver().native().getMaximalNumberOfIterations(); inline void setUpperBound(ValueType const& value) {
hasUpperBound = true;
uint64_t iterations = 0; upperBound = value;
bool converged = false;
bool terminate = false;
uint64_t minIndex(0), maxIndex(0);
ValueType minValueBound, maxValueBound;
bool hasMinValueBound(false), hasMaxValueBound(false);
// Prepare initial bounds for the solution (if given)
if (this->hasLowerBound()) {
minValueBound = this->getLowerBound(true);
hasMinValueBound = true;
} }
if (this->hasUpperBound()) { void multiplyRow(uint64_t const& row, storm::storage::SparseMatrix<ValueType> const& A, ValueType const& bi, ValueType& xi, ValueType& yi) {
maxValueBound = this->getUpperBound(true); xi = bi;
hasMaxValueBound = true; yi = storm::utility::zero<ValueType>();
for (auto const& entry : A.getRow(row)) {
xi += entry.getValue() * x[entry.getColumn()];
yi += entry.getValue() * y[entry.getColumn()];
}
} }
bool convergencePhase1 = true; void performIterationStep(storm::storage::SparseMatrix<ValueType> const& A, std::vector<ValueType> const& b) {
uint64_t firstIndexViolatingConvergence = 0; auto xIt = x.rbegin();
this->startMeasureProgress(); auto yIt = y.rbegin();
while (!converged && !terminate && iterations < maxIter) { uint64_t row = A.getRowCount();
while (row > 0) {
// Apply step --row;
if (useGaussSeidelMultiplication) { multiplyRow(row, A, b[row], *xIt, *yIt);
this->multiplier.multAddGaussSeidelBackward(*this->A, *stepBoundedX, &b); ++xIt;
this->multiplier.multAddGaussSeidelBackward(*this->A, *stepBoundedStayProbs, nullptr); ++yIt;
} else {
this->multiplier.multAdd(*this->A, *stepBoundedX, &b, *tmp);
std::swap(tmp, stepBoundedX);
this->multiplier.multAdd(*this->A, *stepBoundedStayProbs, nullptr, *tmp);
std::swap(tmp, stepBoundedStayProbs);
} }
}
bool checkConvergenceUpdateBounds(storm::storage::BitVector const* relevantValues = nullptr) {
// Check for convergence
if (convergencePhase1) { if (convergencePhase1) {
// Phase 1: the probability to 'stay within the matrix' has to be < 1 at every state if (checkConvergencePhase1()) {
for (; firstIndexViolatingConvergence != stepBoundedStayProbs->size(); ++firstIndexViolatingConvergence) { firstIndexViolatingConvergence = 0;
static_assert(NumberTraits<ValueType>::IsExact || std::is_same<ValueType, double>::value, "Considered ValueType not handled."); if (relevantValues != nullptr) {
if (NumberTraits<ValueType>::IsExact) { firstIndexViolatingConvergence = relevantValues->getNextSetIndex(firstIndexViolatingConvergence);
if (storm::utility::isOne(stepBoundedStayProbs->at(firstIndexViolatingConvergence))) {
break;
}
} else {
if (storm::utility::isAlmostOne(storm::utility::convertNumber<double>(stepBoundedStayProbs->at(firstIndexViolatingConvergence)))) {
break;
}
} }
} } else {
if (firstIndexViolatingConvergence == stepBoundedStayProbs->size()) { return false;
STORM_LOG_ASSERT(!std::any_of(stepBoundedStayProbs->begin(), stepBoundedStayProbs->end(), [](ValueType value){return storm::utility::isOne(value);}), "Did not expect staying-probability 1 at this point.");
convergencePhase1 = false;
firstIndexViolatingConvergence = this->hasRelevantValues() ? this->getRelevantValues().getNextSetIndex(0) : 0;
} }
} }
if (!convergencePhase1) { STORM_LOG_ASSERT(!std::any_of(y.begin(), y.end(), [](ValueType value){return storm::utility::isOne(value);}), "Did not expect staying-probability 1 at this point.");
// Phase 2: the difference between lower and upper bound has to be < precision at every (relevant) value // Reaching this point means that we are in Phase 2:
// First check with (possibly too tight) bounds from a previous iteration. Only compute the actual bounds if this first check passes. // The difference between lower and upper bound has to be < precision at every (relevant) value
ValueType minValueBoundCandidate = stepBoundedX->at(minIndex) / (storm::utility::one<ValueType>() - stepBoundedStayProbs->at(minIndex)); // For efficiency reasons we first check whether it is worth to compute the actual bounds. We do so by considering possibly too tight bounds
ValueType maxValueBoundCandidate = stepBoundedX->at(maxIndex) / (storm::utility::one<ValueType>() - stepBoundedStayProbs->at(maxIndex)); ValueType lowerBoundCandidate, upperBoundCandidate;
if (hasMinValueBound && minValueBound > minValueBoundCandidate) { if (preliminaryConvergenceCheck(lowerBoundCandidate, upperBoundCandidate)) {
minValueBoundCandidate = minValueBound; updateLowerUpperBound(lowerBoundCandidate, upperBoundCandidate);
} return checkConvergencePhase2(relevantValues);
if (hasMaxValueBound && maxValueBound < maxValueBoundCandidate) { }
maxValueBoundCandidate = maxValueBound; return false;
} }
ValueType const& stayProb = stepBoundedStayProbs->at(firstIndexViolatingConvergence); void setSolutionVector() {
// The error made in this iteration STORM_LOG_WARN_COND(hasLowerBound && hasUpperBound, "No lower or upper result bound could be computed within the given number of Iterations.");
ValueType absoluteError = stayProb * (maxValueBoundCandidate - minValueBoundCandidate); ValueType meanBound = (upperBound + lowerBound) / storm::utility::convertNumber<ValueType>(2.0);
// The maximal allowed error (possibly respecting relative precision) storm::utility::vector::applyPointwise(x, y, x, [&meanBound] (ValueType const& xi, ValueType const& yi) { return xi + yi * meanBound; });
// Note: We implement the relative convergence criterion in a way that avoids division by zero in the case where stepBoundedX[i] is zero. STORM_LOG_INFO("Sound Power Iteration terminated with lower value bound "
ValueType maxAllowedError = relative ? (precision * stepBoundedX->at(firstIndexViolatingConvergence)) : precision; << (hasLowerBound ? lowerBound : storm::utility::zero<ValueType>()) << (hasLowerBound ? "" : "(none)")
if (absoluteError <= maxAllowedError) { << " and upper value bound "
// Compute the actual bounds now << (hasUpperBound ? upperBound : storm::utility::zero<ValueType>()) << (hasUpperBound ? "" : "(none)")
auto valIt = stepBoundedX->begin(); << ".");
auto valIte = stepBoundedX->end(); }
auto probIt = stepBoundedStayProbs->begin(); private:
for (uint64_t index = 0; valIt != valIte; ++valIt, ++probIt, ++index) { bool checkConvergencePhase1() {
ValueType currentBound = *valIt / (storm::utility::one<ValueType>() - *probIt); // Return true if y ('the probability to stay within the 'maybestates') is < 1 at every entry
if (currentBound < minValueBoundCandidate) { for (; firstIndexViolatingConvergence != y.size(); ++firstIndexViolatingConvergence) {
minIndex = index; static_assert(NumberTraits<ValueType>::IsExact || std::is_same<ValueType, double>::value, "Considered ValueType not handled.");
minValueBoundCandidate = std::move(currentBound); if (NumberTraits<ValueType>::IsExact) {
} else if (currentBound > maxValueBoundCandidate) { if (storm::utility::isOne(y[firstIndexViolatingConvergence])) {
maxIndex = index; return false;
maxValueBoundCandidate = std::move(currentBound);
}
} }
if (!hasMinValueBound || minValueBoundCandidate > minValueBound) { } else {
minValueBound = minValueBoundCandidate; if (storm::utility::isAlmostOne(storm::utility::convertNumber<double>(y[firstIndexViolatingConvergence]))) {
hasMinValueBound = true; return false;
} }
if (!hasMaxValueBound || maxValueBoundCandidate < maxValueBound) { }
maxValueBound = maxValueBoundCandidate; }
hasMaxValueBound = true; convergencePhase1 = false;
return true;
}
bool isPreciseEnough(ValueType const& xi, ValueType const& yi, ValueType const& lb, ValueType const& ub) {
return yi * (ub - lb) <= storm::utility::abs<ValueType>((relative ? (precision * xi) : (precision * storm::utility::convertNumber<ValueType>(2.0))));
}
bool preliminaryConvergenceCheck(ValueType& lowerBoundCandidate, ValueType& upperBoundCandidate) {
lowerBoundCandidate = x[minIndex] / (storm::utility::one<ValueType>() - y[minIndex]);
upperBoundCandidate = x[maxIndex] / (storm::utility::one<ValueType>() - y[maxIndex]);
// Make sure that these candidates are at least as tight as the already known bounds
if (hasLowerBound && lowerBoundCandidate < lowerBound) {
lowerBoundCandidate = lowerBound;
}
if (hasUpperBound && upperBoundCandidate > upperBound) {
upperBoundCandidate = upperBound;
}
if (isPreciseEnough(x[firstIndexViolatingConvergence], y[firstIndexViolatingConvergence], lowerBoundCandidate, upperBoundCandidate)) {
return true;
}
return false;
}
void updateLowerUpperBound(ValueType& lowerBoundCandidate, ValueType& upperBoundCandidate) {
auto xIt = x.begin();
auto xIte = x.end();
auto yIt = y.begin();
for (uint64_t index = 0; xIt != xIte; ++xIt, ++yIt, ++index) {
ValueType currentBound = *xIt / (storm::utility::one<ValueType>() - *yIt);
if (currentBound < lowerBoundCandidate) {
minIndex = index;
lowerBoundCandidate = std::move(currentBound);
} else if (currentBound > upperBoundCandidate) {
maxIndex = index;
upperBoundCandidate = std::move(currentBound);
}
}
if (!hasLowerBound || lowerBoundCandidate > lowerBound) {
setLowerBound(lowerBoundCandidate);
}
if (!hasUpperBound || upperBoundCandidate < upperBound) {
setUpperBound(upperBoundCandidate);
}
}
bool checkConvergencePhase2(storm::storage::BitVector const* relevantValues = nullptr) {
// Check whether the desired precision is reached
if (isPreciseEnough(x[firstIndexViolatingConvergence], y[firstIndexViolatingConvergence], lowerBound, upperBound)) {
// The current index satisfies the desired bound. We now move to the next index that violates it
while (true) {
++firstIndexViolatingConvergence;
if (relevantValues != nullptr) {
firstIndexViolatingConvergence = relevantValues->getNextSetIndex(firstIndexViolatingConvergence);
} }
absoluteError = stayProb * (maxValueBound - minValueBound); if (firstIndexViolatingConvergence == x.size()) {
if (absoluteError <= maxAllowedError) { // Converged!
// The current index satisfies the desired bound. We now move to the next index that violates it return true;
while (true) { } else {
++firstIndexViolatingConvergence; if (!isPreciseEnough(x[firstIndexViolatingConvergence], y[firstIndexViolatingConvergence], lowerBound, upperBound)) {
if (this->hasRelevantValues()) { // not converged yet
firstIndexViolatingConvergence = this->getRelevantValues().getNextSetIndex(firstIndexViolatingConvergence); return false;
}
if (firstIndexViolatingConvergence == stepBoundedStayProbs->size()) {
converged = true;
break;
} else {
absoluteError = stepBoundedStayProbs->at(firstIndexViolatingConvergence) * (maxValueBound - minValueBound);
maxAllowedError = relative ? (precision * stepBoundedX->at(firstIndexViolatingConvergence)) : precision;
if (absoluteError > maxAllowedError) {
// not converged yet
break;
}
}
} }
} }
} }
} }
return false;
}
std::vector<ValueType>& x;
std::vector<ValueType>& y;
ValueType lowerBound, upperBound, decisionValue;
bool hasLowerBound, hasUpperBound, hasDecisionValue;
uint64_t minIndex, maxIndex;
bool convergencePhase1;
uint64_t firstIndexViolatingConvergence;
bool relative;
ValueType precision;
};
template<typename ValueType>
bool NativeLinearEquationSolver<ValueType>::solveEquationsSoundPower(Environment const& env, std::vector<ValueType>& x, std::vector<ValueType> const& b) const {
// Prepare the solution vectors.
assert(x.size() == this->A->getRowCount());
if (!this->cachedRowVector) {
this->cachedRowVector = std::make_unique<std::vector<ValueType>>();
}
SoundPowerHelper<ValueType> helper(x, *this->cachedRowVector, env.solver().native().getRelativeTerminationCriterion(), storm::utility::convertNumber<ValueType>(env.solver().native().getPrecision()));
// Prepare initial bounds for the solution (if given)
if (this->hasLowerBound()) {
helper.setLowerBound(this->getLowerBound(true));
}
if (this->hasUpperBound()) {
helper.setUpperBound(this->getUpperBound(true));
}
storm::storage::BitVector const* relevantValuesPtr = nullptr;
if (this->hasRelevantValues()) {
relevantValuesPtr = &this->getRelevantValues();
}
bool converged = false;
bool terminate = false;
this->startMeasureProgress();
uint64_t iterations = 0;
while (!converged && iterations < env.solver().native().getMaximalNumberOfIterations()) {
helper.performIterationStep(*this->A, b);
if (helper.checkConvergenceUpdateBounds(relevantValuesPtr)) {
converged = true;
}
// todo: custom termination check
// terminate = ....
// Update environment variables.
++iterations;
// Potentially show progress. // Potentially show progress.
this->showProgressIterative(iterations); this->showProgressIterative(iterations);
// Set up next iteration.
++iterations;
} }
helper.setSolutionVector();
this->logIterations(converged, terminate, iterations);
// Finally set up the solution vector this->overallPerformedIterations += iterations;
ValueType meanBound = (maxValueBound + minValueBound) / storm::utility::convertNumber<ValueType>(2.0);
storm::utility::vector::applyPointwise(*stepBoundedX, *stepBoundedStayProbs, x, [&meanBound] (ValueType const& v, ValueType const& p) { return v + p * meanBound; });
if (!this->isCachingEnabled()) { if (!this->isCachingEnabled()) {
clearCache(); clearCache();
} }
this->overallPerformedIterations += iterations;
this->logIterations(converged, terminate, iterations);
STORM_LOG_WARN_COND(hasMinValueBound && hasMaxValueBound, "Could not compute lower or upper bound within the given number of iterations.");
STORM_LOG_INFO("Quick Power Iteration terminated with lower value bound " << minValueBound << " and upper value bound " << maxValueBound << ".");
return converged; return converged;
} }
template<typename ValueType> template<typename ValueType>

|||||||
100:0
Loading…
Cancel
Save