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