You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
380 lines
23 KiB
380 lines
23 KiB
#include "OptimisticValueIterationHelper.h"
|
|
|
|
#include "storm/utility/vector.h"
|
|
#include "storm/utility/SignalHandler.h"
|
|
#include "storm/environment/solver/OviSolverEnvironment.h"
|
|
#include "storm/utility/ProgressMeasurement.h"
|
|
|
|
#include "storm/exceptions/NotSupportedException.h"
|
|
|
|
#include "storm/utility/macros.h"
|
|
|
|
namespace storm {
|
|
|
|
namespace solver {
|
|
namespace helper {
|
|
namespace oviinternal {
|
|
|
|
template<typename ValueType>
|
|
ValueType updateIterationPrecision(storm::Environment const& env, ValueType const& diff) {
|
|
auto factor = storm::utility::convertNumber<ValueType>(env.solver().ovi().getPrecisionUpdateFactor());
|
|
return factor * diff;
|
|
}
|
|
|
|
template<typename ValueType>
|
|
void guessUpperBoundRelative(std::vector<ValueType> const& x, std::vector<ValueType> &target, ValueType const& relativeBoundGuessingScaler) {
|
|
storm::utility::vector::applyPointwise<ValueType, ValueType>(x, target, [&relativeBoundGuessingScaler] (ValueType const& argument) -> ValueType { return argument * relativeBoundGuessingScaler; });
|
|
}
|
|
|
|
template<typename ValueType>
|
|
void guessUpperBoundAbsolute(std::vector<ValueType> const& x, std::vector<ValueType> &target, ValueType const& precision) {
|
|
storm::utility::vector::applyPointwise<ValueType, ValueType>(x, target, [&precision] (ValueType const& argument) -> ValueType { return argument + precision; });
|
|
}
|
|
|
|
template <typename ValueType>
|
|
IterationHelper<ValueType>::IterationHelper(storm::storage::SparseMatrix<ValueType> const& matrix) {
|
|
STORM_LOG_THROW(static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) > matrix.getRowCount() + 1, storm::exceptions::NotSupportedException, "Matrix dimensions too large.");
|
|
STORM_LOG_THROW(static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) > matrix.getEntryCount(), storm::exceptions::NotSupportedException, "Matrix dimensions too large.");
|
|
matrixValues.reserve(matrix.getNonzeroEntryCount());
|
|
matrixColumns.reserve(matrix.getColumnCount());
|
|
rowIndications.reserve(matrix.getRowCount() + 1);
|
|
rowIndications.push_back(0);
|
|
for (IndexType r = 0; r < static_cast<IndexType>(matrix.getRowCount()); ++r) {
|
|
for (auto const& entry : matrix.getRow(r)) {
|
|
matrixValues.push_back(entry.getValue());
|
|
matrixColumns.push_back(entry.getColumn());
|
|
}
|
|
rowIndications.push_back(matrixValues.size());
|
|
}
|
|
if (!matrix.hasTrivialRowGrouping()) {
|
|
rowGroupIndices = &matrix.getRowGroupIndices();
|
|
}
|
|
}
|
|
|
|
template <typename ValueType>
|
|
ValueType IterationHelper<ValueType>::singleIterationWithDiff(std::vector<ValueType>& x, std::vector<ValueType> const& b, bool computeRelativeDiff) {
|
|
return singleIterationWithDiffInternal<false, storm::solver::OptimizationDirection::Minimize>(x, b, computeRelativeDiff);
|
|
}
|
|
|
|
template <typename ValueType>
|
|
ValueType IterationHelper<ValueType>::singleIterationWithDiff(storm::solver::OptimizationDirection const& dir, std::vector<ValueType>& x, std::vector<ValueType> const& b, bool computeRelativeDiff) {
|
|
if (minimize(dir)) {
|
|
return singleIterationWithDiffInternal<true, storm::solver::OptimizationDirection::Minimize>(x, b, computeRelativeDiff);
|
|
} else {
|
|
return singleIterationWithDiffInternal<true, storm::solver::OptimizationDirection::Maximize>(x, b, computeRelativeDiff);
|
|
}
|
|
}
|
|
|
|
template <typename ValueType>
|
|
template<bool HasRowGroups, storm::solver::OptimizationDirection Dir>
|
|
ValueType IterationHelper<ValueType>::singleIterationWithDiffInternal(std::vector<ValueType>& x, std::vector<ValueType> const& b, bool computeRelativeDiff) {
|
|
STORM_LOG_ASSERT(x.size() > 0, "Empty equation system not expected.");
|
|
ValueType diff = storm::utility::zero<ValueType>();
|
|
|
|
IndexType i = x.size();
|
|
while (i > 0) {
|
|
--i;
|
|
ValueType newXi = HasRowGroups ? multiplyRowGroup<Dir>(i, b, x) : multiplyRow(i, b[i], x);
|
|
ValueType& oldXi = x[i];
|
|
if (computeRelativeDiff) {
|
|
if (storm::utility::isZero(newXi)) {
|
|
if (storm::utility::isZero(oldXi)) {
|
|
// this operation has no effect:
|
|
// diff = std::max(diff, storm::utility::zero<ValueType>());
|
|
} else {
|
|
diff = std::max(diff, storm::utility::one<ValueType>());
|
|
}
|
|
} else {
|
|
diff = std::max(diff, storm::utility::abs<ValueType>((newXi - oldXi) / newXi));
|
|
}
|
|
} else {
|
|
diff = std::max(diff, storm::utility::abs<ValueType>(newXi - oldXi));
|
|
}
|
|
oldXi = std::move(newXi);
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
template <typename ValueType>
|
|
uint64_t IterationHelper<ValueType>::repeatedIterate(storm::solver::OptimizationDirection const& dir, std::vector<ValueType>& x, std::vector<ValueType> const& b, ValueType precision, bool relative) {
|
|
if (minimize(dir)) {
|
|
return repeatedIterateInternal<true, storm::solver::OptimizationDirection::Minimize>(x, b, precision, relative);
|
|
} else {
|
|
return repeatedIterateInternal<true, storm::solver::OptimizationDirection::Maximize>(x, b, precision, relative);
|
|
}
|
|
}
|
|
|
|
template <typename ValueType>
|
|
uint64_t IterationHelper<ValueType>::repeatedIterate(std::vector<ValueType>& x, const std::vector<ValueType>& b, ValueType precision, bool relative) {
|
|
return repeatedIterateInternal<false, storm::solver::OptimizationDirection::Minimize>(x, b, precision, relative);
|
|
}
|
|
|
|
template <typename ValueType>
|
|
template<bool HasRowGroups, storm::solver::OptimizationDirection Dir>
|
|
uint64_t IterationHelper<ValueType>::repeatedIterateInternal(std::vector<ValueType>& x, std::vector<ValueType> const& b, ValueType precision, bool relative) {
|
|
// Do a backwards gauss-seidel style iteration
|
|
bool convergence = true;
|
|
IndexType i = x.size();
|
|
while (i > 0) {
|
|
--i;
|
|
ValueType newXi = HasRowGroups ? multiplyRowGroup<Dir>(i, b, x) : multiplyRow(i, b[i], x);
|
|
ValueType& oldXi = x[i];
|
|
// Check if we converged
|
|
if (relative) {
|
|
if (storm::utility::isZero(oldXi)) {
|
|
if (!storm::utility::isZero(newXi)) {
|
|
convergence = false;
|
|
break;
|
|
}
|
|
} else if (storm::utility::abs<ValueType>((newXi - oldXi) / oldXi) > precision) {
|
|
convergence = false;
|
|
break;
|
|
}
|
|
} else {
|
|
if (storm::utility::abs<ValueType>((newXi - oldXi)) > precision) {
|
|
convergence = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!convergence) {
|
|
// we now know that we did not converge. We still need to set the remaining values
|
|
while (i > 0) {
|
|
--i;
|
|
x[i] = HasRowGroups ? multiplyRowGroup<Dir>(i, b, x) : multiplyRow(i, b[i], x);
|
|
}
|
|
}
|
|
return convergence;
|
|
}
|
|
|
|
template <typename ValueType>
|
|
typename IterationHelper<ValueType>::IterateResult IterationHelper<ValueType>::iterateUpper(storm::solver::OptimizationDirection const& dir, std::vector<ValueType>& x, std::vector<ValueType> const& b, bool takeMinOfOldAndNew) {
|
|
if (minimize(dir)) {
|
|
return iterateUpperInternal<true, storm::solver::OptimizationDirection::Minimize>(x, b, takeMinOfOldAndNew);
|
|
} else {
|
|
return iterateUpperInternal<true, storm::solver::OptimizationDirection::Maximize>(x, b, takeMinOfOldAndNew);
|
|
}
|
|
}
|
|
|
|
template <typename ValueType>
|
|
typename IterationHelper<ValueType>::IterateResult IterationHelper<ValueType>::iterateUpper(std::vector<ValueType>& x, std::vector<ValueType> const& b, bool takeMinOfOldAndNew) {
|
|
return iterateUpperInternal<false, storm::solver::OptimizationDirection::Minimize>(x, b, takeMinOfOldAndNew);
|
|
}
|
|
|
|
template <typename ValueType>
|
|
template<bool HasRowGroups, storm::solver::OptimizationDirection Dir>
|
|
typename IterationHelper<ValueType>::IterateResult IterationHelper<ValueType>::iterateUpperInternal(std::vector<ValueType>& x, std::vector<ValueType> const& b, bool takeMinOfOldAndNew) {
|
|
// For each row compare the new upper bound candidate with the old one
|
|
bool newUpperBoundAlwaysHigherEqual = true;
|
|
bool newUpperBoundAlwaysLowerEqual = true;
|
|
// Do a backwards gauss-seidel style iteration
|
|
for (IndexType i = x.size(); i > 0;) {
|
|
--i;
|
|
ValueType newXi = HasRowGroups ? multiplyRowGroup<Dir>(i, b, x) : multiplyRow(i, b[i], x);
|
|
ValueType& oldXi = x[i];
|
|
if (newXi > oldXi) {
|
|
newUpperBoundAlwaysLowerEqual = false;
|
|
if (!takeMinOfOldAndNew) {
|
|
oldXi = newXi;
|
|
}
|
|
} else if (newXi != oldXi) {
|
|
assert(newXi < oldXi);
|
|
newUpperBoundAlwaysHigherEqual = false;
|
|
oldXi = newXi;
|
|
}
|
|
}
|
|
// Return appropriate result
|
|
if (newUpperBoundAlwaysLowerEqual) {
|
|
if (newUpperBoundAlwaysHigherEqual) {
|
|
return IterateResult::Equal;
|
|
} else {
|
|
return IterateResult::AlwaysLowerOrEqual;
|
|
}
|
|
} else {
|
|
if (newUpperBoundAlwaysHigherEqual) {
|
|
return IterateResult::AlwaysHigherOrEqual;
|
|
} else {
|
|
return IterateResult::Incomparable;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename ValueType>
|
|
ValueType IterationHelper<ValueType>::multiplyRow(IndexType const& rowIndex, ValueType const& bi, std::vector<ValueType> const& x) {
|
|
assert(rowIndex < rowIndications.size());
|
|
ValueType xRes = bi;
|
|
|
|
auto entryIt = matrixValues.begin() + rowIndications[rowIndex];
|
|
auto entryItE = matrixValues.begin() + rowIndications[rowIndex + 1];
|
|
auto colIt = matrixColumns.begin() + rowIndications[rowIndex];
|
|
for (; entryIt != entryItE; ++entryIt, ++colIt) {
|
|
xRes += *entryIt * x[*colIt];
|
|
}
|
|
return xRes;
|
|
}
|
|
|
|
template <typename ValueType>
|
|
template<storm::solver::OptimizationDirection Dir>
|
|
ValueType IterationHelper<ValueType>::multiplyRowGroup(IndexType const& rowGroupIndex, std::vector<ValueType> const& b, std::vector<ValueType> const& x) {
|
|
STORM_LOG_ASSERT(rowGroupIndices != nullptr, "No row group indices available.");
|
|
auto row = (*rowGroupIndices)[rowGroupIndex];
|
|
auto const& groupEnd = (*rowGroupIndices)[rowGroupIndex + 1];
|
|
STORM_LOG_ASSERT(row < groupEnd, "Empty row group not expected.");
|
|
ValueType xRes = multiplyRow(row, b[row], x);
|
|
for (++row; row < groupEnd; ++row) {
|
|
ValueType xCur = multiplyRow(row, b[row], x);
|
|
xRes = minimize(Dir) ? std::min(xRes, xCur) : std::max(xRes, xCur);
|
|
}
|
|
return xRes;
|
|
}
|
|
}
|
|
|
|
template<typename ValueType>
|
|
OptimisticValueIterationHelper<ValueType>::OptimisticValueIterationHelper(storm::storage::SparseMatrix<ValueType> const& matrix) : iterationHelper(matrix) {
|
|
// Intentionally left empty.
|
|
}
|
|
|
|
template<typename ValueType>
|
|
std::pair<SolverStatus, uint64_t> OptimisticValueIterationHelper<ValueType>::solveEquations(Environment const& env, std::vector<ValueType>* lowerX, std::vector<ValueType>* upperX, std::vector<ValueType> const& b, bool relative, ValueType precision, uint64_t maxOverallIterations, boost::optional<storm::solver::OptimizationDirection> dir, boost::optional<storm::storage::BitVector> const& relevantValues) {
|
|
STORM_LOG_ASSERT(lowerX->size() == upperX->size(), "Dimension missmatch.");
|
|
|
|
// As we will shuffle pointers around, let's store the original positions here.
|
|
std::vector<ValueType>* initLowerX = lowerX;
|
|
std::vector<ValueType>* initUpperX = upperX;
|
|
|
|
uint64_t overallIterations = 0;
|
|
uint64_t lastValueIterationIterations = 0;
|
|
uint64_t currentVerificationIterations = 0;
|
|
|
|
// Get some parameters for the algorithm
|
|
// 2
|
|
ValueType two = storm::utility::convertNumber<ValueType>(2.0);
|
|
// Use no termination guaranteed upper bound iteration method
|
|
bool noTerminationGuarantee = env.solver().ovi().useNoTerminationGuaranteeMinimumMethod();
|
|
// Desired max difference between upperX and lowerX
|
|
ValueType doublePrecision = precision * two;
|
|
// Upper bound only iterations
|
|
uint64_t upperBoundOnlyIterations = env.solver().ovi().getUpperBoundOnlyIterations();
|
|
ValueType relativeBoundGuessingScaler = (storm::utility::one<ValueType>() + storm::utility::convertNumber<ValueType>(env.solver().ovi().getUpperBoundGuessingFactor()) * precision);
|
|
// Initial precision for the value iteration calls
|
|
ValueType iterationPrecision = precision;
|
|
|
|
SolverStatus status = SolverStatus::InProgress;
|
|
|
|
storm::utility::ProgressMeasurement progress("iterations.");
|
|
progress.startNewMeasurement(0);
|
|
while (status == SolverStatus::InProgress && overallIterations < maxOverallIterations) {
|
|
// Perform value iteration until convergence
|
|
lastValueIterationIterations = dir ? iterationHelper.repeatedIterate(dir.get(), *lowerX, b, iterationPrecision, relative) : iterationHelper.repeatedIterate(*lowerX, b, iterationPrecision, relative);
|
|
overallIterations += lastValueIterationIterations;
|
|
|
|
bool intervalIterationNeeded = false;
|
|
currentVerificationIterations = 0;
|
|
|
|
if (relative) {
|
|
oviinternal::guessUpperBoundRelative(*lowerX, *upperX, relativeBoundGuessingScaler);
|
|
} else {
|
|
oviinternal::guessUpperBoundAbsolute(*lowerX, *upperX, precision);
|
|
}
|
|
|
|
bool cancelGuess = false;
|
|
while (status == SolverStatus::InProgress && overallIterations < maxOverallIterations) {
|
|
if (storm::utility::resources::isTerminate()) {
|
|
status = SolverStatus::Aborted;
|
|
}
|
|
++overallIterations;
|
|
++currentVerificationIterations;
|
|
// Perform value iteration stepwise for lower bound and guessed upper bound
|
|
|
|
// Upper bound iteration
|
|
auto upperBoundIterResult = dir ? iterationHelper.iterateUpper(dir.get(), *upperX, b, !noTerminationGuarantee) : iterationHelper.iterateUpper(*upperX, b, !noTerminationGuarantee);
|
|
|
|
if (upperBoundIterResult == oviinternal::IterationHelper<ValueType>::IterateResult::AlwaysHigherOrEqual) {
|
|
// All values moved up (and did not stay the same)
|
|
// That means the guess for an upper bound is actually a lower bound
|
|
auto diff = dir ? iterationHelper.singleIterationWithDiff(dir.get(), *upperX, b, relative) : iterationHelper.singleIterationWithDiff(*upperX, b, relative);
|
|
iterationPrecision = oviinternal::updateIterationPrecision(env, diff);
|
|
// We assume to have a single fixed point. We can thus safely set the new lower bound, to the wrongly guessed upper bound
|
|
// Set lowerX to the upper bound candidate
|
|
std::swap(lowerX, upperX);
|
|
break;
|
|
} else if (upperBoundIterResult == oviinternal::IterationHelper<ValueType>::IterateResult::AlwaysLowerOrEqual) {
|
|
// All values moved down (and stayed not the same)
|
|
// This is a valid upper bound. We still need to check the precision.
|
|
// We can safely use twice the requested precision, as we calculate the center of both vectors
|
|
bool reachedPrecision;
|
|
if (relevantValues) {
|
|
reachedPrecision = storm::utility::vector::equalModuloPrecision(*lowerX, *upperX, relevantValues.get(), doublePrecision, relative);
|
|
} else {
|
|
reachedPrecision = storm::utility::vector::equalModuloPrecision(*lowerX, *upperX, doublePrecision, relative);
|
|
}
|
|
if (reachedPrecision) {
|
|
status = SolverStatus::Converged;
|
|
break;
|
|
} else {
|
|
// From now on, we keep updating both bounds
|
|
intervalIterationNeeded = true;
|
|
}
|
|
// The following case below covers that both vectors (old and new) are equal.
|
|
// Theoretically, this means that the precise fixpoint has been reached. However, numerical instabilities can be tricky and this detection might be incorrect (see the haddad-monmege model).
|
|
// We therefore disable it. It is very unlikely that we guessed the right fixpoint anyway.
|
|
//} else if (upperBoundIterResult == oviinternal::IterationHelper<ValueType>::IterateResult::Equal) {
|
|
// In this case, the guessed upper bound is the precise fixpoint
|
|
// status = SolverStatus::Converged;
|
|
// break;
|
|
}
|
|
|
|
// Check whether we tried this guess for too long
|
|
ValueType scaledIterationCount = storm::utility::convertNumber<ValueType>(currentVerificationIterations) * storm::utility::convertNumber<ValueType>(env.solver().ovi().getMaxVerificationIterationFactor());
|
|
if (!intervalIterationNeeded && scaledIterationCount * iterationPrecision >= storm::utility::one<ValueType>()) {
|
|
cancelGuess = true;
|
|
// In this case we will make one more iteration on the lower bound (mainly to obtain a new iterationPrecision)
|
|
}
|
|
|
|
// Lower bound iteration (only if needed)
|
|
if (cancelGuess || intervalIterationNeeded || currentVerificationIterations > upperBoundOnlyIterations) {
|
|
auto diff = dir ? iterationHelper.singleIterationWithDiff(dir.get(), *lowerX, b, relative) : iterationHelper.singleIterationWithDiff(*lowerX, b, relative);
|
|
|
|
// Check whether the upper and lower bounds have crossed, i.e., the upper bound is smaller than the lower bound.
|
|
bool valuesCrossed = false;
|
|
for (uint64_t i = 0; i < lowerX->size(); ++i) {
|
|
if ((*upperX)[i] < (*lowerX)[i]) {
|
|
valuesCrossed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cancelGuess || valuesCrossed) {
|
|
// A new guess is needed.
|
|
iterationPrecision = oviinternal::updateIterationPrecision(env, diff);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (storm::utility::resources::isTerminate()) {
|
|
status = SolverStatus::Aborted;
|
|
}
|
|
progress.updateProgress(overallIterations);
|
|
} // end while
|
|
// Swap the results into the output vectors (if necessary).
|
|
assert(initLowerX != lowerX || (initLowerX == lowerX && initUpperX == upperX));
|
|
if (initLowerX != lowerX) {
|
|
assert(initUpperX == lowerX);
|
|
assert(initLowerX == upperX);
|
|
lowerX->swap(*upperX);
|
|
}
|
|
|
|
if (overallIterations > maxOverallIterations) {
|
|
status = SolverStatus::MaximalIterationsExceeded;
|
|
}
|
|
|
|
return {status, overallIterations};
|
|
}
|
|
|
|
|
|
template class OptimisticValueIterationHelper<double>;
|
|
template class OptimisticValueIterationHelper<storm::RationalNumber>;
|
|
}
|
|
}
|
|
}
|
|
|