@ -29,6 +29,7 @@ namespace storm {
switch ( method ) {
case MinMaxMethod : : ValueIteration : this - > solutionMethod = SolutionMethod : : ValueIteration ; break ;
case MinMaxMethod : : PolicyIteration : this - > solutionMethod = SolutionMethod : : PolicyIteration ; break ;
case MinMaxMethod : : Acyclic : this - > solutionMethod = SolutionMethod : : Acyclic ; break ;
default :
STORM_LOG_THROW ( false , storm : : exceptions : : InvalidSettingsException , " Unsupported technique. " ) ;
}
@ -91,6 +92,8 @@ namespace storm {
return solveEquationsValueIteration ( dir , x , b ) ;
case StandardMinMaxLinearEquationSolverSettings < ValueType > : : SolutionMethod : : PolicyIteration :
return solveEquationsPolicyIteration ( dir , x , b ) ;
case StandardMinMaxLinearEquationSolverSettings < ValueType > : : SolutionMethod : : Acyclic :
return solveEquationsAcyclic ( dir , x , b ) ;
default :
STORM_LOG_THROW ( false , storm : : exceptions : : InvalidSettingsException , " This solver does not implement the selected solution method " ) ;
}
@ -293,6 +296,113 @@ namespace storm {
return status = = Status : : Converged | | status = = Status : : TerminatedEarly ;
}
template < typename ValueType >
bool StandardMinMaxLinearEquationSolver < ValueType > : : solveEquationsAcyclic ( OptimizationDirection dir , std : : vector < ValueType > & x , std : : vector < ValueType > const & b ) const {
uint64_t numGroups = this - > A . getRowGroupCount ( ) ;
// Allocate memory for the scheduler (if required)
if ( this - > isTrackSchedulerSet ( ) ) {
if ( this - > schedulerChoices ) {
this - > schedulerChoices - > resize ( numGroups ) ;
} else {
this - > schedulerChoices = std : : vector < uint_fast64_t > ( numGroups ) ;
}
}
// We now compute a topological sort of the row groups.
// If caching is enabled, it might be possible to obtain this sort from the cache.
if ( this - > isCachingEnabled ( ) ) {
if ( rowGroupOrdering ) {
for ( auto const & group : * rowGroupOrdering ) {
computeOptimalValueForRowGroup ( group , dir , x , b , this - > isTrackSchedulerSet ( ) ? & this - > schedulerChoices . get ( ) [ group ] : nullptr ) ;
}
return true ;
} else {
rowGroupOrdering = std : : make_unique < std : : vector < uint64_t > > ( ) ;
rowGroupOrdering - > reserve ( numGroups ) ;
}
}
auto transposedMatrix = this - > A . transpose ( true ) ;
// We store the groups that have already been processed, i.e., the groups for which x[group] was already set to the correct value.
storm : : storage : : BitVector processedGroups ( numGroups , false ) ;
// Furthermore, we keep track of all candidate groups for which we still need to check whether this group can be processed now.
// A group can be processed if all successors have already been processed.
// Notice that the BitVector candidates is considered in a reversed way, i.e., group i is a candidate iff candidates.get(numGroups - i - 1) is true.
// This is due to the observation that groups with higher indices usually need to be processed earlier.
storm : : storage : : BitVector candidates ( numGroups , true ) ;
uint64_t candidate = numGroups - 1 ;
for ( uint64_t numCandidates = candidates . size ( ) ; numCandidates > 0 ; - - numCandidates ) {
candidates . set ( numGroups - candidate - 1 , false ) ;
// Check if the candidate row group has an unprocessed successor
bool hasUnprocessedSuccessor = false ;
for ( auto const & entry : this - > A . getRowGroup ( candidate ) ) {
if ( ! processedGroups . get ( entry . getColumn ( ) ) ) {
hasUnprocessedSuccessor = true ;
break ;
}
}
uint64_t nextCandidate = numGroups - candidates . getNextSetIndex ( numGroups - candidate - 1 + 1 ) - 1 ;
if ( ! hasUnprocessedSuccessor ) {
// This candidate can be processed.
processedGroups . set ( candidate ) ;
computeOptimalValueForRowGroup ( candidate , dir , x , b , this - > isTrackSchedulerSet ( ) ? & this - > schedulerChoices . get ( ) [ candidate ] : nullptr ) ;
if ( this - > isCachingEnabled ( ) ) {
rowGroupOrdering - > push_back ( candidate ) ;
}
// Add new candidates
for ( auto const & predecessorEntry : transposedMatrix . getRow ( candidate ) ) {
uint64_t predecessor = predecessorEntry . getColumn ( ) ;
if ( ! candidates . get ( numGroups - predecessor - 1 ) ) {
candidates . set ( numGroups - predecessor - 1 , true ) ;
nextCandidate = std : : max ( nextCandidate , predecessor ) ;
+ + numCandidates ;
}
}
}
candidate = nextCandidate ;
}
assert ( candidates . empty ( ) ) ;
STORM_LOG_THROW ( processedGroups . full ( ) , storm : : exceptions : : InvalidOperationException , " The MinMax equation system is not acyclic. " ) ;
return true ;
}
template < typename ValueType >
void StandardMinMaxLinearEquationSolver < ValueType > : : computeOptimalValueForRowGroup ( uint_fast64_t group , OptimizationDirection dir , std : : vector < ValueType > & x , std : : vector < ValueType > const & b , uint_fast64_t * choice ) const {
uint64_t row = this - > A . getRowGroupIndices ( ) [ group ] ;
uint64_t groupEnd = this - > A . getRowGroupIndices ( ) [ group + 1 ] ;
assert ( row ! = groupEnd ) ;
auto bIt = b . begin ( ) + row ;
ValueType & xi = x [ group ] ;
xi = this - > A . multiplyRowWithVector ( row , x ) + * bIt ;
uint64_t optimalRow = row ;
for ( + + row , + + bIt ; row < groupEnd ; + + row , + + bIt ) {
ValueType choiceVal = this - > A . multiplyRowWithVector ( row , x ) + * bIt ;
if ( minimize ( dir ) ) {
if ( choiceVal < xi ) {
xi = choiceVal ;
optimalRow = row ;
}
} else {
if ( choiceVal > xi ) {
xi = choiceVal ;
optimalRow = row ;
}
}
}
if ( choice ! = nullptr ) {
* choice = optimalRow - this - > A . getRowGroupIndices ( ) [ group ] ;
}
}
template < typename ValueType >
void StandardMinMaxLinearEquationSolver < ValueType > : : repeatedMultiply ( OptimizationDirection dir , std : : vector < ValueType > & x , std : : vector < ValueType > const * b , uint_fast64_t n ) const {
if ( ! linEqSolverA ) {
@ -356,6 +466,7 @@ namespace storm {
linEqSolverA . reset ( ) ;
auxiliaryRowVector . reset ( ) ;
auxiliaryRowGroupVector . reset ( ) ;
rowGroupOrdering . reset ( ) ;
MinMaxLinearEquationSolver < ValueType > : : clearCache ( ) ;
}