diff --git a/src/storm/exceptions/PrecisionExceededException.h b/src/storm/exceptions/PrecisionExceededException.h new file mode 100644 index 000000000..e520d73bc --- /dev/null +++ b/src/storm/exceptions/PrecisionExceededException.h @@ -0,0 +1,13 @@ +#pragma once + +#include "storm/exceptions/BaseException.h" +#include "storm/exceptions/ExceptionMacros.h" + +namespace storm { + namespace exceptions { + + STORM_NEW_EXCEPTION(PrecisionExceededException) + + } // namespace exceptions +} // namespace storm + diff --git a/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp b/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp index f181cb1d0..45b7479b8 100644 --- a/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp +++ b/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp @@ -6,6 +6,8 @@ #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/MinMaxEquationSolverSettings.h" +#include "storm/utility/KwekMehlhorn.h" + #include "storm/utility/vector.h" #include "storm/utility/macros.h" #include "storm/exceptions/InvalidSettingsException.h" @@ -632,7 +634,10 @@ namespace storm { // If the value does not match the one in the values vector, the given vector is not a solution. if (!comparator.isEqual(groupValue, *valueIt)) { + std::cout << "group value for " << group << " is " << groupValue << " is not " << *valueIt << ", diff is " << (groupValue - *valueIt) << std::endl; return false; + } else { + std::cout << "group value for " << group << " is " << groupValue << " is actually " << *valueIt << std::endl; } } @@ -641,63 +646,13 @@ namespace storm { } template<typename ValueType> - std::pair<ValueType, ValueType> findRational(ValueType const& alpha, ValueType const& beta, ValueType const& gamma, ValueType const& delta) { - ValueType alphaDivBeta = alpha / beta; - ValueType alphaDivBetaFloor = storm::utility::floor(alphaDivBeta); - ValueType gammaDivDeltaFloor = storm::utility::floor<ValueType>(gamma / delta); - - if (alphaDivBetaFloor == gammaDivDeltaFloor && !storm::utility::isInteger(alphaDivBeta)) { - std::pair<ValueType, ValueType> subresult = findRational(delta, storm::utility::mod(gamma, delta), beta, storm::utility::mod(alpha, beta)); - auto result = std::make_pair(alphaDivBetaFloor * subresult.first + subresult.second, subresult.first); - - return result; - } else { - auto result = std::make_pair(storm::utility::ceil(alphaDivBeta), storm::utility::one<ValueType>()); - return result; - } - } - - template<typename PreciseValueType, typename ImpreciseValueType> - PreciseValueType truncateToRational(ImpreciseValueType const& value, uint64_t precision) { - STORM_LOG_ASSERT(precision < 16, "Truncating via double is not sound."); - PreciseValueType powerOfTen = storm::utility::pow(storm::utility::convertNumber<PreciseValueType>(10), precision); - PreciseValueType truncated = storm::utility::trunc<PreciseValueType>(value * powerOfTen); - return truncated / powerOfTen; - } - - template<typename PreciseValueType> - PreciseValueType truncateToRational(double const& value, uint64_t precision) { - STORM_LOG_ASSERT(precision < 16, "Truncating via double is not sound."); - auto powerOfTen = std::pow(10, precision); - double truncated = std::trunc(value * powerOfTen); - return storm::utility::convertNumber<PreciseValueType>(truncated) / storm::utility::convertNumber<PreciseValueType>(powerOfTen); - } - - template<typename PreciseValueType, typename ImpreciseValueType> - PreciseValueType findRational(uint64_t precision, ImpreciseValueType const& value) { - PreciseValueType truncatedFraction = truncateToRational<PreciseValueType>(value, precision); - - PreciseValueType ten = storm::utility::convertNumber<PreciseValueType>(10); - PreciseValueType powerOfTen = storm::utility::pow<PreciseValueType>(ten, precision); - PreciseValueType multiplier = powerOfTen / storm::utility::denominator(truncatedFraction); - PreciseValueType numerator = storm::utility::numerator(truncatedFraction) * multiplier; + template<typename RationalType, typename ImpreciseType> + bool IterativeMinMaxLinearEquationSolver<ValueType>::sharpen(storm::OptimizationDirection dir, uint64_t precision, storm::storage::SparseMatrix<RationalType> const& A, std::vector<ImpreciseType> const& x, std::vector<RationalType> const& b, std::vector<RationalType>& tmp) { - std::pair<PreciseValueType, PreciseValueType> result = findRational<PreciseValueType>(numerator, powerOfTen, numerator + storm::utility::one<PreciseValueType>(), powerOfTen); - return result.first / result.second; - } - - template<typename ValueType> - template<typename PreciseValueType, typename ImpreciseValueType> - bool IterativeMinMaxLinearEquationSolver<ValueType>::sharpen(storm::OptimizationDirection dir, uint64_t precision, storm::storage::SparseMatrix<PreciseValueType> const& A, std::vector<ImpreciseValueType> const& x, std::vector<PreciseValueType> const& b, std::vector<PreciseValueType>& tmp) { for (uint64_t p = 1; p < precision; ++p) { - for (uint64_t state = 0; state < x.size(); ++state) { - PreciseValueType rationalX = storm::utility::convertNumber<PreciseValueType, ImpreciseValueType>(x[state]); - PreciseValueType integer = storm::utility::floor(rationalX); - PreciseValueType fraction = rationalX - integer; - - tmp[state] = integer + findRational<PreciseValueType>(p, storm::utility::convertNumber<ImpreciseValueType, PreciseValueType>(fraction)); - } - if (IterativeMinMaxLinearEquationSolver<PreciseValueType>::isSolution(dir, A, tmp, b)) { + storm::utility::kwek_mehlhorn::sharpen(precision, x, tmp); + + if (IterativeMinMaxLinearEquationSolver<RationalType>::isSolution(dir, A, tmp, b)) { return true; } } @@ -710,8 +665,8 @@ namespace storm { } template<typename ValueType> - template<typename ImpreciseValueType> - typename std::enable_if<std::is_same<ValueType, ImpreciseValueType>::value && !NumberTraits<ValueType>::IsExact, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { + template<typename ImpreciseType> + typename std::enable_if<std::is_same<ValueType, ImpreciseType>::value && !NumberTraits<ValueType>::IsExact, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { // Create a rational representation of the input so we can check for a proper solution later. storm::storage::SparseMatrix<storm::RationalNumber> rationalA = this->A->template toValueType<storm::RationalNumber>(); @@ -727,52 +682,25 @@ namespace storm { auxiliaryRowGroupVector = std::make_unique<std::vector<ValueType>>(this->A->getRowGroupCount()); } - std::vector<ValueType>* newX = auxiliaryRowGroupVector.get(); - std::vector<ValueType>* currentX = &x; - - Status status = Status::InProgress; - uint64_t overallIterations = 0; - ValueType precision = this->getSettings().getPrecision(); - this->startMeasureProgress(); - while (status == Status::InProgress && overallIterations < this->getSettings().getMaximalNumberOfIterations()) { - // Perform value iteration with the current precision. - ValueIterationResult result = performValueIteration(dir, currentX, newX, b, precision, this->getSettings().getRelativeTerminationCriterion(), SolverGuarantee::LessOrEqual, overallIterations); - - // Count the iterations. - overallIterations += result.iterations; - - // Try to sharpen the result. - uint64_t p = storm::utility::convertNumber<uint64_t>(storm::utility::ceil(storm::utility::log10<storm::RationalNumber>(storm::utility::one<storm::RationalNumber>() / storm::utility::convertNumber<storm::RationalNumber>(precision)))); - bool foundSolution = sharpen(dir, p, rationalA, *currentX, rationalB, rationalX); - - if (foundSolution) { - status = Status::Converged; - - auto rationalIt = rationalX.begin(); - for (auto it = x.begin(), ite = x.end(); it != ite; ++it, ++rationalIt) { - *it = storm::utility::convertNumber<ValueType>(*rationalIt); - } - } else { - precision = precision / 10; - } - } + // Forward the call to the core rational search routine. + bool converged = solveEquationsRationalSearchHelper<storm::RationalNumber, ImpreciseType>(dir, *this, rationalA, rationalX, rationalB, *this->A, x, b, *auxiliaryRowGroupVector); - if (status == Status::InProgress && overallIterations == this->getSettings().getMaximalNumberOfIterations()) { - status = Status::MaximalIterationsExceeded; + // Translate back rational result to imprecise result. + auto targetIt = x.begin(); + for (auto it = rationalX.begin(), ite = rationalX.end(); it != ite; ++it, ++targetIt) { + *targetIt = storm::utility::convertNumber<ValueType>(*it); } - - reportStatus(status, overallIterations); - + if (!this->isCachingEnabled()) { - clearCache(); + this->clearCache(); } - return status == Status::Converged || status == Status::TerminatedEarly; + return converged; } template<typename ValueType> - template<typename ImpreciseValueType> - typename std::enable_if<std::is_same<ValueType, ImpreciseValueType>::value && NumberTraits<ValueType>::IsExact, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { + template<typename ImpreciseType> + typename std::enable_if<std::is_same<ValueType, ImpreciseType>::value && NumberTraits<ValueType>::IsExact, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { if (!this->linEqSolverA) { this->linEqSolverA = this->linearEquationSolverFactory->create(*this->A); @@ -783,95 +711,168 @@ namespace storm { auxiliaryRowGroupVector = std::make_unique<std::vector<ValueType>>(this->A->getRowGroupCount()); } - std::vector<ValueType>* newX = auxiliaryRowGroupVector.get(); - std::vector<ValueType>* currentX = &x; - - Status status = Status::InProgress; - uint64_t overallIterations = 0; - ValueType precision = this->getSettings().getPrecision(); - this->startMeasureProgress(); - while (status == Status::InProgress && overallIterations < this->getSettings().getMaximalNumberOfIterations()) { - // Perform value iteration with the current precision. - ValueIterationResult result = performValueIteration(dir, currentX, newX, b, precision, this->getSettings().getRelativeTerminationCriterion(), SolverGuarantee::LessOrEqual, overallIterations); - - // Count the iterations. - overallIterations += result.iterations; - - // Try to sharpen the result. - uint64_t p = storm::utility::convertNumber<uint64_t>(storm::utility::ceil(storm::utility::log10<ValueType>(storm::utility::one<ValueType>() / precision))); - bool foundSolution = sharpen(dir, p, *this->A, *currentX, b, *newX); - - if (foundSolution) { - status = Status::Converged; - - // Swap the result in place. - if (&x == currentX) { - std::swap(*currentX, *newX); - } - } else { - precision = precision / 10; - } - } + // Forward the call to the core rational search routine. + bool converged = solveEquationsRationalSearchHelper<ValueType, ImpreciseType>(dir, *this, *this->A, x, b, *this->A, *auxiliaryRowGroupVector, b, x); - if (status == Status::InProgress && overallIterations == this->getSettings().getMaximalNumberOfIterations()) { - status = Status::MaximalIterationsExceeded; - } - - reportStatus(status, overallIterations); - if (!this->isCachingEnabled()) { - clearCache(); + this->clearCache(); } - return status == Status::Converged || status == Status::TerminatedEarly; + return converged; } template<typename ValueType> - template<typename ImpreciseValueType> - typename std::enable_if<!std::is_same<ValueType, ImpreciseValueType>::value, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { - // Create an imprecise solver with caching enabled. - IterativeMinMaxLinearEquationSolver<ImpreciseValueType> impreciseSolver(std::make_unique<storm::solver::GeneralLinearEquationSolverFactory<ImpreciseValueType>>()); - impreciseSolver.setMatrix(this->A->template toValueType<ImpreciseValueType>()); - impreciseSolver.createLinearEquationSolver(); - impreciseSolver.setCachingEnabled(true); + template<typename ImpreciseType> + typename std::enable_if<!std::is_same<ValueType, ImpreciseType>::value, bool>::type IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const { + // Translate A to its imprecise version. + storm::storage::SparseMatrix<ImpreciseType> impreciseA = this->A->template toValueType<ImpreciseType>(); - std::vector<ImpreciseValueType> impreciseX(x.size()); + // Translate x to its imprecise version. + std::vector<ImpreciseType> impreciseX(x.size()); { std::vector<ValueType> tmp(x.size()); this->createLowerBoundsVector(tmp); auto targetIt = impreciseX.begin(); for (auto sourceIt = tmp.begin(); targetIt != impreciseX.end(); ++targetIt, ++sourceIt) { - *targetIt = storm::utility::convertNumber<ImpreciseValueType, ValueType>(*sourceIt); + *targetIt = storm::utility::convertNumber<ImpreciseType, ValueType>(*sourceIt); } } - std::vector<ImpreciseValueType> impreciseTmpX(x.size()); - std::vector<ImpreciseValueType> impreciseB(b.size()); + + // Create temporary storage for an imprecise x. + std::vector<ImpreciseType> impreciseTmpX(x.size()); + + // Translate b to its imprecise version. + std::vector<ImpreciseType> impreciseB(b.size()); auto targetIt = impreciseB.begin(); for (auto sourceIt = b.begin(); targetIt != impreciseB.end(); ++targetIt, ++sourceIt) { - *targetIt = storm::utility::convertNumber<ImpreciseValueType, ValueType>(*sourceIt); + *targetIt = storm::utility::convertNumber<ImpreciseType, ValueType>(*sourceIt); } - std::vector<ImpreciseValueType>* currentX = &impreciseX; - std::vector<ImpreciseValueType>* newX = &impreciseTmpX; + // Create imprecise solver from the imprecise data. + IterativeMinMaxLinearEquationSolver<ImpreciseType> impreciseSolver(std::make_unique<storm::solver::GeneralLinearEquationSolverFactory<ImpreciseType>>()); + impreciseSolver.setMatrix(impreciseA); + impreciseSolver.createLinearEquationSolver(); + impreciseSolver.setCachingEnabled(true); + + bool converged = false; + try { + // Forward the call to the core rational search routine. + converged = solveEquationsRationalSearchHelper<ValueType, ImpreciseType>(dir, impreciseSolver, *this->A, x, b, impreciseA, impreciseX, impreciseB, impreciseTmpX); + } catch (storm::exceptions::PrecisionExceededException const& e) { + STORM_LOG_WARN("Precision of double was exceeded, trying to recover by switching to rational arithmetic."); + + if (!auxiliaryRowGroupVector) { + auxiliaryRowGroupVector = std::make_unique<std::vector<ValueType>>(this->A->getRowGroupCount()); + } + + // Translate the imprecise value iteration result to the one we are going to use from now on. + auto targetIt = auxiliaryRowGroupVector->begin(); + for (auto it = impreciseX.begin(), ite = impreciseX.end(); it != ite; ++it, ++targetIt) { + *targetIt = storm::utility::convertNumber<ValueType>(*it); + + std::cout << "converting vector[" << std::distance(impreciseX.begin(), it) << "] from " << *it << " to " << *targetIt << std::endl; + } + + + // Get rid of the superfluous data structures. + impreciseX = std::vector<ImpreciseType>(); + impreciseTmpX = std::vector<ImpreciseType>(); + impreciseB = std::vector<ImpreciseType>(); + impreciseA = storm::storage::SparseMatrix<ImpreciseType>(); + + if (!this->linEqSolverA) { + this->linEqSolverA = this->linearEquationSolverFactory->create(*this->A); + this->linEqSolverA->setCachingEnabled(true); + } + + // Forward the call to the core rational search routine, but now with our value type as the imprecise value type. + converged = solveEquationsRationalSearchHelper<ValueType, ValueType>(dir, *this, *this->A, x, b, *this->A, *auxiliaryRowGroupVector, b, x); + } + if (!this->isCachingEnabled()) { + this->clearCache(); + } + + return converged; + } + + template<typename RationalType, typename ImpreciseType> + struct TemporaryHelper { + static std::vector<RationalType>* getFreeRational(std::vector<RationalType>& rationalX, std::vector<ImpreciseType>*& currentX, std::vector<ImpreciseType>*& newX) { + return &rationalX; + } + + static void swapSolutions(std::vector<RationalType>& rationalX, std::vector<RationalType>*& rationalSolution, std::vector<ImpreciseType>& x, std::vector<ImpreciseType>*& currentX, std::vector<ImpreciseType>*& newX) { + // Nothing to do. + } + }; + + template<typename RationalType> + struct TemporaryHelper<RationalType, RationalType> { + static std::vector<RationalType>* getFreeRational(std::vector<RationalType>& rationalX, std::vector<RationalType>*& currentX, std::vector<RationalType>*& newX) { + if (&rationalX == currentX) { + return newX; + } else { + return currentX; + } + } + + static void swapSolutions(std::vector<RationalType>& rationalX, std::vector<RationalType>*& rationalSolution, std::vector<RationalType>& x, std::vector<RationalType>*& currentX, std::vector<RationalType>*& newX) { + if (&rationalX == rationalSolution) { + // In this case, the rational solution is in place. + + // However, since the rational solution is no alias to current x, the imprecise solution is stored + // in current x and and rational x is not an alias to x, we can swap the contents of currentX to x. + std::swap(x, *currentX); + } else { + // Still, we may assume that the rational solution is not current x and is therefore new x. + std::swap(rationalX, *rationalSolution); + std::swap(x, *currentX); + } + } + }; + + template<typename ValueType> + template<typename RationalType, typename ImpreciseType> + bool IterativeMinMaxLinearEquationSolver<ValueType>::solveEquationsRationalSearchHelper(OptimizationDirection dir, IterativeMinMaxLinearEquationSolver<ImpreciseType> const& impreciseSolver, storm::storage::SparseMatrix<RationalType> const& rationalA, std::vector<RationalType>& rationalX, std::vector<RationalType> const& rationalB, storm::storage::SparseMatrix<ImpreciseType> const& A, std::vector<ImpreciseType>& x, std::vector<ImpreciseType> const& b, std::vector<ImpreciseType>& tmpX) const { + + std::vector<ImpreciseType>* currentX = &x; + std::vector<ImpreciseType>* newX = &tmpX; + Status status = Status::InProgress; uint64_t overallIterations = 0; + uint64_t valueIterationInvocations = 0; ValueType precision = this->getSettings().getPrecision(); impreciseSolver.startMeasureProgress(); while (status == Status::InProgress && overallIterations < this->getSettings().getMaximalNumberOfIterations()) { // Perform value iteration with the current precision. - typename IterativeMinMaxLinearEquationSolver<ImpreciseValueType>::ValueIterationResult result = impreciseSolver.performValueIteration(dir, currentX, newX, impreciseB, storm::utility::convertNumber<ImpreciseValueType, ValueType>(precision), this->getSettings().getRelativeTerminationCriterion(), SolverGuarantee::LessOrEqual, overallIterations); + typename IterativeMinMaxLinearEquationSolver<ImpreciseType>::ValueIterationResult result = impreciseSolver.performValueIteration(dir, currentX, newX, b, storm::utility::convertNumber<ImpreciseType, ValueType>(precision), this->getSettings().getRelativeTerminationCriterion(), SolverGuarantee::LessOrEqual, overallIterations); + + // At this point, the result of the imprecise value iteration is stored in the (imprecise) current x. + + ++valueIterationInvocations; + STORM_LOG_TRACE("Completed " << valueIterationInvocations << " value iteration invocations, the last one with precision " << precision << "."); // Count the iterations. overallIterations += result.iterations; // Try to sharpen the result. uint64_t p = storm::utility::convertNumber<uint64_t>(storm::utility::ceil(storm::utility::log10<ValueType>(storm::utility::one<ValueType>() / precision))); - bool foundSolution = sharpen(dir, p, *this->A, *currentX, b, x); + + // Make sure that currentX and rationalX are not aliased. + std::vector<RationalType>* freeRational = TemporaryHelper<RationalType, ImpreciseType>::getFreeRational(rationalX, currentX, newX); + + // Sharpen solution and place it in rationalX. + bool foundSolution = sharpen(dir, p, rationalA, *currentX, rationalB, *freeRational); + + // After sharpen, if a solution was found, it is contained in the current rational x. if (foundSolution) { status = Status::Converged; + + TemporaryHelper<RationalType, ImpreciseType>::swapSolutions(rationalX, freeRational, x, currentX, newX); } else { + // Increase the precision. precision = precision / 10; } } @@ -879,14 +880,9 @@ namespace storm { if (status == Status::InProgress && overallIterations == this->getSettings().getMaximalNumberOfIterations()) { status = Status::MaximalIterationsExceeded; } - reportStatus(status, overallIterations); - if (!this->isCachingEnabled()) { - clearCache(); - } - return status == Status::Converged || status == Status::TerminatedEarly; } @@ -1019,7 +1015,7 @@ namespace storm { } template<typename ValueType> - void IterativeMinMaxLinearEquationSolver<ValueType>::reportStatus(Status status, uint64_t iterations) const { + void IterativeMinMaxLinearEquationSolver<ValueType>::reportStatus(Status status, uint64_t iterations) { 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; diff --git a/src/storm/solver/IterativeMinMaxLinearEquationSolver.h b/src/storm/solver/IterativeMinMaxLinearEquationSolver.h index 974faa018..f7d062b4f 100644 --- a/src/storm/solver/IterativeMinMaxLinearEquationSolver.h +++ b/src/storm/solver/IterativeMinMaxLinearEquationSolver.h @@ -73,15 +73,18 @@ namespace storm { bool solveEquationsAcyclic(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; bool solveEquationsRationalSearch(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; - template<typename PreciseValueType, typename ImpreciseValueType> - static bool sharpen(storm::OptimizationDirection dir, uint64_t precision, storm::storage::SparseMatrix<PreciseValueType> const& A, std::vector<ImpreciseValueType> const& x, std::vector<PreciseValueType> const& b, std::vector<PreciseValueType>& tmp); + template<typename RationalType, typename ImpreciseType> + static bool sharpen(storm::OptimizationDirection dir, uint64_t precision, storm::storage::SparseMatrix<RationalType> const& A, std::vector<ImpreciseType> const& x, std::vector<RationalType> const& b, std::vector<RationalType>& tmp); - template<typename ImpreciseValueType> - typename std::enable_if<std::is_same<ValueType, ImpreciseValueType>::value && !NumberTraits<ValueType>::IsExact, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; - template<typename ImpreciseValueType> - typename std::enable_if<std::is_same<ValueType, ImpreciseValueType>::value && NumberTraits<ValueType>::IsExact, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; - template<typename ImpreciseValueType> - typename std::enable_if<!std::is_same<ValueType, ImpreciseValueType>::value, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; + template<typename ImpreciseType> + typename std::enable_if<std::is_same<ValueType, ImpreciseType>::value && !NumberTraits<ValueType>::IsExact, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; + template<typename ImpreciseType> + typename std::enable_if<std::is_same<ValueType, ImpreciseType>::value && NumberTraits<ValueType>::IsExact, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; + template<typename ImpreciseType> + typename std::enable_if<!std::is_same<ValueType, ImpreciseType>::value, bool>::type solveEquationsRationalSearchHelper(OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b) const; + + template<typename RationalType, typename ImpreciseType> + bool solveEquationsRationalSearchHelper(OptimizationDirection dir, IterativeMinMaxLinearEquationSolver<ImpreciseType> const& impreciseSolver, storm::storage::SparseMatrix<RationalType> const& rationalA, std::vector<RationalType>& rationalX, std::vector<RationalType> const& rationalB, storm::storage::SparseMatrix<ImpreciseType> const& A, std::vector<ImpreciseType>& x, std::vector<ImpreciseType> const& b, std::vector<ImpreciseType>& tmpX) const; void computeOptimalValueForRowGroup(uint_fast64_t group, OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType> const& b, uint_fast64_t* choice = nullptr) const; @@ -111,7 +114,7 @@ namespace storm { mutable std::unique_ptr<std::vector<uint64_t>> rowGroupOrdering; // A.rowGroupCount() entries Status updateStatusIfNotConverged(Status status, std::vector<ValueType> const& x, uint64_t iterations, SolverGuarantee const& guarantee) const; - void reportStatus(Status status, uint64_t iterations) const; + static void reportStatus(Status status, uint64_t iterations); /// The settings of this solver. IterativeMinMaxLinearEquationSolverSettings<ValueType> settings; diff --git a/src/storm/solver/MinMaxLinearEquationSolver.cpp b/src/storm/solver/MinMaxLinearEquationSolver.cpp index 9a7a3f357..3cf349c69 100644 --- a/src/storm/solver/MinMaxLinearEquationSolver.cpp +++ b/src/storm/solver/MinMaxLinearEquationSolver.cpp @@ -189,7 +189,7 @@ namespace storm { template<typename ValueType> void MinMaxLinearEquationSolverFactory<ValueType>::setMinMaxMethod(MinMaxMethod const& newMethod) { - STORM_LOG_WARN_COND(!(std::is_same<ValueType, storm::RationalNumber>::value) || newMethod == MinMaxMethod::PolicyIteration, "The selected solution method does not guarantee exact result. Consider using policy iteration."); + STORM_LOG_WARN_COND(!(std::is_same<ValueType, storm::RationalNumber>::value) || newMethod == MinMaxMethod::PolicyIteration, "The selected solution method does not guarantee exact results. Consider using policy iteration."); method = newMethod; } diff --git a/src/storm/utility/KwekMehlhorn.h b/src/storm/utility/KwekMehlhorn.h new file mode 100644 index 000000000..30c48cba3 --- /dev/null +++ b/src/storm/utility/KwekMehlhorn.h @@ -0,0 +1,68 @@ +#pragma once + +#include "storm/utility/constants.h" +#include "storm/utility/macros.h" + +#include "storm/exceptions/PrecisionExceededException.h" + +namespace storm { + namespace utility{ + namespace kwek_mehlhorn { + + template<typename IntegerType> + std::pair<IntegerType, IntegerType> findRational(IntegerType const& alpha, IntegerType const& beta, IntegerType const& gamma, IntegerType const& delta) { + IntegerType alphaDivBetaFloor = alpha / beta; + IntegerType gammaDivDeltaFloor = gamma / delta; + IntegerType alphaModBeta = storm::utility::mod(alpha, beta); + + if (alphaDivBetaFloor == gammaDivDeltaFloor && !storm::utility::isZero(alphaModBeta)) { + std::pair<IntegerType, IntegerType> subresult = findRational(delta, storm::utility::mod(gamma, delta), beta, alphaModBeta); + auto result = std::make_pair(alphaDivBetaFloor * subresult.first + subresult.second, subresult.first); + + return result; + } else { + auto result = std::make_pair(storm::utility::isZero(alphaModBeta) ? alphaDivBetaFloor : alphaDivBetaFloor + storm::utility::one<IntegerType>(), storm::utility::one<IntegerType>()); + return result; + } + } + + template<typename RationalType, typename ImpreciseType> + std::pair<typename NumberTraits<RationalType>::IntegerType, typename NumberTraits<RationalType>::IntegerType> truncateToRational(ImpreciseType const& value, uint64_t precision) { + typedef typename NumberTraits<RationalType>::IntegerType IntegerType; + + IntegerType powerOfTen = storm::utility::pow(storm::utility::convertNumber<IntegerType>(10ull), precision); + IntegerType truncated = storm::utility::trunc<RationalType>(value * powerOfTen); + return std::make_pair(truncated, powerOfTen); + } + + template<typename RationalType> + std::pair<typename NumberTraits<RationalType>::IntegerType, typename NumberTraits<RationalType>::IntegerType> truncateToRational(double const& value, uint64_t precision) { + STORM_LOG_THROW(precision < 17, storm::exceptions::PrecisionExceededException, "Exceeded precision of double, consider switching to rational numbers."); + double powerOfTen = std::pow(10, precision); + double truncated = storm::utility::trunc<double>(value * powerOfTen); + return std::make_pair(truncated, powerOfTen); + } + + template<typename RationalType, typename ImpreciseType> + RationalType findRational(uint64_t precision, ImpreciseType const& value) { + typedef typename NumberTraits<RationalType>::IntegerType IntegerType; + + std::pair<IntegerType, IntegerType> truncatedFraction = truncateToRational<RationalType>(value, precision); + std::pair<IntegerType, IntegerType> result = findRational<IntegerType>(truncatedFraction.first, truncatedFraction.second, truncatedFraction.first + storm::utility::one<IntegerType>(), truncatedFraction.second); + + // Convert one of the arguments to a rational type to not get integer division. + return storm::utility::convertNumber<RationalType>(result.first) / result.second; + } + + template<typename RationalType, typename ImpreciseType> + void sharpen(uint64_t precision, std::vector<ImpreciseType> const& input, std::vector<RationalType>& output) { + for (uint64_t index = 0; index < input.size(); ++index) { + ImpreciseType integer = storm::utility::floor(input[index]); + ImpreciseType fraction = input[index] - integer; + output[index] = storm::utility::convertNumber<RationalType>(integer) + findRational<RationalType>(precision, fraction); + } + } + + } + } +} diff --git a/src/storm/utility/NumberTraits.h b/src/storm/utility/NumberTraits.h index c52dad448..081ce54bc 100644 --- a/src/storm/utility/NumberTraits.h +++ b/src/storm/utility/NumberTraits.h @@ -13,6 +13,8 @@ namespace storm { struct NumberTraits<double> { static const bool SupportsExponential = true; static const bool IsExact = false; + + typedef uint64_t IntegerType; }; #if defined(STORM_HAVE_CLN) @@ -20,6 +22,8 @@ namespace storm { struct NumberTraits<storm::ClnRationalNumber> { static const bool SupportsExponential = false; static const bool IsExact = true; + + typedef cln::cl_I IntegerType; }; #endif @@ -28,6 +32,8 @@ namespace storm { struct NumberTraits<storm::GmpRationalNumber> { static const bool SupportsExponential = false; static const bool IsExact = true; + + typedef mpz_class IntegerType; }; #endif diff --git a/src/storm/utility/constants.cpp b/src/storm/utility/constants.cpp index c5c8d7b82..7b0bb1baf 100644 --- a/src/storm/utility/constants.cpp +++ b/src/storm/utility/constants.cpp @@ -12,6 +12,8 @@ #include "storm/adapters/RationalFunctionAdapter.h" +#include "storm/utility/NumberTraits.h" + #include "storm/utility/macros.h" #include "storm/exceptions/NotSupportedException.h" @@ -233,25 +235,15 @@ namespace storm { } template<typename ValueType> - ValueType trunc(ValueType const& number) { - return std::trunc(number); + typename NumberTraits<ValueType>::IntegerType trunc(ValueType const& number) { + return static_cast<typename NumberTraits<ValueType>::IntegerType>(std::trunc(number)); } - template<typename ValueType> - ValueType mod(ValueType const& first, ValueType const& second) { + template<typename IntegerType> + IntegerType mod(IntegerType const& first, IntegerType const& second) { return std::fmod(first, second); } - template<typename ValueType> - ValueType numerator(ValueType const& number) { - STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Not supported."); - } - - template<typename ValueType> - ValueType denominator(ValueType const& number) { - STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Not supported."); - } - template<typename ValueType> std::string to_string(ValueType const& value) { std::stringstream ss; @@ -306,11 +298,6 @@ namespace storm { return carl::toInt<carl::uint>(number); } - template<> - ClnRationalNumber convertNumber(ClnRationalNumber const& number) { - return number; - } - template<> ClnRationalNumber convertNumber(double const& number) { return carl::rationalize<ClnRationalNumber>(number); @@ -321,12 +308,23 @@ namespace storm { return carl::rationalize<ClnRationalNumber>(number); } + template<> + ClnRationalNumber convertNumber(NumberTraits<ClnRationalNumber>::IntegerType const& number) { + return ClnRationalNumber(number); + } + template<> ClnRationalNumber convertNumber(uint_fast64_t const& number) { STORM_LOG_ASSERT(static_cast<carl::uint>(number) == number, "Rationalizing failed, because the number is too large."); return carl::rationalize<ClnRationalNumber>(static_cast<carl::uint>(number)); } + template<> + typename NumberTraits<ClnRationalNumber>::IntegerType convertNumber(uint_fast64_t const& number){ + STORM_LOG_ASSERT(static_cast<unsigned long int>(number) == number, "Conversion failed, because the number is too large."); + return NumberTraits<ClnRationalNumber>::IntegerType(static_cast<unsigned long int>(number)); + } + template<> ClnRationalNumber convertNumber(int_fast64_t const& number) { STORM_LOG_ASSERT(static_cast<carl::sint>(number) == number, "Rationalizing failed, because the number is too large."); @@ -374,14 +372,18 @@ namespace storm { } template<> - ClnRationalNumber trunc(ClnRationalNumber const& number) { - cln::truncate2(number); + typename NumberTraits<ClnRationalNumber>::IntegerType trunc(ClnRationalNumber const& number) { + return cln::truncate1(number); + } + + template<> + typename NumberTraits<ClnRationalNumber>::IntegerType mod(NumberTraits<ClnRationalNumber>::IntegerType const& first, NumberTraits<ClnRationalNumber>::IntegerType const& second) { + return carl::mod(first, second); } template<> - ClnRationalNumber mod(ClnRationalNumber const& first, ClnRationalNumber const& second) { - STORM_LOG_ASSERT(isInteger(first) && isInteger(second), "Expecting integers in modulo computation."); - return carl::mod(carl::getNum(first), carl::getNum(second)); + typename NumberTraits<ClnRationalNumber>::IntegerType pow(typename NumberTraits<ClnRationalNumber>::IntegerType const& value, uint_fast64_t exponent) { + return carl::pow(value, exponent); } template<> @@ -390,12 +392,12 @@ namespace storm { } template<> - ClnRationalNumber numerator(ClnRationalNumber const& number) { + NumberTraits<ClnRationalNumber>::IntegerType numerator(ClnRationalNumber const& number) { return carl::getNum(number); } template<> - ClnRationalNumber denominator(ClnRationalNumber const& number) { + NumberTraits<ClnRationalNumber>::IntegerType denominator(ClnRationalNumber const& number) { return carl::getDenom(number); } #endif @@ -467,11 +469,6 @@ namespace storm { return carl::toInt<carl::uint>(number); } - template<> - GmpRationalNumber convertNumber(GmpRationalNumber const& number){ - return number; - } - template<> GmpRationalNumber convertNumber(double const& number){ return carl::rationalize<GmpRationalNumber>(number); @@ -487,7 +484,18 @@ namespace storm { STORM_LOG_ASSERT(static_cast<carl::uint>(number) == number, "Rationalizing failed, because the number is too large."); return carl::rationalize<GmpRationalNumber>(static_cast<carl::uint>(number)); } + + template<> + GmpRationalNumber convertNumber(NumberTraits<GmpRationalNumber>::IntegerType const& number) { + return GmpRationalNumber(number); + } + template<> + typename NumberTraits<GmpRationalNumber>::IntegerType convertNumber(uint_fast64_t const& number){ + STORM_LOG_ASSERT(static_cast<unsigned long int>(number) == number, "Conversion failed, because the number is too large."); + return NumberTraits<GmpRationalNumber>::IntegerType(static_cast<unsigned long int>(number)); + } + template<> GmpRationalNumber convertNumber(int_fast64_t const& number){ STORM_LOG_ASSERT(static_cast<carl::sint>(number) == number, "Rationalizing failed, because the number is too large."); @@ -535,15 +543,19 @@ namespace storm { } template<> - GmpRationalNumber trunc(GmpRationalNumber const& number) { + typename NumberTraits<GmpRationalNumber>::IntegerType trunc(GmpRationalNumber const& number) { // FIXME: precision issue. - return carl::rationalize<GmpRationalNumber>(std::trunc(number.get_d())); + return typename NumberTraits<GmpRationalNumber>::IntegerType(static_cast<unsigned long int>(std::trunc(number.get_d()))); } - + template<> - GmpRationalNumber mod(GmpRationalNumber const& first, GmpRationalNumber const& second) { - STORM_LOG_ASSERT(isInteger(first) && isInteger(second), "Expecting integers in modulo computation."); - return carl::mod(carl::getNum(first), carl::getNum(second)); + typename NumberTraits<GmpRationalNumber>::IntegerType mod(typename NumberTraits<GmpRationalNumber>::IntegerType const& first, typename NumberTraits<GmpRationalNumber>::IntegerType const& second) { + return carl::mod(first, second); + } + + template<> + typename NumberTraits<GmpRationalNumber>::IntegerType pow(typename NumberTraits<GmpRationalNumber>::IntegerType const& value, uint_fast64_t exponent) { + return carl::pow(value, exponent); } template<> @@ -552,12 +564,12 @@ namespace storm { } template<> - GmpRationalNumber numerator(GmpRationalNumber const& number) { + typename NumberTraits<GmpRationalNumber>::IntegerType numerator(GmpRationalNumber const& number) { return carl::getNum(number); } template<> - GmpRationalNumber denominator(GmpRationalNumber const& number) { + typename NumberTraits<GmpRationalNumber>::IntegerType denominator(GmpRationalNumber const& number) { return carl::getDenom(number); } #endif @@ -787,8 +799,7 @@ namespace storm { template double ceil(double const& number); template double log(double const& number); template double log10(double const& number); - template double denominator(double const& number); - template double numerator(double const& number); + template typename NumberTraits<double>::IntegerType trunc(double const& number); template double mod(double const& first, double const& second); template std::string to_string(double const& value); @@ -860,14 +871,18 @@ namespace storm { #if defined(STORM_HAVE_CLN) // Instantiations for (CLN) rational number. template storm::ClnRationalNumber one(); + template NumberTraits<storm::ClnRationalNumber>::IntegerType one(); template storm::ClnRationalNumber zero(); template storm::ClnRationalNumber infinity(); template bool isOne(storm::ClnRationalNumber const& value); + template bool isZero(NumberTraits<storm::ClnRationalNumber>::IntegerType const& value); template bool isZero(storm::ClnRationalNumber const& value); template bool isConstant(storm::ClnRationalNumber const& value); template bool isInfinity(storm::ClnRationalNumber const& value); template bool isInteger(storm::ClnRationalNumber const& number); template double convertNumber(storm::ClnRationalNumber const& number); + template storm::NumberTraits<ClnRationalNumber>::IntegerType convertNumber(storm::NumberTraits<ClnRationalNumber>::IntegerType const& number); + template storm::NumberTraits<ClnRationalNumber>::IntegerType convertNumber(uint64_t const& number); template uint_fast64_t convertNumber(storm::ClnRationalNumber const& number); template storm::ClnRationalNumber convertNumber(double const& number); template storm::ClnRationalNumber convertNumber(storm::ClnRationalNumber const& number); @@ -896,15 +911,19 @@ namespace storm { #if defined(STORM_HAVE_GMP) // Instantiations for (GMP) rational number. template storm::GmpRationalNumber one(); + template NumberTraits<storm::GmpRationalNumber>::IntegerType one(); template storm::GmpRationalNumber zero(); template storm::GmpRationalNumber infinity(); template bool isOne(storm::GmpRationalNumber const& value); template bool isZero(storm::GmpRationalNumber const& value); + template bool isZero(NumberTraits<storm::GmpRationalNumber>::IntegerType const& value); template bool isConstant(storm::GmpRationalNumber const& value); template bool isInfinity(storm::GmpRationalNumber const& value); template bool isInteger(storm::GmpRationalNumber const& number); template double convertNumber(storm::GmpRationalNumber const& number); template uint_fast64_t convertNumber(storm::GmpRationalNumber const& number); + template storm::NumberTraits<GmpRationalNumber>::IntegerType convertNumber(storm::NumberTraits<GmpRationalNumber>::IntegerType const& number); + template storm::NumberTraits<GmpRationalNumber>::IntegerType convertNumber(uint64_t const& number); template storm::GmpRationalNumber convertNumber(double const& number); template storm::GmpRationalNumber convertNumber(storm::GmpRationalNumber const& number); template GmpRationalNumber convertNumber(std::string const& number); diff --git a/src/storm/utility/constants.h b/src/storm/utility/constants.h index fbb251855..f2ce01183 100644 --- a/src/storm/utility/constants.h +++ b/src/storm/utility/constants.h @@ -22,6 +22,9 @@ namespace storm { namespace storage { template<typename IndexType, typename ValueType> class MatrixEntry; } + + template<typename RationalType> + struct NumberTraits; namespace utility { @@ -109,17 +112,17 @@ namespace storm { ValueType log10(ValueType const& number); template<typename ValueType> - ValueType trunc(ValueType const& number); + typename NumberTraits<ValueType>::IntegerType trunc(ValueType const& number); - template<typename ValueType> - ValueType mod(ValueType const& first, ValueType const& second); + template<typename RationalType> + typename NumberTraits<RationalType>::IntegerType numerator(RationalType const& number); - template<typename ValueType> - ValueType numerator(ValueType const& number); - - template<typename ValueType> - ValueType denominator(ValueType const& number); + template<typename RationalType> + typename NumberTraits<RationalType>::IntegerType denominator(RationalType const& number); + template<typename IntegerType> + IntegerType mod(IntegerType const& first, IntegerType const& second); + template<typename ValueType> std::string to_string(ValueType const& value); }