From 4d7be96dda859b91f45e7dd2a1d20d65cc2b34df Mon Sep 17 00:00:00 2001 From: dehnert <dehnert@cs.rwth-aachen.de> Date: Fri, 9 Feb 2018 12:10:55 +0100 Subject: [PATCH] MaxSAT-based high-level counterexamples for JANI --- src/storm-cli-utilities/model-handling.h | 7 +- src/storm/api/counterexamples.cpp | 4 +- src/storm/api/counterexamples.h | 2 +- .../SMTMinimalLabelSetGenerator.h | 399 ++++++++++-------- src/storm/storage/expressions/Expression.cpp | 16 + src/storm/storage/jani/Automaton.cpp | 4 + src/storm/storage/jani/Automaton.h | 5 + 7 files changed, 264 insertions(+), 173 deletions(-) diff --git a/src/storm-cli-utilities/model-handling.h b/src/storm-cli-utilities/model-handling.h index 81264a267..74da9a6d6 100644 --- a/src/storm-cli-utilities/model-handling.h +++ b/src/storm-cli-utilities/model-handling.h @@ -389,13 +389,10 @@ namespace storm { } else { STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Dtmc) || sparseModel->isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "Counterexample generation using MaxSAT is currently only supported for discrete-time models."); - STORM_LOG_THROW(input.model && input.model.get().isPrismProgram(), storm::exceptions::NotSupportedException, "Minimal command set counterexamples are only supported for PRISM model input."); - storm::prism::Program const& program = input.model.get().asPrismProgram(); - if (sparseModel->isOfType(storm::models::ModelType::Dtmc)) { - counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(program, sparseModel->template as<storm::models::sparse::Dtmc<ValueType>>(), property.getRawFormula()); + counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(input.model.get(), sparseModel->template as<storm::models::sparse::Dtmc<ValueType>>(), property.getRawFormula()); } else { - counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(program, sparseModel->template as<storm::models::sparse::Mdp<ValueType>>(), property.getRawFormula()); + counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(input.model.get(), sparseModel->template as<storm::models::sparse::Mdp<ValueType>>(), property.getRawFormula()); } } watch.stop(); diff --git a/src/storm/api/counterexamples.cpp b/src/storm/api/counterexamples.cpp index 3fce62b82..c57724ad5 100644 --- a/src/storm/api/counterexamples.cpp +++ b/src/storm/api/counterexamples.cpp @@ -10,9 +10,9 @@ namespace storm { return storm::counterexamples::MILPMinimalLabelSetGenerator<double>::computeCounterexample(env, symbolicModel, *mdp, formula); } - std::shared_ptr<storm::counterexamples::Counterexample> computeHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr<storm::models::sparse::Model<double>> model, std::shared_ptr<storm::logic::Formula const> const& formula) { + std::shared_ptr<storm::counterexamples::Counterexample> computeHighLevelCounterexampleMaxSmt(storm::storage::SymbolicModelDescription const& symbolicModel, std::shared_ptr<storm::models::sparse::Model<double>> model, std::shared_ptr<storm::logic::Formula const> const& formula) { Environment env; - return storm::counterexamples::SMTMinimalLabelSetGenerator<double>::computeCounterexample(env, program, *model, formula); + return storm::counterexamples::SMTMinimalLabelSetGenerator<double>::computeCounterexample(env, symbolicModel, *model, formula); } } diff --git a/src/storm/api/counterexamples.h b/src/storm/api/counterexamples.h index 90167b313..672ddc4df 100644 --- a/src/storm/api/counterexamples.h +++ b/src/storm/api/counterexamples.h @@ -8,7 +8,7 @@ namespace storm { std::shared_ptr<storm::counterexamples::Counterexample> computeHighLevelCounterexampleMilp(storm::storage::SymbolicModelDescription const& symbolicModel, std::shared_ptr<storm::models::sparse::Mdp<double>> mdp, std::shared_ptr<storm::logic::Formula const> const& formula); - std::shared_ptr<storm::counterexamples::Counterexample> computeHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr<storm::models::sparse::Model<double>> model, std::shared_ptr<storm::logic::Formula const> const& formula); + std::shared_ptr<storm::counterexamples::Counterexample> computeHighLevelCounterexampleMaxSmt(storm::storage::SymbolicModelDescription const& symbolicModel, std::shared_ptr<storm::models::sparse::Model<double>> model, std::shared_ptr<storm::logic::Formula const> const& formula); } } diff --git a/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h b/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h index e5b9f039f..48769c75d 100644 --- a/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h +++ b/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h @@ -319,11 +319,12 @@ namespace storm { * Asserts cuts that are derived from the explicit representation of the model and rule out a lot of * suboptimal solutions. * + * @param symbolicModel The symbolic model description used to build the model. * @param model The labeled model for which to compute the cuts. * @param context The Z3 context in which to build the expressions. * @param solver The solver to use for the satisfiability evaluation. */ - static void assertCuts(storm::prism::Program& program, 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 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) { // Walk through the model and // * identify labels enabled in initial states // * identify labels that can directly precede a given action @@ -400,189 +401,249 @@ namespace storm { } } - // Create a new solver over the same variables as the given program to use it for determining the symbolic - // cuts. - std::unique_ptr<storm::solver::SmtSolver> localSolver(new storm::solver::Z3SmtSolver(program.getManager())); - storm::expressions::ExpressionManager const& localManager = program.getManager(); - - // Then add the constraints for bounds of the integer variables.. - for (auto const& integerVariable : program.getGlobalIntegerVariables()) { - localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBoundExpression()); - localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBoundExpression()); - } - for (auto const& module : program.getModules()) { - for (auto const& integerVariable : module.getIntegerVariables()) { - localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBoundExpression()); - localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBoundExpression()); - } - } - - // Construct an expression that exactly characterizes the initial state. - storm::expressions::Expression initialStateExpression = program.getInitialStatesExpression(); - // 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; - - // Now check for possible backward cuts. - for (auto const& labelSetAndPrecedingLabelSetsPair : precedingLabels) { - // Find out the commands for the currently considered label set. - std::vector<std::reference_wrapper<storm::prism::Command const>> currentCommandVector; - for (uint_fast64_t moduleIndex = 0; moduleIndex < program.getNumberOfModules(); ++moduleIndex) { - storm::prism::Module const& module = program.getModule(moduleIndex); + + if (!symbolicModel.isJaniModel() || !symbolicModel.asJaniModel().usesAssignmentLevels()) { + // Create a new solver over the same variables as the given symbolic model description to use it for + // determining the symbolic cuts. + std::unique_ptr<storm::solver::SmtSolver> localSolver; + if (symbolicModel.isPrismProgram()) { + storm::prism::Program const& program = symbolicModel.asPrismProgram(); + localSolver = std::make_unique<storm::solver::Z3SmtSolver>(program.getManager()); - for (uint_fast64_t commandIndex = 0; commandIndex < module.getNumberOfCommands(); ++commandIndex) { - storm::prism::Command const& command = module.getCommand(commandIndex); + // Then add the constraints for bounds of the integer variables. + for (auto const& integerVariable : program.getGlobalIntegerVariables()) { + localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBoundExpression()); + localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBoundExpression()); + } + for (auto const& module : program.getModules()) { + for (auto const& integerVariable : module.getIntegerVariables()) { + localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBoundExpression()); + localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBoundExpression()); + } + } + } else { + storm::jani::Model const& janiModel = symbolicModel.asJaniModel(); + localSolver = std::make_unique<storm::solver::Z3SmtSolver>(janiModel.getManager()); + + for (auto const& integerVariable : janiModel.getGlobalVariables().getBoundedIntegerVariables()) { + if (integerVariable.isTransient()) { + continue; + } - // If the current command is one of the commands we need to consider, store a reference to it in the container. - if (labelSetAndPrecedingLabelSetsPair.first.find(command.getGlobalIndex()) != labelSetAndPrecedingLabelSetsPair.first.end()) { - currentCommandVector.push_back(command); + localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBound()); + localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBound()); + } + for (auto const& automaton : janiModel.getAutomata()) { + for (auto const& integerVariable : automaton.getVariables().getBoundedIntegerVariables()) { + if (integerVariable.isTransient()) { + continue; + } + + localSolver->add(integerVariable.getExpressionVariable() >= integerVariable.getLowerBound()); + localSolver->add(integerVariable.getExpressionVariable() <= integerVariable.getUpperBound()); } } } - // Save the state of the solver so we can easily backtrack. - localSolver->push(); - // Check if the command set is enabled in the initial state. - for (auto const& command : currentCommandVector) { - localSolver->add(command.get().getGuardExpression()); + // Construct an expression that exactly characterizes the initial state. + storm::expressions::Expression initialStateExpression; + if (symbolicModel.isPrismProgram()) { + initialStateExpression = symbolicModel.asPrismProgram().getInitialStatesExpression(); + } else { + initialStateExpression = symbolicModel.asJaniModel().getInitialStatesExpression(); } - localSolver->add(initialStateExpression); - - storm::solver::SmtSolver::CheckResult checkResult = localSolver->check(); - localSolver->pop(); - localSolver->push(); -// std::cout << "combi" << std::endl; -// for (auto const& e : labelSetAndPrecedingLabelSetsPair.first) { -// std::cout << e << ", "; -// } -// std::cout << std::endl; - - // If the solver reports unsat, then we know that the current selection is not enabled in the initial state. - if (checkResult == storm::solver::SmtSolver::CheckResult::Unsat) { - STORM_LOG_DEBUG("Selection not enabled in initial state."); + // Now check for possible backward cuts. + for (auto const& labelSetAndPrecedingLabelSetsPair : precedingLabels) { + // Find out the commands for the currently considered label set. storm::expressions::Expression guardConjunction; - if (currentCommandVector.size() == 1) { - guardConjunction = currentCommandVector.begin()->get().getGuardExpression(); - } else if (currentCommandVector.size() > 1) { - std::vector<std::reference_wrapper<storm::prism::Command const>>::const_iterator setIterator = currentCommandVector.begin(); - storm::expressions::Expression first = setIterator->get().getGuardExpression(); - ++setIterator; - storm::expressions::Expression second = setIterator->get().getGuardExpression(); - guardConjunction = first && second; - ++setIterator; - - while (setIterator != currentCommandVector.end()) { - guardConjunction = guardConjunction && setIterator->get().getGuardExpression(); - ++setIterator; - } - } else { - STORM_LOG_ASSERT(false, "Choice label set is empty."); - } - - STORM_LOG_DEBUG("About to assert that combination is not enabled in the current state."); -// std::cout << "negated guard expr " << !guardConjunction << std::endl; - localSolver->add(!guardConjunction); - STORM_LOG_DEBUG("Asserted disjunction of negated guards."); - // Now check the possible preceding label sets for the essential ones. - for (auto const& precedingLabelSet : labelSetAndPrecedingLabelSetsPair.second) { - if (labelSetAndPrecedingLabelSetsPair.first == precedingLabelSet) continue; + if (symbolicModel.isPrismProgram()) { + storm::prism::Program const& program = symbolicModel.asPrismProgram(); -// std::cout << "new preceeding label set" << std::endl; -// for (auto const& e : precedingLabelSet) { -// std::cout << e << ", "; -// } -// std::cout << std::endl; - - // Create a restore point so we can easily pop-off all weakest precondition expressions. - localSolver->push(); - - // Find out the commands for the currently considered preceding label set. - std::vector<std::reference_wrapper<storm::prism::Command const>> currentPrecedingCommandVector; for (uint_fast64_t moduleIndex = 0; moduleIndex < program.getNumberOfModules(); ++moduleIndex) { storm::prism::Module const& module = program.getModule(moduleIndex); for (uint_fast64_t commandIndex = 0; commandIndex < module.getNumberOfCommands(); ++commandIndex) { storm::prism::Command const& command = module.getCommand(commandIndex); - // If the current command is one of the commands we need to consider, store a reference to it in the container. - if (precedingLabelSet.find(command.getGlobalIndex()) != precedingLabelSet.end()) { - currentPrecedingCommandVector.push_back(command); + // If the current command is one of the commands we need to consider, add its guard. + if (labelSetAndPrecedingLabelSetsPair.first.find(command.getGlobalIndex()) != labelSetAndPrecedingLabelSetsPair.first.end()) { + guardConjunction = guardConjunction && command.getGuardExpression(); } } } - - // Assert all the guards of the preceding command set. - for (auto const& command : currentPrecedingCommandVector) { -// std::cout << "command guard " << command.get().getGuardExpression() << std::endl; - localSolver->add(command.get().getGuardExpression()); + } else { + storm::jani::Model const& janiModel = symbolicModel.asJaniModel(); + + for (uint_fast64_t automatonIndex = 0; automatonIndex < janiModel.getNumberOfAutomata(); ++automatonIndex) { + storm::jani::Automaton const& automaton = janiModel.getAutomaton(automatonIndex); + + for (uint_fast64_t edgeIndex = 0; edgeIndex < automaton.getNumberOfEdges(); ++edgeIndex) { + // If the current edge is one of the edges we need to consider, add its guard. + if (labelSetAndPrecedingLabelSetsPair.first.find(janiModel.encodeAutomatonAndEdgeIndices(automatonIndex, edgeIndex)) != labelSetAndPrecedingLabelSetsPair.first.end()) { + storm::jani::Edge const& edge = automaton.getEdge(edgeIndex); + + guardConjunction = guardConjunction && edge.getGuard(); + } + } } + } + + // Save the state of the solver so we can easily backtrack. + localSolver->push(); + + // Push initial state expression. + STORM_LOG_DEBUG("About to assert that combination is not enabled in the current state."); + localSolver->add(initialStateExpression); + + // Check if the label set is enabled in the initial state. + localSolver->add(guardConjunction); + + storm::solver::SmtSolver::CheckResult checkResult = localSolver->check(); + localSolver->pop(); + localSolver->push(); + + // If the solver reports unsat, then we know that the current selection is not enabled in the initial state. + if (checkResult == storm::solver::SmtSolver::CheckResult::Unsat) { + STORM_LOG_DEBUG("Selection not enabled in initial state."); + + localSolver->add(!guardConjunction); + STORM_LOG_DEBUG("Asserted disjunction of negated guards."); - std::vector<std::vector<storm::prism::Update>::const_iterator> iteratorVector; - for (auto const& command : currentPrecedingCommandVector) { - iteratorVector.push_back(command.get().getUpdates().begin()); - } - // Iterate over all possible combinations of updates of the preceding command set. - std::vector<storm::expressions::Expression> formulae; - bool done = false; - while (!done) { - std::map<storm::expressions::Variable, storm::expressions::Expression> currentUpdateCombinationMap; - for (auto const& updateIterator : iteratorVector) { - for (auto const& assignment : updateIterator->getAssignments()) { - currentUpdateCombinationMap.emplace(assignment.getVariable(), assignment.getExpression()); + // Now check the possible preceding label sets for the essential ones. + for (auto const& precedingLabelSet : labelSetAndPrecedingLabelSetsPair.second) { + if (labelSetAndPrecedingLabelSetsPair.first == precedingLabelSet) continue; + + // Create a restore point so we can easily pop-off all weakest precondition expressions. + localSolver->push(); + + // Find out the commands for the currently considered preceding label set. + std::vector<std::vector<boost::container::flat_map<storm::expressions::Variable, storm::expressions::Expression>>> currentPreceedingVariableUpdates; + storm::expressions::Expression preceedingGuardConjunction; + if (symbolicModel.isPrismProgram()) { + storm::prism::Program const& program = symbolicModel.asPrismProgram(); + for (uint_fast64_t moduleIndex = 0; moduleIndex < program.getNumberOfModules(); ++moduleIndex) { + storm::prism::Module const& module = program.getModule(moduleIndex); + + for (uint_fast64_t commandIndex = 0; commandIndex < module.getNumberOfCommands(); ++commandIndex) { + storm::prism::Command const& command = module.getCommand(commandIndex); + + // If the current command is one of the commands we need to consider, store a reference to it in the container. + if (precedingLabelSet.find(command.getGlobalIndex()) != precedingLabelSet.end()) { + preceedingGuardConjunction = preceedingGuardConjunction && command.getGuardExpression(); + + currentPreceedingVariableUpdates.emplace_back(); + + for (uint64_t updateIndex = 0; updateIndex < command.getNumberOfUpdates(); ++updateIndex) { + storm::prism::Update const& update = command.getUpdate(updateIndex); + boost::container::flat_map<storm::expressions::Variable, storm::expressions::Expression> variableUpdates; + for (auto const& assignment : update.getAssignments()) { + variableUpdates.emplace(assignment.getVariable(), assignment.getExpression()); + } + currentPreceedingVariableUpdates.back().emplace_back(std::move(variableUpdates)); + } + } + } + } + } else { + storm::jani::Model const& janiModel = symbolicModel.asJaniModel(); + for (uint_fast64_t automatonIndex = 0; automatonIndex < janiModel.getNumberOfAutomata(); ++automatonIndex) { + storm::jani::Automaton const& automaton = janiModel.getAutomaton(automatonIndex); + + for (uint_fast64_t edgeIndex = 0; edgeIndex < automaton.getNumberOfEdges(); ++edgeIndex) { + // If the current command is one of the commands we need to consider, store a reference to it in the container. + if (precedingLabelSet.find(janiModel.encodeAutomatonAndEdgeIndices(automatonIndex, edgeIndex)) != precedingLabelSet.end()) { + storm::jani::Edge const& edge = automaton.getEdge(edgeIndex); + + preceedingGuardConjunction = preceedingGuardConjunction && edge.getGuard(); + + currentPreceedingVariableUpdates.emplace_back(); + + for (uint64_t destinationIndex = 0; destinationIndex < edge.getNumberOfDestinations(); ++destinationIndex) { + storm::jani::EdgeDestination const& destination = edge.getDestination(destinationIndex); + boost::container::flat_map<storm::expressions::Variable, storm::expressions::Expression> variableUpdates; + for (auto const& assignment : destination.getOrderedAssignments().getNonTransientAssignments()) { + variableUpdates.emplace(assignment.getVariable().getExpressionVariable(), assignment.getAssignedExpression()); + } + currentPreceedingVariableUpdates.back().emplace_back(std::move(variableUpdates)); + } + } + } } } - STORM_LOG_DEBUG("About to assert a weakest precondition."); - storm::expressions::Expression wp = guardConjunction.substitute(currentUpdateCombinationMap); -// std::cout << "wp: " << wp << std::endl; - formulae.push_back(wp); - STORM_LOG_DEBUG("Asserted weakest precondition."); + // Assert all the guards of the preceding command set. + localSolver->add(preceedingGuardConjunction); - // Now try to move iterators to the next position if possible. If we could properly move it, we can directly - // move on to the next combination of updates. If we have to reset it to the start, we - uint_fast64_t k = iteratorVector.size(); - for (; k > 0; --k) { - ++iteratorVector[k - 1]; - if (iteratorVector[k - 1] == currentPrecedingCommandVector[k - 1].get().getUpdates().end()) { - iteratorVector[k - 1] = currentPrecedingCommandVector[k - 1].get().getUpdates().begin(); - } else { - break; + std::vector<std::vector<boost::container::flat_map<storm::expressions::Variable, storm::expressions::Expression>>::const_iterator> iteratorVector; + for (auto const& variableUpdates : currentPreceedingVariableUpdates) { + iteratorVector.push_back(variableUpdates.begin()); + } + + // Iterate over all possible combinations of updates of the preceding command set. + std::vector<storm::expressions::Expression> formulae; + bool done = false; + while (!done) { + std::map<storm::expressions::Variable, storm::expressions::Expression> currentVariableUpdateCombinationMap; + for (auto const& updateIterator : iteratorVector) { + for (auto const& variableUpdatePair : *updateIterator) { + currentVariableUpdateCombinationMap.emplace(variableUpdatePair.first, variableUpdatePair.second); + } + } + + STORM_LOG_DEBUG("About to assert a weakest precondition."); + storm::expressions::Expression wp = guardConjunction.substitute(currentVariableUpdateCombinationMap); + formulae.push_back(wp); + STORM_LOG_DEBUG("Asserted weakest precondition."); + + // Now try to move iterators to the next position if possible. If we could properly + // move it, we can directly move on to the next combination of updates. If we have + // to reset it to the start, we do so unless there is no more iterator to reset. + uint_fast64_t k = iteratorVector.size(); + for (; k > 0; --k) { + ++iteratorVector[k - 1]; + if (iteratorVector[k - 1] == currentPreceedingVariableUpdates[k - 1].end()) { + iteratorVector[k - 1] = currentPreceedingVariableUpdates[k - 1].begin(); + } else { + break; + } + } + + // If we had to reset all iterator to the start, we are done. + if (k == 0) { + done = true; } } - // If we had to reset all iterator to the start, we are done. - if (k == 0) { - done = true; + // Now assert the disjunction of all weakest preconditions of all considered update combinations. + assertDisjunction(*localSolver, formulae, symbolicModel.isPrismProgram() ? symbolicModel.asPrismProgram().getManager() : symbolicModel.asJaniModel().getManager()); + + STORM_LOG_DEBUG("Asserted disjunction of all weakest preconditions."); + + if (localSolver->check() == storm::solver::SmtSolver::CheckResult::Sat) { + backwardImplications[labelSetAndPrecedingLabelSetsPair.first].insert(precedingLabelSet); } + + localSolver->pop(); } - // Now assert the disjunction of all weakest preconditions of all considered update combinations. - assertDisjunction(*localSolver, formulae, localManager); - - STORM_LOG_DEBUG("Asserted disjunction of all weakest preconditions."); - - if (localSolver->check() == storm::solver::SmtSolver::CheckResult::Sat) { -// std::cout << "sat" << std::endl; - backwardImplications[labelSetAndPrecedingLabelSetsPair.first].insert(precedingLabelSet); - } -// else { -// std::cout << "unsat" << std::endl; -// } - + // Popping the disjunction of negated guards from the solver stack. localSolver->pop(); + } else { + STORM_LOG_DEBUG("Selection is enabled in initial state."); } - - // Popping the disjunction of negated guards from the solver stack. - localSolver->pop(); - } else { - STORM_LOG_DEBUG("Selection is enabled in initial state."); } + } else if (symbolicModel.isJaniModel()) { + STORM_LOG_WARN("Model uses assignment levels, did not assert backward implications."); } + STORM_LOG_DEBUG("Successfully gathered data for cuts."); + // Compute the sets of labels such that the transitions labeled with this set possess at least one known label. boost::container::flat_set<boost::container::flat_set<uint_fast64_t>> hasKnownSuccessor; for (auto const& labelSetFollowingSetsPair : followingLabels) { @@ -604,9 +665,6 @@ namespace storm { } } } - - - STORM_LOG_DEBUG("Successfully gathered data for cuts."); STORM_LOG_DEBUG("Asserting initial combination is taken."); { @@ -1563,7 +1621,7 @@ namespace storm { /*! * Computes the minimal command set that is needed in the given model to exceed the given probability threshold for satisfying phi until psi. * - * @param program The program that was used to build the model. + * @param symbolicModel The symbolic model description that was used to build the model. * @param model The sparse model in which to find the minimal command set. * @param phiStates A bit vector characterizing all phi states in the model. * @param psiStates A bit vector characterizing all psi states in the model. @@ -1572,7 +1630,7 @@ namespace storm { * @param checkThresholdFeasible If set, it is verified that the model can actually achieve/exceed the given probability value. If this check * is made and fails, an exception is thrown. */ - static boost::container::flat_set<uint_fast64_t> getMinimalCommandSet(Environment const& env, storm::prism::Program program, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, double probabilityThreshold, bool strictBound, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), bool checkThresholdFeasible = false, bool includeReachabilityEncoding = false) { + static boost::container::flat_set<uint_fast64_t> getMinimalLabelSet(Environment const& env, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, double probabilityThreshold, bool strictBound, boost::container::flat_set<uint_fast64_t> const& dontCareLabels = boost::container::flat_set<uint_fast64_t>(), bool checkThresholdFeasible = false, bool includeReachabilityEncoding = false) { #ifdef STORM_HAVE_Z3 // Set up all clocks used for time measurement. auto totalClock = std::chrono::high_resolution_clock::now(); @@ -1594,12 +1652,19 @@ namespace storm { // (0) Obtain the label sets for each choice. // The label set of a choice corresponds to the set of prism commands that induce the choice. STORM_LOG_THROW(model.hasChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to minimal command set is impossible for model without choice origins."); - STORM_LOG_THROW(model.getChoiceOrigins()->isPrismChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to command set is impossible for model without prism choice origins."); - storm::storage::sparse::PrismChoiceOrigins const& choiceOrigins = model.getChoiceOrigins()->asPrismChoiceOrigins(); - std::vector<boost::container::flat_set<uint_fast64_t>> labelSets; - labelSets.reserve(model.getNumberOfChoices()); - for (uint_fast64_t choice = 0; choice < model.getNumberOfChoices(); ++choice) { - labelSets.push_back(choiceOrigins.getCommandSet(choice)); + STORM_LOG_THROW(model.getChoiceOrigins()->isPrismChoiceOrigins() || model.getChoiceOrigins()->isJaniChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to label set is impossible for model without PRISM or JANI choice origins."); + + std::vector<boost::container::flat_set<uint_fast64_t>> labelSets(model.getNumberOfChoices()); + if (model.getChoiceOrigins()->isPrismChoiceOrigins()) { + storm::storage::sparse::PrismChoiceOrigins const& choiceOrigins = model.getChoiceOrigins()->asPrismChoiceOrigins(); + for (uint_fast64_t choice = 0; choice < model.getNumberOfChoices(); ++choice) { + labelSets[choice] = choiceOrigins.getCommandSet(choice); + } + } else { + storm::storage::sparse::JaniChoiceOrigins const& choiceOrigins = model.getChoiceOrigins()->asJaniChoiceOrigins(); + for (uint_fast64_t choice = 0; choice < model.getNumberOfChoices(); ++choice) { + labelSets[choice] = choiceOrigins.getEdgeIndexSet(choice); + } } // (1) Check whether its possible to exceed the threshold if checkThresholdFeasible is set. @@ -1630,7 +1695,7 @@ namespace storm { // (6) Add constraints that cut off a lot of suboptimal solutions. STORM_LOG_DEBUG("Asserting cuts."); - assertCuts(program, model, labelSets, psiStates, variableInformation, relevancyInformation, *solver); + assertCuts(symbolicModel, model, labelSets, psiStates, variableInformation, relevancyInformation, *solver); STORM_LOG_DEBUG("Asserted cuts."); if (includeReachabilityEncoding) { assertReachabilityCuts(model, labelSets, psiStates, variableInformation, relevancyInformation, *solver); @@ -1727,7 +1792,7 @@ namespace storm { #endif } - static void extendCommandSetLowerBound(storm::models::sparse::Model<T> const& model, boost::container::flat_set<uint_fast64_t>& commandSet, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { + static void extendLabelSetLowerBound(storm::models::sparse::Model<T> const& model, boost::container::flat_set<uint_fast64_t>& commandSet, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { auto startTime = std::chrono::high_resolution_clock::now(); // Create sub-model that only contains the choices allowed by the given command set. @@ -1796,7 +1861,7 @@ namespace storm { std::cout << std::endl << "Extended command for lower bounded property to size " << commandSet.size() << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "ms." << std::endl; } - static boost::container::flat_set<uint_fast64_t> computeCounterexampleCommandSet(Environment const& env, storm::prism::Program program, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula) { + static boost::container::flat_set<uint_fast64_t> computeCounterexampleLabelSet(Environment const& env, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula) { 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."); std::cout << std::endl << "Generating minimal label counterexample for formula " << *formula << std::endl; @@ -1864,23 +1929,27 @@ namespace storm { // Delegate the actual computation work to the function of equal name. auto startTime = std::chrono::high_resolution_clock::now(); - auto commandSet = getMinimalCommandSet(env, program, model, phiStates, psiStates, threshold, strictBound, boost::container::flat_set<uint_fast64_t>(), true, storm::settings::getModule<storm::settings::modules::CounterexampleGeneratorSettings>().isEncodeReachabilitySet()); + auto labelSet = getMinimalLabelSet(env, symbolicModel, model, phiStates, psiStates, threshold, strictBound, boost::container::flat_set<uint_fast64_t>(), true, storm::settings::getModule<storm::settings::modules::CounterexampleGeneratorSettings>().isEncodeReachabilitySet()); auto endTime = std::chrono::high_resolution_clock::now(); - std::cout << std::endl << "Computed minimal command set of size " << commandSet.size() << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "ms." << std::endl; + std::cout << std::endl << "Computed minimal label set of size " << labelSet.size() << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "ms." << std::endl; // Extend the command set properly. if (lowerBoundedFormula) { - extendCommandSetLowerBound(model, commandSet, phiStates, psiStates); + extendLabelSetLowerBound(model, labelSet, phiStates, psiStates); } - return commandSet; + return labelSet; } - static std::shared_ptr<HighLevelCounterexample> computeCounterexample(Environment const& env, storm::prism::Program program, storm::models::sparse::Model<T> const& model, std::shared_ptr<storm::logic::Formula const> const& formula) { + 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 - auto commandSet = computeCounterexampleCommandSet(env, program, model, formula); + auto labelSet = computeCounterexampleLabelSet(env, symbolicModel, model, formula); - return std::make_shared<HighLevelCounterexample>(program.restrictCommands(commandSet)); - + if (symbolicModel.isPrismProgram()) { + return std::make_shared<HighLevelCounterexample>(symbolicModel.asPrismProgram().restrictCommands(labelSet)); + } else { + STORM_LOG_ASSERT(symbolicModel.isJaniModel(), "Unknown symbolic model description type."); + return std::make_shared<HighLevelCounterexample>(symbolicModel.asJaniModel().restrictEdges(labelSet)); + } #else throw storm::exceptions::NotImplementedException() << "This functionality is unavailable since storm has been compiled without support for Z3."; return nullptr; diff --git a/src/storm/storage/expressions/Expression.cpp b/src/storm/storage/expressions/Expression.cpp index ed00728f0..1537e633e 100644 --- a/src/storm/storage/expressions/Expression.cpp +++ b/src/storm/storage/expressions/Expression.cpp @@ -229,6 +229,11 @@ namespace storm { } Expression operator+(Expression const& first, Expression const& second) { + if (!first.isInitialized()) { + return second; + } else if (!second.isInitialized()) { + return first; + } assertSameManager(first.getBaseExpression(), second.getBaseExpression()); return Expression(std::shared_ptr<BaseExpression>(new BinaryNumericalFunctionExpression(first.getManager(), first.getType().plusMinusTimes(second.getType()), first.getBaseExpressionPointer(), second.getBaseExpressionPointer(), BinaryNumericalFunctionExpression::OperatorType::Plus))); } @@ -273,6 +278,12 @@ namespace storm { } Expression operator&&(Expression const& first, Expression const& second) { + if (!first.isInitialized()) { + return second; + } else if (!second.isInitialized()) { + return first; + } + assertSameManager(first.getBaseExpression(), second.getBaseExpression()); if (first.isTrue()) { STORM_LOG_THROW(second.hasBooleanType(), storm::exceptions::InvalidTypeException, "Operator requires boolean operands."); @@ -287,6 +298,11 @@ namespace storm { } Expression operator||(Expression const& first, Expression const& second) { + if (!first.isInitialized()) { + return second; + } else if (!second.isInitialized()) { + return first; + } assertSameManager(first.getBaseExpression(), second.getBaseExpression()); return Expression(std::shared_ptr<BaseExpression>(new BinaryBooleanFunctionExpression(first.getBaseExpression().getManager(), first.getType().logicalConnective(second.getType()), first.getBaseExpressionPointer(), second.getBaseExpressionPointer(), BinaryBooleanFunctionExpression::OperatorType::Or))); } diff --git a/src/storm/storage/jani/Automaton.cpp b/src/storm/storage/jani/Automaton.cpp index 6c06ed096..8715a27a8 100644 --- a/src/storm/storage/jani/Automaton.cpp +++ b/src/storm/storage/jani/Automaton.cpp @@ -175,6 +175,10 @@ namespace storm { return locationExpressionVariable; } + Edge const& Automaton::getEdge(uint64_t index) const { + return edges[index]; + } + Automaton::Edges Automaton::getEdgesFromLocation(std::string const& name) { auto it = locationToIndex.find(name); STORM_LOG_THROW(it != locationToIndex.end(), storm::exceptions::InvalidArgumentException, "Cannot retrieve edges from unknown location '" << name << "."); diff --git a/src/storm/storage/jani/Automaton.h b/src/storm/storage/jani/Automaton.h index 2643aa0fc..3ca635f7b 100644 --- a/src/storm/storage/jani/Automaton.h +++ b/src/storm/storage/jani/Automaton.h @@ -218,6 +218,11 @@ namespace storm { */ storm::expressions::Variable const& getLocationExpressionVariable() const; + /*! + * Retrieves the edge with the given index in this automaton. + */ + Edge const& getEdge(uint64_t index) const; + /*! * Retrieves the edges of the location with the given name. */