@ -1,5 +1,7 @@
# include "storm-pomdp/transformer/GlobalPOMDPSelfLoopEliminator.h"
# include "storm/storage/BitVector.h"
# include "storm/logic/Formulas.h"
# include "storm/logic/FragmentSpecification.h"
# include <vector>
# include <storm/transformer/ChoiceSelector.h>
@ -7,79 +9,92 @@ namespace storm {
namespace transformer {
template < typename ValueType >
bool GlobalPOMDPSelfLoopEliminator < ValueType > : : preservesFormula ( storm : : logic : : Formula const & formula ) const {
if ( formula . isOperatorFormula ( ) & & formula . asOperatorFormula ( ) . hasOptimalityType ( ) ) {
bool maxProb = formula . isProbabilityOperatorFormula ( ) & & storm : : solver : : maximize ( formula . asOperatorFormula ( ) . getOptimalityType ( ) ) ;
bool minRew = formula . isRewardOperatorFormula ( ) & & storm : : solver : : minimize ( formula . asOperatorFormula ( ) . getOptimalityType ( ) ) ;
auto const & subformula = formula . asOperatorFormula ( ) . getSubformula ( ) ;
if ( subformula . isEventuallyFormula ( ) ) {
if ( subformula . asEventuallyFormula ( ) . getSubformula ( ) . isInFragment ( storm : : logic : : propositional ( ) ) ) {
return maxProb | | minRew ;
}
} else if ( subformula . isUntilFormula ( ) ) {
if ( subformula . asUntilFormula ( ) . getLeftSubformula ( ) . isInFragment ( storm : : logic : : propositional ( ) ) & & subformula . asUntilFormula ( ) . getRightSubformula ( ) . isInFragment ( storm : : logic : : propositional ( ) ) ) {
return maxProb ;
}
}
}
return false ;
}
template < typename ValueType >
std : : shared_ptr < storm : : models : : sparse : : Pomdp < ValueType > > GlobalPOMDPSelfLoopEliminator < ValueType > : : transform ( ) const
{
uint64_t nrStates = pomdp . getNumberOfStates ( ) ;
std : : vector < storm : : storage : : BitVector > observationSelfLoopMasks ;
for ( uint64_t obs = 0 ; obs < pomdp . getNrObservations ( ) ; + + obs ) {
observationSelfLoopMasks . push_back ( storm : : storage : : BitVector ( 1 , false ) ) ;
assert ( observationSelfLoopMasks . back ( ) . size ( ) = = 1 ) ;
}
assert ( pomdp . getNrObservations ( ) > = 1 ) ;
assert ( observationSelfLoopMasks . size ( ) = = pomdp . getNrObservations ( ) ) ;
// For each observation, get the choice indices that have a selfloop at every state with that observation
std : : vector < storm : : storage : : BitVector > observationSelfLoopMasks ( pomdp . getNrObservations ( ) ) ;
for ( uint64_t state = 0 ; state < nrStates ; + + state ) {
uint32_t observation = pomdp . getObservation ( state ) ;
assert ( pomdp . getNumberOfChoices ( state ) ! = 0 ) ;
if ( pomdp . getNumberOfChoices ( state ) = = 1 ) {
continue ;
STORM_LOG_ASSERT ( observation < observationSelfLoopMasks . size ( ) , " Observation index ( " < < observation < < " ) should be less than number of observations ( " < < observationSelfLoopMasks . size ( ) < < " ). " ) ;
auto & observationSelfLoopMask = observationSelfLoopMasks [ observation ] ;
// If we see this observation for the first time, add the corresponding amount of bits to the observation mask
if ( observationSelfLoopMask . size ( ) = = 0 ) {
observationSelfLoopMask . resize ( pomdp . getNumberOfChoices ( state ) , true ) ;
}
storm : : storage : : BitVector actionVector ( pomdp . getNumberOfChoices ( state ) , false ) ;
for ( uint64_t action = 0 ; action < pomdp . getNumberOfChoices ( state ) ; + + action ) {
STORM_LOG_ASSERT ( observationSelfLoopMask . size ( ) = = pomdp . getNumberOfChoices ( state ) , " State " < < state < < " has " < < pomdp . getNumberOfChoices ( state ) < < " actions, different from other state(s) with same observation, which have " < < observationSelfLoopMask . size ( ) < < " actions. " ) ;
// Iterate over all set bits in observationSelfLoopMask
// We use `getNextSetIndex` (instead of the more concise `for each`-syntax) to safely unset bits in the BitVector while iterating over it.
for ( uint64_t localChoiceIndex = observationSelfLoopMask . getNextSetIndex ( 0 ) ; localChoiceIndex < observationSelfLoopMask . size ( ) ; localChoiceIndex = observationSelfLoopMask . getNextSetIndex ( localChoiceIndex + 1 ) ) {
// We just look at the first entry.
for ( auto const & entry : pomdp . getTransitionMatrix ( ) . getRow ( state , action ) ) {
if ( storm : : utility : : isOne ( entry . getValue ( ) ) & & entry . getColumn ( ) = = state ) {
actionVector . set ( action ) ;
}
break ;
auto row = pomdp . getTransitionMatrix ( ) . getRow ( state , localChoiceIndex ) ;
STORM_LOG_ASSERT ( row . getNumberOfEntries ( ) > 0 , " Unexpected empty row. " ) ;
if ( row . getNumberOfEntries ( ) > 1 | | row . begin ( ) - > getColumn ( ) ! = state ) {
observationSelfLoopMask . set ( localChoiceIndex , false ) ;
}
}
}
STORM_LOG_ASSERT ( observation < observationSelfLoopMasks . size ( ) , " Observation index ( " < < observation < < " ) should be less than number of observations ( " < < observationSelfLoopMasks . size ( ) < < " ). " ) ;
if ( observationSelfLoopMasks [ observation ] . size ( ) = = 1 ) {
observationSelfLoopMasks [ observation ] = actionVector ;
} else {
STORM_LOG_ASSERT ( observationSelfLoopMasks [ observation ] . size ( ) = = pomdp . getNumberOfChoices ( state ) , " State " + std : : to_string ( state ) + " has " + std : : to_string ( pomdp . getNumberOfChoices ( state ) ) + " actions, different from other with same observation ( " + std : : to_string ( observationSelfLoopMasks [ observation ] . size ( ) ) + " ). " ) ;
observationSelfLoopMasks [ observation ] & = actionVector ;
// For each observation, make sure that we keep at least one choice
for ( auto & obsMask : observationSelfLoopMasks ) {
if ( obsMask . size ( ) > 0 & & obsMask . full ( ) ) {
obsMask . set ( 0 , false ) ;
}
}
storm : : storage : : BitVector filter ( pomdp . getNumberOfChoices ( ) , false ) ;
// Finally create a filter that is 1 for every choice we can keep.
storm : : storage : : BitVector filter ( pomdp . getNumberOfChoices ( ) , true ) ;
uint64_t offset = 0 ;
for ( uint64_t state = 0 ; state < nrStates ; + + state ) {
uint32_t observation = pomdp . getObservation ( state ) ;
storm : : storage : : BitVector & vec = observationSelfLoopMasks [ observation ] ;
if ( vec . full ( ) ) {
vec . set ( 0 , false ) ;
}
assert ( ! vec . full ( ) ) ;
// std::cout << "state " << state << " vec " << vec << std::endl;
for ( uint64_t action = 0 ; action < pomdp . getNumberOfChoices ( state ) ; + + action ) {
if ( vec . get ( action ) ) {
filter . set ( offset + action ) ;
}
storm : : storage : : BitVector & observationSelfLoopMask = observationSelfLoopMasks [ pomdp . getObservation ( state ) ] ;
assert ( ! observationSelfLoopMask . full ( ) ) ;
for ( auto const & localChoiceIndex : observationSelfLoopMask ) {
filter . set ( offset + localChoiceIndex , false ) ;
}
offset + = pomdp . getNumberOfChoices ( state ) ;
}
// std::cout << "filter: " << filter << std::endl;
assert ( filter . size ( ) = = pomdp . getNumberOfChoices ( ) ) ;
// TODO rewards with state-action rewards
filter . complement ( ) ;
// std::cout << "selection: " << filter << std::endl;
std : : cout < < " Filter: " < < filter < < std : : endl ;
// We do not consider rewards right now, since this reduction only preserves maximizing probabilities and minimizing expected rewards, anyway.
// In both cases, the reward at a selfloop has no effect (assuming not reaching goal means infinite reward)
STORM_LOG_INFO ( " Selfloop reduction eliminates " < < ( filter . size ( ) - filter . getNumberOfSetBits ( ) ) < < " choices. " ) ;
ChoiceSelector < ValueType > cs ( pomdp ) ;
auto res = cs . transform ( filter ) - > template as < storm : : models : : sparse : : Pomdp < ValueType > > ( ) ;
res - > setIsCanonic ( ) ;
// The transformation preserves canonicity.
res - > setIsCanonic ( pomdp . isCanonic ( ) ) ;
return res ;
}
template class GlobalPOMDPSelfLoopEliminator < storm : : RationalNumber > ;
template
class GlobalPOMDPSelfLoopEliminator < double > ;
template class GlobalPOMDPSelfLoopEliminator < double > ;
}
}