Browse Source

initial support for multi-reward structures in counterexample generation

tempestpy_adaptions
Sebastian Junges 6 years ago
parent
commit
6051363782
  1. 156
      src/storm-counterexamples/counterexamples/SMTMinimalLabelSetGenerator.h

156
src/storm-counterexamples/counterexamples/SMTMinimalLabelSetGenerator.h

@ -1482,7 +1482,7 @@ namespace storm {
* Returns the sub-model obtained from removing all choices that do not originate from the specified filterLabelSet.
* Also returns the Labelsets of the sub-model.
*/
static std::pair<std::shared_ptr<storm::models::sparse::Model<T>>, std::vector<boost::container::flat_set<uint_fast64_t>>> restrictModelToLabelSet(storm::models::sparse::Model<T> const& model, boost::container::flat_set<uint_fast64_t> const& filterLabelSet, boost::optional<std::string> const& rewardName = boost::none, boost::optional<uint64_t> absorbState = boost::none) {
static std::pair<std::shared_ptr<storm::models::sparse::Model<T>>, std::vector<boost::container::flat_set<uint_fast64_t>>> restrictModelToLabelSet(storm::models::sparse::Model<T> const& model, boost::container::flat_set<uint_fast64_t> const& filterLabelSet, boost::optional<uint64_t> absorbState = boost::none) {
bool customRowGrouping = model.isOfType(storm::models::ModelType::Mdp);
@ -1535,30 +1535,42 @@ namespace storm {
return std::make_pair(resultModel, std::move(resultLabelSet));
}
static T computeMaximalReachabilityProbability(Environment const& env, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, boost::optional<std::string> const& rewardName) {
T result = storm::utility::zero<T>();
static std::vector<T> computeMaximalReachabilityProbability(Environment const& env, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, boost::optional<std::vector<std::string>> const& rewardName) {
std::vector<T> results;
std::vector<T> allStatesResult;
STORM_LOG_DEBUG("Invoking model checker.");
if (model.isOfType(storm::models::ModelType::Dtmc)) {
if (rewardName == boost::none) {
results.push_back(storm::utility::zero<T>());
allStatesResult = storm::modelchecker::helper::SparseDtmcPrctlHelper<T>::computeUntilProbabilities(env, false, model.getTransitionMatrix(), model.getBackwardTransitions(), phiStates, psiStates, false);
for (auto state : model.getInitialStates()) {
results.back() = std::max(results.back(), allStatesResult[state]);
}
} else {
allStatesResult = storm::modelchecker::helper::SparseDtmcPrctlHelper<T>::computeReachabilityRewards(env, false, model.getTransitionMatrix(), model.getBackwardTransitions(), model.getRewardModel(rewardName.get()), psiStates, false);
for (auto const &rewName : rewardName.get()) {
results.push_back(storm::utility::zero<T>());
allStatesResult = storm::modelchecker::helper::SparseDtmcPrctlHelper<T>::computeReachabilityRewards(env, false, model.getTransitionMatrix(), model.getBackwardTransitions(), model.getRewardModel(rewName), psiStates, false);
for (auto state : model.getInitialStates()) {
results.back() = std::max(results.back(), allStatesResult[state]);
}
}
}
} else {
if (rewardName == boost::none) {
results.push_back(storm::utility::zero<T>());
storm::modelchecker::helper::SparseMdpPrctlHelper<T> modelCheckerHelper;
allStatesResult = std::move(modelCheckerHelper.computeUntilProbabilities(env, false, model.getTransitionMatrix(),model.getBackwardTransitions(), phiStates,psiStates, false, false).values);
for (auto state : model.getInitialStates()) {
results.back() = std::max(results.back(), allStatesResult[state]);
}
} else {
STORM_LOG_THROW(rewardName != boost::none, storm::exceptions::NotSupportedException, "Reward property counterexample generation is currently only supported for DTMCs.");
}
}
for (auto state : model.getInitialStates()) {
result = std::max(result, allStatesResult[state]);
}
return result;
return results;
}
public:
@ -1598,8 +1610,10 @@ namespace storm {
* @param strictBound Indicates whether the threshold needs to be achieved (true) or exceeded (false).
* @param options A set of options for customization.
*/
static std::vector<boost::container::flat_set<uint_fast64_t>> getMinimalLabelSet(Environment const& env, GeneratorStats& stats, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, double propertyThreshold, boost::optional<std::string> const& rewardName, bool strictBound, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), Options const& options = Options()) {
static std::vector<boost::container::flat_set<uint_fast64_t>> getMinimalLabelSet(Environment const& env, GeneratorStats& stats, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, std::vector<double> propertyThreshold, boost::optional<std::vector<std::string>> const& rewardName, bool strictBound, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), Options const& options = Options()) {
#ifdef STORM_HAVE_Z3
STORM_LOG_THROW(propertyThreshold.size() > 0, storm::exceptions::InvalidArgumentException, "At least one threshold has to be specified.");
STORM_LOG_THROW(propertyThreshold.size() == 1 || (rewardName && rewardName.get().size() == propertyThreshold.size()), storm::exceptions::InvalidArgumentException, "Multiple thresholds is only supported for multiple reward structures");
std::vector<boost::container::flat_set<uint_fast64_t>> result;
// Set up all clocks used for time measurement.
auto totalClock = std::chrono::high_resolution_clock::now();
@ -1638,12 +1652,15 @@ namespace storm {
assert(labelSets.size() == model.getNumberOfChoices());
// (1) Check whether its possible to exceed the threshold if checkThresholdFeasible is set.
double maximalReachabilityProbability = 0;
std::vector<double> maximalReachabilityProbability;
if (options.checkThresholdFeasible) {
maximalReachabilityProbability = computeMaximalReachabilityProbability(env, model, phiStates, psiStates, rewardName);
STORM_LOG_THROW((strictBound && maximalReachabilityProbability >= propertyThreshold) || (!strictBound && maximalReachabilityProbability > propertyThreshold), storm::exceptions::InvalidArgumentException, "Given probability threshold " << propertyThreshold << " can not be " << (strictBound ? "achieved" : "exceeded") << " in model with maximal reachability probability of " << maximalReachabilityProbability << ".");
std::cout << std::endl << "Maximal property value in model is " << maximalReachabilityProbability << "." << std::endl << std::endl;
for (uint64_t i = 0; i < maximalReachabilityProbability.size(); ++i) {
STORM_LOG_THROW((strictBound && maximalReachabilityProbability[i] >= propertyThreshold[i]) || (!strictBound && maximalReachabilityProbability[i] > propertyThreshold[i]), storm::exceptions::InvalidArgumentException, "Given probability threshold " << propertyThreshold[i] << " can not be " << (strictBound ? "achieved" : "exceeded") << " in model with maximal reachability probability of " << maximalReachabilityProbability[i] << ".");
std::cout << std::endl << "Maximal property value in model is " << maximalReachabilityProbability[i] << "." << std::endl << std::endl;
}
}
// (2) Identify all states and commands that are relevant, because only these need to be considered later.
@ -1695,7 +1712,7 @@ namespace storm {
uint_fast64_t lastSize = 0;
uint_fast64_t iterations = 0;
uint_fast64_t currentBound = 0;
double maximalPropertyValue = 0;
std::vector<double> maximalPropertyValue;
uint_fast64_t zeroProbabilityCount = 0;
uint64_t smallestCounterexampleSize = model.getNumberOfChoices(); // Definitive u
uint64_t progressDelay = storm::settings::getModule<storm::settings::modules::GeneralSettings>().getShowProgressDelay();
@ -1722,7 +1739,7 @@ namespace storm {
}
auto subChoiceOrigins = restrictModelToLabelSet(model, commandSet, rewardName, psiStates.getNextSetIndex(0));
auto subChoiceOrigins = restrictModelToLabelSet(model, commandSet, psiStates.getNextSetIndex(0));
std::shared_ptr<storm::models::sparse::Model<T>> const& subModel = subChoiceOrigins.first;
std::vector<boost::container::flat_set<uint_fast64_t>> const& subLabelSets = subChoiceOrigins.second;
@ -1732,8 +1749,13 @@ namespace storm {
// Depending on whether the threshold was successfully achieved or not, we proceed by either analyzing the bad solution or stopping the iteration process.
analysisClock = std::chrono::high_resolution_clock::now();
if ((strictBound && maximalPropertyValue < propertyThreshold) || (!strictBound && maximalPropertyValue <= propertyThreshold)) {
if (maximalPropertyValue == storm::utility::zero<T>()) {
bool violation = false;
for (uint64_t i = 0; i < maximalPropertyValue.size(); i++) {
violation |= (strictBound && maximalPropertyValue[i] < propertyThreshold[i]) || (!strictBound && maximalPropertyValue[i] <= propertyThreshold[i]);
}
if (violation) {
if (!rewardName && maximalPropertyValue.front() == storm::utility::zero<T>()) {
++zeroProbabilityCount;
}
@ -1910,47 +1932,57 @@ namespace storm {
}
static std::vector<boost::container::flat_set<uint_fast64_t>> computeCounterexampleLabelSet(Environment const& env, GeneratorStats& stats, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), Options const& options = Options(true)) {
STORM_LOG_THROW(model.isOfType(storm::models::ModelType::Dtmc) || model.isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "MaxSAT-based counterexample generation is supported only for discrete-time models.");
if (!options.silent) {
std::cout << std::endl << "Generating minimal label counterexample for formula " << *formula << std::endl;
}
struct CexInput {
storm::logic::ComparisonType comparisonType;
double threshold;
boost::optional<std::string> rewardName = boost::none;
std::vector<double> threshold;
boost::optional<std::vector<std::string>> rewardName = boost::none;
bool lowerBoundedFormula = false;
bool strictBound;
storm::storage::BitVector phiStates;
storm::storage::BitVector psiStates;
void addRewardThresholdCombination(std::string reward, double thresh) {
STORM_LOG_THROW(rewardName, storm::exceptions::InvalidOperationException, "Can only add more reward names if a reward name is already set");
rewardName.get().push_back(reward);
threshold.push_back(thresh);
}
};
static CexInput precompute(Environment const& env, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula) {
CexInput result;
STORM_LOG_THROW(formula->isProbabilityOperatorFormula() || formula->isRewardOperatorFormula(), storm::exceptions::InvalidPropertyException, "Counterexample generation does not support this kind of formula. Expecting a probability operator as the outermost formula element.");
if (formula->isProbabilityOperatorFormula()) {
storm::logic::ProbabilityOperatorFormula const& probabilityOperator = formula->asProbabilityOperatorFormula();
STORM_LOG_THROW(probabilityOperator.hasBound(), storm::exceptions::InvalidPropertyException, "Counterexample generation only supports bounded formulas.");
STORM_LOG_THROW(probabilityOperator.getSubformula().isUntilFormula() || probabilityOperator.getSubformula().isEventuallyFormula(), storm::exceptions::InvalidPropertyException, "Path formula is required to be of the form 'phi U psi' for counterexample generation.");
comparisonType = probabilityOperator.getComparisonType();
threshold = probabilityOperator.getThresholdAs<T>();
result.comparisonType = probabilityOperator.getComparisonType();
result.threshold.push_back(probabilityOperator.getThresholdAs<T>());
} else {
assert(formula->isRewardOperatorFormula());
storm::logic::RewardOperatorFormula const& rewardOperator = formula->asRewardOperatorFormula();
STORM_LOG_THROW(rewardOperator.hasBound(), storm::exceptions::InvalidPropertyException, "Counterexample generation only supports bounded formulas.");
STORM_LOG_THROW( rewardOperator.getSubformula().isEventuallyFormula(), storm::exceptions::InvalidPropertyException, "Path formula is required to be of the form 'F phi' for counterexample generation.");
comparisonType = rewardOperator.getComparisonType();
threshold = rewardOperator.getThresholdAs<T>();
rewardName = rewardOperator.getRewardModelName();
STORM_LOG_THROW(!storm::logic::isLowerBound(comparisonType), storm::exceptions::NotSupportedException, "Lower bounds in counterexamples are only supported for probability formulas.");
STORM_LOG_THROW(model.hasRewardModel(rewardName.get()), storm::exceptions::InvalidPropertyException, "Property refers to reward " << rewardName.get() << " but model does not contain such a reward model.");
STORM_LOG_THROW(model.getRewardModel(rewardName.get()).hasOnlyStateRewards(), storm::exceptions::NotSupportedException, "We only support state-based rewards at the moment.");
result.comparisonType = rewardOperator.getComparisonType();
result.threshold.push_back(rewardOperator.getThresholdAs<T>());
result.rewardName = std::vector<std::string>();
result.rewardName.get().push_back(rewardOperator.getRewardModelName());
STORM_LOG_THROW(!storm::logic::isLowerBound(result.comparisonType), storm::exceptions::NotSupportedException, "Lower bounds in counterexamples are only supported for probability formulas.");
STORM_LOG_THROW(model.hasRewardModel(result.rewardName.get().front()), storm::exceptions::InvalidPropertyException, "Property refers to reward " << result.rewardName.get().front() << " but model does not contain such a reward model.");
STORM_LOG_THROW(model.getRewardModel(result.rewardName.get().front()).hasOnlyStateRewards(), storm::exceptions::NotSupportedException, "We only support state-based rewards at the moment.");
}
bool strictBound = comparisonType == storm::logic::ComparisonType::Less;
result.strictBound = result.comparisonType == storm::logic::ComparisonType::Less;
storm::logic::Formula const& subformula = formula->asOperatorFormula().getSubformula();
storm::storage::BitVector phiStates;
storm::storage::BitVector psiStates;
storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<T>> modelchecker(model);
if (subformula.isUntilFormula()) {
STORM_LOG_THROW(!storm::logic::isLowerBound(comparisonType), storm::exceptions::NotSupportedException, "Lower bounds in counterexamples are only supported for eventually formulas.");
STORM_LOG_THROW(!storm::logic::isLowerBound(result.comparisonType), storm::exceptions::NotSupportedException, "Lower bounds in counterexamples are only supported for eventually formulas.");
storm::logic::UntilFormula const& untilFormula = subformula.asUntilFormula();
std::unique_ptr<storm::modelchecker::CheckResult> leftResult = modelchecker.check(env, untilFormula.getLeftSubformula());
@ -1959,8 +1991,8 @@ namespace storm {
storm::modelchecker::ExplicitQualitativeCheckResult const& leftQualitativeResult = leftResult->asExplicitQualitativeCheckResult();
storm::modelchecker::ExplicitQualitativeCheckResult const& rightQualitativeResult = rightResult->asExplicitQualitativeCheckResult();
phiStates = leftQualitativeResult.getTruthValuesVector();
psiStates = rightQualitativeResult.getTruthValuesVector();
result.phiStates = leftQualitativeResult.getTruthValuesVector();
result.psiStates = rightQualitativeResult.getTruthValuesVector();
} else if (subformula.isEventuallyFormula()) {
storm::logic::EventuallyFormula const& eventuallyFormula = subformula.asEventuallyFormula();
@ -1968,12 +2000,12 @@ namespace storm {
storm::modelchecker::ExplicitQualitativeCheckResult const& subQualitativeResult = subResult->asExplicitQualitativeCheckResult();
phiStates = storm::storage::BitVector(model.getNumberOfStates(), true);
psiStates = subQualitativeResult.getTruthValuesVector();
result.phiStates = storm::storage::BitVector(model.getNumberOfStates(), true);
result.psiStates = subQualitativeResult.getTruthValuesVector();
}
bool lowerBoundedFormula = false;
if (storm::logic::isLowerBound(comparisonType)) {
if (storm::logic::isLowerBound(result.comparisonType)) {
// If the formula specifies a lower bound, we need to modify the phi and psi states.
// More concretely, we convert P(min)>lambda(F psi) to P(max)<(1-lambda)(G !psi) = P(max)<(1-lambda)(!psi U prob0E(psi))
// where prob0E(psi) is the set of states for which there exists a strategy \sigma_0 that avoids
@ -1982,27 +2014,32 @@ namespace storm {
// This means that from all states in prob0E(psi) we need to include labels such that \sigma_0
// is actually included in the resulting model. This prevents us from guaranteeing the minimality of
// the returned counterexample, so we warn about that.
if (!options.silent) {
STORM_LOG_WARN("Generating counterexample for lower-bounded property. The resulting command set need not be minimal.");
}
// Modify bound appropriately.
comparisonType = storm::logic::invertPreserveStrictness(comparisonType);
threshold = storm::utility::one<T>() - threshold;
result.comparisonType = storm::logic::invertPreserveStrictness(result.comparisonType);
result.threshold.back() = storm::utility::one<T>() - result.threshold.back();
// Modify the phi and psi states appropriately.
storm::storage::BitVector statesWithProbability0E = storm::utility::graph::performProb0E(model.getTransitionMatrix(), model.getTransitionMatrix().getRowGroupIndices(), model.getBackwardTransitions(), phiStates, psiStates);
phiStates = ~psiStates;
psiStates = std::move(statesWithProbability0E);
storm::storage::BitVector statesWithProbability0E = storm::utility::graph::performProb0E(model.getTransitionMatrix(), model.getTransitionMatrix().getRowGroupIndices(), model.getBackwardTransitions(), result.phiStates, result.psiStates);
result.phiStates = ~result.psiStates;
result.psiStates = std::move(statesWithProbability0E);
// Remember our transformation so we can add commands to guarantee that the prob0E(a) states actually
// have a strategy that voids a states.
lowerBoundedFormula = true;
}
return result;
}
static std::vector<boost::container::flat_set<uint_fast64_t>> computeCounterexampleLabelSet(Environment const& env, GeneratorStats& stats, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, CexInput const& counterexInput, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), Options const& options = Options(true)) {
STORM_LOG_THROW(model.isOfType(storm::models::ModelType::Dtmc) || model.isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "MaxSAT-based counterexample generation is supported only for discrete-time models.");
// Delegate the actual computation work to the function of equal name.
auto startTime = std::chrono::high_resolution_clock::now();
auto labelSets = getMinimalLabelSet(env, stats, symbolicModel, model, phiStates, psiStates, threshold, rewardName, strictBound, dontCareLabels, options);
auto labelSets = getMinimalLabelSet(env, stats, symbolicModel, model, counterexInput.phiStates, counterexInput.psiStates, counterexInput.threshold, counterexInput.rewardName, counterexInput.strictBound, dontCareLabels, options);
auto endTime = std::chrono::high_resolution_clock::now();
if (!options.silent) {
for (auto const& labelSet : labelSets) {
@ -2015,8 +2052,8 @@ namespace storm {
// Extend the command set properly.
for (auto& labelSet : labelSets) {
if (lowerBoundedFormula) {
extendLabelSetLowerBound(model, labelSet, phiStates, psiStates, options.silent);
if (counterexInput.lowerBoundedFormula) {
extendLabelSetLowerBound(model, labelSet, counterexInput.phiStates, counterexInput.psiStates, options.silent);
}
}
return labelSets;
@ -2024,8 +2061,13 @@ namespace storm {
static std::shared_ptr<HighLevelCounterexample> computeCounterexample(Environment const& env, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula) {
#ifdef STORM_HAVE_Z3
std::cout << std::endl << "Generating minimal label counterexample for formula " << *formula << std::endl;
GeneratorStats stats;
auto labelSets = computeCounterexampleLabelSet(env, stats, symbolicModel, model, formula);
CexInput prec = precompute(env, symbolicModel, model, formula);
if (prec.lowerBoundedFormula) {
STORM_LOG_WARN("Generating counterexample for lower-bounded property. The resulting command set need not be minimal.");
}
auto labelSets = computeCounterexampleLabelSet(env, stats, symbolicModel, model, prec);
if (symbolicModel.isPrismProgram()) {

Loading…
Cancel
Save