@ -1,10 +1,12 @@
# include "SparseNondeterministicInfiniteHorizonHelper.h"
# include "storm/modelchecker/helper/infinitehorizon/internal/LraViHelper.h"
# include "storm/solver/MinMaxLinearEquationSolver.h"
# include "storm/solver/LinearEquationSolver.h"
# include "storm/solver/Multiplier.h"
# include "storm/solver/LpSolver.h"
# include "storm/utility/graph.h"
# include "storm/utility/SignalHandler.h"
# include "storm/utility/solver.h"
# include "storm/utility/vector.h"
@ -84,9 +86,11 @@ namespace storm {
auto underlyingSolverEnvironment = env ;
if ( env . solver ( ) . isForceSoundness ( ) ) {
// For sound computations, the error in the MECS plus the error in the remaining system should not exceed the user defined precsion.
underlyingSolverEnvironment . solver ( ) . minMax ( ) . setPrecision ( env . solver ( ) . lra ( ) . getPrecision ( ) / storm : : utility : : convertNumber < storm : : RationalNumber > ( 2 ) ) ;
storm : : RationalNumber newPrecision = env . solver ( ) . lra ( ) . getPrecision ( ) / storm : : utility : : convertNumber < storm : : RationalNumber > ( 2 ) ;
underlyingSolverEnvironment . solver ( ) . minMax ( ) . setPrecision ( newPrecision ) ;
underlyingSolverEnvironment . solver ( ) . minMax ( ) . setRelativeTerminationCriterion ( env . solver ( ) . lra ( ) . getRelativeTerminationCriterion ( ) ) ;
underlyingSolverEnvironment . solver ( ) . lra ( ) . setPrecision ( env . solver ( ) . lra ( ) . getPrecision ( ) / storm : : utility : : convertNumber < storm : : RationalNumber > ( 2 ) ) ;
underlyingSolverEnvironment . solver ( ) . setLinearEquationSolverPrecision ( newPrecision , env . solver ( ) . lra ( ) . getRelativeTerminationCriterion ( ) ) ;
underlyingSolverEnvironment . solver ( ) . lra ( ) . setPrecision ( newPrecision ) ;
}
// If requested, allocate memory for the choices made
@ -204,545 +208,26 @@ namespace storm {
}
}
/*!
* Abstract helper class that performs a single iteration of the value iteration method
*/
template < typename ValueType >
class LraViHelper {
public :
LraViHelper ( storm : : storage : : MaximalEndComponent const & mec , storm : : storage : : SparseMatrix < ValueType > const & transitionMatrix ) : _mec ( mec ) , _transitionMatrix ( transitionMatrix ) {
// Intentionally left empty
}
virtual ~ LraViHelper ( ) = default ;
/*!
* performs a single iteration step .
* If a choices vector is given , the optimal choices will be inserted at the appropriate states .
* Note that these choices will be inserted w . r . t . the original model states / choices , i . e . the size of the vector should match the state - count of the input model
* @ return the current estimate of the LRA value
*/
virtual void iterate ( Environment const & env , storm : : solver : : OptimizationDirection const & dir , std : : vector < uint64_t > * choices = nullptr ) = 0 ;
struct ConvergenceCheckResult {
bool isPrecisionAchieved ;
ValueType currentValue ;
} ;
/*!
* Checks whether the curently computed value achieves the desired precision
*/
virtual ConvergenceCheckResult checkConvergence ( bool relative , ValueType precision ) = 0 ;
/*!
* Must be called between two calls of iterate .
*/
virtual void prepareNextIteration ( Environment const & env ) = 0 ;
protected :
/*!
*
* @ param xPrevious the ' old ' values
* @ param xCurrent the ' new ' values
* @ param threshold the threshold
* @ param relative whether the relative difference should be considered
* @ return The first component is true if the ( relative ) difference between the maximal and the minimal entry - wise change of the two value vectors is below or equal to the provided threshold .
* In this case , the second component is the average of the maximal and the minimal change .
* If the threshold is exceeded , the computation is aborted early and the second component is only an approximation of the averages .
*/
std : : pair < bool , ValueType > checkMinMaxDiffBelowThreshold ( std : : vector < ValueType > const & xPrevious , std : : vector < ValueType > const & xCurrent , ValueType const & threshold , bool relative ) const {
STORM_LOG_ASSERT ( xPrevious . size ( ) = = xCurrent . size ( ) , " Unexpected Dimension Mismatch " ) ;
STORM_LOG_ASSERT ( threshold > storm : : utility : : zero < ValueType > ( ) , " Did not expect a non-positive threshold. " ) ;
auto x1It = xPrevious . begin ( ) ;
auto x1Ite = xPrevious . end ( ) ;
auto x2It = xCurrent . begin ( ) ;
ValueType maxDiff = ( * x2It - * x1It ) ;
ValueType minDiff = maxDiff ;
bool result = true ;
// The difference between maxDiff and minDiff is zero at this point. Thus, it doesn't make sense to check the threshold now.
for ( + + x1It , + + x2It ; x1It ! = x1Ite ; + + x1It , + + x2It ) {
ValueType diff = ( * x2It - * x1It ) ;
// Potentially update maxDiff or minDiff
bool skipCheck = false ;
if ( maxDiff < diff ) {
maxDiff = diff ;
} else if ( minDiff > diff ) {
minDiff = diff ;
} else {
skipCheck = true ;
}
// Check convergence
if ( ! skipCheck & & ( maxDiff - minDiff ) > ( relative ? ( threshold * minDiff ) : threshold ) ) {
result = false ;
break ;
}
}
ValueType avgDiff = ( maxDiff + minDiff ) / ( storm : : utility : : convertNumber < ValueType > ( 2.0 ) ) ;
return { result , avgDiff } ;
}
storm : : storage : : MaximalEndComponent const & _mec ;
storm : : storage : : SparseMatrix < ValueType > const & _transitionMatrix ;
} ;
/*!
* Abstract helper class that performs a single iteration of the value iteration method for MDP
* @ see Ashok et al . : Value Iteration for Long - Run Average Reward in Markov Decision Processes ( CAV ' 17 ) , https : //doi.org/10.1007/978-3-319-63387-9_10
*/
template < typename ValueType >
class MdpLraViHelper : public LraViHelper < ValueType > {
public :
MdpLraViHelper ( storm : : storage : : MaximalEndComponent const & mec , storm : : storage : : SparseMatrix < ValueType > const & transitionMatrix , std : : function < ValueType ( uint64_t stateIndex ) > const & stateRewardsGetter , std : : function < ValueType ( uint64_t globalChoiceIndex ) > const & actionRewardsGetter , ValueType const & aperiodicFactor ) : LraViHelper < ValueType > ( mec , transitionMatrix ) , _x1 ( mec . size ( ) , storm : : utility : : zero < ValueType > ( ) ) , _x2 ( _x1 ) , _x1IsCurrent ( true ) {
// We add a selfloop to each state (which is necessary for convergence)
// Very roughly, this selfloop avoids that the values can flip around like this: [1, 0] -> [0, 1] -> [1, 0] -> ...
ValueType selfLoopProb = aperiodicFactor ;
// Introducing the selfloop also requires the rewards to be scaled by the following factor.
_scalingFactor = storm : : utility : : one < ValueType > ( ) - selfLoopProb ;
uint64_t numMecStates = this - > _mec . size ( ) ;
boost : : container : : flat_map < uint64_t , uint64_t > toSubModelStateMapping ;
toSubModelStateMapping . reserve ( numMecStates ) ;
uint64_t currState = 0 ;
uint64_t numMecChoices = 0 ;
for ( auto const & stateChoices : this - > _mec ) {
toSubModelStateMapping . emplace ( stateChoices . first , currState ) ;
+ + currState ;
numMecChoices + = stateChoices . second . size ( ) ;
}
assert ( currState = = numMecStates ) ;
// Get a transition matrix that only considers the states and choices within the MEC
storm : : storage : : SparseMatrixBuilder < ValueType > mecTransitionBuilder ( numMecChoices , numMecStates , 0 , true , true , numMecStates ) ;
_choiceValues . reserve ( numMecChoices ) ;
uint64_t currRow = 0 ;
for ( auto const & stateChoices : this - > _mec ) {
auto const & mecState = stateChoices . first ;
auto const & mecChoices = stateChoices . second ;
mecTransitionBuilder . newRowGroup ( currRow ) ;
for ( auto const & choice : mecChoices ) {
bool insertedDiagElement = false ;
for ( auto const & entry : this - > _transitionMatrix . getRow ( choice ) ) {
uint64_t column = toSubModelStateMapping [ entry . getColumn ( ) ] ;
if ( ! insertedDiagElement & & entry . getColumn ( ) > mecState ) {
mecTransitionBuilder . addNextValue ( currRow , toSubModelStateMapping [ mecState ] , selfLoopProb ) ;
insertedDiagElement = true ;
}
if ( ! insertedDiagElement & & entry . getColumn ( ) = = mecState ) {
mecTransitionBuilder . addNextValue ( currRow , column , selfLoopProb + _scalingFactor * entry . getValue ( ) ) ;
insertedDiagElement = true ;
} else {
mecTransitionBuilder . addNextValue ( currRow , column , _scalingFactor * entry . getValue ( ) ) ;
}
}
if ( ! insertedDiagElement ) {
mecTransitionBuilder . addNextValue ( currRow , toSubModelStateMapping [ mecState ] , selfLoopProb ) ;
}
// Compute the rewards obtained for this choice
_choiceValues . push_back ( _scalingFactor * ( stateRewardsGetter ( mecState ) + actionRewardsGetter ( choice ) ) ) ;
+ + currRow ;
}
}
_mecTransitions = mecTransitionBuilder . build ( ) ;
STORM_LOG_ASSERT ( _mecTransitions . isProbabilistic ( ) , " The MEC-Matrix is not probabilistic. " ) ;
STORM_LOG_ASSERT ( _mecTransitions . getRowGroupCount ( ) = = _x1 . size ( ) , " Unexpected size mismatch for created matrix. " ) ;
STORM_LOG_ASSERT ( _x1 . size ( ) = = _x2 . size ( ) , " Unexpected size mismatch for created matrix. " ) ;
}
virtual void iterate ( Environment const & env , storm : : solver : : OptimizationDirection const & dir , std : : vector < uint64_t > * choices = nullptr ) override {
// Initialize a multipler if it does not exist, yet
if ( ! _multiplier ) {
_multiplier = storm : : solver : : MultiplierFactory < ValueType > ( ) . create ( env , _mecTransitions ) ;
}
if ( choices = = nullptr ) {
// Perform a simple matrix-vector multiplication
_multiplier - > multiplyAndReduce ( env , dir , xCurrent ( ) , & _choiceValues , xPrevious ( ) ) ;
} else {
// Perform a simple matrix-vector multiplication but also keep track of the choices within the _mecTransitions
std : : vector < uint64_t > mecChoices ( _mecTransitions . getRowGroupCount ( ) ) ;
_multiplier - > multiplyAndReduce ( env , dir , xCurrent ( ) , & _choiceValues , xPrevious ( ) , & mecChoices ) ;
// Transform the local choices (within this mec) to global indices
uint64_t mecState = 0 ;
for ( auto const & stateChoices : this - > _mec ) {
uint64_t mecChoice = mecChoices [ mecState ] ;
STORM_LOG_ASSERT ( mecChoice < stateChoices . second . size ( ) , " The selected choice does not seem to exist. " ) ;
uint64_t globalChoiceIndex = * ( stateChoices . second . begin ( ) + mecChoice ) ;
( * choices ) [ stateChoices . first ] = globalChoiceIndex - this - > _transitionMatrix . getRowGroupIndices ( ) [ stateChoices . first ] ;
+ + mecState ;
}
}
// Swap current and previous x vectors
_x1IsCurrent = ! _x1IsCurrent ;
}
virtual typename LraViHelper < ValueType > : : ConvergenceCheckResult checkConvergence ( bool relative , ValueType precision ) override {
typename LraViHelper < ValueType > : : ConvergenceCheckResult res ;
std : : tie ( res . isPrecisionAchieved , res . currentValue ) = this - > checkMinMaxDiffBelowThreshold ( xPrevious ( ) , xCurrent ( ) , precision , relative ) ;
res . currentValue / = _scalingFactor ; // "Undo" the scaling of the rewards
return res ;
}
virtual void prepareNextIteration ( Environment const & ) override {
// To avoid large (and numerically unstable) x-values, we substract a reference value.
ValueType referenceValue = xCurrent ( ) . front ( ) ;
storm : : utility : : vector : : applyPointwise < ValueType , ValueType > ( xCurrent ( ) , xCurrent ( ) , [ & referenceValue ] ( ValueType const & x_i ) - > ValueType { return x_i - referenceValue ; } ) ;
}
private :
std : : vector < ValueType > & xCurrent ( ) {
return _x1IsCurrent ? _x1 : _x2 ;
}
std : : vector < ValueType > & xPrevious ( ) {
return _x1IsCurrent ? _x2 : _x1 ;
}
storm : : storage : : SparseMatrix < ValueType > _mecTransitions ;
std : : vector < ValueType > _x1 , _x2 , _choiceValues ;
bool _x1IsCurrent ;
std : : unique_ptr < storm : : solver : : Multiplier < ValueType > > _multiplier ;
ValueType _scalingFactor ;
} ;
/*!
* Abstract helper class that performs a single iteration of the value iteration method for MA
* @ see Butkova , Wimmer , Hermanns : Long - Run Rewards for Markov Automata ( TACAS ' 17 ) , https : //doi.org/10.1007/978-3-662-54580-5_11
*/
template < typename ValueType >
class MaLraViHelper : public LraViHelper < ValueType > {
public :
MaLraViHelper ( storm : : storage : : MaximalEndComponent const & mec , storm : : storage : : SparseMatrix < ValueType > const & transitionMatrix , storm : : storage : : BitVector const & markovianStates , std : : vector < ValueType > const & exitRates , std : : function < ValueType ( uint64_t stateIndex ) > const & stateRewardsGetter , std : : function < ValueType ( uint64_t globalChoiceIndex ) > const & actionRewardsGetter , ValueType const & aperiodicFactor ) : LraViHelper < ValueType > ( mec , transitionMatrix ) , _markovianStates ( markovianStates ) , _Msx1IsCurrent ( false ) {
// Run through the Mec and collect some data:
// We consider two submodels, one consisting of the Markovian MEC states and one consisting of the probabilistic MEC states.
// For this, we create a state index map that point from state indices of the input model to indices of the corresponding submodel of that state.
boost : : container : : flat_map < uint64_t , uint64_t > toSubModelStateMapping ;
// We also obtain state and choices counts of the two submodels
uint64_t numPsSubModelStates ( 0 ) , numPsSubModelChoices ( 0 ) ;
uint64_t numMsSubModelStates ( 0 ) ; // The number of choices coincide
// We will need to uniformize the Markovian MEC states by introducing a selfloop.
// For this, we need to find a uniformization rate which will be a little higher (given by aperiodicFactor) than the maximum rate occurring in the MEC.
_uniformizationRate = storm : : utility : : zero < ValueType > ( ) ;
// Now run over the MEC and collect the required data.
for ( auto const & stateChoices : this - > _mec ) {
uint64_t const & mecState = stateChoices . first ;
if ( _markovianStates . get ( mecState ) ) {
toSubModelStateMapping . emplace ( mecState , numMsSubModelStates ) ;
+ + numMsSubModelStates ;
STORM_LOG_ASSERT ( stateChoices . second . size ( ) = = 1 , " Markovian state has multiple MEC choices. " ) ;
_uniformizationRate = std : : max ( _uniformizationRate , exitRates [ mecState ] ) ;
} else {
toSubModelStateMapping . emplace ( mecState , numPsSubModelStates ) ;
+ + numPsSubModelStates ;
numPsSubModelChoices + = stateChoices . second . size ( ) ;
}
}
assert ( numPsSubModelStates + numMsSubModelStates = = mec . size ( ) ) ;
STORM_LOG_THROW ( numMsSubModelStates > 0 , storm : : exceptions : : InvalidOperationException , " Markov Automaton has Zeno behavior. Computation of Long Run Average values not supported. " ) ;
_hasProbabilisticStates = numPsSubModelStates > 0 ;
// We make sure that every Markovian state gets a selfloop to make the model aperiodic
_uniformizationRate * = storm : : utility : : one < ValueType > ( ) + aperiodicFactor ;
// Now build the Markovian and the Probabilistic submodels.
// In addition, we also need the transitions between the two.
storm : : storage : : SparseMatrixBuilder < ValueType > msTransitionsBuilder ( numMsSubModelStates , numMsSubModelStates ) ;
_MsChoiceValues . reserve ( numMsSubModelStates ) ;
storm : : storage : : SparseMatrixBuilder < ValueType > msToPsTransitionsBuilder , psTransitionsBuilder , psToMsTransitionsBuilder ;
if ( _hasProbabilisticStates ) {
msToPsTransitionsBuilder = storm : : storage : : SparseMatrixBuilder < ValueType > ( numMsSubModelStates , numPsSubModelStates ) ;
psTransitionsBuilder = storm : : storage : : SparseMatrixBuilder < ValueType > ( numPsSubModelChoices , numPsSubModelStates , 0 , true , true , numPsSubModelStates ) ;
psToMsTransitionsBuilder = storm : : storage : : SparseMatrixBuilder < ValueType > ( numPsSubModelChoices , numMsSubModelStates , 0 , true , true , numPsSubModelStates ) ;
_PsChoiceValues . reserve ( numPsSubModelChoices ) ;
}
uint64_t currMsRow = 0 ;
uint64_t currPsRow = 0 ;
for ( auto const & stateChoices : this - > _mec ) {
uint64_t const & mecState = stateChoices . first ;
auto const & mecChoices = stateChoices . second ;
if ( ! _hasProbabilisticStates | | _markovianStates . get ( mecState ) ) {
// The currently processed state is Markovian.
// We need to uniformize!
ValueType uniformizationFactor = exitRates [ mecState ] / _uniformizationRate ;
ValueType selfLoopProb = storm : : utility : : one < ValueType > ( ) - uniformizationFactor ;
STORM_LOG_ASSERT ( mecChoices . size ( ) = = 1 , " Unexpected number of choices at Markovian state. " ) ;
for ( auto const & mecChoice : mecChoices ) {
bool insertedDiagElement = false ;
for ( auto const & entry : this - > _transitionMatrix . getRow ( mecChoice ) ) {
uint64_t subModelColumn = toSubModelStateMapping [ entry . getColumn ( ) ] ;
if ( ! _hasProbabilisticStates | | _markovianStates . get ( entry . getColumn ( ) ) ) {
// We have a transition from a Markovian state to a Markovian state
STORM_LOG_ASSERT ( subModelColumn < numMsSubModelStates , " Invalid state for Markovian submodel " ) ;
if ( ! insertedDiagElement & & subModelColumn > currMsRow ) {
// We passed the diagonal entry, so add it now before moving on to the next entry
msTransitionsBuilder . addNextValue ( currMsRow , currMsRow , selfLoopProb ) ;
insertedDiagElement = true ;
}
if ( ! insertedDiagElement & & subModelColumn = = currMsRow ) {
// The current entry is the diagonal (selfloop) entry
msTransitionsBuilder . addNextValue ( currMsRow , subModelColumn , selfLoopProb + uniformizationFactor * entry . getValue ( ) ) ;
insertedDiagElement = true ;
} else {
// The diagonal element either has been inserted already or still lies in front
msTransitionsBuilder . addNextValue ( currMsRow , subModelColumn , uniformizationFactor * entry . getValue ( ) ) ;
}
} else {
// We have a transition from a Markovian to a probabilistic state
STORM_LOG_ASSERT ( subModelColumn < numPsSubModelStates , " Invalid state for probabilistic submodel " ) ;
msToPsTransitionsBuilder . addNextValue ( currMsRow , subModelColumn , uniformizationFactor * entry . getValue ( ) ) ;
}
}
// If the diagonal entry for the MS matrix still has not been set, we do that now
if ( ! insertedDiagElement ) {
msTransitionsBuilder . addNextValue ( currMsRow , currMsRow , selfLoopProb ) ;
}
// Compute the rewards obtained for this choice.
_MsChoiceValues . push_back ( stateRewardsGetter ( mecState ) / _uniformizationRate + actionRewardsGetter ( mecChoice ) * exitRates [ mecState ] / _uniformizationRate ) ;
+ + currMsRow ;
}
} else {
// The currently processed state is probabilistic
psTransitionsBuilder . newRowGroup ( currPsRow ) ;
psToMsTransitionsBuilder . newRowGroup ( currPsRow ) ;
for ( auto const & mecChoice : mecChoices ) {
for ( auto const & entry : this - > _transitionMatrix . getRow ( mecChoice ) ) {
uint64_t subModelColumn = toSubModelStateMapping [ entry . getColumn ( ) ] ;
if ( _markovianStates . get ( entry . getColumn ( ) ) ) {
// We have a transition from a probabilistic state to a Markovian state
STORM_LOG_ASSERT ( subModelColumn < numMsSubModelStates , " Invalid state for Markovian submodel " ) ;
psToMsTransitionsBuilder . addNextValue ( currPsRow , subModelColumn , entry . getValue ( ) ) ;
} else {
// We have a transition from a probabilistic to a probabilistic state
STORM_LOG_ASSERT ( subModelColumn < numPsSubModelStates , " Invalid state for probabilistic submodel " ) ;
psTransitionsBuilder . addNextValue ( currPsRow , subModelColumn , entry . getValue ( ) ) ;
}
}
// Compute the rewards obtained for this choice.
// State rewards do not count here since no time passes in probabilistic states.
_PsChoiceValues . push_back ( actionRewardsGetter ( mecChoice ) ) ;
+ + currPsRow ;
}
}
}
_MsTransitions = msTransitionsBuilder . build ( ) ;
if ( _hasProbabilisticStates ) {
_MsToPsTransitions = msToPsTransitionsBuilder . build ( ) ;
_PsTransitions = psTransitionsBuilder . build ( ) ;
_PsToMsTransitions = psToMsTransitionsBuilder . build ( ) ;
}
}
void initializeIterations ( Environment const & env , storm : : solver : : OptimizationDirection const & dir ) {
_Msx1 . resize ( _MsTransitions . getRowGroupCount ( ) , storm : : utility : : zero < ValueType > ( ) ) ;
_Msx2 = _Msx1 ;
_MsMultiplier = storm : : solver : : MultiplierFactory < ValueType > ( ) . create ( env , _MsTransitions ) ;
if ( _hasProbabilisticStates ) {
if ( _PsTransitions . getNonzeroEntryCount ( ) > 0 ) {
// Set-up a solver for transitions within PS states
_PsSolverEnv = env ;
if ( env . solver ( ) . isForceSoundness ( ) ) {
// To get correct results, the inner equation systems are solved exactly.
// TODO investigate how an error would propagate
_PsSolverEnv . solver ( ) . setForceExact ( true ) ;
}
storm : : solver : : GeneralMinMaxLinearEquationSolverFactory < ValueType > factory ;
bool isAcyclic = ! storm : : utility : : graph : : hasCycle ( _PsTransitions ) ;
if ( isAcyclic ) {
STORM_LOG_INFO ( " Probabilistic transitions are acyclic. " ) ;
_PsSolverEnv . solver ( ) . minMax ( ) . setMethod ( storm : : solver : : MinMaxMethod : : Acyclic ) ;
}
_PsSolver = factory . create ( _PsSolverEnv , _PsTransitions ) ;
_PsSolver - > setHasUniqueSolution ( true ) ; // Assume non-zeno MA
_PsSolver - > setHasNoEndComponents ( true ) ; // assume non-zeno MA
_PsSolver - > setCachingEnabled ( true ) ;
_PsSolver - > setRequirementsChecked ( true ) ;
auto req = _PsSolver - > getRequirements ( _PsSolverEnv , dir ) ;
req . clearUniqueSolution ( ) ;
if ( isAcyclic ) {
req . clearAcyclic ( ) ;
}
// Computing a priori lower/upper bounds is not particularly easy, as there might be selfloops with high probabilities
// Which accumulate a lot of reward. Moreover, the right-hand-side of the equation system changes dynamically.
STORM_LOG_THROW ( ! req . hasEnabledCriticalRequirement ( ) , storm : : exceptions : : UnmetRequirementException , " The solver requirement " < < req . getEnabledRequirementsAsString ( ) < < " has not been checked. " ) ;
}
// Set up multipliers for transitions connecting Markovian and probabilistic states
_MsToPsMultiplier = storm : : solver : : MultiplierFactory < ValueType > ( ) . create ( env , _MsToPsTransitions ) ;
_PsToMsMultiplier = storm : : solver : : MultiplierFactory < ValueType > ( ) . create ( env , _PsToMsTransitions ) ;
// Set-up vectors for storing intermediate results for PS states.
_Psx . resize ( _PsTransitions . getRowGroupCount ( ) , storm : : utility : : zero < ValueType > ( ) ) ;
_Psb = _PsChoiceValues ;
}
}
void setInputModelChoices ( std : : vector < uint64_t > & choices , std : : vector < uint64_t > const & localMecChoices , bool setChoiceZeroToMarkovianStates ) {
// Transform the local choices (within this mec) to choice indices for the input model
uint64_t mecState = 0 ;
for ( auto const & stateChoices : this - > _mec ) {
if ( setChoiceZeroToMarkovianStates & & _markovianStates . get ( stateChoices . first ) ) {
choices [ stateChoices . first ] = 0 ;
} else {
uint64_t mecChoice = localMecChoices [ mecState ] ;
STORM_LOG_ASSERT ( mecChoice < stateChoices . second . size ( ) , " The selected choice does not seem to exist. " ) ;
uint64_t globalChoiceIndex = * ( stateChoices . second . begin ( ) + mecChoice ) ;
choices [ stateChoices . first ] = globalChoiceIndex - this - > _transitionMatrix . getRowGroupIndices ( ) [ stateChoices . first ] ;
+ + mecState ;
}
}
STORM_LOG_ASSERT ( mecState = = localMecChoices . size ( ) , " Did not traverse all mec states. " ) ;
}
virtual void iterate ( Environment const & env , storm : : solver : : OptimizationDirection const & dir , std : : vector < uint64_t > * choices = nullptr ) override {
// Initialize value vectors, multiplers, and solver if this has not been done, yet
if ( ! _MsMultiplier ) {
initializeIterations ( env , dir ) ;
}
// Compute new x values for the Markovian states
// Flip what is current and what is previous
_Msx1IsCurrent = ! _Msx1IsCurrent ;
// At this point, xPrevious() points to what has been computed in the previous call of iterate (initially, this is the 0-vector).
// The result of this computation will be stored in xCurrent()
// Compute the values obtained by a single uniformization step between Markovian states only
_MsMultiplier - > multiply ( env , xPrevious ( ) , & _MsChoiceValues , xCurrent ( ) ) ;
if ( _hasProbabilisticStates ) {
// Add the values obtained by taking a single uniformization step that leads to a Probabilistic state followed by arbitrarily many probabilistic steps.
// First compute the total values when taking arbitrarily many probabilistic transitions (in no time)
if ( _PsSolver ) {
// We might need to track the optimal choices.
if ( choices = = nullptr ) {
_PsSolver - > solveEquations ( _PsSolverEnv , dir , _Psx , _Psb ) ;
} else {
_PsSolver - > setTrackScheduler ( ) ;
_PsSolver - > solveEquations ( _PsSolverEnv , dir , _Psx , _Psb ) ;
setInputModelChoices ( * choices , _PsSolver - > getSchedulerChoices ( ) , true ) ;
}
} else {
STORM_LOG_ASSERT ( _PsTransitions . getNonzeroEntryCount ( ) = = 0 , " If no solver was initialized, an empty matrix would have been expected. " ) ;
if ( choices = = nullptr ) {
storm : : utility : : vector : : reduceVectorMinOrMax ( dir , _Psb , _Psx , _PsTransitions . getRowGroupIndices ( ) ) ;
} else {
std : : vector < uint64_t > psMecChoices ( _PsTransitions . getRowGroupCount ( ) ) ;
storm : : utility : : vector : : reduceVectorMinOrMax ( dir , _Psb , _Psx , _PsTransitions . getRowGroupIndices ( ) , & psMecChoices ) ;
setInputModelChoices ( * choices , _PsSolver - > getSchedulerChoices ( ) , true ) ;
}
}
// Now add the (weighted) values of the probabilistic states to the values of the Markovian states.
_MsToPsMultiplier - > multiply ( env , _Psx , & xCurrent ( ) , xCurrent ( ) ) ;
}
}
virtual typename LraViHelper < ValueType > : : ConvergenceCheckResult checkConvergence ( bool relative , ValueType precision ) override {
typename LraViHelper < ValueType > : : ConvergenceCheckResult res ;
// All values are scaled according to the uniformizationRate.
// We need to 'revert' this scaling when computing the absolute precision.
// However, for relative precision, the scaling cancels out.
ValueType threshold = relative ? precision : ValueType ( precision / _uniformizationRate ) ;
std : : tie ( res . isPrecisionAchieved , res . currentValue ) = this - > checkMinMaxDiffBelowThreshold ( xPrevious ( ) , xCurrent ( ) , threshold , relative ) ;
res . currentValue * = _uniformizationRate ; // "Undo" the scaling of the values
return res ;
}
virtual void prepareNextIteration ( Environment const & env ) override {
// To avoid large (and numerically unstable) x-values, we substract a reference value.
ValueType referenceValue = xCurrent ( ) . front ( ) ;
storm : : utility : : vector : : applyPointwise < ValueType , ValueType > ( xCurrent ( ) , xCurrent ( ) , [ & referenceValue ] ( ValueType const & x_i ) - > ValueType { return x_i - referenceValue ; } ) ;
if ( _hasProbabilisticStates ) {
// Update the RHS of the equation system for the probabilistic states by taking the new values of Markovian states into account.
_PsToMsMultiplier - > multiply ( env , xCurrent ( ) , & _PsChoiceValues , _Psb ) ;
}
}
private :
std : : vector < ValueType > & xCurrent ( ) {
return _Msx1IsCurrent ? _Msx1 : _Msx2 ;
}
std : : vector < ValueType > & xPrevious ( ) {
return _Msx1IsCurrent ? _Msx2 : _Msx1 ;
}
storm : : storage : : BitVector const & _markovianStates ;
bool _hasProbabilisticStates ;
ValueType _uniformizationRate ;
storm : : storage : : SparseMatrix < ValueType > _MsTransitions , _MsToPsTransitions , _PsTransitions , _PsToMsTransitions ;
std : : vector < ValueType > _Msx1 , _Msx2 , _MsChoiceValues ;
bool _Msx1IsCurrent ;
std : : vector < ValueType > _Psx , _Psb , _PsChoiceValues ;
std : : unique_ptr < storm : : solver : : Multiplier < ValueType > > _MsMultiplier , _MsToPsMultiplier , _PsToMsMultiplier ;
std : : unique_ptr < storm : : solver : : MinMaxLinearEquationSolver < ValueType > > _PsSolver ;
Environment _PsSolverEnv ;
} ;
template < typename ValueType >
ValueType SparseNondeterministicInfiniteHorizonHelper < ValueType > : : computeLraForMecVi ( Environment const & env , std : : function < ValueType ( uint64_t stateIndex ) > const & stateRewardsGetter , std : : function < ValueType ( uint64_t globalChoiceIndex ) > const & actionRewardsGetter , storm : : storage : : MaximalEndComponent const & mec ) {
// Collect some parameters of the computation.
// Collect some parameters of the computation
ValueType aperiodicFactor = storm : : utility : : convertNumber < ValueType > ( env . solver ( ) . lra ( ) . getAperiodicFactor ( ) ) ;
ValueType precision = storm : : utility : : convertNumber < ValueType > ( env . solver ( ) . lra ( ) . getPrecision ( ) ) / aperiodicFactor ;
bool relative = env . solver ( ) . lra ( ) . getRelativeTerminationCriterion ( ) ;
boost : : optional < uint64_t > maxIter ;
if ( env . solver ( ) . lra ( ) . isMaximalIterationCountSet ( ) ) {
maxIter = env . solver ( ) . lra ( ) . getMaximalIterationCount ( ) ;
std : : vector < uint64_t > * optimalChoices = nullptr ;
if ( isProduceSchedulerSet ( ) ) {
optimalChoices = & _producedOptimalChoices . get ( ) ;
}
auto dir = this - > getOptimizationDirection ( ) ;
// Create an object for the iterations
std : : shared_ptr < LraViHelper < ValueType > > iterationHelper ;
// Now create a helper and perform the algorithm
if ( isContinuousTime ( ) ) {
iterationHelper = std : : make_shared < MaLraViHelper < ValueType > > ( mec , _transitionMatrix , * _markovianStates , * _exitRates , stateRewardsGetter , actionRewardsGetter , aperiodicFactor ) ;
} else {
iterationHelper = std : : make_shared < MdpLraViHelper < ValueType > > ( mec , _transitionMatrix , stateRewardsGetter , actionRewardsGetter , aperiodicFactor ) ;
}
// start the iterations
ValueType result = storm : : utility : : zero < ValueType > ( ) ;
uint64_t iter = 0 ;
while ( ! maxIter . is_initialized ( ) | | iter < maxIter . get ( ) ) {
+ + iter ;
iterationHelper - > iterate ( env , dir ) ;
// Check if we are done
auto convergenceCheckResult = iterationHelper - > checkConvergence ( relative , precision ) ;
result = convergenceCheckResult . currentValue ;
if ( convergenceCheckResult . isPrecisionAchieved ) {
break ;
}
if ( storm : : utility : : resources : : isTerminate ( ) ) {
break ;
}
iterationHelper - > prepareNextIteration ( env ) ;
}
if ( maxIter . is_initialized ( ) & & iter = = maxIter . get ( ) ) {
STORM_LOG_WARN ( " LRA computation did not converge within " < < iter < < " iterations. " ) ;
} else if ( storm : : utility : : resources : : isTerminate ( ) ) {
STORM_LOG_WARN ( " LRA computation aborted after " < < iter < < " iterations. " ) ;
// We assume a Markov Automaton (with deterministic timed states and nondeterministic instant states)
storm : : modelchecker : : helper : : internal : : LraViHelper < ValueType , storm : : storage : : MaximalEndComponent , storm : : modelchecker : : helper : : internal : : LraViTransitionsType : : DetTsNondetIs > viHelper ( mec , _transitionMatrix , aperiodicFactor , _markovianStates , _exitRates ) ;
return viHelper . performValueIteration ( env , stateRewardsGetter , actionRewardsGetter , _exitRates , & this - > getOptimizationDirection ( ) , optimalChoices ) ;
} else {
STORM_LOG_TRACE ( " LRA computation converged after " < < iter < < " iterations. " ) ;
// We assume an MDP (with nondeterministic timed states and no instant states)
storm : : modelchecker : : helper : : internal : : LraViHelper < ValueType , storm : : storage : : MaximalEndComponent , storm : : modelchecker : : helper : : internal : : LraViTransitionsType : : NondetTsNoIs > viHelper ( mec , _transitionMatrix , aperiodicFactor ) ;
return viHelper . performValueIteration ( env , stateRewardsGetter , actionRewardsGetter , nullptr , & this - > getOptimizationDirection ( ) , optimalChoices ) ;
}
if ( isProduceSchedulerSet ( ) ) {
// We will be doing one more iteration step and track scheduler choices this time.
iterationHelper - > prepareNextIteration ( env ) ;
iterationHelper - > iterate ( env , dir , & _producedOptimalChoices . get ( ) ) ;
}
return result ;
}
template < typename ValueType >