Browse Source
further refactoring of exploration (better name than 'learning') based engine
further refactoring of exploration (better name than 'learning') based engine
Former-commit-id: 72622d821d
main
12 changed files with 810 additions and 455 deletions
-
0src/modelchecker/exploration/BoundValues.cpp
-
137src/modelchecker/exploration/BoundValues.h
-
155src/modelchecker/exploration/Bounds.cpp
-
76src/modelchecker/exploration/Bounds.h
-
209src/modelchecker/exploration/ExplorationInformation.cpp
-
120src/modelchecker/exploration/ExplorationInformation.h
-
81src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp
-
308src/modelchecker/exploration/SparseMdpExplorationModelChecker.h
-
45src/modelchecker/exploration/StateGeneration.cpp
-
44src/modelchecker/exploration/StateGeneration.h
-
46src/modelchecker/exploration/Statistics.cpp
-
44src/modelchecker/exploration/Statistics.h
@ -1,137 +0,0 @@ |
|||||
#ifndef STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ |
|
||||
#define STORM_MODELCHECKER_REACHABILITY_SPARSEMDPEXPLORATIONMODELCHECKER_H_ |
|
||||
|
|
||||
namespace storm { |
|
||||
namespace modelchecker { |
|
||||
namespace exploration_detail { |
|
||||
|
|
||||
// A struct containg the lower and upper bounds per state and action. |
|
||||
struct BoundValues { |
|
||||
std::vector<ValueType> lowerBoundsPerState; |
|
||||
std::vector<ValueType> upperBoundsPerState; |
|
||||
std::vector<ValueType> lowerBoundsPerAction; |
|
||||
std::vector<ValueType> upperBoundsPerAction; |
|
||||
|
|
||||
std::pair<ValueType, ValueType> getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { |
|
||||
ActionType index = explorationInformation.getRowGroup(state); |
|
||||
if (index == explorationInformation.getUnexploredMarker()) { |
|
||||
return std::make_pair(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>()); |
|
||||
} else { |
|
||||
return std::make_pair(lowerBoundsPerState[index], upperBoundsPerState[index]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ValueType getLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const { |
|
||||
ActionType index = explorationInformation.getRowGroup(state); |
|
||||
if (index == explorationInformation.getUnexploredMarker()) { |
|
||||
return storm::utility::zero<ValueType>(); |
|
||||
} else { |
|
||||
return getLowerBoundForRowGroup(index); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ValueType const& getLowerBoundForRowGroup(StateType const& rowGroup) const { |
|
||||
return lowerBoundsPerState[rowGroup]; |
|
||||
} |
|
||||
|
|
||||
ValueType getUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const { |
|
||||
ActionType index = explorationInformation.getRowGroup(state); |
|
||||
if (index == explorationInformation.getUnexploredMarker()) { |
|
||||
return storm::utility::one<ValueType>(); |
|
||||
} else { |
|
||||
return getUpperBoundForRowGroup(index); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const { |
|
||||
return upperBoundsPerState[rowGroup]; |
|
||||
} |
|
||||
|
|
||||
std::pair<ValueType, ValueType> getBoundsForAction(ActionType const& action) const { |
|
||||
return std::make_pair(lowerBoundsPerAction[action], upperBoundsPerAction[action]); |
|
||||
} |
|
||||
|
|
||||
ValueType const& getLowerBoundForAction(ActionType const& action) const { |
|
||||
return lowerBoundsPerAction[action]; |
|
||||
} |
|
||||
|
|
||||
ValueType const& getUpperBoundForAction(ActionType const& action) const { |
|
||||
return upperBoundsPerAction[action]; |
|
||||
} |
|
||||
|
|
||||
ValueType const& getBoundForAction(storm::OptimizationDirection const& direction, ActionType const& action) const { |
|
||||
if (direction == storm::OptimizationDirection::Maximize) { |
|
||||
return getUpperBoundForAction(action); |
|
||||
} else { |
|
||||
return getLowerBoundForAction(action); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ValueType getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) const { |
|
||||
std::pair<ValueType, ValueType> bounds = getBoundsForState(state, explorationInformation); |
|
||||
return bounds.second - bounds.first; |
|
||||
} |
|
||||
|
|
||||
void initializeBoundsForNextState(std::pair<ValueType, ValueType> const& vals = std::pair<ValueType, ValueType>(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>())) { |
|
||||
lowerBoundsPerState.push_back(vals.first); |
|
||||
upperBoundsPerState.push_back(vals.second); |
|
||||
} |
|
||||
|
|
||||
void initializeBoundsForNextAction(std::pair<ValueType, ValueType> const& vals = std::pair<ValueType, ValueType>(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>())) { |
|
||||
lowerBoundsPerAction.push_back(vals.first); |
|
||||
upperBoundsPerAction.push_back(vals.second); |
|
||||
} |
|
||||
|
|
||||
void setLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { |
|
||||
setLowerBoundForRowGroup(explorationInformation.getRowGroup(state), value); |
|
||||
} |
|
||||
|
|
||||
void setLowerBoundForRowGroup(StateType const& group, ValueType const& value) { |
|
||||
lowerBoundsPerState[group] = value; |
|
||||
} |
|
||||
|
|
||||
void setUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { |
|
||||
setUpperBoundForRowGroup(explorationInformation.getRowGroup(state), value); |
|
||||
} |
|
||||
|
|
||||
void setUpperBoundForRowGroup(StateType const& group, ValueType const& value) { |
|
||||
upperBoundsPerState[group] = value; |
|
||||
} |
|
||||
|
|
||||
void setBoundsForAction(ActionType const& action, std::pair<ValueType, ValueType> const& values) { |
|
||||
lowerBoundsPerAction[action] = values.first; |
|
||||
upperBoundsPerAction[action] = values.second; |
|
||||
} |
|
||||
|
|
||||
void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair<ValueType, ValueType> const& values) { |
|
||||
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
|
||||
setBoundsForRowGroup(rowGroup, values); |
|
||||
} |
|
||||
|
|
||||
void setBoundsForRowGroup(StateType const& rowGroup, std::pair<ValueType, ValueType> const& values) { |
|
||||
lowerBoundsPerState[rowGroup] = values.first; |
|
||||
upperBoundsPerState[rowGroup] = values.second; |
|
||||
} |
|
||||
|
|
||||
bool setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newLowerValue) { |
|
||||
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
|
||||
if (lowerBoundsPerState[rowGroup] < newLowerValue) { |
|
||||
lowerBoundsPerState[rowGroup] = newLowerValue; |
|
||||
return true; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
bool setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newUpperValue) { |
|
||||
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
|
||||
if (newUpperValue < upperBoundsPerState[rowGroup]) { |
|
||||
upperBoundsPerState[rowGroup] = newUpperValue; |
|
||||
return true; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,155 @@ |
|||||
|
#include "src/modelchecker/exploration/Bounds.h"
|
||||
|
|
||||
|
#include "src/modelchecker/exploration/ExplorationInformation.h"
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::pair<ValueType, ValueType> Bounds<StateType, ValueType>::getBoundsForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
ActionType index = explorationInformation.getRowGroup(state); |
||||
|
if (index == explorationInformation.getUnexploredMarker()) { |
||||
|
return std::make_pair(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>()); |
||||
|
} else { |
||||
|
return boundsPerState[index]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::pair<ValueType, ValueType> const& Bounds<StateType, ValueType>::getBoundsForExploredState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
ActionType index = explorationInformation.getRowGroup(state); |
||||
|
return boundsPerState[index]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType Bounds<StateType, ValueType>::getLowerBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
ActionType index = explorationInformation.getRowGroup(state); |
||||
|
if (index == explorationInformation.getUnexploredMarker()) { |
||||
|
return storm::utility::zero<ValueType>(); |
||||
|
} else { |
||||
|
return getLowerBoundForRowGroup(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType const& Bounds<StateType, ValueType>::getLowerBoundForRowGroup(StateType const& rowGroup) const { |
||||
|
return boundsPerState[rowGroup].first; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType Bounds<StateType, ValueType>::getUpperBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
ActionType index = explorationInformation.getRowGroup(state); |
||||
|
if (index == explorationInformation.getUnexploredMarker()) { |
||||
|
return storm::utility::one<ValueType>(); |
||||
|
} else { |
||||
|
return getUpperBoundForRowGroup(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType const& Bounds<StateType, ValueType>::getUpperBoundForRowGroup(StateType const& rowGroup) const { |
||||
|
return boundsPerState[rowGroup].second; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::pair<ValueType, ValueType> const& Bounds<StateType, ValueType>::getBoundsForAction(ActionType const& action) const { |
||||
|
return boundsPerAction[action]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType const& Bounds<StateType, ValueType>::getLowerBoundForAction(ActionType const& action) const { |
||||
|
return boundsPerAction[action].first; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType const& Bounds<StateType, ValueType>::getUpperBoundForAction(ActionType const& action) const { |
||||
|
return boundsPerAction[action].second; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType const& Bounds<StateType, ValueType>::getBoundForAction(storm::OptimizationDirection const& direction, ActionType const& action) const { |
||||
|
if (direction == storm::OptimizationDirection::Maximize) { |
||||
|
return getUpperBoundForAction(action); |
||||
|
} else { |
||||
|
return getLowerBoundForAction(action); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ValueType Bounds<StateType, ValueType>::getDifferenceOfStateBounds(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
std::pair<ValueType, ValueType> const& bounds = getBoundsForExploredState(state, explorationInformation); |
||||
|
return bounds.second - bounds.first; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::initializeBoundsForNextState(std::pair<ValueType, ValueType> const& vals) { |
||||
|
boundsPerState.push_back(vals); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::initializeBoundsForNextAction(std::pair<ValueType, ValueType> const& vals) { |
||||
|
boundsPerAction.push_back(vals); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setLowerBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& value) { |
||||
|
setLowerBoundForRowGroup(explorationInformation.getRowGroup(state), value); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setLowerBoundForRowGroup(StateType const& group, ValueType const& value) { |
||||
|
boundsPerState[group].first = value; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setUpperBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& value) { |
||||
|
setUpperBoundForRowGroup(explorationInformation.getRowGroup(state), value); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setUpperBoundForRowGroup(StateType const& group, ValueType const& value) { |
||||
|
boundsPerState[group].second = value; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setBoundsForAction(ActionType const& action, std::pair<ValueType, ValueType> const& values) { |
||||
|
boundsPerAction[action] = values; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setBoundsForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, std::pair<ValueType, ValueType> const& values) { |
||||
|
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
||||
|
setBoundsForRowGroup(rowGroup, values); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Bounds<StateType, ValueType>::setBoundsForRowGroup(StateType const& rowGroup, std::pair<ValueType, ValueType> const& values) { |
||||
|
boundsPerState[rowGroup] = values; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool Bounds<StateType, ValueType>::setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& newLowerValue) { |
||||
|
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
||||
|
if (boundsPerState[rowGroup].first < newLowerValue) { |
||||
|
boundsPerState[rowGroup].first = newLowerValue; |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool Bounds<StateType, ValueType>::setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& newUpperValue) { |
||||
|
StateType const& rowGroup = explorationInformation.getRowGroup(state); |
||||
|
if (newUpperValue < boundsPerState[rowGroup].second) { |
||||
|
boundsPerState[rowGroup].second = newUpperValue; |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
template class Bounds<uint32_t, double>; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,76 @@ |
|||||
|
#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ |
||||
|
#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <utility> |
||||
|
|
||||
|
#include "src/solver/OptimizationDirection.h" |
||||
|
|
||||
|
#include "src/utility/constants.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
class ExplorationInformation; |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
class Bounds { |
||||
|
public: |
||||
|
typedef StateType ActionType; |
||||
|
|
||||
|
std::pair<ValueType, ValueType> getBoundsForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
std::pair<ValueType, ValueType> const& getBoundsForExploredState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
ValueType getLowerBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
ValueType const& getLowerBoundForRowGroup(StateType const& rowGroup) const; |
||||
|
|
||||
|
ValueType getUpperBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const; |
||||
|
|
||||
|
std::pair<ValueType, ValueType> const& getBoundsForAction(ActionType const& action) const; |
||||
|
|
||||
|
ValueType const& getLowerBoundForAction(ActionType const& action) const; |
||||
|
|
||||
|
ValueType const& getUpperBoundForAction(ActionType const& action) const; |
||||
|
|
||||
|
ValueType const& getBoundForAction(storm::OptimizationDirection const& direction, ActionType const& action) const; |
||||
|
|
||||
|
ValueType getDifferenceOfStateBounds(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
void initializeBoundsForNextState(std::pair<ValueType, ValueType> const& vals = std::pair<ValueType, ValueType>(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>())); |
||||
|
|
||||
|
void initializeBoundsForNextAction(std::pair<ValueType, ValueType> const& vals = std::pair<ValueType, ValueType>(storm::utility::zero<ValueType>(), storm::utility::one<ValueType>())); |
||||
|
|
||||
|
void setLowerBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& value); |
||||
|
|
||||
|
void setLowerBoundForRowGroup(StateType const& group, ValueType const& value); |
||||
|
|
||||
|
void setUpperBoundForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& value); |
||||
|
|
||||
|
void setUpperBoundForRowGroup(StateType const& group, ValueType const& value); |
||||
|
|
||||
|
void setBoundsForAction(ActionType const& action, std::pair<ValueType, ValueType> const& values); |
||||
|
|
||||
|
void setBoundsForState(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, std::pair<ValueType, ValueType> const& values); |
||||
|
|
||||
|
void setBoundsForRowGroup(StateType const& rowGroup, std::pair<ValueType, ValueType> const& values); |
||||
|
|
||||
|
bool setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& newLowerValue); |
||||
|
|
||||
|
bool setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation<StateType, ValueType> const& explorationInformation, ValueType const& newUpperValue); |
||||
|
|
||||
|
private: |
||||
|
std::vector<std::pair<ValueType, ValueType>> boundsPerState; |
||||
|
std::vector<std::pair<ValueType, ValueType>> boundsPerAction; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ */ |
@ -0,0 +1,209 @@ |
|||||
|
#include "src/modelchecker/exploration/ExplorationInformation.h"
|
||||
|
|
||||
|
#include "src/settings/SettingsManager.h"
|
||||
|
#include "src/settings/modules/ExplorationSettings.h"
|
||||
|
|
||||
|
#include "src/utility/macros.h"
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
ExplorationInformation<StateType, ValueType>::ExplorationInformation(uint_fast64_t bitsPerBucket, storm::OptimizationDirection const& direction, ActionType const& unexploredMarker) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), optimizationDirection(direction), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability) { |
||||
|
|
||||
|
storm::settings::modules::ExplorationSettings const& settings = storm::settings::explorationSettings(); |
||||
|
localPrecomputation = settings.isLocalPrecomputationSet(); |
||||
|
numberOfExplorationStepsUntilPrecomputation = settings.getNumberOfExplorationStepsUntilPrecomputation(); |
||||
|
if (settings.isNumberOfSampledPathsUntilPrecomputationSet()) { |
||||
|
numberOfSampledPathsUntilPrecomputation = settings.getNumberOfSampledPathsUntilPrecomputation(); |
||||
|
} |
||||
|
|
||||
|
nextStateHeuristic = settings.getNextStateHeuristic(); |
||||
|
STORM_LOG_ASSERT(useDifferenceWeightedProbabilityHeuristic() || useProbabilityHeuristic(), "Illegal next-state heuristic."); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::setInitialStates(std::vector<StateType> const& initialStates) { |
||||
|
stateStorage.initialStateIndices = initialStates; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
StateType ExplorationInformation<StateType, ValueType>::getFirstInitialState() const { |
||||
|
return stateStorage.initialStateIndices.front(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::size_t ExplorationInformation<StateType, ValueType>::getNumberOfInitialStates() const { |
||||
|
return stateStorage.initialStateIndices.size(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::addUnexploredState(storm::generator::CompressedState const& compressedState) { |
||||
|
stateToRowGroupMapping.push_back(unexploredMarker); |
||||
|
unexploredStates[stateStorage.numberOfStates] = compressedState; |
||||
|
++stateStorage.numberOfStates; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::assignStateToRowGroup(StateType const& state, ActionType const& rowGroup) { |
||||
|
stateToRowGroupMapping[state] = rowGroup; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
StateType ExplorationInformation<StateType, ValueType>::assignStateToNextRowGroup(StateType const& state) { |
||||
|
stateToRowGroupMapping[state] = rowGroupIndices.size() - 1; |
||||
|
return stateToRowGroupMapping[state]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
StateType ExplorationInformation<StateType, ValueType>::getNextRowGroup() const { |
||||
|
return rowGroupIndices.size() - 1; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::newRowGroup(ActionType const& action) { |
||||
|
rowGroupIndices.push_back(action); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::newRowGroup() { |
||||
|
newRowGroup(matrix.size()); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::size_t ExplorationInformation<StateType, ValueType>::getNumberOfUnexploredStates() const { |
||||
|
return unexploredStates.size(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::size_t ExplorationInformation<StateType, ValueType>::getNumberOfDiscoveredStates() const { |
||||
|
return stateStorage.numberOfStates; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
StateType const& ExplorationInformation<StateType, ValueType>::getRowGroup(StateType const& state) const { |
||||
|
return stateToRowGroupMapping[state]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
StateType const& ExplorationInformation<StateType, ValueType>::getUnexploredMarker() const { |
||||
|
return unexploredMarker; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::isUnexplored(StateType const& state) const { |
||||
|
return stateToRowGroupMapping[state] == unexploredMarker; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::isTerminal(StateType const& state) const { |
||||
|
return terminalStates.find(state) != terminalStates.end(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
typename ExplorationInformation<StateType, ValueType>::ActionType const& ExplorationInformation<StateType, ValueType>::getStartRowOfGroup(StateType const& group) const { |
||||
|
return rowGroupIndices[group]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::size_t ExplorationInformation<StateType, ValueType>::getRowGroupSize(StateType const& group) const { |
||||
|
return rowGroupIndices[group + 1] - rowGroupIndices[group]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::onlyOneActionAvailable(StateType const& group) const { |
||||
|
return getRowGroupSize(group) == 1; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::addTerminalState(StateType const& state) { |
||||
|
terminalStates.insert(state); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::vector<storm::storage::MatrixEntry<StateType, ValueType>>& ExplorationInformation<StateType, ValueType>::getRowOfMatrix(ActionType const& row) { |
||||
|
return matrix[row]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
std::vector<storm::storage::MatrixEntry<StateType, ValueType>> const& ExplorationInformation<StateType, ValueType>::getRowOfMatrix(ActionType const& row) const { |
||||
|
return matrix[row]; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::addRowsToMatrix(std::size_t const& count) { |
||||
|
matrix.resize(matrix.size() + count); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::maximize() const { |
||||
|
return optimizationDirection == storm::OptimizationDirection::Maximize; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::minimize() const { |
||||
|
return !maximize(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::performPrecomputationExcessiveExplorationSteps(std::size_t& numberExplorationStepsSinceLastPrecomputation) const { |
||||
|
bool result = numberExplorationStepsSinceLastPrecomputation > numberOfExplorationStepsUntilPrecomputation; |
||||
|
if (result) { |
||||
|
numberExplorationStepsSinceLastPrecomputation = 0; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::performPrecomputationExcessiveSampledPaths(std::size_t& numberOfSampledPathsSinceLastPrecomputation) const { |
||||
|
if (!numberOfSampledPathsUntilPrecomputation) { |
||||
|
return false; |
||||
|
} else { |
||||
|
bool result = numberOfSampledPathsSinceLastPrecomputation > numberOfSampledPathsUntilPrecomputation.get(); |
||||
|
if (result) { |
||||
|
numberOfSampledPathsSinceLastPrecomputation = 0; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::useLocalPrecomputation() const { |
||||
|
return localPrecomputation; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::useGlobalPrecomputation() const { |
||||
|
return !useLocalPrecomputation(); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
storm::settings::modules::ExplorationSettings::NextStateHeuristic const& ExplorationInformation<StateType, ValueType>::getNextStateHeuristic() const { |
||||
|
return nextStateHeuristic; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::useDifferenceWeightedProbabilityHeuristic() const { |
||||
|
return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
bool ExplorationInformation<StateType, ValueType>::useProbabilityHeuristic() const { |
||||
|
return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Probability; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
storm::OptimizationDirection const& ExplorationInformation<StateType, ValueType>::getOptimizationDirection() const { |
||||
|
return optimizationDirection; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void ExplorationInformation<StateType, ValueType>::setOptimizationDirection(storm::OptimizationDirection const& direction) { |
||||
|
optimizationDirection = direction; |
||||
|
} |
||||
|
|
||||
|
template class ExplorationInformation<uint32_t, double>; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,120 @@ |
|||||
|
#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ |
||||
|
#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <limits> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include <boost/optional.hpp> |
||||
|
#include <boost/container/flat_set.hpp> |
||||
|
|
||||
|
#include "src/solver/OptimizationDirection.h" |
||||
|
|
||||
|
#include "src/generator/CompressedState.h" |
||||
|
|
||||
|
#include "src/storage/SparseMatrix.h" |
||||
|
#include "src/storage/sparse/StateStorage.h" |
||||
|
|
||||
|
#include "src/settings/modules/ExplorationSettings.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
template<typename StateType, typename ValueType> |
||||
|
class ExplorationInformation { |
||||
|
public: |
||||
|
typedef StateType ActionType; |
||||
|
typedef boost::container::flat_set<StateType> StateSet; |
||||
|
|
||||
|
ExplorationInformation(uint_fast64_t bitsPerBucket, storm::OptimizationDirection const& direction, ActionType const& unexploredMarker = std::numeric_limits<ActionType>::max()); |
||||
|
|
||||
|
void setInitialStates(std::vector<StateType> const& initialStates); |
||||
|
|
||||
|
StateType getFirstInitialState() const; |
||||
|
|
||||
|
std::size_t getNumberOfInitialStates() const; |
||||
|
|
||||
|
void addUnexploredState(storm::generator::CompressedState const& compressedState); |
||||
|
|
||||
|
void assignStateToRowGroup(StateType const& state, ActionType const& rowGroup); |
||||
|
|
||||
|
StateType assignStateToNextRowGroup(StateType const& state); |
||||
|
|
||||
|
StateType getNextRowGroup() const; |
||||
|
|
||||
|
void newRowGroup(ActionType const& action); |
||||
|
|
||||
|
void newRowGroup(); |
||||
|
|
||||
|
std::size_t getNumberOfUnexploredStates() const; |
||||
|
|
||||
|
std::size_t getNumberOfDiscoveredStates() const; |
||||
|
|
||||
|
StateType const& getRowGroup(StateType const& state) const; |
||||
|
|
||||
|
StateType const& getUnexploredMarker() const; |
||||
|
|
||||
|
bool isUnexplored(StateType const& state) const; |
||||
|
|
||||
|
bool isTerminal(StateType const& state) const; |
||||
|
|
||||
|
ActionType const& getStartRowOfGroup(StateType const& group) const; |
||||
|
|
||||
|
std::size_t getRowGroupSize(StateType const& group) const; |
||||
|
|
||||
|
bool onlyOneActionAvailable(StateType const& group) const; |
||||
|
|
||||
|
void addTerminalState(StateType const& state); |
||||
|
|
||||
|
std::vector<storm::storage::MatrixEntry<StateType, ValueType>>& getRowOfMatrix(ActionType const& row); |
||||
|
|
||||
|
std::vector<storm::storage::MatrixEntry<StateType, ValueType>> const& getRowOfMatrix(ActionType const& row) const; |
||||
|
|
||||
|
void addRowsToMatrix(std::size_t const& count); |
||||
|
|
||||
|
bool maximize() const; |
||||
|
|
||||
|
bool minimize() const; |
||||
|
|
||||
|
bool performPrecomputationExcessiveExplorationSteps(std::size_t& numberExplorationStepsSinceLastPrecomputation) const; |
||||
|
|
||||
|
bool performPrecomputationExcessiveSampledPaths(std::size_t& numberOfSampledPathsSinceLastPrecomputation) const; |
||||
|
|
||||
|
bool useLocalPrecomputation() const; |
||||
|
|
||||
|
bool useGlobalPrecomputation() const; |
||||
|
|
||||
|
storm::settings::modules::ExplorationSettings::NextStateHeuristic const& getNextStateHeuristic() const; |
||||
|
|
||||
|
bool useDifferenceWeightedProbabilityHeuristic() const; |
||||
|
|
||||
|
bool useProbabilityHeuristic() const; |
||||
|
|
||||
|
storm::OptimizationDirection const& getOptimizationDirection() const; |
||||
|
|
||||
|
void setOptimizationDirection(storm::OptimizationDirection const& direction); |
||||
|
|
||||
|
private: |
||||
|
storm::storage::sparse::StateStorage<StateType> stateStorage; |
||||
|
|
||||
|
std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> matrix; |
||||
|
std::vector<StateType> rowGroupIndices; |
||||
|
|
||||
|
std::vector<StateType> stateToRowGroupMapping; |
||||
|
StateType unexploredMarker; |
||||
|
std::unordered_map<StateType, storm::generator::CompressedState> unexploredStates; |
||||
|
|
||||
|
storm::OptimizationDirection optimizationDirection; |
||||
|
StateSet terminalStates; |
||||
|
|
||||
|
bool localPrecomputation; |
||||
|
std::size_t numberOfExplorationStepsUntilPrecomputation; |
||||
|
boost::optional<std::size_t> numberOfSampledPathsUntilPrecomputation; |
||||
|
|
||||
|
storm::settings::modules::ExplorationSettings::NextStateHeuristic nextStateHeuristic; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ */ |
@ -0,0 +1,45 @@ |
|||||
|
#include "src/modelchecker/exploration/StateGeneration.h"
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
StateGeneration<StateType, ValueType>::StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& conditionStateExpression, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), conditionStateExpression(conditionStateExpression), targetStateExpression(targetStateExpression) { |
||||
|
// Intentionally left empty.
|
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
void StateGeneration<StateType, ValueType>::setStateToIdCallback(std::function<StateType (storm::generator::CompressedState const&)> const& stateToIdCallback) { |
||||
|
this->stateToIdCallback = stateToIdCallback; |
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
void StateGeneration<StateType, ValueType>::load(storm::generator::CompressedState const& state) { |
||||
|
generator.load(state); |
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
std::vector<StateType> StateGeneration<StateType, ValueType>::getInitialStates() { |
||||
|
return generator.getInitialStates(stateToIdCallback); |
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
storm::generator::StateBehavior<ValueType, StateType> StateGeneration<StateType, ValueType>::expand() { |
||||
|
return generator.expand(stateToIdCallback); |
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
bool StateGeneration<StateType, ValueType>::isConditionState() const { |
||||
|
return generator.satisfies(conditionStateExpression); |
||||
|
} |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
bool StateGeneration<StateType, ValueType>::isTargetState() const { |
||||
|
return generator.satisfies(targetStateExpression); |
||||
|
} |
||||
|
|
||||
|
template class StateGeneration<uint32_t, double>; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATEGENERATION_H_ |
||||
|
#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATEGENERATION_H_ |
||||
|
|
||||
|
#include "src/generator/CompressedState.h" |
||||
|
#include "src/generator/PrismNextStateGenerator.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
namespace generator { |
||||
|
template<typename ValueType, typename StateType> |
||||
|
class PrismNextStateGenerator; |
||||
|
} |
||||
|
|
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template <typename StateType, typename ValueType> |
||||
|
class StateGeneration { |
||||
|
public: |
||||
|
StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& conditionStateExpression, storm::expressions::Expression const& targetStateExpression); |
||||
|
|
||||
|
void setStateToIdCallback(std::function<StateType (storm::generator::CompressedState const&)> const& stateToIdCallback); |
||||
|
|
||||
|
void load(storm::generator::CompressedState const& state); |
||||
|
|
||||
|
std::vector<StateType> getInitialStates(); |
||||
|
|
||||
|
storm::generator::StateBehavior<ValueType, StateType> expand(); |
||||
|
|
||||
|
bool isConditionState() const; |
||||
|
|
||||
|
bool isTargetState() const; |
||||
|
|
||||
|
private: |
||||
|
storm::generator::PrismNextStateGenerator<ValueType, StateType> generator; |
||||
|
std::function<StateType (storm::generator::CompressedState const&)> stateToIdCallback; |
||||
|
storm::expressions::Expression conditionStateExpression; |
||||
|
storm::expressions::Expression targetStateExpression; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATEGENERATION_H_ */ |
@ -0,0 +1,46 @@ |
|||||
|
#include "src/modelchecker/exploration/Statistics.h"
|
||||
|
|
||||
|
#include "src/modelchecker/exploration/ExplorationInformation.h"
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
Statistics<StateType, ValueType>::Statistics() : pathsSampled(0), pathsSampledSinceLastPrecomputation(0), explorationSteps(0), explorationStepsSinceLastPrecomputation(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), numberOfPrecomputations(0), ecDetections(0), failedEcDetections(0), totalNumberOfEcDetected(0) { |
||||
|
// Intentionally left empty.
|
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Statistics<StateType, ValueType>::explorationStep() { |
||||
|
++explorationSteps; |
||||
|
++explorationStepsSinceLastPrecomputation; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Statistics<StateType, ValueType>::sampledPath() { |
||||
|
++pathsSampled; |
||||
|
++pathsSampledSinceLastPrecomputation; |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Statistics<StateType, ValueType>::updateMaxPathLength(std::size_t const& currentPathLength) { |
||||
|
maxPathLength = std::max(maxPathLength, currentPathLength); |
||||
|
} |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
void Statistics<StateType, ValueType>::printToStream(std::ostream& out, ExplorationInformation<StateType, ValueType> const& explorationInformation) const { |
||||
|
out << std::endl << "Exploration statistics:" << std::endl; |
||||
|
out << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << numberOfTargetStates << " target)" << std::endl; |
||||
|
out << "Exploration steps: " << explorationSteps << std::endl; |
||||
|
out << "Sampled paths: " << pathsSampled << std::endl; |
||||
|
out << "Maximal path length: " << maxPathLength << std::endl; |
||||
|
out << "Precomputations: " << numberOfPrecomputations << std::endl; |
||||
|
out << "EC detections: " << ecDetections << " (" << failedEcDetections << " failed, " << totalNumberOfEcDetected << " EC(s) detected)" << std::endl; |
||||
|
} |
||||
|
|
||||
|
template class Statistics<uint32_t, double>; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATISTICS_H_ |
||||
|
#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATISTICS_H_ |
||||
|
|
||||
|
#include <cstddef> |
||||
|
#include <iostream> |
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace exploration_detail { |
||||
|
|
||||
|
template<typename StateType, typename ValueType> |
||||
|
class ExplorationInformation; |
||||
|
|
||||
|
// A struct that keeps track of certain statistics during the exploration. |
||||
|
template<typename StateType, typename ValueType> |
||||
|
struct Statistics { |
||||
|
Statistics(); |
||||
|
|
||||
|
void explorationStep(); |
||||
|
|
||||
|
void sampledPath(); |
||||
|
|
||||
|
void updateMaxPathLength(std::size_t const& currentPathLength); |
||||
|
|
||||
|
void printToStream(std::ostream& out, ExplorationInformation<StateType, ValueType> const& explorationInformation) const; |
||||
|
|
||||
|
std::size_t pathsSampled; |
||||
|
std::size_t pathsSampledSinceLastPrecomputation; |
||||
|
std::size_t explorationSteps; |
||||
|
std::size_t explorationStepsSinceLastPrecomputation; |
||||
|
std::size_t maxPathLength; |
||||
|
std::size_t numberOfTargetStates; |
||||
|
std::size_t numberOfExploredStates; |
||||
|
std::size_t numberOfPrecomputations; |
||||
|
std::size_t ecDetections; |
||||
|
std::size_t failedEcDetections; |
||||
|
std::size_t totalNumberOfEcDetected; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATISTICS_H_ */ |
Reference in new issue
xxxxxxxxxx