Browse Source

advanced stopping criteria for multi-counterexamples, additional measurements for cuts, an option to properly disable backward implications, and some cleaning

tempestpy_adaptions
Sebastian Junges 6 years ago
parent
commit
73900f1bbe
  1. 215
      src/storm-counterexamples/counterexamples/SMTMinimalLabelSetGenerator.h

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

@ -28,7 +28,20 @@ namespace storm {
class Environment; class Environment;
namespace counterexamples { namespace counterexamples {
/**
* Helper to avoid case disticinot between prism and jani
* Returns the number of edges/commands in a symbolic model description.
*/
size_t nrCommands(storm::storage::SymbolicModelDescription const& descr) {
if (descr.isJaniModel()) {
return descr.asJaniModel().getNumberOfEdges();
} else {
assert(descr.isPrismModel());
return descr.asPrismProgram().getNumberOfCommands();
}
}
/*! /*!
* This class provides functionality to generate a minimal counterexample to a probabilistic reachability * This class provides functionality to generate a minimal counterexample to a probabilistic reachability
* property in terms of used labels. * property in terms of used labels.
@ -313,12 +326,18 @@ namespace storm {
* @param context The Z3 context in which to build the expressions. * @param context The Z3 context in which to build the expressions.
* @param solver The solver to use for the satisfiability evaluation. * @param solver The solver to use for the satisfiability evaluation.
*/ */
static void assertCuts(storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::vector<boost::container::flat_set<uint_fast64_t>> const& labelSets, storm::storage::BitVector const& psiStates, VariableInformation const& variableInformation, RelevancyInformation const& relevancyInformation, storm::solver::SmtSolver& solver) {
static std::chrono::milliseconds assertCuts(storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::vector<boost::container::flat_set<uint_fast64_t>> const& labelSets, storm::storage::BitVector const& psiStates, VariableInformation const& variableInformation, RelevancyInformation const& relevancyInformation, storm::solver::SmtSolver& solver, bool addBackwardImplications) {
// Walk through the model and // Walk through the model and
// * identify labels enabled in initial states // * identify labels enabled in initial states
// * identify labels that can directly precede a given action // * identify labels that can directly precede a given action
// * identify labels that directly reach a target state // * identify labels that directly reach a target state
// * identify labels that can directly follow a given action // * identify labels that can directly follow a given action
auto assertCutsClock = std::chrono::high_resolution_clock::now();
//
if (addBackwardImplications) {
STORM_LOG_THROW(!symbolicModel.isJaniModel() || !symbolicModel.asJaniModel().usesAssignmentLevels(), storm::exceptions::NotSupportedException, "Counterexample generation with backward implications is not supported for indexed assignments");
}
boost::container::flat_set<uint_fast64_t> initialLabels; boost::container::flat_set<uint_fast64_t> initialLabels;
std::set<boost::container::flat_set<uint_fast64_t>> initialCombinations; std::set<boost::container::flat_set<uint_fast64_t>> initialCombinations;
@ -334,6 +353,7 @@ namespace storm {
storm::storage::BitVector const& initialStates = model.getInitialStates(); storm::storage::BitVector const& initialStates = model.getInitialStates();
for (auto currentState : relevancyInformation.relevantStates) { for (auto currentState : relevancyInformation.relevantStates) {
bool isInitial = initialStates.get(currentState);
for (auto currentChoice : relevancyInformation.relevantChoicesForRelevantStates.at(currentState)) { for (auto currentChoice : relevancyInformation.relevantChoicesForRelevantStates.at(currentState)) {
// If the choice is a synchronization choice, we need to record it. // If the choice is a synchronization choice, we need to record it.
@ -344,7 +364,7 @@ namespace storm {
} }
// If the state is initial, we need to add all the choice labels to the initial label set. // If the state is initial, we need to add all the choice labels to the initial label set.
if (initialStates.get(currentState)) {
if (isInitial) {
initialLabels.insert(labelSets[currentChoice].begin(), labelSets[currentChoice].end()); initialLabels.insert(labelSets[currentChoice].begin(), labelSets[currentChoice].end());
initialCombinations.insert(labelSets[currentChoice]); initialCombinations.insert(labelSets[currentChoice]);
} }
@ -378,6 +398,7 @@ namespace storm {
for (auto const& successorEntry : transitionMatrix.getRow(predecessorChoice)) { for (auto const& successorEntry : transitionMatrix.getRow(predecessorChoice)) {
if (successorEntry.getColumn() == currentState) { if (successorEntry.getColumn() == currentState) {
choiceTargetsCurrentState = true; choiceTargetsCurrentState = true;
break;
} }
} }
@ -389,11 +410,10 @@ namespace storm {
} }
} }
} }
// Store the found implications in a container similar to the preceding label sets. // Store the found implications in a container similar to the preceding label sets.
std::map<boost::container::flat_set<uint_fast64_t>, std::set<boost::container::flat_set<uint_fast64_t>>> backwardImplications; std::map<boost::container::flat_set<uint_fast64_t>, std::set<boost::container::flat_set<uint_fast64_t>>> backwardImplications;
if (!symbolicModel.isJaniModel() || !symbolicModel.asJaniModel().usesAssignmentLevels()) {
if (addBackwardImplications) {
// Create a new solver over the same variables as the given symbolic model description to use it for // Create a new solver over the same variables as the given symbolic model description to use it for
// determining the symbolic cuts. // determining the symbolic cuts.
std::unique_ptr<storm::solver::SmtSolver> localSolver; std::unique_ptr<storm::solver::SmtSolver> localSolver;
@ -587,8 +607,7 @@ namespace storm {
// Iterate over all possible combinations of updates of the preceding command set. // Iterate over all possible combinations of updates of the preceding command set.
std::vector<storm::expressions::Expression> formulae; std::vector<storm::expressions::Expression> formulae;
bool done = false;
while (!done) {
while (true) {
std::map<storm::expressions::Variable, storm::expressions::Expression> currentVariableUpdateCombinationMap; std::map<storm::expressions::Variable, storm::expressions::Expression> currentVariableUpdateCombinationMap;
for (auto const& updateIterator : iteratorVector) { for (auto const& updateIterator : iteratorVector) {
for (auto const& variableUpdatePair : *updateIterator) { for (auto const& variableUpdatePair : *updateIterator) {
@ -616,7 +635,7 @@ namespace storm {
// If we had to reset all iterator to the start, we are done. // If we had to reset all iterator to the start, we are done.
if (k == 0) { if (k == 0) {
done = true;
break;
} }
} }
@ -722,85 +741,88 @@ namespace storm {
assertDisjunction(solver, formulae, *variableInformation.manager); assertDisjunction(solver, formulae, *variableInformation.manager);
} }
} }
STORM_LOG_DEBUG("Asserting taken labels are followed and preceeded by another label if they are not a target label or an initial label, respectively.");
boost::container::flat_map<boost::container::flat_set<uint_fast64_t>, storm::expressions::Expression> labelSetToFormula;
for (auto const& labelSet : relevancyInformation.relevantLabelSets) {
storm::expressions::Expression labelSetFormula = variableInformation.manager->boolean(false);
// Compute the set of unknown labels on the left-hand side of the implication.
boost::container::flat_set<uint_fast64_t> unknownLhsLabels;
std::set_difference(labelSet.begin(), labelSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(unknownLhsLabels, unknownLhsLabels.end()));
for (auto label : unknownLhsLabels) {
labelSetFormula = labelSetFormula || !variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
}
// Only build a constraint if the combination does not lead to a target state and
// no successor set is already known.
storm::expressions::Expression successorExpression;
if (targetCombinations.find(labelSet) == targetCombinations.end() && hasKnownSuccessor.find(labelSet) == hasKnownSuccessor.end()) {
successorExpression = variableInformation.manager->boolean(false);
if(addBackwardImplications) {
auto const& followingLabelSets = followingLabels.at(labelSet);
STORM_LOG_DEBUG("Asserting taken labels are followed and preceeded by another label if they are not a target label or an initial label, respectively.");
boost::container::flat_map<boost::container::flat_set<uint_fast64_t>, storm::expressions::Expression> labelSetToFormula;
for (auto const &labelSet : relevancyInformation.relevantLabelSets) {
storm::expressions::Expression labelSetFormula = variableInformation.manager->boolean(false);
for (auto const& followingSet : followingLabelSets) {
boost::container::flat_set<uint_fast64_t> tmpSet;
// Check which labels of the current following set are not known. This set must be non-empty, because
// otherwise a successor combination would already be known and control cannot reach this point.
std::set_difference(followingSet.begin(), followingSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(tmpSet, tmpSet.end()));
// Construct an expression that enables all unknown labels of the current following set.
storm::expressions::Expression conj = variableInformation.manager->boolean(true);
for (auto label : tmpSet) {
conj = conj && variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
// Compute the set of unknown labels on the left-hand side of the implication.
boost::container::flat_set<uint_fast64_t> unknownLhsLabels;
std::set_difference(labelSet.begin(), labelSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(unknownLhsLabels, unknownLhsLabels.end()));
for (auto label : unknownLhsLabels) {
labelSetFormula = labelSetFormula || !variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
}
// Only build a constraint if the combination does not lead to a target state and
// no successor set is already known.
storm::expressions::Expression successorExpression;
if (targetCombinations.find(labelSet) == targetCombinations.end() && hasKnownSuccessor.find(labelSet) == hasKnownSuccessor.end()) {
successorExpression = variableInformation.manager->boolean(false);
auto const &followingLabelSets = followingLabels.at(labelSet);
for (auto const &followingSet : followingLabelSets) {
boost::container::flat_set<uint_fast64_t> tmpSet;
// Check which labels of the current following set are not known. This set must be non-empty, because
// otherwise a successor combination would already be known and control cannot reach this point.
std::set_difference(followingSet.begin(), followingSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(tmpSet, tmpSet.end()));
// Construct an expression that enables all unknown labels of the current following set.
storm::expressions::Expression conj = variableInformation.manager->boolean(true);
for (auto label : tmpSet) {
conj = conj && variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
}
successorExpression = successorExpression || conj;
} }
successorExpression = successorExpression || conj;
} else {
successorExpression = variableInformation.manager->boolean(true);
} }
} else {
successorExpression = variableInformation.manager->boolean(true);
}
// Constructed following cuts at this point.
// Only build a constraint if the combination is no initial combination and no
// predecessor set is already known.
storm::expressions::Expression predecessorExpression;
if (initialCombinations.find(labelSet) == initialCombinations.end() && hasKnownPredecessor.find(labelSet) == hasKnownPredecessor.end()) {
predecessorExpression = variableInformation.manager->boolean(false);
// Constructed following cuts at this point.
// Only build a constraint if the combination is no initial combination and no
// predecessor set is already known.
storm::expressions::Expression predecessorExpression;
if (initialCombinations.find(labelSet) == initialCombinations.end() && hasKnownPredecessor.find(labelSet) == hasKnownPredecessor.end()) {
predecessorExpression = variableInformation.manager->boolean(false);
// std::cout << "labelSet" << std::endl; // std::cout << "labelSet" << std::endl;
// for (auto const& e : labelSet) { // for (auto const& e : labelSet) {
// std::cout << e << ", "; // std::cout << e << ", ";
// } // }
// std::cout << std::endl; // std::cout << std::endl;
auto const& preceedingLabelSets = backwardImplications.at(labelSet);
auto const &preceedingLabelSets = backwardImplications.at(labelSet);
for (auto const& preceedingSet : preceedingLabelSets) {
boost::container::flat_set<uint_fast64_t> tmpSet;
// Check which labels of the current following set are not known. This set must be non-empty, because
// otherwise a predecessor combination would already be known and control cannot reach this point.
std::set_difference(preceedingSet.begin(), preceedingSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(tmpSet, tmpSet.end()));
// Construct an expression that enables all unknown labels of the current following set.
storm::expressions::Expression conj = variableInformation.manager->boolean(true);
for (auto label : tmpSet) {
conj = conj && variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
for (auto const &preceedingSet : preceedingLabelSets) {
boost::container::flat_set<uint_fast64_t> tmpSet;
// Check which labels of the current following set are not known. This set must be non-empty, because
// otherwise a predecessor combination would already be known and control cannot reach this point.
std::set_difference(preceedingSet.begin(), preceedingSet.end(), relevancyInformation.knownLabels.begin(), relevancyInformation.knownLabels.end(), std::inserter(tmpSet, tmpSet.end()));
// Construct an expression that enables all unknown labels of the current following set.
storm::expressions::Expression conj = variableInformation.manager->boolean(true);
for (auto label : tmpSet) {
conj = conj && variableInformation.labelVariables.at(variableInformation.labelToIndexMap.at(label));
}
predecessorExpression = predecessorExpression || conj;
} }
predecessorExpression = predecessorExpression || conj;
} else {
predecessorExpression = variableInformation.manager->boolean(true);
} }
} else {
predecessorExpression = variableInformation.manager->boolean(true);
labelSetFormula = labelSetFormula || (successorExpression && predecessorExpression);
labelSetToFormula[labelSet] = labelSetFormula;
} }
labelSetFormula = labelSetFormula || (successorExpression && predecessorExpression);
labelSetToFormula[labelSet] = labelSetFormula;
}
for (auto const& labelSetFormula : labelSetToFormula) {
solver.add(labelSetFormula.second || getOtherSynchronizationEnabledFormula(labelSetFormula.first, synchronizingLabels, labelSetToFormula, variableInformation, relevancyInformation));
for (auto const &labelSetFormula : labelSetToFormula) {
solver.add(labelSetFormula.second || getOtherSynchronizationEnabledFormula(labelSetFormula.first, synchronizingLabels, labelSetToFormula, variableInformation, relevancyInformation));
}
} }
STORM_LOG_DEBUG("Asserting synchronization cuts."); STORM_LOG_DEBUG("Asserting synchronization cuts.");
@ -837,6 +859,9 @@ namespace storm {
assertDisjunction(solver, formulae, *variableInformation.manager); assertDisjunction(solver, formulae, *variableInformation.manager);
} }
} }
auto endTime = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - assertCutsClock);
} }
/*! /*!
@ -1021,10 +1046,12 @@ namespace storm {
*/ */
static std::vector<std::vector<storm::expressions::Expression>> createAdderPairs(VariableInformation const& variableInformation, std::vector<std::vector<storm::expressions::Expression>> const& in) { static std::vector<std::vector<storm::expressions::Expression>> createAdderPairs(VariableInformation const& variableInformation, std::vector<std::vector<storm::expressions::Expression>> const& in) {
std::vector<std::vector<storm::expressions::Expression>> result; std::vector<std::vector<storm::expressions::Expression>> result;
result.reserve(in.size() / 2 + in.size() % 2);
for (uint_fast64_t index = 0; index < in.size() / 2; ++index) {
uint64_t maxIndex = in.size() / 2;
result.reserve(maxIndex + in.size() % 2);
for (uint_fast64_t index = 0; index < maxIndex; ++index) {
result.push_back(createAdder(variableInformation, in[2 * index], in[2 * index + 1])); result.push_back(createAdder(variableInformation, in[2 * index], in[2 * index + 1]));
} }
@ -1586,9 +1613,11 @@ namespace storm {
bool encodeReachability; bool encodeReachability;
bool useDynamicConstraints; bool useDynamicConstraints;
bool silent = false; bool silent = false;
bool addBackwardImplicationCuts = true;
uint64_t continueAfterFirstCounterexampleUntil = 0; uint64_t continueAfterFirstCounterexampleUntil = 0;
uint64_t maximumCounterexamples = 1; uint64_t maximumCounterexamples = 1;
uint64_t multipleCounterexampleSizeCap = 100000000; uint64_t multipleCounterexampleSizeCap = 100000000;
uint64_t maximumExtraIterations = 100000000;
}; };
struct GeneratorStats { struct GeneratorStats {
@ -1596,8 +1625,11 @@ namespace storm {
std::chrono::milliseconds solverTime; std::chrono::milliseconds solverTime;
std::chrono::milliseconds modelCheckingTime; std::chrono::milliseconds modelCheckingTime;
std::chrono::milliseconds analysisTime; std::chrono::milliseconds analysisTime;
std::chrono::milliseconds cutTime;
uint64_t iterations;
}; };
/*! /*!
* Computes the minimal command set that is needed in the given model to exceed the given probability threshold for satisfying phi until psi. * Computes the minimal command set that is needed in the given model to exceed the given probability threshold for satisfying phi until psi.
* *
@ -1679,19 +1711,19 @@ namespace storm {
// and subsequently relax that. // and subsequently relax that.
variableInformation.adderVariables = assertAdder(*solver, variableInformation); variableInformation.adderVariables = assertAdder(*solver, variableInformation);
variableInformation.auxiliaryVariables.push_back(assertLessOrEqualKRelaxed(*solver, variableInformation, 0)); variableInformation.auxiliaryVariables.push_back(assertLessOrEqualKRelaxed(*solver, variableInformation, 0));
// As we are done with the setup at this point, stop the clock for the setup time.
totalSetupTime = std::chrono::high_resolution_clock::now() - setupTimeClock;
// (6) Add constraints that cut off a lot of suboptimal solutions. // (6) Add constraints that cut off a lot of suboptimal solutions.
STORM_LOG_DEBUG("Asserting cuts."); STORM_LOG_DEBUG("Asserting cuts.");
assertCuts(symbolicModel, model, labelSets, psiStates, variableInformation, relevancyInformation, *solver);
stats.cutTime = assertCuts(symbolicModel, model, labelSets, psiStates, variableInformation, relevancyInformation, *solver, options.addBackwardImplicationCuts);
STORM_LOG_DEBUG("Asserted cuts."); STORM_LOG_DEBUG("Asserted cuts.");
if (options.encodeReachability) { if (options.encodeReachability) {
assertReachabilityCuts(model, labelSets, psiStates, variableInformation, relevancyInformation, *solver); assertReachabilityCuts(model, labelSets, psiStates, variableInformation, relevancyInformation, *solver);
STORM_LOG_DEBUG("Asserted reachability cuts."); STORM_LOG_DEBUG("Asserted reachability cuts.");
} }
// As we are done with the setup at this point, stop the clock for the setup time.
totalSetupTime = std::chrono::high_resolution_clock::now() - setupTimeClock;
// (7) Find the smallest set of commands that satisfies all constraints. If the probability of // (7) Find the smallest set of commands that satisfies all constraints. If the probability of
// satisfying phi until psi exceeds the given threshold, the set of labels is minimal and can be returned. // satisfying phi until psi exceeds the given threshold, the set of labels is minimal and can be returned.
// Otherwise, the current solution has to be ruled out and the next smallest solution is retrieved from // Otherwise, the current solution has to be ruled out and the next smallest solution is retrieved from
@ -1712,11 +1744,17 @@ namespace storm {
uint_fast64_t lastSize = 0; uint_fast64_t lastSize = 0;
uint_fast64_t iterations = 0; uint_fast64_t iterations = 0;
uint_fast64_t currentBound = 0; uint_fast64_t currentBound = 0;
uint64_t firstCounterexampleFound = 0; // The value is not queried before being set.
std::vector<double> maximalPropertyValue; std::vector<double> maximalPropertyValue;
uint_fast64_t zeroProbabilityCount = 0; uint_fast64_t zeroProbabilityCount = 0;
uint64_t smallestCounterexampleSize = model.getNumberOfChoices(); // Definitive u
size_t smallestCounterexampleSize = model.getNumberOfChoices(); // Definitive upper bound
uint64_t progressDelay = storm::settings::getModule<storm::settings::modules::GeneralSettings>().getShowProgressDelay(); uint64_t progressDelay = storm::settings::getModule<storm::settings::modules::GeneralSettings>().getShowProgressDelay();
do { do {
++iterations;
if (result.size() > 0 && iterations > firstCounterexampleFound + options.maximumExtraIterations) {
break;
}
STORM_LOG_DEBUG("Computing minimal command set."); STORM_LOG_DEBUG("Computing minimal command set.");
solverClock = std::chrono::high_resolution_clock::now(); solverClock = std::chrono::high_resolution_clock::now();
boost::optional<boost::container::flat_set<uint_fast64_t>> smallest = findSmallestCommandSet(*solver, variableInformation, currentBound); boost::optional<boost::container::flat_set<uint_fast64_t>> smallest = findSmallestCommandSet(*solver, variableInformation, currentBound);
@ -1781,19 +1819,21 @@ namespace storm {
} }
} else { } else {
STORM_LOG_DEBUG("Found a counterexample."); STORM_LOG_DEBUG("Found a counterexample.");
if (result.empty()) {
// If this is the first counterexample we find, we store when we found it.
firstCounterexampleFound = iterations;
}
result.push_back(commandSet); result.push_back(commandSet);
if (options.maximumCounterexamples > result.size()) { if (options.maximumCounterexamples > result.size()) {
STORM_LOG_DEBUG("Exclude counterexample for future."); STORM_LOG_DEBUG("Exclude counterexample for future.");
ruleOutBiggerSolutions(*solver, commandSet, variableInformation, relevancyInformation); ruleOutBiggerSolutions(*solver, commandSet, variableInformation, relevancyInformation);
} else { } else {
STORM_LOG_DEBUG("Stop searching for further counterexamples."); STORM_LOG_DEBUG("Stop searching for further counterexamples.");
done = true; done = true;
} }
smallestCounterexampleSize = std::min(smallestCounterexampleSize, commandSet.size());
} }
totalAnalysisTime += (std::chrono::high_resolution_clock::now() - analysisClock); totalAnalysisTime += (std::chrono::high_resolution_clock::now() - analysisClock);
++iterations;
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
auto durationSinceLastMessage = std::chrono::duration_cast<std::chrono::seconds>(now - timeOfLastMessage).count(); auto durationSinceLastMessage = std::chrono::duration_cast<std::chrono::seconds>(now - timeOfLastMessage).count();
@ -1816,6 +1856,7 @@ namespace storm {
stats.setupTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalSetupTime); stats.setupTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalSetupTime);
stats.modelCheckingTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalModelCheckingTime); stats.modelCheckingTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalModelCheckingTime);
stats.solverTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalSolverTime); stats.solverTime = std::chrono::duration_cast<std::chrono::milliseconds>(totalSolverTime);
stats.iterations = iterations;
if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) { if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
boost::container::flat_set<uint64_t> allLabels; boost::container::flat_set<uint64_t> allLabels;

Loading…
Cancel
Save