@ -17,11 +17,11 @@ namespace storm {
namespace multiobjective {
template < typename ValueType >
ProductModel < ValueType > : : ProductModel ( storm : : models : : sparse : : Mdp < ValueType > const & model , storm : : storage : : MemoryStructure const & memory , std : : vector < storm : : storage : : BitVector > const & objectiveDimensions , EpochManager const & epochManager , std : : vector < storm : : storage : : BitVector > & & memoryStateMap , std : : vector < Epoch > const & originalModelSteps ) : objectiveDimensions ( objectiveDimensions ) , epochManager ( epochManager ) , memoryStateMap ( std : : move ( memoryStateMap ) ) {
ProductModel < ValueType > : : ProductModel ( storm : : models : : sparse : : Mdp < ValueType > const & model , storm : : storage : : MemoryStructure const & memory , std : : vector < Dimension < ValueType > > const & dimensions , std : : vector < storm : : storage : : BitVector > const & objectiveDimensions , EpochManager const & epochManager , std : : vector < storm : : storage : : BitVector > & & memoryStateMap , std : : vector < Epoch > const & originalModelSteps ) : dimensions ( dimensions ) , objectiveDimensions ( objectiveDimensions ) , epochManager ( epochManager ) , memoryStateMap ( std : : move ( memoryStateMap ) ) {
storm : : storage : : SparseModelMemoryProduct < ValueType > productBuilder ( memory . product ( model ) ) ;
setReachableStates ( productBuilder , originalModelSteps ) ;
setReachableProduct States ( productBuilder , originalModelSteps ) ;
product = productBuilder . build ( ) - > template as < storm : : models : : sparse : : Mdp < ValueType > > ( ) ;
uint64_t numModelStates = productBuilder . getOriginalModel ( ) . getNumberOfStates ( ) ;
@ -71,52 +71,97 @@ namespace storm {
}
}
}
computeReachableStatesInEpochClasses ( ) ;
}
template < typename ValueType >
void ProductModel < ValueType > : : setReachableStates ( storm : : storage : : SparseModelMemoryProduct < ValueType > & productBuilder , std : : vector < Epoch > const & originalModelSteps ) const {
void ProductModel < ValueType > : : setReachableProductStates ( storm : : storage : : SparseModelMemoryProduct < ValueType > & productBuilder , std : : vector < Epoch > const & originalModelSteps ) const {
storm : : storage : : BitVector lowerBoundedDimensions ( dimensions . size ( ) , false ) ;
for ( uint64_t dim = 0 ; dim < dimensions . size ( ) ; + + dim ) {
if ( ! dimensions [ dim ] . isUpperBounded ) {
lowerBoundedDimensions . set ( dim ) ;
}
}
// We add additional reachable states until no more states are found
std : : vector < storm : : storage : : BitVector > additionalReachableStates ( memoryStateMap . size ( ) , storm : : storage : : BitVector ( productBuilder . getOriginalModel ( ) . getNumberOfStates ( ) , false ) ) ;
for ( uint64_t memState = 0 ; memState < memoryStateMap . size ( ) ; + + memState ) {
auto const & memStateBv = memoryStateMap [ memState ] ;
storm : : storage : : BitVector consideredObjectives ( objectiveDimensions . size ( ) , false ) ;
do {
storm : : storage : : BitVector memStatePrimeBv = memStateBv ;
for ( auto const & objIndex : consideredObjectives ) {
memStatePrimeBv & = ~ objectiveDimensions [ objIndex ] ;
}
if ( memStatePrimeBv ! = memStateBv ) {
for ( uint64_t choice = 0 ; choice < productBuilder . getOriginalModel ( ) . getTransitionMatrix ( ) . getRowCount ( ) ; + + choice ) {
bool consideredChoice = true ;
bool converged = false ;
while ( ! converged ) {
for ( uint64_t memState = 0 ; memState < memoryStateMap . size ( ) ; + + memState ) {
auto const & memStateBv = memoryStateMap [ memState ] ;
storm : : storage : : BitVector consideredObjectives ( objectiveDimensions . size ( ) , false ) ;
do {
// The current set of considered objectives can be skipped if it contains an objective that only consists of dimensions with lower reward bounds
bool skipThisObjectiveSet = false ;
for ( auto const & objindex : consideredObjectives ) {
if ( objectiveDimensions [ objindex ] . isSubsetOf ( lowerBoundedDimensions ) ) {
skipThisObjectiveSet = true ;
}
}
if ( ! skipThisObjectiveSet ) {
storm : : storage : : BitVector memStatePrimeBv = memStateBv ;
for ( auto const & objIndex : consideredObjectives ) {
bool objectiveHasStep = false ;
for ( auto const & dim : objectiveDimensions [ objIndex ] ) {
if ( epochManager . getDimensionOfEpoch ( originalModelSteps [ choice ] , dim ) > 0 ) {
objectiveHasStep = true ;
break ;
memStatePrimeBv & = ~ objectiveDimensions [ objIndex ] ;
}
if ( memStatePrimeBv ! = memStateBv ) {
uint64_t memStatePrime = convertMemoryState ( memStatePrimeBv ) ;
for ( uint64_t choice = 0 ; choice < productBuilder . getOriginalModel ( ) . getTransitionMatrix ( ) . getRowCount ( ) ; + + choice ) {
// Consider the choice only if for every considered objective there is one dimension for which the step of this choice is positive
bool consideredChoice = true ;
for ( auto const & objIndex : consideredObjectives ) {
bool objectiveHasStep = false ;
auto upperBoundedObjectiveDimensions = objectiveDimensions [ objIndex ] & ( ~ lowerBoundedDimensions ) ;
for ( auto const & dim : upperBoundedObjectiveDimensions ) {
if ( epochManager . getDimensionOfEpoch ( originalModelSteps [ choice ] , dim ) > 0 ) {
objectiveHasStep = true ;
break ;
}
}
if ( ! objectiveHasStep ) {
consideredChoice = false ;
break ;
}
}
if ( consideredChoice ) {
for ( auto const & successor : productBuilder . getOriginalModel ( ) . getTransitionMatrix ( ) . getRow ( choice ) ) {
if ( productBuilder . isStateReachable ( successor . getColumn ( ) , memState ) & & ! productBuilder . isStateReachable ( successor . getColumn ( ) , memStatePrime ) ) {
additionalReachableStates [ memStatePrime ] . set ( successor . getColumn ( ) ) ;
}
}
}
}
if ( ! objectiveHasStep ) {
consideredChoice = false ;
break ;
}
}
if ( consideredChoice ) {
for ( auto const & successor : productBuilder . getOriginalModel ( ) . getTransitionMatrix ( ) . getRow ( choice ) ) {
if ( productBuilder . isStateReachable ( successor . getColumn ( ) , memState ) ) {
additionalReachableStates [ convertMemoryState ( memStatePrimeBv ) ] . set ( successor . getColumn ( ) ) ;
}
}
consideredObjectives . increment ( ) ;
} while ( ! consideredObjectives . empty ( ) ) ;
}
storm : : storage : : BitVector consideredDimensions ( dimensions . size ( ) , false ) ;
do {
if ( consideredDimensions . isSubsetOf ( lowerBoundedDimensions ) ) {
for ( uint64_t memoryState = 0 ; memoryState < productBuilder . getMemory ( ) . getNumberOfStates ( ) ; + + memoryState ) {
uint64_t memoryStatePrime = convertMemoryState ( convertMemoryState ( memoryState ) | consideredDimensions ) ;
for ( uint64_t modelState = 0 ; modelState < productBuilder . getOriginalModel ( ) . getNumberOfStates ( ) ; + + modelState ) {
if ( productBuilder . isStateReachable ( modelState , memoryState ) & & ! productBuilder . isStateReachable ( modelState , memoryStatePrime ) ) {
additionalReachableStates [ memoryStatePrime ] . set ( modelState ) ;
}
}
}
}
consideredObjectives . increment ( ) ;
} while ( ! consideredObjectives . empty ( ) ) ;
}
for ( uint64_t memState = 0 ; memState < memoryStateMap . size ( ) ; + + memState ) {
for ( auto const & modelState : additionalReachableStates [ memState ] ) {
productBuilder . addReachableState ( modelState , memState ) ;
consideredDimensions . increment ( ) ;
} while ( ! consideredDimensions . empty ( ) ) ;
converged = true ;
for ( uint64_t memState = 0 ; memState < memoryStateMap . size ( ) ; + + memState ) {
if ( ! additionalReachableStates [ memState ] . empty ( ) ) {
converged = false ;
for ( auto const & modelState : additionalReachableStates [ memState ] ) {
productBuilder . addReachableState ( modelState , memState ) ;
}
additionalReachableStates [ memState ] . clear ( ) ;
}
}
}
}
@ -178,7 +223,7 @@ namespace storm {
}
template < typename ValueType >
std : : vector < std : : vector < ValueType > > ProductModel < ValueType > : : computeObjectiveRewards ( Epoch const & epoch , std : : vector < storm : : modelchecker : : multiobjective : : Objective < ValueType > > const & objectives , std : : vector < Dimension < ValueType > > const & dimension s ) const {
std : : vector < std : : vector < ValueType > > ProductModel < ValueType > : : computeObjectiveRewards ( EpochClass const & epochClass , std : : vector < storm : : modelchecker : : multiobjective : : Objective < ValueType > > const & objectives ) const {
std : : vector < std : : vector < ValueType > > objectiveRewards ;
objectiveRewards . reserve ( objectives . size ( ) ) ;
@ -208,10 +253,10 @@ namespace storm {
while ( ! relevantObjectives . full ( ) ) {
relevantObjectives . increment ( ) ;
// find out whether objective reward should be earned within this epoch
// find out whether objective reward should be earned within this epoch class
bool collectRewardInEpoch = true ;
for ( auto const & subObjIndex : relevantObjectives ) {
if ( dimensions [ dimensionIndexMap [ subObjIndex ] ] . isUpperBounded = = epochManager . isBottomDimension ( epoch , dimensionIndexMap [ subObjIndex ] ) ) {
if ( dimensions [ dimensionIndexMap [ subObjIndex ] ] . isUpperBounded = = epochManager . isBottomDimensionEpochClass ( epochClass , dimensionIndexMap [ subObjIndex ] ) ) {
collectRewardInEpoch = false ;
break ;
}
@ -252,7 +297,7 @@ namespace storm {
bool rewardCollectedInEpoch = true ;
if ( formula . getSubformula ( ) . isCumulativeRewardFormula ( ) ) {
assert ( objectiveDimensions [ objIndex ] . getNumberOfSetBits ( ) = = 1 ) ;
rewardCollectedInEpoch = ! epochManager . isBottomDimension ( epoch , * objectiveDimensions [ objIndex ] . begin ( ) ) ;
rewardCollectedInEpoch = ! epochManager . isBottomDimensionEpochClass ( epochClass , * objectiveDimensions [ objIndex ] . begin ( ) ) ;
} else {
STORM_LOG_THROW ( formula . getSubformula ( ) . isTotalRewardFormula ( ) , storm : : exceptions : : UnexpectedException , " Unexpected type of formula " < < formula ) ;
}
@ -270,115 +315,157 @@ namespace storm {
}
template < typename ValueType >
storm : : storage : : BitVector ProductModel < ValueType > : : computeInStates ( Epoch const & epoch ) const {
storm : : storage : : SparseMatrix < ValueType > const & productMatrix = getProduct ( ) . getTransitionMatrix ( ) ;
storm : : storage : : BitVector const & ProductModel < ValueType > : : getInStates ( EpochClass const & epochClass ) const {
STORM_LOG_ASSERT ( inStates . find ( epochClass ) ! = inStates . end ( ) , " Could not find InStates for the given epoch class " ) ;
return inStates . find ( epochClass ) - > second ;
}
template < typename ValueType >
void ProductModel < ValueType > : : computeReachableStatesInEpochClasses ( ) {
std : : set < Epoch > possibleSteps ( steps . begin ( ) , steps . end ( ) ) ;
std : : set < EpochClass , std : : function < bool ( EpochClass const & , EpochClass const & ) > > reachableEpochClasses ( std : : bind ( & EpochManager : : epochClassOrder , & epochManager , std : : placeholders : : _1 , std : : placeholders : : _2 ) ) ;
// Initialize the result. Initial states are only considered if the epoch contains no bottom dimension.
storm : : storage : : BitVector result ;
if ( epochManager . hasBottomDimension ( epoch ) ) {
result = storm : : storage : : BitVector ( getProduct ( ) . getNumberOfStates ( ) ) ;
} else {
result = getProduct ( ) . getInitialStates ( ) ;
std : : vector < Epoch > candidates ( { epochManager . getBottomEpoch ( ) } ) ;
std : : set < Epoch > newCandidates ;
bool converged = false ;
while ( ! converged ) {
converged = true ;
for ( auto const & candidate : candidates ) {
converged & = ! reachableEpochClasses . insert ( epochManager . getEpochClass ( candidate ) ) . second ;
for ( auto const & step : possibleSteps ) {
epochManager . gatherPredecessorEpochs ( newCandidates , candidate , step ) ;
}
}
candidates . assign ( newCandidates . begin ( ) , newCandidates . end ( ) ) ;
newCandidates . clear ( ) ;
}
// Compute the set of objectives that can not be satisfied anymore in the current epoch
storm : : storage : : BitVector irrelevantObjectives ( objectiveDimensions . size ( ) , false ) ;
for ( uint64_t objIndex = 0 ; objIndex < objectiveDimensions . size ( ) ; + + objIndex ) {
bool objIrrelevant = true ;
for ( auto const & dim : objectiveDimensions [ objIndex ] ) {
if ( ! epochManager . isBottomDimension ( epoch , dim ) ) {
objIrrelevant = false ;
for ( auto epochClassIt = reachableEpochClasses . rbegin ( ) ; epochClassIt ! = reachableEpochClasses . rend ( ) ; + + epochClassIt ) {
std : : vector < EpochClass > predecessors ;
for ( auto predecessorIt = reachableEpochClasses . rbegin ( ) ; predecessorIt ! = epochClassIt ; + + predecessorIt ) {
if ( epochManager . isPredecessorEpochClass ( * predecessorIt , * epochClassIt ) ) {
predecessors . push_back ( * predecessorIt ) ;
}
}
if ( objIrrelevant ) {
irrelevantObjectives . set ( objIndex , true ) ;
computeReachableStates ( * epochClassIt , predecessors ) ;
}
}
template < typename ValueType >
void ProductModel < ValueType > : : computeReachableStates ( EpochClass const & epochClass , std : : vector < EpochClass > const & predecessors ) {
storm : : storage : : BitVector bottomDimensions ( epochManager . getDimensionCount ( ) , false ) ;
for ( uint64_t dim = 0 ; dim < epochManager . getDimensionCount ( ) ; + + dim ) {
if ( epochManager . isBottomDimensionEpochClass ( epochClass , dim ) ) {
bottomDimensions . set ( dim , true ) ;
}
}
// Perform DFS
storm : : storage : : BitVector reachableStates = getProduct ( ) . getInitialStates ( ) ;
std : : vector < uint_fast64_t > stack ( reachableStates . begin ( ) , reachableStates . end ( ) ) ;
storm : : storage : : BitVector nonBottomDimensions = ~ bottomDimensions ;
while ( ! stack . empty ( ) ) {
uint64_t state = stack . back ( ) ;
stack . pop_back ( ) ;
for ( uint64_t choice = productMatrix . getRowGroupIndices ( ) [ state ] ; choice < productMatrix . getRowGroupIndices ( ) [ state + 1 ] ; + + choice ) {
auto const & choiceStep = getSteps ( ) [ choice ] ;
if ( ! epochManager . isZeroEpoch ( choiceStep ) ) {
// Compute the set of objectives that might or might not become irrelevant when the epoch is reached via the current choice
storm : : storage : : BitVector maybeIrrelevantObjectives ( objectiveDimensions . size ( ) , false ) ;
for ( uint64_t objIndex = 0 ; objIndex < objectiveDimensions . size ( ) ; + + objIndex ) {
for ( auto const & dim : objectiveDimensions [ objIndex ] ) {
if ( epochManager . isBottomDimension ( epoch , dim ) & & epochManager . getDimensionOfEpoch ( choiceStep , dim ) > 0 ) {
maybeIrrelevantObjectives . set ( objIndex ) ;
break ;
}
}
// Bottom dimensions corresponding to upper bounded subobjectives can not be relevant anymore
// Dimensions with a lower bound where the epoch class is not bottom should stay relevant
storm : : storage : : BitVector allowedRelevantDimensions ( epochManager . getDimensionCount ( ) , true ) ;
storm : : storage : : BitVector forcedRelevantDimensions ( epochManager . getDimensionCount ( ) , false ) ;
for ( uint64_t dim = 0 ; dim < epochManager . getDimensionCount ( ) ; + + dim ) {
if ( dimensions [ dim ] . isUpperBounded & & bottomDimensions . get ( dim ) ) {
allowedRelevantDimensions . set ( dim , false ) ;
} else if ( ! dimensions [ dim ] . isUpperBounded & & nonBottomDimensions . get ( dim ) ) {
forcedRelevantDimensions . set ( dim , false ) ;
}
}
assert ( forcedRelevantDimensions . isSubsetOf ( allowedRelevantDimensions ) ) ;
storm : : storage : : BitVector ecInStates ( getProduct ( ) . getNumberOfStates ( ) , false ) ;
if ( ! epochManager . hasBottomDimensionEpochClass ( epochClass ) ) {
for ( auto const & initState : getProduct ( ) . getInitialStates ( ) ) {
uint64_t transformedInitState = transformProductState ( initState , allowedRelevantDimensions , forcedRelevantDimensions ) ;
ecInStates . set ( transformedInitState , true ) ;
}
}
for ( auto const & predecessor : predecessors ) {
storm : : storage : : BitVector positiveStepDimensions ( epochManager . getDimensionCount ( ) , false ) ;
for ( uint64_t dim = 0 ; dim < epochManager . getDimensionCount ( ) ; + + dim ) {
if ( ! epochManager . isBottomDimensionEpochClass ( predecessor , dim ) & & bottomDimensions . get ( dim ) ) {
positiveStepDimensions . set ( dim , true ) ;
}
}
STORM_LOG_ASSERT ( reachableStates . find ( predecessor ) ! = reachableStates . end ( ) , " Could not find reachable states of predecessor epoch class. " ) ;
storm : : storage : : BitVector predecessorChoices = getProduct ( ) . getTransitionMatrix ( ) . getRowFilter ( reachableStates . find ( predecessor ) - > second ) ;
for ( auto const & choice : predecessorChoices ) {
bool choiceLeadsToThisClass = false ;
Epoch const & choiceStep = getSteps ( ) [ choice ] ;
for ( auto const & dim : positiveStepDimensions ) {
if ( epochManager . getDimensionOfEpoch ( choiceStep , dim ) > 0 ) {
choiceLeadsToThisClass = true ;
}
maybeIrrelevantObjectives & = ~ irrelevantObjectives ;
// For optimization purposes, we treat the case that all objectives will be relevant seperately
if ( maybeIrrelevantObjectives . empty ( ) & & irrelevantObjectives . empty ( ) ) {
for ( auto const & choiceSuccessor : productMatrix . getRow ( choice ) ) {
result . set ( choiceSuccessor . getColumn ( ) , true ) ;
if ( ! reachableStates . get ( choiceSuccessor . getColumn ( ) ) ) {
reachableStates . set ( choiceSuccessor . getColumn ( ) ) ;
stack . push_back ( choiceSuccessor . getColumn ( ) ) ;
}
}
} else {
// Enumerate all possible combinations of maybe relevant objectives
storm : : storage : : BitVector maybeObjSubset ( maybeIrrelevantObjectives . getNumberOfSetBits ( ) , false ) ;
do {
for ( auto const & choiceSuccessor : productMatrix . getRow ( choice ) ) {
// Compute the successor memory state for the current objective-subset and transition
storm : : storage : : BitVector successorMemoryState = convertMemoryState ( getMemoryState ( choiceSuccessor . getColumn ( ) ) ) ;
// Unselect dimensions belonging to irrelevant objectives
for ( auto const & irrelevantObjIndex : irrelevantObjectives ) {
successorMemoryState & = ~ objectiveDimensions [ irrelevantObjIndex ] ;
}
// Unselect objectives that are not in the current subset of maybe relevant objectives
// We can skip a subset if it selects an objective that is irrelevant anyway (according to the original successor memorystate).
bool skipThisSubSet = false ;
uint64_t i = 0 ;
for ( auto const & objIndex : maybeIrrelevantObjectives ) {
if ( maybeObjSubset . get ( i ) ) {
if ( successorMemoryState . isDisjointFrom ( objectiveDimensions [ objIndex ] ) ) {
skipThisSubSet = true ;
break ;
} else {
successorMemoryState & = ~ objectiveDimensions [ objIndex ] ;
}
}
+ + i ;
}
if ( ! skipThisSubSet ) {
uint64_t successorState = getProductState ( getModelState ( choiceSuccessor . getColumn ( ) ) , convertMemoryState ( successorMemoryState ) ) ;
result . set ( successorState , true ) ;
if ( ! reachableStates . get ( successorState ) ) {
reachableStates . set ( successorState ) ;
stack . push_back ( successorState ) ;
}
}
}
maybeObjSubset . increment ( ) ;
} while ( ! maybeObjSubset . empty ( ) ) ;
}
if ( choiceLeadsToThisClass ) {
for ( auto const & transition : getProduct ( ) . getTransitionMatrix ( ) . getRow ( choice ) ) {
uint64_t successorState = transformProductState ( transition . getColumn ( ) , allowedRelevantDimensions , forcedRelevantDimensions ) ;
ecInStates . set ( successorState , true ) ;
}
} else {
for ( auto const & choiceSuccessor : productMatrix . getRow ( choice ) ) {
if ( ! reachableStates . get ( choiceSuccessor . getColumn ( ) ) ) {
reachableStates . set ( choiceSuccessor . getColumn ( ) ) ;
stack . push_back ( choiceSuccessor . getColumn ( ) ) ;
}
}
}
}
// Find all states reachable from an InState via DFS.
storm : : storage : : BitVector ecReachableStates = ecInStates ;
std : : vector < uint64_t > dfsStack ( ecReachableStates . begin ( ) , ecReachableStates . end ( ) ) ;
while ( ! dfsStack . empty ( ) ) {
uint64_t currentState = dfsStack . back ( ) ;
dfsStack . pop_back ( ) ;
for ( uint64_t choice = getProduct ( ) . getTransitionMatrix ( ) . getRowGroupIndices ( ) [ currentState ] ; choice ! = getProduct ( ) . getTransitionMatrix ( ) . getRowGroupIndices ( ) [ currentState + 1 ] ; + + choice ) {
bool choiceLeadsOutsideOfEpoch = false ;
Epoch const & choiceStep = getSteps ( ) [ choice ] ;
for ( auto const & dim : nonBottomDimensions ) {
if ( epochManager . getDimensionOfEpoch ( choiceStep , dim ) > 0 ) {
choiceLeadsOutsideOfEpoch = true ;
}
}
for ( auto const & transition : getProduct ( ) . getTransitionMatrix ( ) . getRow ( choice ) ) {
uint64_t successorState = transformProductState ( transition . getColumn ( ) , allowedRelevantDimensions , forcedRelevantDimensions ) ;
if ( choiceLeadsOutsideOfEpoch ) {
ecInStates . set ( successorState , true ) ;
}
if ( ! ecReachableStates . get ( successorState ) ) {
ecReachableStates . set ( successorState , true ) ;
dfsStack . push_back ( successorState ) ;
}
}
}
}
return result ;
reachableStates [ epochClass ] = std : : move ( ecReachableStates ) ;
inStates [ epochClass ] = std : : move ( ecInStates ) ;
}
template < typename ValueType >
uint64_t ProductModel < ValueType > : : transformProductState ( uint64_t productState , storm : : storage : : BitVector const & allowedRelevantDimensions , storm : : storage : : BitVector const & forcedRelevantDimensions ) const {
return getProductState ( getModelState ( productState ) , transformMemoryState ( getMemoryState ( productState ) , allowedRelevantDimensions , forcedRelevantDimensions ) ) ;
}
template < typename ValueType >
uint64_t ProductModel < ValueType > : : transformMemoryState ( uint64_t memoryState , storm : : storage : : BitVector const & allowedRelevantDimensions , storm : : storage : : BitVector const & forcedRelevantDimensions ) const {
if ( allowedRelevantDimensions . full ( ) & & forcedRelevantDimensions . empty ( ) ) {
return memoryState ;
}
storm : : storage : : BitVector memoryStateBv = convertMemoryState ( memoryState ) | forcedRelevantDimensions ;
for ( uint64_t dim = 0 ; dim < epochManager . getDimensionCount ( ) ; + + dim ) {
if ( ! allowedRelevantDimensions . get ( dim ) & & memoryStateBv . get ( dim ) ) {
memoryStateBv & = ~ objectiveDimensions [ dimensions [ dim ] . objectiveIndex ] ;
}
}
return convertMemoryState ( memoryStateBv ) ;
}
template class ProductModel < double > ;
template class ProductModel < storm : : RationalNumber > ;